/* png8 - Simple PNG sprite strip converter for assembly targets Copyright (C) 2016 Ian Cowburn This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . ------------------------------------------------------------------------- Main */ #include #include #include #include #include #include #include /* ---------------------------------------- MACROS */ #ifndef TRUE #define TRUE (1) #endif #ifndef FALSE #define FALSE (0) #endif #define COLOUR(ul) ((ul) & 0x00FFFFFFul) #define IS_BLACK(ul) (COLOUR(ul) == 0) #define IS_TRANS(ul) (((ul) && 0xFF000000ul) == 0) /* ---------------------------------------- VERSION INFO */ static const char *png8_usage = "Version 1.0 development\n" "\n" "This program is distributed in the hope that it will be useful,\n" "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" "GNU General Public License (Version 3) for more details.\n" "\n" "usage: png8 -- convert a file\n" "usage: png8 -h -- get help on \n"; /* ---------------------------------------- TYPES */ typedef enum { OUTPUT_Undefined, OUTPUT_Mono, OUTPUT_4Col } output_type_t; typedef unsigned long ulong; /* ---------------------------------------- HELP & USAGE */ static void Usage(void) { fprintf(stderr,"%s\n", png8_usage); exit(EXIT_FAILURE); } static void Help(void) { static const char *help = "Usage:\n" " png8 \n" "\nGeneral switches:\n" "-h - This help text.\n" "\nOne of these switches must be provided:\n" "-1 - Produce a monochrome sprite data set (e.g. Spectrum).\n" "-4 - Produce a 4-colour sprite data set (e.g. Commodore 64).\n" "\nOptional switches:\n" "-b - Don't treat black the same as a transparent pixel.\n" "-p - Use in place of 'byte' for output.\n" "-s - For monochrome data, produce pre-shifted values.\n" "-w - Set the sprite width to . This must be a multiple of 8.\n" "-d - Don't use double-width pixels in 4 colour mode.\n" "-c0 - Set colour 0 to RGB value RRGGBB, expressed as a hex number.\n" "-c1 - Set colour 1 to RGB value RRGGBB, expressed as a hex number.\n" "-c2 - Set colour 2 to RGB value RRGGBB, expressed as a hex number.\n" "-c3 - Set colour 3 to RGB value RRGGBB, expressed as a hex number.\n" ""; fprintf(stderr,"%s\n", help); exit(EXIT_SUCCESS); } /* ---------------------------------------- UTIL */ static void Exit(const char *fmt, ...) { va_list va; va_start(va, fmt); vfprintf(stderr, fmt, va); va_end(va); exit(EXIT_FAILURE); } static void Warning(const char *fmt, ...) { va_list va; fprintf(stderr, "WARNING: "); va_start(va, fmt); vfprintf(stderr, fmt, va); va_end(va); } static void *Malloc(size_t size) { void *p = malloc(size); if (!p) { Exit("memory allocation failed\n"); } return p; } static ulong RGBA(png_byte r, png_byte g, png_byte b, png_byte a) { return ((ulong)r << 16ul) | ((ulong)g << 8ul) | ((ulong)b) | ((ulong)a << 24ul); } static void PrintSpriteData(const char *opcode, ulong sprite_data[], int max, int pre_shift) { int f; printf("\t%s ", opcode); for(f = 0; f < max; f++) { if (f) { printf(","); } printf("%lu", sprite_data[f]); } if (pre_shift) { printf(",0"); } printf("\n"); } static void PrintSpriteDataShifted(const char *opcode, ulong sprite_data[], int max) { int s; int f; ulong data[8][5] = {0}; for(s = 1; s < 8; s++) { for(f = 0; f <= max; f++) { if (f == max) { data[s][f] = (sprite_data[f-1] << (8 - s)) & 0xff; } else if (f) { data[s][f] = ((sprite_data[f] >> s) | (sprite_data[f-1] << (8 - s))) & 0xff; } else { data[s][f] = sprite_data[f] >> s; } } } max++; for(s = 1; s < 8; s++) { printf("\t%s ", opcode); for(f = 0; f < max; f++) { if (f) { printf(","); } printf("%lu", data[s][f]); } printf("\n"); } } /* ---------------------------------------- MAIN */ int main(int argc, char *argv[]) { png_structp png; png_infop png_info; int num_passes; png_bytep *data; int width, height; png_byte colour_type, colour_depth; const char *opcode = "byte"; output_type_t output = OUTPUT_Undefined; int pre_shift = FALSE; int use_black = TRUE; int sprite_width = 8; int pix_per_byte = 8; int step = 1; ulong col[4] = {0, 0xff0000ul, 0x00ff00ul, 0x0000fful}; int f; FILE *fp; char header[8]; f = 1; while(argv[f] && argv[f][0] == '-') { switch(argv[f][1]) { case 'h': Help(); break; case '1': output = OUTPUT_Mono; break; case '4': output = OUTPUT_4Col; sprite_width = 24; step = 2; pix_per_byte = 4; pre_shift = FALSE; break; case 'b': use_black = FALSE; break; case 's': if (output != OUTPUT_4Col) { pre_shift = TRUE; } break; case 'd': step = 1; break; case 'c': { int c = argv[f][2] - '0'; if (argv[++f] && c >= 0 && c <= 3) { col[c] = strtoul(argv[f], NULL, 16); } else { Usage(); } break; } case 'p': if (argv[++f]) { opcode = argv[f]; } else { Usage(); } break; case 'w': if (argv[++f]) { sprite_width = atoi(argv[f]); if (sprite_width % 8) { Exit("Sprite width must be a multiple of 8.\n"); } if (sprite_width < 8 || sprite_width > 32) { Exit("Sprite width mustn't be greater than " "32 or less than 8.\n"); } } else { Usage(); } break; default: Usage(); break; } f++; } if (output == OUTPUT_Undefined || !argv[f]) { Usage(); } if (!(fp = fopen(argv[f], "rb"))) { Exit("Failed to open %s\n", argv[f]); } fread(header, 1, sizeof header, fp); if (png_sig_cmp(header, 0, sizeof header)) { Exit("%s not recognised as a PNG file\n", argv[f]); } if (!(png = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL))) { Exit("png_create_read_struct failed\n"); } if (!(png_info = png_create_info_struct(png))) { Exit("png_create_info_struct failed\n"); } if (setjmp(png_jmpbuf(png))) { Exit("png_init_io failed\n"); } png_init_io(png, fp); png_set_sig_bytes(png, sizeof header); png_read_info(png, png_info); width = png_get_image_width(png, png_info); height = png_get_image_height(png, png_info); colour_type = png_get_color_type(png, png_info); colour_depth = png_get_bit_depth(png, png_info); num_passes = png_set_interlace_handling(png); if (width < sprite_width * step) { Exit("Width %d less than sprite width %d\n", width, sprite_width * step); } if (width % (sprite_width * step)) { Warning("Width not a multiple of %d.\n", sprite_width * step); } if (sprite_width > 16 && output == OUTPUT_4Col) { Exit("Width must be 16 or less in 4-colour mode.\n"); } if (colour_type != PNG_COLOR_TYPE_RGBA) { Exit("PNG must be an RGB image with an alpha channel.\n"); } png_read_update_info(png, png_info); data = Malloc((sizeof *data) * height); for(f = 0; f < height; f++) { data[f] = Malloc(png_get_rowbytes(png, png_info)); } if (setjmp(png_jmpbuf(png))) { Exit("png_read_image failed\n"); } png_read_image(png, data); fclose(fp); for(f = 0; f < width/(sprite_width*step); f++) { int x,y; int bx; printf("sprite_%d:\n", f); bx = sprite_width * step * f * 4; for(y = 0; y < height; y++) { ulong sprite_data[4] = {0}; int i = 0; for(x = 0; x < sprite_width; x++) { ulong pcol = RGBA(data[y][bx + x * 4 * step + 0], data[y][bx + x * 4 * step + 1], data[y][bx + x * 4 * step + 2], data[y][bx + x * 4 * step + 3]); if ((x) && (x % pix_per_byte) == 0) { i++; } switch(output) { case OUTPUT_Mono: if (IS_TRANS(pcol) || (use_black && IS_BLACK(pcol))) { sprite_data[i] <<= 1ul; sprite_data[i] |= 0; } else { sprite_data[i] <<= 1ul; sprite_data[i] |= 1; } break; case OUTPUT_4Col: if (COLOUR(pcol) == col[1]) { sprite_data[i] <<= 2ul; sprite_data[i] |= 0x01; } else if (COLOUR(pcol) == col[2]) { sprite_data[i] <<= 2ul; sprite_data[i] |= 0x02; } else if (COLOUR(pcol) == col[3]) { sprite_data[i] <<= 2ul; sprite_data[i] |= 0x03; } else { sprite_data[i] <<= 2ul; sprite_data[i] |= 0; } break; default: break; } } if ((x) && (x % pix_per_byte) == 0) { i++; } PrintSpriteData(opcode, sprite_data, i, pre_shift); if (pre_shift) { PrintSpriteDataShifted(opcode, sprite_data, i); } } } for(f = 0; f < height; f++) { free(data[f]); } free(data); return EXIT_SUCCESS; } /* vim: ai sw=4 ts=8 expandtab */