| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Convert a logo in ASCII PNM format to C source suitable for inclusion in |
| * the Linux kernel |
| * |
| * (C) Copyright 2001-2003 by Geert Uytterhoeven <geert@linux-m68k.org> |
| */ |
| |
| #include <ctype.h> |
| #include <errno.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| |
| static const char *programname; |
| static const char *filename; |
| static const char *logoname = "linux_logo"; |
| static const char *outputname; |
| static FILE *out; |
| |
| |
| #define LINUX_LOGO_MONO 1 /* monochrome black/white */ |
| #define LINUX_LOGO_VGA16 2 /* 16 colors VGA text palette */ |
| #define LINUX_LOGO_CLUT224 3 /* 224 colors */ |
| #define LINUX_LOGO_GRAY256 4 /* 256 levels grayscale */ |
| |
| static const char *logo_types[LINUX_LOGO_GRAY256+1] = { |
| [LINUX_LOGO_MONO] = "LINUX_LOGO_MONO", |
| [LINUX_LOGO_VGA16] = "LINUX_LOGO_VGA16", |
| [LINUX_LOGO_CLUT224] = "LINUX_LOGO_CLUT224", |
| [LINUX_LOGO_GRAY256] = "LINUX_LOGO_GRAY256" |
| }; |
| |
| #define MAX_LINUX_LOGO_COLORS 224 |
| |
| struct color { |
| unsigned char red; |
| unsigned char green; |
| unsigned char blue; |
| }; |
| |
| static const struct color clut_vga16[16] = { |
| { 0x00, 0x00, 0x00 }, |
| { 0x00, 0x00, 0xaa }, |
| { 0x00, 0xaa, 0x00 }, |
| { 0x00, 0xaa, 0xaa }, |
| { 0xaa, 0x00, 0x00 }, |
| { 0xaa, 0x00, 0xaa }, |
| { 0xaa, 0x55, 0x00 }, |
| { 0xaa, 0xaa, 0xaa }, |
| { 0x55, 0x55, 0x55 }, |
| { 0x55, 0x55, 0xff }, |
| { 0x55, 0xff, 0x55 }, |
| { 0x55, 0xff, 0xff }, |
| { 0xff, 0x55, 0x55 }, |
| { 0xff, 0x55, 0xff }, |
| { 0xff, 0xff, 0x55 }, |
| { 0xff, 0xff, 0xff }, |
| }; |
| |
| |
| static int logo_type = LINUX_LOGO_CLUT224; |
| static unsigned int logo_width; |
| static unsigned int logo_height; |
| static struct color **logo_data; |
| static struct color logo_clut[MAX_LINUX_LOGO_COLORS]; |
| static unsigned int logo_clutsize; |
| static int is_plain_pbm = 0; |
| |
| static void die(const char *fmt, ...) |
| __attribute__((noreturn)) __attribute((format (printf, 1, 2))); |
| static void usage(void) __attribute((noreturn)); |
| |
| |
| static unsigned int get_number(FILE *fp) |
| { |
| int c, val; |
| |
| /* Skip leading whitespace */ |
| do { |
| c = fgetc(fp); |
| if (c == EOF) |
| die("%s: end of file\n", filename); |
| if (c == '#') { |
| /* Ignore comments 'till end of line */ |
| do { |
| c = fgetc(fp); |
| if (c == EOF) |
| die("%s: end of file\n", filename); |
| } while (c != '\n'); |
| } |
| } while (isspace(c)); |
| |
| /* Parse decimal number */ |
| val = 0; |
| while (isdigit(c)) { |
| val = 10*val+c-'0'; |
| /* some PBM are 'broken'; GiMP for example exports a PBM without space |
| * between the digits. This is Ok cause we know a PBM can only have a '1' |
| * or a '0' for the digit. |
| */ |
| if (is_plain_pbm) |
| break; |
| c = fgetc(fp); |
| if (c == EOF) |
| die("%s: end of file\n", filename); |
| } |
| return val; |
| } |
| |
| static unsigned int get_number255(FILE *fp, unsigned int maxval) |
| { |
| unsigned int val = get_number(fp); |
| |
| return (255*val+maxval/2)/maxval; |
| } |
| |
| static void read_image(void) |
| { |
| FILE *fp; |
| unsigned int i, j; |
| int magic; |
| unsigned int maxval; |
| |
| /* open image file */ |
| fp = fopen(filename, "r"); |
| if (!fp) |
| die("Cannot open file %s: %s\n", filename, strerror(errno)); |
| |
| /* check file type and read file header */ |
| magic = fgetc(fp); |
| if (magic != 'P') |
| die("%s is not a PNM file\n", filename); |
| magic = fgetc(fp); |
| switch (magic) { |
| case '1': |
| case '2': |
| case '3': |
| /* Plain PBM/PGM/PPM */ |
| break; |
| |
| case '4': |
| case '5': |
| case '6': |
| /* Binary PBM/PGM/PPM */ |
| die("%s: Binary PNM is not supported\n" |
| "Use pnmnoraw(1) to convert it to ASCII PNM\n", filename); |
| |
| default: |
| die("%s is not a PNM file\n", filename); |
| } |
| logo_width = get_number(fp); |
| logo_height = get_number(fp); |
| |
| /* allocate image data */ |
| logo_data = (struct color **)malloc(logo_height*sizeof(struct color *)); |
| if (!logo_data) |
| die("%s\n", strerror(errno)); |
| for (i = 0; i < logo_height; i++) { |
| logo_data[i] = malloc(logo_width*sizeof(struct color)); |
| if (!logo_data[i]) |
| die("%s\n", strerror(errno)); |
| } |
| |
| /* read image data */ |
| switch (magic) { |
| case '1': |
| /* Plain PBM */ |
| is_plain_pbm = 1; |
| for (i = 0; i < logo_height; i++) |
| for (j = 0; j < logo_width; j++) |
| logo_data[i][j].red = logo_data[i][j].green = |
| logo_data[i][j].blue = 255*(1-get_number(fp)); |
| break; |
| |
| case '2': |
| /* Plain PGM */ |
| maxval = get_number(fp); |
| for (i = 0; i < logo_height; i++) |
| for (j = 0; j < logo_width; j++) |
| logo_data[i][j].red = logo_data[i][j].green = |
| logo_data[i][j].blue = get_number255(fp, maxval); |
| break; |
| |
| case '3': |
| /* Plain PPM */ |
| maxval = get_number(fp); |
| for (i = 0; i < logo_height; i++) |
| for (j = 0; j < logo_width; j++) { |
| logo_data[i][j].red = get_number255(fp, maxval); |
| logo_data[i][j].green = get_number255(fp, maxval); |
| logo_data[i][j].blue = get_number255(fp, maxval); |
| } |
| break; |
| } |
| |
| /* close file */ |
| fclose(fp); |
| } |
| |
| static inline int is_black(struct color c) |
| { |
| return c.red == 0 && c.green == 0 && c.blue == 0; |
| } |
| |
| static inline int is_white(struct color c) |
| { |
| return c.red == 255 && c.green == 255 && c.blue == 255; |
| } |
| |
| static inline int is_gray(struct color c) |
| { |
| return c.red == c.green && c.red == c.blue; |
| } |
| |
| static inline int is_equal(struct color c1, struct color c2) |
| { |
| return c1.red == c2.red && c1.green == c2.green && c1.blue == c2.blue; |
| } |
| |
| static void write_header(void) |
| { |
| /* open logo file */ |
| if (outputname) { |
| out = fopen(outputname, "w"); |
| if (!out) |
| die("Cannot create file %s: %s\n", outputname, strerror(errno)); |
| } else { |
| out = stdout; |
| } |
| |
| fputs("/*\n", out); |
| fputs(" * DO NOT EDIT THIS FILE!\n", out); |
| fputs(" *\n", out); |
| fprintf(out, " * Linux logo %s\n", logoname); |
| fputs(" */\n\n", out); |
| fputs("#include <linux/linux_logo.h>\n\n", out); |
| fprintf(out, "static const unsigned char %s_data[] __initconst = {\n", |
| logoname); |
| } |
| |
| static void write_footer(void) |
| { |
| fputs("\n};\n\n", out); |
| fprintf(out, "const struct linux_logo %s __initconst = {\n", logoname); |
| fprintf(out, "\t.type\t\t= %s,\n", logo_types[logo_type]); |
| fprintf(out, "\t.width\t\t= %u,\n", logo_width); |
| fprintf(out, "\t.height\t\t= %u,\n", logo_height); |
| if (logo_type == LINUX_LOGO_CLUT224) { |
| fprintf(out, "\t.clutsize\t= %u,\n", logo_clutsize); |
| fprintf(out, "\t.clut\t\t= %s_clut,\n", logoname); |
| } |
| fprintf(out, "\t.data\t\t= %s_data\n", logoname); |
| fputs("};\n\n", out); |
| |
| /* close logo file */ |
| if (outputname) |
| fclose(out); |
| } |
| |
| static int write_hex_cnt; |
| |
| static void write_hex(unsigned char byte) |
| { |
| if (write_hex_cnt % 12) |
| fprintf(out, ", 0x%02x", byte); |
| else if (write_hex_cnt) |
| fprintf(out, ",\n\t0x%02x", byte); |
| else |
| fprintf(out, "\t0x%02x", byte); |
| write_hex_cnt++; |
| } |
| |
| static void write_logo_mono(void) |
| { |
| unsigned int i, j; |
| unsigned char val, bit; |
| |
| /* validate image */ |
| for (i = 0; i < logo_height; i++) |
| for (j = 0; j < logo_width; j++) |
| if (!is_black(logo_data[i][j]) && !is_white(logo_data[i][j])) |
| die("Image must be monochrome\n"); |
| |
| /* write file header */ |
| write_header(); |
| |
| /* write logo data */ |
| for (i = 0; i < logo_height; i++) { |
| for (j = 0; j < logo_width;) { |
| for (val = 0, bit = 0x80; bit && j < logo_width; j++, bit >>= 1) |
| if (logo_data[i][j].red) |
| val |= bit; |
| write_hex(val); |
| } |
| } |
| |
| /* write logo structure and file footer */ |
| write_footer(); |
| } |
| |
| static void write_logo_vga16(void) |
| { |
| unsigned int i, j, k; |
| unsigned char val; |
| |
| /* validate image */ |
| for (i = 0; i < logo_height; i++) |
| for (j = 0; j < logo_width; j++) { |
| for (k = 0; k < 16; k++) |
| if (is_equal(logo_data[i][j], clut_vga16[k])) |
| break; |
| if (k == 16) |
| die("Image must use the 16 console colors only\n" |
| "Use ppmquant(1) -map clut_vga16.ppm to reduce the number " |
| "of colors\n"); |
| } |
| |
| /* write file header */ |
| write_header(); |
| |
| /* write logo data */ |
| for (i = 0; i < logo_height; i++) |
| for (j = 0; j < logo_width; j++) { |
| for (k = 0; k < 16; k++) |
| if (is_equal(logo_data[i][j], clut_vga16[k])) |
| break; |
| val = k<<4; |
| if (++j < logo_width) { |
| for (k = 0; k < 16; k++) |
| if (is_equal(logo_data[i][j], clut_vga16[k])) |
| break; |
| val |= k; |
| } |
| write_hex(val); |
| } |
| |
| /* write logo structure and file footer */ |
| write_footer(); |
| } |
| |
| static void write_logo_clut224(void) |
| { |
| unsigned int i, j, k; |
| |
| /* validate image */ |
| for (i = 0; i < logo_height; i++) |
| for (j = 0; j < logo_width; j++) { |
| for (k = 0; k < logo_clutsize; k++) |
| if (is_equal(logo_data[i][j], logo_clut[k])) |
| break; |
| if (k == logo_clutsize) { |
| if (logo_clutsize == MAX_LINUX_LOGO_COLORS) |
| die("Image has more than %d colors\n" |
| "Use ppmquant(1) to reduce the number of colors\n", |
| MAX_LINUX_LOGO_COLORS); |
| logo_clut[logo_clutsize++] = logo_data[i][j]; |
| } |
| } |
| |
| /* write file header */ |
| write_header(); |
| |
| /* write logo data */ |
| for (i = 0; i < logo_height; i++) |
| for (j = 0; j < logo_width; j++) { |
| for (k = 0; k < logo_clutsize; k++) |
| if (is_equal(logo_data[i][j], logo_clut[k])) |
| break; |
| write_hex(k+32); |
| } |
| fputs("\n};\n\n", out); |
| |
| /* write logo clut */ |
| fprintf(out, "static const unsigned char %s_clut[] __initconst = {\n", |
| logoname); |
| write_hex_cnt = 0; |
| for (i = 0; i < logo_clutsize; i++) { |
| write_hex(logo_clut[i].red); |
| write_hex(logo_clut[i].green); |
| write_hex(logo_clut[i].blue); |
| } |
| |
| /* write logo structure and file footer */ |
| write_footer(); |
| } |
| |
| static void write_logo_gray256(void) |
| { |
| unsigned int i, j; |
| |
| /* validate image */ |
| for (i = 0; i < logo_height; i++) |
| for (j = 0; j < logo_width; j++) |
| if (!is_gray(logo_data[i][j])) |
| die("Image must be grayscale\n"); |
| |
| /* write file header */ |
| write_header(); |
| |
| /* write logo data */ |
| for (i = 0; i < logo_height; i++) |
| for (j = 0; j < logo_width; j++) |
| write_hex(logo_data[i][j].red); |
| |
| /* write logo structure and file footer */ |
| write_footer(); |
| } |
| |
| static void die(const char *fmt, ...) |
| { |
| va_list ap; |
| |
| va_start(ap, fmt); |
| vfprintf(stderr, fmt, ap); |
| va_end(ap); |
| |
| exit(1); |
| } |
| |
| static void usage(void) |
| { |
| die("\n" |
| "Usage: %s [options] <filename>\n" |
| "\n" |
| "Valid options:\n" |
| " -h : display this usage information\n" |
| " -n <name> : specify logo name (default: linux_logo)\n" |
| " -o <output> : output to file <output> instead of stdout\n" |
| " -t <type> : specify logo type, one of\n" |
| " mono : monochrome black/white\n" |
| " vga16 : 16 colors VGA text palette\n" |
| " clut224 : 224 colors (default)\n" |
| " gray256 : 256 levels grayscale\n" |
| "\n", programname); |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int opt; |
| |
| programname = argv[0]; |
| |
| opterr = 0; |
| while (1) { |
| opt = getopt(argc, argv, "hn:o:t:"); |
| if (opt == -1) |
| break; |
| |
| switch (opt) { |
| case 'h': |
| usage(); |
| break; |
| |
| case 'n': |
| logoname = optarg; |
| break; |
| |
| case 'o': |
| outputname = optarg; |
| break; |
| |
| case 't': |
| if (!strcmp(optarg, "mono")) |
| logo_type = LINUX_LOGO_MONO; |
| else if (!strcmp(optarg, "vga16")) |
| logo_type = LINUX_LOGO_VGA16; |
| else if (!strcmp(optarg, "clut224")) |
| logo_type = LINUX_LOGO_CLUT224; |
| else if (!strcmp(optarg, "gray256")) |
| logo_type = LINUX_LOGO_GRAY256; |
| else |
| usage(); |
| break; |
| |
| default: |
| usage(); |
| break; |
| } |
| } |
| if (optind != argc-1) |
| usage(); |
| |
| filename = argv[optind]; |
| |
| read_image(); |
| switch (logo_type) { |
| case LINUX_LOGO_MONO: |
| write_logo_mono(); |
| break; |
| |
| case LINUX_LOGO_VGA16: |
| write_logo_vga16(); |
| break; |
| |
| case LINUX_LOGO_CLUT224: |
| write_logo_clut224(); |
| break; |
| |
| case LINUX_LOGO_GRAY256: |
| write_logo_gray256(); |
| break; |
| } |
| exit(0); |
| } |