git.y1.nz

gbdk-2020

GameBoy Development Kit
download: https://git.y1.nz/archives/gbdk.tar.gz
README | Files | Log | Refs | LICENSE

commit bc9429d002c4daab51c71d4cf356ea9ab54b4ce4
parent 22f287b1b643b23551aa81cc29bc8852ec78133d
Author: bbbbbr <bbbbbr@users.noreply.github.com>
Date:   Sat, 11 Jan 2025 02:00:57 -0800

Merge pull request #741 from bbbbbr/tools/png2hicolor_updates

png2hicolorgb: updates + add option -s to specify variable/symbol name in C output
Diffstat:
Mgbdk-support/png2hicolorgb/src/c_source.c11+++++++----
Mgbdk-support/png2hicolorgb/src/c_source.h4+++-
Mgbdk-support/png2hicolorgb/src/common.h11+++++++++++
Mgbdk-support/png2hicolorgb/src/files.c18++++++++----------
Mgbdk-support/png2hicolorgb/src/files.h13++++++++-----
Mgbdk-support/png2hicolorgb/src/hicolor/defines.h29+++++++++++++++++++++++++++++
Mgbdk-support/png2hicolorgb/src/hicolor/hicolour.c372++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mgbdk-support/png2hicolorgb/src/hicolor/hicolour.h25++++++++++++++++++-------
Mgbdk-support/png2hicolorgb/src/hicolor/median.c19+++++++++----------
Mgbdk-support/png2hicolorgb/src/hicolor/median.h6++++--
Mgbdk-support/png2hicolorgb/src/image_info.h4++--
Mgbdk-support/png2hicolorgb/src/image_load.c22+++++++++++-----------
Mgbdk-support/png2hicolorgb/src/image_load.h2++
Mgbdk-support/png2hicolorgb/src/logging.c38+++++++++++---------------------------
Mgbdk-support/png2hicolorgb/src/logging.h21++++++++++-----------
Mgbdk-support/png2hicolorgb/src/main.c177++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
Mgbdk-support/png2hicolorgb/src/options.c32++++++++++++++++++++++++++++++++
Mgbdk-support/png2hicolorgb/src/options.h15++++++++++++++-
Mgbdk-support/png2hicolorgb/src/path_ops.c2++
Mgbdk-support/png2hicolorgb/src/path_ops.h6+++---
Mgbdk-support/png2hicolorgb/src/tile_dedupe.c2+-
Mgbdk-support/png2hicolorgb/src/tile_dedupe.h2+-
22 files changed, 581 insertions(+), 250 deletions(-)

diff --git a/gbdk-support/png2hicolorgb/src/c_source.c b/gbdk-support/png2hicolorgb/src/c_source.c @@ -16,18 +16,21 @@ // Writes a buffer to a file in C source format // Adds a matching .h if possible // -bool file_c_output_write(const char * fname_base, int bank_num, int tile_count, int height_in_tiles) { +bool file_c_output_write(const char * fname_base, const char * varname_in, int bank_num, int tile_count, int height_in_tiles) { char varname_withpath[MAX_PATH * 2]; char filename_c[MAX_PATH * 2]; char filename_h[MAX_PATH * 2]; - strcpy(varname_withpath, fname_base); + strcpy(varname_withpath, varname_in); strcpy(filename_c, fname_base); strcpy(filename_h, fname_base); strcat(filename_c, ".c"); strcat(filename_h, ".h"); - // Fix up filename to be usable in the C source file + // Fix up varname if needed to be usable in the C source file + // + // If varname arg wasn't specified it will be derived from + // filename out and may include path that needs to be stripped. char * varname = (char *)get_filename_from_path(varname_withpath); char * ch = varname; while (*ch != '\0') { @@ -35,7 +38,7 @@ bool file_c_output_write(const char * fname_base, int bank_num, int tile_count, ch++; } - log_verbose("Writing C format to: %s, %s\n", filename_c, filename_h); + VERBOSE("Writing C format to: %s, %s\n", filename_c, filename_h); // === C Source output === FILE * file_out_c = fopen(filename_c, "w"); diff --git a/gbdk-support/png2hicolorgb/src/c_source.h b/gbdk-support/png2hicolorgb/src/c_source.h @@ -3,7 +3,9 @@ #ifndef C_SOURCE_H #define C_SOURCE_H -bool file_c_output_write(const char * fname_base, int bank_num, int tile_count, int height_in_tiles); +#include <stdbool.h> + +bool file_c_output_write(const char * fname_base, const char * varname_in, int bank_num, int tile_count, int height_in_tiles); #endif diff --git a/gbdk-support/png2hicolorgb/src/common.h b/gbdk-support/png2hicolorgb/src/common.h @@ -3,6 +3,8 @@ #ifndef _COMMON_H #define _COMMON_H +#include <stdbool.h> + #define TILE_HEIGHT_PX 8 #define TILE_WIDTH_PX 8 @@ -13,6 +15,15 @@ #define MAX_PATH (MAX_STR_LEN) +#ifndef __has_attribute +#define __has_attribute(attr) 0 +#endif +#if __has_attribute(format) +#define FMT(kind, str_idx, first_to_check) __attribute__((format(kind, str_idx, first_to_check))) +#else +#define FMT(kind, str_idx, first_to_check) +#endif + enum { IMG_TYPE_PNG }; diff --git a/gbdk-support/png2hicolorgb/src/files.c b/gbdk-support/png2hicolorgb/src/files.c @@ -7,7 +7,7 @@ // Read from a file into a buffer (will allocate needed memory) // Returns NULL if reading file didn't succeed -uint8_t * file_read_into_buffer(char * filename, uint32_t *ret_size) { +uint8_t * file_read_into_buffer(char * filename, size_t *ret_size) { long fsize; FILE * file_in = fopen(filename, "rb"); @@ -22,12 +22,12 @@ uint8_t * file_read_into_buffer(char * filename, uint32_t *ret_size) { filedata = (uint8_t *)malloc(fsize); if (filedata) { - if (fsize != fread(filedata, 1, fsize, file_in)) { + if (fsize != (long)fread(filedata, 1, fsize, file_in)) { printf("Warning: File read size didn't match expected for %s\n", filename); filedata = NULL; } // Read was successful, set return size - *ret_size = fsize; + *ret_size = (size_t)fsize; } else printf("ERROR: Failed to allocate memory to read file %s\n", filename); } else printf("ERROR: Failed to read size of file %s\n", filename); @@ -41,10 +41,9 @@ uint8_t * file_read_into_buffer(char * filename, uint32_t *ret_size) { // Writes a buffer to a file -bool file_write_from_buffer(char * filename, uint8_t * p_buf, uint32_t data_len) { +bool file_write_from_buffer(char * filename, uint8_t * p_buf, size_t data_len) { bool status = false; - size_t wrote_bytes; FILE * file_out = fopen(filename, "wb"); if (file_out) { @@ -64,7 +63,7 @@ bool file_write_from_buffer(char * filename, uint8_t * p_buf, uint32_t data_len) // Read from a file into a buffer (will allocate needed memory) // Returns NULL if reading file didn't succeed -char * file_read_into_buffer_char(char * filename, uint32_t *ret_size) { +char * file_read_into_buffer_char(char * filename, size_t *ret_size) { long fsize; // On windows windows fread() will auto-translate CRLF to just LF and @@ -81,12 +80,12 @@ char * file_read_into_buffer_char(char * filename, uint32_t *ret_size) { filedata = (char *)malloc(fsize); if (filedata) { - if (fsize != fread(filedata, 1, fsize, file_in)) { + if (fsize != (long)fread(filedata, 1, fsize, file_in)) { printf("Warning: File read size didn't match expected for %s\n", filename); filedata = NULL; } // Read was successful, set return size - *ret_size = fsize; + *ret_size = (size_t)fsize; } else printf("ERROR: Failed to allocate memory to read file %s\n", filename); } else printf("ERROR: Failed to read size of file %s\n", filename); @@ -100,10 +99,9 @@ char * file_read_into_buffer_char(char * filename, uint32_t *ret_size) { // Writes a buffer to a file -bool file_write_from_buffer_char(char * filename, char * p_buf, uint32_t data_len) { +bool file_write_from_buffer_char(char * filename, char * p_buf, size_t data_len) { bool status = false; - size_t wrote_bytes; FILE * file_out = fopen(filename, "w"); if (file_out) { diff --git a/gbdk-support/png2hicolorgb/src/files.h b/gbdk-support/png2hicolorgb/src/files.h @@ -1,11 +1,14 @@ #ifndef _FILES_H #define _FILES_H -uint8_t * file_read_into_buffer(char * filename, uint32_t *ret_size); -bool file_write_from_buffer(char * filename, uint8_t * p_buf, uint32_t data_len); +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> -char * file_read_into_buffer_char(char * filename, uint32_t *ret_size); -bool file_write_from_buffer_char(char * filename, char * p_buf, uint32_t data_len); +uint8_t * file_read_into_buffer(char * filename, size_t *ret_size); +bool file_write_from_buffer(char * filename, uint8_t * p_buf, size_t data_len); -#endif // _FILES_H +char * file_read_into_buffer_char(char * filename, size_t *ret_size); +bool file_write_from_buffer_char(char * filename, char * p_buf, size_t data_len); +#endif // _FILES_H diff --git a/gbdk-support/png2hicolorgb/src/hicolor/defines.h b/gbdk-support/png2hicolorgb/src/hicolor/defines.h @@ -47,12 +47,36 @@ enum conversion_types { CONV_TYPE_MAX = CONV_TYPE_WU }; +enum pattern_types { + HICOLOR_PATTERN_ADAPTIVE_FAST = 0, + HICOLOR_PATTERN_ADAPTIVE_MED = 1, + HICOLOR_PATTERN_ADAPTIVE_BEST = 2, + + HICOLOR_PATTERN_ADAPTIVE_COUNT = 3, + HICOLOR_PATTERN_FIXED_COUNT = 80, + HICOLOR_PATTERN_MIN = 0, + HICOLOR_PATTERN_MAX = (HICOLOR_PATTERN_FIXED_COUNT - 1), + + HICOLOR_PATTERN_OPT_MIN = 0, + HICOLOR_PATTERN_OPT_MAX = ((HICOLOR_PATTERN_FIXED_COUNT + HICOLOR_PATTERN_ADAPTIVE_COUNT) - 1), + + HICOLOR_PATTERN_NOT_FOUND = 998, + HICOLOR_PATTERN_NOT_FOUND_HAS_CHARS = 999 +}; + #define CONV_SIDE_LEFT 0 #define CONV_SIDE_RIGHT 1 #define CONV_Y_SHIFT_UP_1 1 #define CONV_Y_SHIFT_NO 0 +#define SM83_OPCODE_HALT 0x76u +#define SM83_OPCODE_LD_HL_B 0x70u +#define SM83_OPCODE_LD_HL_C 0x71u +#define SM83_OPCODE_LD_HL_D 0x72u +#define SM83_OPCODE_LD_HL_E 0x73u +#define SM83_OPCODE_LD_HL_IMM8 0x36u +#define SM83_OPCODE_RET 0xC9u #define PAL_REGION_HEIGHT_PX 2 @@ -66,4 +90,9 @@ enum conversion_types { #define VALIDATE_WIDTH 160 // (BUF_WIDTH) #define VALIDATE_HEIGHT (BUF_HEIGHT) +#define BYTES_PER_COLOR 2 +#define COLORS_PER_PAL 4 +#define PALS_PER_SIDE 4 + + #endif diff --git a/gbdk-support/png2hicolorgb/src/hicolor/hicolour.c b/gbdk-support/png2hicolorgb/src/hicolor/hicolour.c @@ -1,6 +1,8 @@ +#include <stddef.h> #include <stdio.h> #include <string.h> +#include <ctype.h> #include <stdbool.h> #include <stdint.h> @@ -10,13 +12,13 @@ #include "wu.h" -#include <common.h> -#include <options.h> -#include <files.h> -#include <image_info.h> -#include <logging.h> -#include <c_source.h> -#include <tile_dedupe.h> +#include "common.h" +#include "options.h" +#include "files.h" +#include "image_info.h" +#include "logging.h" +#include "c_source.h" +#include "tile_dedupe.h" /* Gameboy Hi-Colour Convertor */ /* Glen Cook */ @@ -143,17 +145,23 @@ static uint8_t *pBitsdest = Bitsdest; #define MAX_CONVERSION_TYPES 83 #define MAX_QUANTISER_TYPES 4 -int image_y_min; -int image_y_max; -int image_height; -int y_region_count_left; -int y_region_count_right; -int y_region_count_lr_rndup; -int y_region_count_both_sides; -int y_height_in_tiles_left; -int y_height_in_tiles_right; -int y_height_in_tiles; -int y_height_in_tiles_lr_rndup; +pattern_entry named_patterns[] = { + {.num = HICOLOR_PATTERN_ADAPTIVE_FAST, .name="adaptive-fast"}, + {.num = HICOLOR_PATTERN_ADAPTIVE_MED, .name="adaptive-medium"}, + {.num = HICOLOR_PATTERN_ADAPTIVE_BEST, .name="adaptive-best"} +}; + +static unsigned int image_y_min; +static unsigned int image_y_max; +static unsigned int image_height; +static unsigned int y_region_count_left; +static unsigned int y_region_count_right; +static unsigned int y_region_count_lr_rndup; +static unsigned int y_region_count_both_sides; +static unsigned int y_height_in_tiles_left; +static unsigned int y_height_in_tiles_right; +static unsigned int y_height_in_tiles; +static unsigned int y_height_in_tiles_lr_rndup; static void PrepareTileSet(void); @@ -163,6 +171,7 @@ static void PrepareAttributes(void); static void DedupeTileset(void); static void ExportPalettes(const char * fname_base); +static void ExportPalettesPrecompiled(const char * fname_base); static void ExportTileSet(const char * fname_base); static void ExportMap(const char * fname_base); static void ExportMapAttributes(const char * fname_base); @@ -170,13 +179,14 @@ static void ExportMapAttributes(const char * fname_base); void hicolor_init(void) { // Defaults - LConversion = 3; // Default Conversion (Fixed 3-2-3-2) Left Screen - RConversion = 3; // Default Conversion (Fixed 3-2-3-2) Righ Screen - ConvertType = 1; // Normal default is 1 ("Median cut - no dither") + LConversion = HICOLOR_PATTERN_ADAPTIVE_MED; // Default Conversion adaptive-medium Left Screen + RConversion = HICOLOR_PATTERN_ADAPTIVE_MED; // Default Conversion adaptive-medium Righ Screen + ConvertType = CONV_TYPE_MED_CUT_NO_DITHER; // Normal default is 1 ("Median cut - no dither") } + static void hicolor_vars_prep(image_data * p_loaded_image) { - log_debug("hicolor_vars_prep()\n"); + DBG("hicolor_vars_prep()\n"); image_height = p_loaded_image->height; image_y_min = 0; @@ -204,25 +214,53 @@ static void hicolor_vars_prep(image_data * p_loaded_image) { } +// Look up user specified L/R pattern by name if possible +unsigned int hicolor_get_pattern_by_name(const char * opt_str) { + char opt_str_lower[MAX_STR_LEN]; + unsigned int c; + + // Convert user input to lowercase first + for(c = 0; (opt_str[c] != '\0') && (c < MAX_STR_LEN); c++) + opt_str_lower[c] = tolower(opt_str[c]); + opt_str_lower[c] = '\0'; + + // Return if it matches any names in the named pattern list + for (c = 0; c < ARRAY_LEN(named_patterns); c++) { + if (strcmp(opt_str_lower, named_patterns[c].name) == 0) + return named_patterns[c].num; + } + + // If there was no match, return if it contained any non-digit characters, + // meaning it should not later be converted as a raw numeric value for the option + while (*opt_str != '\0') { + if (isdigit(*opt_str) == 0) { + return HICOLOR_PATTERN_NOT_FOUND_HAS_CHARS; + } + opt_str++; + } + + return HICOLOR_PATTERN_NOT_FOUND; +} + void hicolor_set_convert_left_pattern(uint8_t new_value) { // IDC_CONVERTLEFT LConversion = new_value; - log_verbose("HiColor: Left pattern set to %d\n", new_value); + VERBOSE("HiColor: Left pattern set to %d\n", new_value); } void hicolor_set_convert_right_pattern(uint8_t new_value) { // IDC_CONVERTRIGHT RConversion = new_value; - log_verbose("HiColor: Right pattern set to %d\n", new_value); + VERBOSE("HiColor: Right pattern set to %d\n", new_value); } void hicolor_set_type(uint8_t new_value) { // IDC_CONVERTTYPE ConvertType = new_value; - log_verbose("HiColor: Convert type set to %d\n", new_value); + VERBOSE("HiColor: Convert type set to %d\n", new_value); } @@ -230,14 +268,14 @@ void hicolor_set_type(uint8_t new_value) { // Equivalent of former file loading static void hicolor_image_import(image_data * p_loaded_image) { - log_debug("hicolor_image_import()\n"); + DBG("hicolor_image_import()\n"); // TODO: input guarding // TODO: deduplicate some of the array copying around uint8_t * p_input_img = p_loaded_image->p_img_data; - for (int y=0; y< image_height; y++) { - for (int x=0; x< 160; x++) { + for (unsigned int y=0; y< image_height; y++) { + for (unsigned int x=0; x< 160; x++) { // Clamp to CGB max R/G/B value in RGB 888 mode (31u << 3) // png_image[].rgb -> pic2[].rgb -> pBitssource[].bgr?? @@ -253,9 +291,9 @@ static void hicolor_image_import(image_data * p_loaded_image) { // It's convoluted, but pBitssource & pBitsdest are used for: // - display as windows DIBs (formerly) // - and for some calculations at the end of ConvertRegions() - for (int y=0; y<image_height; y++) { - for (int x=0; x<160; x++) { - for (int z=0; z<3; z++) { + for (unsigned int y=0; y<image_height; y++) { + for (unsigned int x=0; x<160; x++) { + for (unsigned int z=0; z<3; z++) { // TODO: (2-z) seems to be swapping RGB for BGR? *(pBitssource+(image_y_max-y)*3*160+x*3+z) = pic2[x][y][2-z]; // Invert the dib, cos windows likes it like that !! } @@ -268,17 +306,17 @@ static void hicolor_image_import(image_data * p_loaded_image) { // TODO: fix // TODO: Operates on RGB data in pic[] copied from RGB data in pic2 static void hicolor_convert(void) { - log_debug("hicolor_convert()\n"); + DBG("hicolor_convert()\n"); - for(int x=0; x<160; x++) + for(unsigned int x=0; x<160; x++) { - for(int y=0; y<image_height; y++) + for(unsigned int y=0; y<image_height; y++) { pic[x][y][0] = pic2[x][y][0]; pic[x][y][1] = pic2[x][y][1]; pic[x][y][2] = pic2[x][y][2]; - for(int i=0; i<3; i++) + for(unsigned int i=0; i<3; i++) { *(Data + y*160*3+x*3+i) = pic[x][y][i]; } @@ -289,12 +327,12 @@ static void hicolor_convert(void) { } -static void hicolor_save(const char * fname_base) { +static void hicolor_save(const char * fname_base, const char * varname) { // Default tile count to non-deduplicated number int tile_count = y_height_in_tiles * (160 / TILE_WIDTH_PX); - log_debug("hicolor_save()\n"); + DBG("hicolor_save()\n"); PrepareTileSet(); PrepareMap(); PrepareAttributes(); @@ -305,24 +343,28 @@ static void hicolor_save(const char * fname_base) { } ExportTileSet(fname_base); - ExportPalettes(fname_base); + if (opt_get_precompiled_palette()) + ExportPalettesPrecompiled(fname_base); + else + ExportPalettes(fname_base); + ExportMap(fname_base); ExportMapAttributes(fname_base); if (opt_get_c_file_output()) { - file_c_output_write(fname_base, opt_get_bank_num(), tile_count, y_height_in_tiles); + file_c_output_write(fname_base, varname, opt_get_bank_num(), tile_count, y_height_in_tiles); } } // Currently expects width x height x 3(RGB888) -void hicolor_process_image(image_data * p_loaded_image, const char * fname_base) { - log_debug("hicolor_process_image(), fname_base: \"%s\"\n", fname_base); +void hicolor_process_image(image_data * p_loaded_image, const char * fname_base, const char *varname) { + DBG("hicolor_process_image(), fname_base: \"%s\"\n", fname_base); hicolor_vars_prep(p_loaded_image); hicolor_image_import(p_loaded_image); hicolor_convert(); - hicolor_save(fname_base); + hicolor_save(fname_base, varname); } @@ -340,8 +382,8 @@ static void DedupeTileset(void) TileCountDeduped = 0; // Traverse all tiles in the image/map - for (int mapy = 0; mapy < y_height_in_tiles; mapy++) { - for (int mapx = 0; mapx < 20; mapx++) { + for (unsigned int mapy = 0; mapy < y_height_in_tiles; mapy++) { + for (unsigned int mapx = 0; mapx < 20; mapx++) { map_tile_id = MapTileIDs[mapx][mapy]; map_tile_id += (MapAttributes[mapx][mapy] & CGB_ATTR_TILES_BANK) ? CGB_TILES_START_BANK_1 : CGB_TILES_START_BANK_0; @@ -360,7 +402,7 @@ static void DedupeTileset(void) MapAttributes[mapx][mapy] = (MapAttributes[mapx][mapy] & CGB_ATTR_PALETTES_ONLY) | new_attribs; } } - log_verbose("DedupeTileset(): Reduced tiles from %d (%d bytes) to %d (%d bytes) = %d bytes saved. %%%d of original size\n", + VERBOSE("DedupeTileset(): Reduced tiles from %d (%d bytes) to %d (%d bytes) = %d bytes saved. %%%d of original size\n", map_tile_id + 1, (map_tile_id + 1) * TILE_SZ, TileCountDeduped, TileCountDeduped * TILE_SZ, ((map_tile_id + 1) * TILE_SZ) - (TileCountDeduped * TILE_SZ), (TileCountDeduped * 100) / (map_tile_id + 1)); } @@ -370,7 +412,6 @@ static void DedupeTileset(void) static void PrepareTileSet(void) { - uint32_t byteWritten; u32 x, y; u8 c1,c2; u8 dx,dy; @@ -408,11 +449,11 @@ static void PrepareMap(void) { // Set up export Map Tile IDs // Note: The indexes are clipped to 0-255 (instead of 0-512), // the attribute tile index+256 bit is auto-calculated in the attribute map in PrepareAttributes() - int tile_id = 0; - for (int mapy = 0; mapy < y_height_in_tiles; mapy++) { - for (int mapx = 0; mapx < 20; mapx++) { + uint8_t tile_id = 0; + for (unsigned int mapy = 0; mapy < y_height_in_tiles; mapy++) { + for (unsigned int mapx = 0; mapx < 20; mapx++) { - MapTileIDs[mapx][mapy] = (uint8_t)tile_id; + MapTileIDs[mapx][mapy] = tile_id; tile_id++; } } @@ -422,9 +463,9 @@ static void PrepareMap(void) { static void PrepareAttributes(void) { // Set up the Map Attributes table unsigned int tile_id = 0; - for(int MastY=0;MastY<y_height_in_tiles_right;MastY++) + for(unsigned int MastY=0;MastY<y_height_in_tiles_right;MastY++) { - for(int MastX=0;MastX<2;MastX++) + for(unsigned int MastX=0;MastX<2;MastX++) { int Line=Best[MastX][MastY]; int width=0; @@ -454,7 +495,7 @@ static void ExportTileSet(const char * fname_base) strcpy(filename, fname_base); strcat(filename, ".til"); - log_verbose("Writing Tile Patterns to: %s\n", filename); + VERBOSE("Writing Tile Patterns to: %s\n", filename); if (opt_get_tile_dedupe()) { @@ -473,16 +514,25 @@ static void ExportTileSet(const char * fname_base) static void ExportPalettes(const char * fname_base) { char filename[MAX_PATH * 2]; - uint32_t byteWritten; - uint8_t tmpByte; - s32 i, j, k; + unsigned int i, j, k; s32 r,g,b,v; + uint16_t pal_end_color_bgr555 = 0x0000u; + int pal_end_color_count = 0; + strcpy(filename, fname_base); strcat(filename, ".pal"); - log_verbose("Writing Palette to: %s\n", filename); + VERBOSE("Writing Palette to: %s\n", filename); + + // No longer +1 for the trailing 0x2D + int outbuf_sz_pals = (y_region_count_both_sides * PALS_PER_SIDE * COLORS_PER_PAL * BYTES_PER_COLOR); + + // Handle resize if trailing end colors have been appended + if (opt_get_enable_pal_end_color()) { + opt_load_pal_end_color(&pal_end_color_bgr555, &pal_end_color_count); + outbuf_sz_pals += (pal_end_color_count * BYTES_PER_COLOR); + } - int outbuf_sz_pals = (((y_region_count_both_sides) * 4 * 4 * 2) + 1); uint8_t output_buf[outbuf_sz_pals]; uint8_t * p_buf = output_buf; @@ -497,7 +547,7 @@ static void ExportPalettes(const char * fname_base) g = IdealPal[(i%2)*4+j][i/2][k][1]; b = IdealPal[(i%2)*4+j][i/2][k][2]; - // TODO: Converting to BGR555 probably + // Converting to BGR555 v = ((b/8)*32*32) + ((g/8)*32) + (r/8); // 2 bytes per color @@ -507,8 +557,22 @@ static void ExportPalettes(const char * fname_base) } } - // TODO: What is this and why? :) - *p_buf++ = 0x2d; + // Add trailing 32 colors to clear BG if enabled + if (opt_get_enable_pal_end_color()) { + for (int c = 0; c < pal_end_color_count; c++) { + *p_buf++ = (u8)(pal_end_color_bgr555 & 255); + *p_buf++ = (u8)(pal_end_color_bgr555 / 256); + } + } + + // Set unused bit .15 = 1 for last u16 palette entry + // to indicate it's the final one + if (opt_get_pal_end_bit()) + output_buf[outbuf_sz_pals - 1] |= 0x80u; + + // This has an unknown purpose and was present in + // the original source code, but doesn't appear to be needed. + // *p_buf++ = 0x2d; if (!file_write_from_buffer(filename, output_buf, outbuf_sz_pals)) set_exit_error(); @@ -516,6 +580,104 @@ static void ExportPalettes(const char * fname_base) } +#define LDHL_2x_SZ 2 // Scale factor for pal color bytes loaded via `ld [hl], <byte>` +#define RET_SZ 1 // Size of ret opcode +#define VBLANK_LOAD_LINE_CNT 2 // Number of lines loaded in vblank +#define HALT_LOAD_SZ 5 // Size of Halt + LD HL, B/C/D/E on non-vblank scanlines +#define STAT_PRELOAD_SAVE_SZ 4 // Number of pal color bytes that get pre-loaded in STAT isr, so don't need 2x sizing for ld [hl], <byte> +#define PAL_BYTES_PER_LINE (PALS_PER_SIDE * COLORS_PER_PAL * BYTES_PER_COLOR) + +static void ExportPalettesPrecompiled(const char * fname_base) +{ + char filename[MAX_PATH * 2]; + unsigned int line, pal, col; + s32 r,g,b,v; + size_t outbuf_sz_pals = 0; + + strcpy(filename, fname_base); + strcat(filename, ".pal"); + VERBOSE("Writing Pre-compiled Palette to: %s\n", filename); + + // How to calculate output size: + // + // VBLANK ISR (2 Lines) + // ~ No wait + load header code + // + Always uses LD [HL] (so 2x num pal bytes) + // + 1 ret shared by the 2 lines + // = (Pal bytes per line x 2) x (2 lines) + 1 ret + // (((4 x 4 x 2) x 2) x 2) + 1 = 129 + outbuf_sz_pals = ((PAL_BYTES_PER_LINE * LDHL_2x_SZ) * VBLANK_LOAD_LINE_CNT) + RET_SZ; + + // Then... + // STAT ISR (Num Lines - 2) + // - 4 bytes preload in STAT ISR without LD [HL] (so: 4 pal bytes without 2x sizing) + // + Then wait + load header code (so +5 bytes) + // + Then remainder of pal bytes get LD [HL] (so 2x num pal bytes) + // + 1 ret per line + // = (( (Pal bytes per line x 2) - 4 preload bytes + 5 header + 1 ret) x (num lines - 2 vblank lines) + // ( ( (4 x 4 x 2) x 2) - 4) + 5 + 1) = 66 x (num lines - 2) + outbuf_sz_pals += ((PAL_BYTES_PER_LINE * LDHL_2x_SZ) - STAT_PRELOAD_SAVE_SZ + HALT_LOAD_SZ + RET_SZ) * (y_region_count_both_sides - VBLANK_LOAD_LINE_CNT); + + uint8_t output_buf[outbuf_sz_pals]; + uint8_t * p_buf = output_buf; + + // Note: "line" 0 is equivalent to something like scanline -1 + // (due to left side region starting 1 scanline before line 0) + for (line = 0; line < (y_region_count_both_sides); line++) // Number of palette sets (left side updates + right side updates) + { + for (pal = 0; pal < 4; pal++) // Each palette in the line + { + for(col = 0; col < 4;col++) // Each color in the palette + { + // Precompiled mode has a "header" inserted after the first two colours of palette 0, + // except for the first two scanline lines (which are during VBlank so can load directly without a preload + wait) + if (line >= 2 && pal == 0 && col == 2) { + *p_buf++ = SM83_OPCODE_HALT; + *p_buf++ = SM83_OPCODE_LD_HL_B; + *p_buf++ = SM83_OPCODE_LD_HL_C; + *p_buf++ = SM83_OPCODE_LD_HL_D; + *p_buf++ = SM83_OPCODE_LD_HL_E; + } + + r = IdealPal[(line % 2)*4 + pal][line / 2][col][0]; + g = IdealPal[(line % 2)*4 + pal][line / 2][col][1]; + b = IdealPal[(line % 2)*4 + pal][line / 2][col][2]; + + // Converting to BGR555 + v = ((b/8)*32*32) + ((g/8)*32) + (r/8); + + // Load 2 bytes per color + + // Insert LD [HL] opcode before pal data bytes... when: + // - Any time during first two lines (i.e for all pal bytes in vblank) + // - Or is the Second Palette or more (of each Line) + // - Or is the Third Color or more (of each Palette. the STAT isr has pre-load code for the first two pal colors) + if (line < 2 || pal >= 1 || col >= 2) { + *p_buf++ = SM83_OPCODE_LD_HL_IMM8; // ld [hl], <imm8> + } + *p_buf++ = (u8)(v & 255); + + if (line < 2 || pal >= 1 || col >= 2) { + *p_buf++ = SM83_OPCODE_LD_HL_IMM8; // ld [hl], <imm8> + } + *p_buf++ = (u8)(v / 256); + } + } + + // Skip return for the first palette line (during vblank) + if (line >= 1) + *p_buf++ = SM83_OPCODE_RET; + } + + // This has an unknown purpose and was present in + // the original source code, but doesn't appear to be needed. + // *p_buf++ = 0x2d; + + if (!file_write_from_buffer(filename, output_buf, outbuf_sz_pals)) + set_exit_error(); +} + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -525,14 +687,14 @@ static void ExportMap(const char * fname_base) strcpy(filename, fname_base); strcat(filename, ".map"); - log_verbose("Writing Tile Map to: %s\n", filename); + VERBOSE("Writing Tile Map to: %s\n", filename); int outbuf_sz_map = (20 * y_height_in_tiles); uint8_t output_buf_map[outbuf_sz_map]; int tile_id = 0; - for (int y = 0; y < y_height_in_tiles; y++) { - for (int x = 0; x < 20; x++) { + for (unsigned int y = 0; y < y_height_in_tiles; y++) { + for (unsigned int x = 0; x < 20; x++) { uint8_t tile_num = MapTileIDs[x][y]; // This needs to happen here, after optional deduplication stage @@ -556,15 +718,15 @@ static void ExportMapAttributes(const char * fname_base) strcpy(filename, fname_base); strcat(filename, ".atr"); - log_verbose("Writing Attribute Map to: %s\n", filename); + VERBOSE("Writing Attribute Map to: %s\n", filename); int outbuf_sz_map = (20 * y_height_in_tiles); uint8_t output_buf_map[outbuf_sz_map]; int tile_id = 0; - for (int y = 0; y < y_height_in_tiles; y++) + for (unsigned int y = 0; y < y_height_in_tiles; y++) { - for (int x = 0; x < 20; x++) + for (unsigned int x = 0; x < 20; x++) { output_buf_map[tile_id++] = MapAttributes[x][y]; } @@ -653,7 +815,7 @@ RGBQUAD translate(uint8_t rgb[3]) // The higher the adaptive level, the more combinations of attributes are tested. -u8 SplitData[80][4]= +u8 SplitData[HICOLOR_PATTERN_FIXED_COUNT][4]= { {3,2,3,2},{2,3,2,3},{2,2,3,3},{2,3,3,2},{3,2,2,3},{3,3,2,2},{4,2,2,2},{2,2,2,4},{2,2,4,2},{2,4,2,2},{1,1,2,6}, {1,1,3,5},{1,1,4,4},{1,1,5,3},{1,1,6,2},{1,2,1,6},{1,2,2,5},{1,2,3,4},{1,2,4,3},{1,2,5,2},{1,2,6,1},{1,3,1,5}, @@ -669,7 +831,7 @@ u8 SplitData[80][4]= unsigned int ImageRating(u8 *src, u8 *dest, int StartX, int StartY, int Width, int Height) { - log_debug("ImageRating()\n"); + DBG("ImageRating()\n"); unsigned int tot; int x,y; unsigned int accum=0; @@ -694,16 +856,15 @@ unsigned int ImageRating(u8 *src, u8 *dest, int StartX, int StartY, int Width, i // TODO: rename to something that aligns with other convert functions void ConvertToHiColor(int ConvertType) { - log_debug("ConvertToHiColor()\n"); + DBG("ConvertToHiColor()\n"); int res; - int x,y,z,i; + unsigned int x,y; + // TODO: Change "Adaptive Pattern" settings to be a separate variable so StartSplit doesn't have to be offset by -3 + // Just set these directly: + // * StartSplit (first pattern to start checking with) + // * NumSplit (number of patterns to iterate through for testing, 1 = just use the one in StartSplit) int StartSplit=0; int NumSplit=1; - int Steps; - int MastX,MastY; - int Line; - int width; - unsigned int tile_id; switch(LConversion) { @@ -711,55 +872,27 @@ void ConvertToHiColor(int ConvertType) StartSplit=0; NumSplit=6; - Steps=504; break; case 1: StartSplit=0; NumSplit=10; - Steps=792; break; case 2: StartSplit=0; NumSplit=80; - Steps=5832; break; default: StartSplit=LConversion-3; NumSplit=1; - Steps=image_height; - break; - } - - switch(RConversion) - { - case 0: - - Steps+=504; - break; - - case 1: - - Steps+=792; - break; - - case 2: - - Steps+=5832; - break; - - default: - - Steps+=image_height; break; } - // Convert left side with one extra tile of height to fix // the glitching where the last scanline on left bottom region // lacks tile and palette data @@ -823,7 +956,7 @@ void ConvertToHiColor(int ConvertType) raw[1][x][y][2] = GBView.rgbBlue; } } - log_progress("\n"); + VERBOSE("\n"); } @@ -833,12 +966,12 @@ void ConvertToHiColor(int ConvertType) // StartY = 0 - 17 : Starting attribute block // Height = Number of attribute blocks to check / process -int ConvertRegions(int StartX, int Width, int StartY, int Height, int StartJ, int FinishJ, int ConvertType) +int ConvertRegions(unsigned int StartX, unsigned int Width, unsigned int StartY, unsigned int Height, unsigned int StartJ, unsigned int FinishJ, int ConvertType) { - log_debug("ConvertRegions()\n"); - u32 Accum,width,x1,ts,tw,y2,x2,y_offset; - s32 x,y; - s32 i,j; + DBG("ConvertRegions()\n"); + u32 width,x1,ts,tw,y2,x2,y_offset; + unsigned int x,y; + unsigned int i,j; u8 col; @@ -856,7 +989,6 @@ int ConvertRegions(int StartX, int Width, int StartY, int Height, int StartJ, in for(j=StartJ;j<(StartJ+FinishJ);j++) { - Accum=0; width=0; for(i=0;i<4;i++) { @@ -867,7 +999,7 @@ int ConvertRegions(int StartX, int Width, int StartY, int Height, int StartJ, in for(y=StartY*4;y<(StartY+Height)*4;y++) { - log_progress("."); + VERBOSE("."); for(x1=0;x1<4;x1++) { @@ -876,10 +1008,14 @@ int ConvertRegions(int StartX, int Width, int StartY, int Height, int StartJ, in for(y2=0;y2<2;y2++) { + // Skip case where y_line would evaluate to -1 to avoid unsigned wraparound) + // (scanline 0, left side of the image where 80 x 2 pixel box goes from scanline -1 to 0) + if (y_offset > ((y*2) + y2)) continue; + // Skip if Y line is outside image borders (prevents buffer overflow) // (Left side calcs hang off top and bottom of screen // due to Left/Right palette update interleaving) - s32 y_line = (y*2+y2-y_offset); + unsigned int y_line = (y*2+y2-y_offset); if ((y_line < image_y_min) || (y_line > image_y_max)) continue; for(x2=0;x2<tw;x2++) @@ -895,12 +1031,12 @@ int ConvertRegions(int StartX, int Width, int StartY, int Height, int StartJ, in switch(ConvertType) { case 0: - to_indexed(Data,4,0,TileWidth[x1],2); // Median Reduction No Dither + to_indexed(Data,0,TileWidth[x1],2); // Median Reduction No Dither break; case 1: - to_indexed(Data,4,1,TileWidth[x1],2); // Median Reduction With Dither + to_indexed(Data,1,TileWidth[x1],2); // Median Reduction With Dither break; case 2: @@ -924,9 +1060,13 @@ int ConvertRegions(int StartX, int Width, int StartY, int Height, int StartJ, in { for(x2=0;x2<tw;x2++) { + // Skip case where y_line would evaluate to -1 to avoid unsigned wraparound) + // (scanline 0, left side of the image where 80 x 2 pixel box goes from scanline -1 to 0) + if (y_offset > ((y*2) + y2)) continue; + // Skip if Y line is outside image borders (prevents buffer overflow) // since Left side calcs hang off top and bottom of image/screen - s32 y_line = (y*2+y2-y_offset); + unsigned int y_line = (y*2+y2-y_offset); if ((y_line < image_y_min) || (y_line > image_y_max)) continue; col=Picture256[y2*tw+x2]; diff --git a/gbdk-support/png2hicolorgb/src/hicolor/hicolour.h b/gbdk-support/png2hicolorgb/src/hicolor/hicolour.h @@ -13,13 +13,18 @@ // For displaying available conversion patterns to the user #define HELP_CONV_PATTERN_STR \ - "Available conversion palette/attribute pattern widths for -cL:N and -cR:N\n" \ + "Available screen palette arrangement widths for -L=N and -R=N\n" \ "\n" \ - "- Default for Left and Right is 3\n" \ - "- Fixed pattern widths are faster but less optimal than adaptive\n" \ - "- Adaptive pattern width speeds are 1: fastest, 2: medium, 3: slowest (slower = higher quality)\n" \ + "- Default for Left and Right is \"adaptive-medium\"\n" \ + "- Fixed settings are faster but the converted image tends to have more visual artifacts.\n" \ + "- Adaptive settings try to find an optimal palette arrangement.\n" \ + "- The adaptive tiers select between a trade-off of faster conversion or reduced visual artifacts.\n" \ "\n" \ - " 0: Adaptive-1 1: Adaptive-2 2: Adaptive-3 3: 3-2-3-2 4: 2-3-2-3 \n" \ + " Adaptive screen palette arrangements:\n" \ + " adaptive-fast, adaptive-medium (**Default**), adaptive-best\n" \ + "\n" \ + " Fixed screen palette arrangements:\n" \ + " 3: 3-2-3-2 4: 2-3-2-3 \n" \ " 5: 2-2-3-3 6: 2-3-3-2 7: 3-2-2-3 8: 3-3-2-2 9: 4-2-2-2 \n" \ " 10: 2-2-2-4 11: 2-2-4-2 12: 2-4-2-2 13: 1-1-2-6 14: 1-1-3-5 \n" \ " 15: 1-1-4-4 16: 1-1-5-3 17: 1-1-6-2 18: 1-2-1-6 19: 1-2-2-5 \n" \ @@ -37,6 +42,11 @@ " 75: 5-1-2-2 76: 5-1-3-1 77: 5-2-1-2 78: 5-2-2-1 79: 5-3-1-1 \n" \ " 80: 6-1-1-2 81: 6-1-2-1 82: 6-2-1-1 \n" +typedef struct pattern_entry { + unsigned int num; + const char * name; +} pattern_entry; + extern u8 TileOffset[4]; // Offset into screen for attribute start extern u8 TileWidth[4]; // No of character attributes width @@ -46,18 +56,19 @@ extern RGBQUAD GBView; void hicolor_init(void); +unsigned int hicolor_get_pattern_by_name(const char * option_name); void hicolor_set_type(uint8_t new_value); void hicolor_set_convert_left_pattern(uint8_t new_value); void hicolor_set_convert_right_pattern(uint8_t new_value); -void hicolor_process_image(image_data * p_decoded_image, const char * fname); +void hicolor_process_image(image_data * p_loaded_image, const char * fname_base, const char *varname); ///////////////////////////////////////////////////////////////////////////// RGBQUAD translate(uint8_t rgb[3]); unsigned int ImageRating(u8 *src, u8 *dest, int StartX, int StartY, int Width, int Height); void ConvertToHiColor(int ConvertType); -int ConvertRegions(int StartX, int Width, int StartY, int Height, int StartJ, int FinishJ, int ConvertType); +int ConvertRegions(unsigned int StartX, unsigned int Width, unsigned int StartY, unsigned int Height, unsigned int StartJ, unsigned int FinishJ, int ConvertType); #endif diff --git a/gbdk-support/png2hicolorgb/src/hicolor/median.c b/gbdk-support/png2hicolorgb/src/hicolor/median.c @@ -116,7 +116,7 @@ void update_box_rgb(boxptr boxp) } } - have_Rmin: +have_Rmin: if (Rmax > Rmin) for (R = Rmax; R >= Rmin; R--) @@ -131,7 +131,7 @@ void update_box_rgb(boxptr boxp) } } - have_Rmax: +have_Rmax: if (Gmax > Gmin) for (G = Gmin; G <= Gmax; G++) @@ -146,7 +146,7 @@ void update_box_rgb(boxptr boxp) } } - have_Gmin: +have_Gmin: if (Gmax > Gmin) for (G = Gmax; G >= Gmin; G--) @@ -161,7 +161,7 @@ void update_box_rgb(boxptr boxp) } } - have_Gmax: +have_Gmax: if (Bmax > Bmin) for (B = Bmin; B <= Bmax; B++) @@ -176,7 +176,7 @@ void update_box_rgb(boxptr boxp) } } - have_Bmin: +have_Bmin: if (Bmax > Bmin) for (B = Bmax; B >= Bmin; B--) @@ -191,7 +191,7 @@ void update_box_rgb(boxptr boxp) } } - have_Bmax: +have_Bmax: // Update box volume. // We use 2-norm rather than real volume here; this biases the method @@ -390,7 +390,6 @@ s32 find_nearby_colors(s32 minR,s32 minG,s32 minB,s32 colorlist[]) * the colors that need further consideration. */ { - s32 numcolors = actual_number_of_colors; s32 maxR, maxG, maxB; s32 centerR, centerG, centerB; s32 i, x, ncolors; @@ -694,7 +693,7 @@ void fill_inverse_cmap_rgb(s32 R, s32 G, s32 B) /* This is pass 1 */ -void median_cut_pass1_rgb(u8 *src,u8 *dest,s32 width,s32 height) +void median_cut_pass1_rgb(u8 *src,s32 width,s32 height) { s32 num_elems; ColorFreq *col; @@ -797,7 +796,7 @@ s32 *init_error_limit(void) -void to_indexed(u8 *input,s32 ncolors,s32 dither,s32 width,s32 height) +void to_indexed(u8 *input,s32 dither,s32 width,s32 height) { s32 i, j; u8 *src; @@ -828,7 +827,7 @@ void to_indexed(u8 *input,s32 ncolors,s32 dither,s32 width,s32 height) histogram = AHistorgram; - median_cut_pass1_rgb(input, Picture256, width, height); + median_cut_pass1_rgb(input, width, height); src=input; dest=Picture256; diff --git a/gbdk-support/png2hicolorgb/src/hicolor/median.h b/gbdk-support/png2hicolorgb/src/hicolor/median.h @@ -1,6 +1,8 @@ #ifndef __median_h__ #define __median_h__ +#include "defines.h" + #define MAXNUMCOLORS 256 @@ -66,9 +68,9 @@ void compute_color_rgb(boxptr boxp,s32 icolor); s32 find_nearby_colors(s32 minR,s32 minG,s32 minB,s32 colorlist[]); void find_best_colors(s32 minR, s32 minG, s32 minB,s32 numcolors,s32 colorlist[],s32 bestcolor[]); void fill_inverse_cmap_rgb(s32 R, s32 G, s32 B); -void median_cut_pass1_rgb(u8 *src,u8 *dest,s32 width,s32 height); +void median_cut_pass1_rgb(u8 *src,s32 width,s32 height); s32 *init_error_limit(void); -void to_indexed(u8 *input,s32 ncolors,s32 dither,s32 width,s32 height); +void to_indexed(u8 *input,s32 dither,s32 width,s32 height); #endif diff --git a/gbdk-support/png2hicolorgb/src/image_info.h b/gbdk-support/png2hicolorgb/src/image_info.h @@ -2,11 +2,11 @@ // image_info.h // -#include <stdint.h> - #ifndef IMAGE_INFO_HEADER #define IMAGE_INFO_HEADER +#include <stdint.h> + #define RGBA_32SZ 4 // 4 bytes #define RGBA_RED 0 #define RGBA_GREEN 1 diff --git a/gbdk-support/png2hicolorgb/src/image_load.c b/gbdk-support/png2hicolorgb/src/image_load.c @@ -26,7 +26,7 @@ static bool image_load_png(image_data *, const char *); static bool image_validate(image_data * p_decoded_image) { if (p_decoded_image->width != VALIDATE_WIDTH) { - log_error("Error: Image width is %d, must be %d\n", p_decoded_image->width, VALIDATE_WIDTH); + ERR("Error: Image width is %d, must be %d\n", p_decoded_image->width, VALIDATE_WIDTH); return false; } @@ -36,17 +36,17 @@ static bool image_validate(image_data * p_decoded_image) { // Which is the highest possible within the 512 available on the CGB // without reloading the tiles and maps using an offset if ((p_decoded_image->height == 0) || (p_decoded_image->height > VALIDATE_HEIGHT)) { - log_error("Error: Image height is %d, must be between 0 and %d\n", p_decoded_image->height, VALIDATE_HEIGHT); + ERR("Error: Image height is %d, must be between 0 and %d\n", p_decoded_image->height, VALIDATE_HEIGHT); return false; } if ((p_decoded_image->height % 8) != 0u) { - log_verbose("Error: Image height must be a multiple of 8, %d is not\n", p_decoded_image->height); + VERBOSE("Error: Image height must be a multiple of 8, %d is not\n", p_decoded_image->height); return false; } if ((p_decoded_image->width % 8) != 0u) { - log_verbose("Error: Image width must be a multiple of 8, %d is not\n", p_decoded_image->width); + VERBOSE("Error: Image width must be a multiple of 8, %d is not\n", p_decoded_image->width); return false; } @@ -67,7 +67,7 @@ static bool image_load_png(image_data * p_decoded_image, const char * filename) if (error) { status = false; - log_error("Error: PNG load: %u: %s\n", error, lodepng_error_text(error)); + ERR("Error: PNG load: %u: %s\n", error, lodepng_error_text(error)); } else { p_decoded_image->bytes_per_pixel = RGB_24SZ; p_decoded_image->size = p_decoded_image->width * p_decoded_image->height * p_decoded_image->bytes_per_pixel; @@ -86,7 +86,7 @@ bool image_load(image_data * p_decoded_image, const char * filename, const uint8 bool status = true; - log_verbose("Loading image from file: %s, type: %d\n", filename, image_type); + VERBOSE("Loading image from file: %s, type: %d\n", filename, image_type); switch (image_type) { case IMG_TYPE_PNG: @@ -95,7 +95,7 @@ bool image_load(image_data * p_decoded_image, const char * filename, const uint8 default: status = false; - log_error("Invalid image format. No image will be loaded\n"); + ERR("Invalid image format. No image will be loaded\n"); break; } @@ -103,10 +103,10 @@ bool image_load(image_data * p_decoded_image, const char * filename, const uint8 status = image_validate(p_decoded_image); if (status) { - log_verbose("Decoded image.width: %d\n", p_decoded_image->width); - log_verbose("Decoded image.height: %d\n", p_decoded_image->height); - log_verbose("Decoded image.size: %d\n", p_decoded_image->size); - log_verbose("Decoded image.bytes_per_pixel: %d\n", p_decoded_image->bytes_per_pixel); + VERBOSE("Decoded image.width: %d\n", p_decoded_image->width); + VERBOSE("Decoded image.height: %d\n", p_decoded_image->height); + VERBOSE("Decoded image.size: %d\n", p_decoded_image->size); + VERBOSE("Decoded image.bytes_per_pixel: %d\n", p_decoded_image->bytes_per_pixel); } return status; diff --git a/gbdk-support/png2hicolorgb/src/image_load.h b/gbdk-support/png2hicolorgb/src/image_load.h @@ -2,6 +2,8 @@ #ifndef _IMAGE_LOAD_C #define _IMAGE_LOAD_C +#include <stdbool.h> + #include "image_info.h" bool image_load(image_data * p_src_image, const char * filename, const uint8_t img_type); diff --git a/gbdk-support/png2hicolorgb/src/logging.c b/gbdk-support/png2hicolorgb/src/logging.c @@ -5,7 +5,7 @@ #include "logging.h" -int output_level = OUTPUT_LEVEL_DEFAULT; +static enum output_level output_level = OUTPUT_LEVEL_DEFAULT; #define VA_LIST_PRINT() \ @@ -16,33 +16,18 @@ int output_level = OUTPUT_LEVEL_DEFAULT; fflush(stdout); // If logging a lot of output to a file turn off the excessive flushing -void log_set_level(int new_output_level) { +void set_log_level(enum output_level new_output_level) { output_level = new_output_level; } -void log_debug(const char * format, ...){ +int log_(enum output_level level, const char *fmt, ...) { + if (output_level > level) { + return 0; // Pretend everything went smoothly. (It did, kind of!) + } - if (output_level > OUTPUT_LEVEL_DEBUG) return; - VA_LIST_PRINT(); + va_list ap; + va_start(ap, fmt); + int ret = vfprintf(stderr, fmt, ap); + va_end(ap); + return ret; } - -void log_verbose(const char * format, ...){ - - if (output_level > OUTPUT_LEVEL_VERBOSE) return; - VA_LIST_PRINT(); -} - -void log_standard(const char * format, ...){ - - // Only print if quiet mode and error_only are NOT enabled - if ((output_level == OUTPUT_LEVEL_QUIET) || - (output_level == OUTPUT_LEVEL_ONLY_ERRORS)) return; - VA_LIST_PRINT(); -} - -void log_error(const char * format, ...){ - - // Only print if quiet mode is NOT enabled - if (output_level == OUTPUT_LEVEL_QUIET) return; - VA_LIST_PRINT(); -} - diff --git a/gbdk-support/png2hicolorgb/src/logging.h b/gbdk-support/png2hicolorgb/src/logging.h @@ -3,20 +3,20 @@ #ifndef _LOGGING_H #define _LOGGING_H -enum output_levels { +#include "common.h" + +enum output_level { OUTPUT_LEVEL_DEBUG, OUTPUT_LEVEL_VERBOSE, OUTPUT_LEVEL_DEFAULT, OUTPUT_LEVEL_ONLY_ERRORS, - OUTPUT_LEVEL_QUIET }; -#define log_progress log_verbose - -void log_set_level(int new_output_level); -void log_debug(const char * format, ...); -void log_verbose(const char * format, ...); -void log_standard(const char * format, ...); -void log_error(const char * format, ...); +void set_log_level(enum output_level new_output_level); +int log_(enum output_level level, char const *fmt, ...) FMT(printf, 2, 3); +#define DBG(...) log_(OUTPUT_LEVEL_DEBUG, __VA_ARGS__) +#define VERBOSE(...) log_(OUTPUT_LEVEL_VERBOSE, __VA_ARGS__) +#define LOG(...) log_(OUTPUT_LEVEL_DEFAULT, __VA_ARGS__) +#define ERR(...) log_(OUTPUT_LEVEL_ONLY_ERRORS, __VA_ARGS__) -#endif - +#endif diff --git a/gbdk-support/png2hicolorgb/src/main.c b/gbdk-support/png2hicolorgb/src/main.c @@ -17,20 +17,37 @@ #include <hicolour.h> -#define VERSION "version 1.4.1" +#define VERSION "version 1.4.2" + +#define HELP_HISTORICAL_CREDITS_STR \ + "Historical credits and info:\n" \ + " Original Concept : Icarus Productions\n" \ + " Original Code : Jeff Frohwein\n" \ + " Full Screen Modification : Anon\n" \ + " Adaptive Code : Glen Cook\n" \ + " Windows Interface : Glen Cook\n" \ + " Additional Windows Programming : Rob Jones\n" \ + " Original Quantiser Code : Benny\n" \ + " Quantiser Conversion : Glen Cook\n" \ + "\n" static void init(void); static void cleanup(void); static void display_help(void); static int handle_args(int argc, char * argv[]); -static void set_drag_and_drop_mode_defaults(void); +static bool handle_args_L_R_patterns(const char * str_opt_side, const char * str_value); +#ifdef DRAG_AND_DROP_MODE + static void set_drag_and_drop_mode_defaults(void); +#endif image_data src_image; -char filename_in[MAX_STR_LEN] = {'\0'}; +char filename_in[MAX_STR_LEN] = ""; char opt_filename_out[MAX_STR_LEN] = ""; +char opt_varname[MAX_STR_LEN] = ""; // bool opt_strip_output_filename_ext = true; -int show_help_and_exit = false; +bool show_help_and_exit = false; +bool show_full_credits = false; static void init(void) { @@ -81,7 +98,12 @@ int main( int argc, char *argv[] ) { strcpy(opt_filename_out, filename_in); filename_remove_extension(opt_filename_out); - hicolor_process_image(&src_image, opt_filename_out); + + // If output symbol name is not specified, use output filename + if (opt_varname[0] == '\0') + strcpy(opt_varname, opt_filename_out); + + hicolor_process_image(&src_image, opt_filename_out, opt_varname); ret = EXIT_SUCCESS; // Exit with success } } @@ -97,7 +119,7 @@ int main( int argc, char *argv[] ) { #ifdef DRAG_AND_DROP_MODE // Wait for input to keep the console window open after processing - log_standard("\n\nPress Any Key to Continue\n"); + LOG("\n\nPress Any Key to Continue\n"); getchar(); #endif @@ -106,7 +128,7 @@ int main( int argc, char *argv[] ) { static void display_help(void) { - log_standard( + LOG( "\n" "png2hicolorgb input_image.png [options]\n" VERSION": bbbbbr. Based on Glen Cook's Windows GUI \"hicolour.exe\" 1.2\n" @@ -116,42 +138,45 @@ static void display_help(void) { "-h : Show this help\n" "-v* : Set log level: \"-v\" verbose, \"-vQ\" quiet, \"-vE\" only errors, \"-vD\" debug\n" "-o <file> : Set base output filename (otherwise from input image)\n" + "-s <name> : Set output variable/symbol name (otherwise derived from output filename)\n" // "--keepext : Do not strip extension from output filename\n" "--csource : Export C source format with incbins for data files\n" "--bank=N : Set bank number for C source output where N is decimal bank number 1-511\n" "--type=N : Set conversion type where N is one of below \n" " 1: Median Cut - No Dither (*Default*)\n" " 2: Median Cut - With Dither\n" - " 3: Wu Quantiser\n" + " 3: Wu Quantiser (best quality)\n" "-p : Show screen attribute pattern options (no processing)\n" - "-L=N : Set Left screen attribute pattern where N is decimal entry (-p to show patterns)\n" - "-R=N : Set Right screen attribute pattern where N is decimal entry (-p to show patterns)\n" + "-L=N : Set Left side of screen palette arrangement where N is name listed below or decimal entry\n" + "-R=N : Set Right side of screen palette arrangement where N is name listed below or decimal entry\n" + " Named options for N: \"adaptive-fast\", \"adaptive-medium\", \"adaptive-best\" (-p for full options) \n" + "--best : Use highest quality conversion settings (--type=3 -L=adaptive-best -R=adaptive-best)\n" "--vaddrid : Map uses vram id (128->255->0->127) instead of (*Default*) sequential tile order (0->255)\n" "--nodedupe : Turn off tile pattern deduplication\n" + "--precompiled : Export Palette data as pre-compiled executable loading code\n" + "--palendbit : Set unused bit .15 = 1 for last u16 entry in palette data indicating end (not in precompiled)\n" + "--addendcolor=N : Append 32 x color N (hex BGR555) in pal data to clear BG for shorter images (64 bytes) (not in precompiled)\n" "\n" "Example 1: \"png2hicolorgb myimage.png\"\n" - "Example 2: \"png2hicolorgb myimage.png --csource -o=my_output_filename\"\n" - "* Default settings provide good results. Better quality but slower: \"--type=3 -L=2 -R=2\"\n" - "\n" - "Historical credits and info:\n" - " Original Concept : Icarus Productions\n" - " Original Code : Jeff Frohwein\n" - " Full Screen Modification : Anon\n" - " Adaptive Code : Glen Cook\n" - " Windows Interface : Glen Cook\n" - " Additional Windows Programming : Rob Jones\n" - " Original Quantiser Code : Benny\n" - " Quantiser Conversion : Glen Cook\n" + "Example 2: \"png2hicolorgb myimage.png --csource -o my_output_filename\"\n" + "Example 2: \"png2hicolorgb myimage.png --palendbit --addendcolor 0x7FFF -o my_output_filename -s my_variable_name\"\n" + "* Default settings provide good results. Better quality but slower: \"--type=3 -L=adaptive-best -R=adaptive-best\"\n" "\n" ); + + if (show_full_credits) + LOG(HELP_HISTORICAL_CREDITS_STR); } +#ifdef DRAG_AND_DROP_MODE // Default options for Windows Drag and Drop recipient mode static void set_drag_and_drop_mode_defaults(void) { // Set some options here } +#endif + static int handle_args(int argc, char * argv[]) { @@ -171,27 +196,28 @@ static int handle_args(int argc, char * argv[]) { for (i = 1; i <= (argc -1); i++ ) { if ((strstr(argv[i], "-h") == argv[i]) || (strstr(argv[i], "-?") == argv[i])) { + show_full_credits = true; display_help(); show_help_and_exit = true; return true; // Don't parse further input when -h is used } else if (strstr(argv[i], "-p") == argv[i]) { - log_standard(HELP_CONV_PATTERN_STR); + LOG(HELP_CONV_PATTERN_STR); show_help_and_exit = true; return true; // Don't parse further input when -h is used } else if (strstr(argv[i], "-vD") == argv[i]) { - log_set_level(OUTPUT_LEVEL_DEBUG); + set_log_level(OUTPUT_LEVEL_DEBUG); } else if (strstr(argv[i], "-vE") == argv[i]) { - log_set_level(OUTPUT_LEVEL_ONLY_ERRORS); + set_log_level(OUTPUT_LEVEL_ONLY_ERRORS); } else if (strstr(argv[i], "-vQ") == argv[i]) { - log_set_level(OUTPUT_LEVEL_QUIET); + set_log_level(OUTPUT_LEVEL_ONLY_ERRORS); } else if (strstr(argv[i], "-v") == argv[i]) { - log_set_level(OUTPUT_LEVEL_VERBOSE); + set_log_level(OUTPUT_LEVEL_VERBOSE); } else if (strstr(argv[i], "--type=") == argv[i]) { uint8_t new_type = strtol(argv[i] + strlen("--type="), NULL, 10); if ((new_type < CONV_TYPE_MIN) || (new_type > CONV_TYPE_MAX)) { - log_standard("Error: --type specified with invalid conversion setting: %d\n", new_type); + LOG("Error: --type specified with invalid conversion setting: %d\n", new_type); display_help(); show_help_and_exit = true; return false; // Abort @@ -199,26 +225,49 @@ static int handle_args(int argc, char * argv[]) { else hicolor_set_type(new_type); } else if (strstr(argv[i], "-L=") == argv[i]) { - hicolor_set_convert_left_pattern( strtol(argv[i] + strlen("-L="), NULL, 10)); + if (!handle_args_L_R_patterns((const char *)"-L=", argv[i] + strlen("-L="))) { + show_help_and_exit = true; + return false; // Abort + } + } else if (strstr(argv[i], "-R=") == argv[i]) { - hicolor_set_convert_right_pattern( strtol(argv[i] + strlen("-R="), NULL, 10)); + if (!handle_args_L_R_patterns((const char *)"-R=", argv[i] + strlen("-R="))) { + show_help_and_exit = true; + return false; // Abort + } + + } else if (strstr(argv[i], "--best") == argv[i]) { + hicolor_set_type(CONV_TYPE_WU); + hicolor_set_convert_left_pattern(HICOLOR_PATTERN_ADAPTIVE_BEST); + hicolor_set_convert_right_pattern(HICOLOR_PATTERN_ADAPTIVE_BEST); + } else if (strstr(argv[i], "-o") == argv[i]) { if (i < (argc -1)) i++; // Move to next argument if one is available else { - log_standard("Error: -o specified but filename is missing\n"); + LOG("Error: -o specified but filename is missing\n"); show_help_and_exit = true; return false; // Abort } - // Require colon and filename to be present - if (*argv[i] == '-') - log_standard("Warning: -o specified but filename has dash and looks like an option argument. Usage: -o my_base_output_filename\n"); + // Require filename to be present + if (*argv[i] == '-') LOG("Warning: -o specified but filename (%s) has dash and looks like an option argument. Usage: -o my_base_output_filename\n", argv[i]); snprintf(opt_filename_out, sizeof(opt_filename_out), "%s", argv[i]); - // } else if (strstr(argv[i], "--keepext") == argv[i]) { - // opt_strip_output_filename_ext = false; + } else if (strstr(argv[i], "-s") == argv[i]) { + if (i < (argc -1)) + i++; // Move to next argument if one is available + else { + LOG("Error: -s specified but variable name is missing\n"); + show_help_and_exit = true; + return false; // Abort + } + + // Require varname to be present + if (*argv[i] == '-') LOG("Warning: -s specified but varname (%s) has dash and looks like an option argument. Usage: -s my_varname\n", argv[i]); + snprintf(opt_varname, sizeof(opt_varname), "%s", argv[i]); + } else if (strstr(argv[i], "--csource") == argv[i]) { opt_set_c_file_output(true); @@ -226,7 +275,7 @@ static int handle_args(int argc, char * argv[]) { } else if (strstr(argv[i], "--bank=") == argv[i]) { opt_set_bank_num( strtol(argv[i] + strlen("--bank="), NULL, 10) ); if ((opt_get_bank_num() < BANK_NUM_MIN) || (opt_get_bank_num() > BANK_NUM_MAX)) { - log_standard("Error: Invalid bank number specified with --bank=%d\n", opt_get_bank_num()); + LOG("Error: Invalid bank number specified with --bank=%d\n", opt_get_bank_num()); display_help(); show_help_and_exit = true; return false; // Abort @@ -238,14 +287,66 @@ static int handle_args(int argc, char * argv[]) { } else if (strstr(argv[i], "--nodedupe") == argv[i]) { opt_set_tile_dedupe(false); + } else if (strstr(argv[i], "--precompiled") == argv[i]) { + opt_set_precompiled_palette(true); + + } else if (strstr(argv[i], "--palendbit") == argv[i]) { + opt_set_pal_end_bit(true); + + } else if (strstr(argv[i], "--addendcolor=") == argv[i]) { + // Color should be in BGR555 hex format, so white: 0x7FFFF, max red 0x7C00, max green 0x3E00, etc + opt_set_enable_pal_end_color( strtol(argv[i] + strlen("--addendcolor="), NULL, 16) ); + } else if (argv[i][0] == '-') { - log_error("Unknown argument: %s\n\n", argv[i]); + ERR("Unknown argument: %s\n\n", argv[i]); display_help(); return false; } } + + // Check and warn about option compatibility + if (opt_get_pal_end_bit() && opt_get_precompiled_palette()) { + LOG("Warning: --palendbit ignored when --precompiled is enabled\n"); + } + + if (opt_get_enable_pal_end_color() && opt_get_precompiled_palette()) { + LOG("Warning: --addendcolor ignored when --precompiled is enabled\n"); + } + return true; } + +static bool handle_args_L_R_patterns(const char * str_opt_side, const char * str_value) { + + if (*str_value == '\0') { + ERR("Missing value for %s argument\n", str_opt_side); + return false; + } + + unsigned int pattern_num = hicolor_get_pattern_by_name(str_value); + + if (pattern_num == HICOLOR_PATTERN_NOT_FOUND_HAS_CHARS) { + ERR("Invalid value for %s argument: %s\n", str_opt_side, str_value); + return false; + } + else if (pattern_num == HICOLOR_PATTERN_NOT_FOUND) { + // Try numeric conversion on option instead named conversion + pattern_num = (unsigned int)strtoul(str_value, NULL, 10); + } + + if (pattern_num > HICOLOR_PATTERN_OPT_MAX) { + ERR("Invalid value for %s argument: %s\n", str_opt_side, str_value); + return false; + } + + // Got a valid option value, now apply it + if (strstr(str_opt_side, "-L=") == str_opt_side) + hicolor_set_convert_left_pattern(pattern_num); + else + hicolor_set_convert_right_pattern(pattern_num); + + return true; +} diff --git a/gbdk-support/png2hicolorgb/src/options.c b/gbdk-support/png2hicolorgb/src/options.c @@ -12,7 +12,11 @@ bool opt_map_use_sequential_tile_index = true; bool opt_tile_dedupe = true; bool opt_c_file_output = false; +bool opt_c_precompiled = false; int opt_bank_num = BANK_NUM_UNSET; +bool opt_pal_end_bit = false; +bool opt_enable_pal_end_col = false; +uint16_t pal_end_color = 0x0000u; // Black in BGR555 void opt_set_map_tile_order(bool newval) { opt_map_use_sequential_tile_index = newval; } bool opt_get_map_tile_order(void) { return opt_map_use_sequential_tile_index; } @@ -26,3 +30,31 @@ bool opt_get_c_file_output(void) { return opt_c_file_output; } void opt_set_bank_num(int newval) { opt_bank_num = newval; } int opt_get_bank_num(void) { return opt_bank_num; } +void opt_set_precompiled_palette(bool newval) { opt_c_precompiled = newval; } +bool opt_get_precompiled_palette(void) { return opt_c_precompiled; } + +void opt_set_pal_end_bit(bool newval) { opt_pal_end_bit = newval; } +bool opt_get_pal_end_bit(void) { return opt_pal_end_bit; } + +void opt_set_enable_pal_end_color(uint16_t end_color_bgr555) { + + opt_enable_pal_end_col = true; + // Make sure unused high bit is not set for compat with pal_end_bit + pal_end_color = (end_color_bgr555 & BGR555_MASK); +} + +bool opt_get_enable_pal_end_color(void) { + + return opt_enable_pal_end_col; +} + +void opt_load_pal_end_color(uint16_t * p_end_color, int * pal_end_color_count) { + + *p_end_color = pal_end_color; + + if (opt_enable_pal_end_col) { + *pal_end_color_count = APPEND_END_COLOR_COUNT; // Add 8 Palettes x 4 Colors to palette data (2 bytes each) + } else { + *pal_end_color_count = 0; // Not enabled, so add zero bytes to pal size + } +} diff --git a/gbdk-support/png2hicolorgb/src/options.h b/gbdk-support/png2hicolorgb/src/options.h @@ -3,8 +3,8 @@ #ifndef OPTIONS_H #define OPTIONS_H -#include <stdint.h> #include <stdbool.h> +#include <stdint.h> #define BANK_NUM_UNSET 0 #define BANK_NUM_MIN 1 @@ -12,6 +12,9 @@ #define OPTION_UNSET 0xFFFF +#define BGR555_MASK 0x7FFFu +#define APPEND_END_COLOR_COUNT (8 * 4); // Add 8 Palettes x 4 Colors each x 2 bytes per color to pal size + #define OPT_MAP_TILE_ORDER_BY_VRAM_ID false #define OPT_MAP_TILE_SEQUENTIAL_ORDER true @@ -24,6 +27,16 @@ bool opt_get_tile_dedupe(void); void opt_set_c_file_output(bool newval); bool opt_get_c_file_output(void); +void opt_set_precompiled_palette(bool newval); +bool opt_get_precompiled_palette(void); + +void opt_set_pal_end_bit(bool newval); +bool opt_get_pal_end_bit(void); + +void opt_set_enable_pal_end_color(uint16_t end_color_bgr555); +bool opt_get_enable_pal_end_color(void); +void opt_load_pal_end_color(uint16_t * p_end_color, int * pal_end_color_count); + void opt_set_bank_num(int newval); int opt_get_bank_num(void); diff --git a/gbdk-support/png2hicolorgb/src/path_ops.c b/gbdk-support/png2hicolorgb/src/path_ops.c @@ -4,7 +4,9 @@ #include <stdio.h> #include <string.h> +#include <strings.h> #include <stdbool.h> + #include "common.h" #include "path_ops.h" diff --git a/gbdk-support/png2hicolorgb/src/path_ops.h b/gbdk-support/png2hicolorgb/src/path_ops.h @@ -5,8 +5,9 @@ #ifndef _PATH_OPS_H #define _PATH_OPS_H -#include <stdint.h> #include <stdbool.h> +#include <stddef.h> +#include <stdint.h> void filename_replace_extension(char * filename, char * new_ext, size_t maxlen); void filename_replace_path(char * filename, char * new_path, size_t maxlen); @@ -15,4 +16,4 @@ void filename_remove_extension(char * path); bool get_path_without_filename(const char * path, char * path_only, uint32_t str_max); bool matches_extension(char *, char *); -#endif // _PATH_OPS_H - +#endif // _PATH_OPS_H diff --git a/gbdk-support/png2hicolorgb/src/tile_dedupe.c b/gbdk-support/png2hicolorgb/src/tile_dedupe.c @@ -92,7 +92,7 @@ bool tileset_find_matching_tile(const uint8_t * src_tile, const uint8_t * tile_s for (unsigned int index_dedupe = 0; index_dedupe < tile_count_deduped; index_dedupe++) { - for (int f = 0; f < ARRAY_LEN(tile_flip_flags); f++) { + for (size_t f = 0; f < ARRAY_LEN(tile_flip_flags); f++) { if (memcmp(&tile_set_deduped[index_dedupe * TILE_SZ], &tile_permutations[f * TILE_SZ], TILE_SZ) == 0) { // Match Found diff --git a/gbdk-support/png2hicolorgb/src/tile_dedupe.h b/gbdk-support/png2hicolorgb/src/tile_dedupe.h @@ -1,8 +1,8 @@ #ifndef TILE_DEDUPE_H #define TILE_DEDUPE_H -#include <stdint.h> #include <stdbool.h> +#include <stdint.h> bool tileset_find_matching_tile(const uint8_t * src_tile, const uint8_t * tile_set_deduped, unsigned int tile_count_deduped, uint8_t * new_tile_id, uint8_t * new_attribs);

This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.