git.y1.nz

gbdk-2020

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

gbdk-support/png2hicolorgb/src/hicolor/hicolour.c

      1 
      2 #include <stddef.h>
      3 #include <stdio.h>
      4 #include <string.h>
      5 #include <ctype.h>
      6 #include <stdbool.h>
      7 #include <stdint.h>
      8 
      9 #include "defines.h"
     10 #include "hicolour.h"
     11 #include "median.h"
     12 #include "wu.h"
     13 
     14 
     15 #include "common.h"
     16 #include "options.h"
     17 #include "files.h"
     18 #include "image_info.h"
     19 #include "logging.h"
     20 #include "c_source.h"
     21 #include "tile_dedupe.h"
     22 
     23 /* Gameboy Hi-Colour Convertor */
     24 /* Glen Cook */
     25 /* Jeff Frohwein */
     26 /* Rob Jones */
     27 
     28 
     29 /*
     30 
     31 This code is based on the code written by Jeff Frohwein. Jeff originally wrote a 128x128
     32 Gameboy HiColour convertor and made the source publically available. The problem with
     33 the original work, is that the output from the original code had a large white border
     34 making the picture look framed.
     35 
     36 The original code was then modified by another party to produce a full screen image, using
     37 a fixed attribute block size of 3-2-3-2-3-2-3-2. The output from this modified code looked
     38 great, but some pictures had artifacts, due to the fixed attribute size.
     39 
     40 I then decided to modify the full screen code, to produce pictures with less artifacts, the
     41 attribute blocks are not fixed, they can adapt their size based on the type of picture that
     42 is being converted.
     43 
     44 This program will step through every possible combination of attributes to find the best
     45 possible solution.
     46 
     47 The program gives the user the option of using fixed or adaptive attribute blocks, fixed
     48 attribute blocks are much quicker to calculate, but the picture quality may not be perfect.
     49 
     50 After creating a DOS version of this program, I then went ahead and wrote a windows interface
     51 for it, to tidy it up, and also give me the chance to learn some windows programming. This is
     52 my first windows program, so please be kind.
     53 
     54 The best method for converting the pictures, is to use Adaptive method 3, although this can
     55 take quite a bit longer to calculate than the fixed size calculations.
     56 
     57 I believe that the new median cut method with dither produces the best results in general,
     58 but the other quantisers can produce better results for other picture types.
     59 
     60 I am releasing this program into the public domain, feel free to adapt it in anyway that you
     61 deem fit. I you feel you have improved this program in anyway, drop me a line, and I will
     62 incorperate the changes into newer versions. (GlenCook@hotmail.com)
     63 
     64 */
     65 
     66 
     67 /* HISTORY */
     68 
     69 
     70 // V1.0 - 27th March 2000 - First public release
     71 // V1.1 - 30th March 2000 - Rob Jones added seperate thread for conversion process
     72 // V1.2 - 8th  April 2000 - Added other quantisation methods
     73 // V1.4 -            2023 - Converted to cross platform console utility with PNG support (bbbbbr)
     74 
     75 
     76 // Function prototypes
     77 
     78 int    br,bg,bb;
     79 
     80 typedef struct
     81 {
     82     u8        p1;
     83     u8        p2;
     84     u8        FileType;
     85     u8        p3[9];
     86     u16       XSize;
     87     u16       YSize;
     88     u8        BitDepth;
     89     u8        c1;
     90     u8        data[160*BUF_HEIGHT][3];
     91 } IMG_TYPE;
     92 
     93 
     94 // u8            QR[BUF_HEIGHT][160][3];
     95 u8            TileOffset[4];                 // Offset into screen for attribute start
     96 u8            TileWidth[4];                  // No of character attributes width
     97 u8            Pal[8][BUF_Y_REGION_COUNT_LR_RNDUP][28][3];             // Palettes for every other line
     98 u8            IdealPal[8][BUF_Y_REGION_COUNT_LR_RNDUP][4][3];         // The best fit palette
     99 u8            pic[160][BUF_HEIGHT][3];              // Original Picture
    100 u8            pic2[160][BUF_HEIGHT][3];             // Output picture
    101 u8            out[160][BUF_HEIGHT];                 // Output data
    102 
    103 u8            raw[2][160][BUF_HEIGHT][3];           // Original Picture Raw format.
    104                                              // sourced from [0] = Normal , [1] = GB Color selected by ViewType
    105 
    106 // TODO: delete?
    107 s32           ViewType=0;                    // Type of view to show: 0 = Normal , 1 = GB Color
    108 
    109 u8            Best[2][BUF_HEIGHT_IN_TILES_RNDUP];  // Best Attribute type to use
    110 u8            LConversion;                   // Conversion type for left hand side of the screen
    111 u8            RConversion;                   // Conversion type for right hand side of the screen
    112 // HWND          Ghdwnd;                          // Global window handle
    113 u8            MapTileIDs[20][BUF_HEIGHT_IN_TILES];           // Attribute table for final render
    114 u8            MapAttributes[20][BUF_HEIGHT_IN_TILES];        // Attribute table for final render
    115 uint8_t       TileSet[20 * BUF_HEIGHT_IN_TILES * TILE_SZ];   // Sequential Tileset Data in Game Boy 2bpp format
    116 uint8_t       TileSetDeduped[20 * BUF_HEIGHT_IN_TILES * TILE_SZ];   // Sequential Tileset Data in Game Boy 2bpp format
    117 unsigned int  TileCountDeduped;
    118 // u8            OldLConv=0;                    // Conversion type
    119 // u8            OldRConv=0;
    120 uint8_t *     pBuffer;
    121 // u8            Message[2000];
    122 s32           ConvertType; //=2;
    123 
    124 u8            Data[160*BUF_HEIGHT*3];  // Gets used for quantizing regions. Maybe other things too?
    125 
    126 u32           TempD;
    127 s32           BestLine=0;  // TODO: convert to local var
    128 u32           BestQuantLine;
    129 // RGBQUAD       GBView; // converted to local vars
    130 
    131 
    132 
    133 // Shim buffers for the former windows rendered images that were also used for some calculationss
    134 // bmihsource.biWidth          = 160;
    135 // bmihsource.biHeight         = BUF_HEIGHT;
    136 // bmihsource.biPlanes         = 1;
    137 // bmihsource.biBitCount       = 24;
    138 static      uint8_t Bitsdest[160 * BUF_HEIGHT * 3]; // TODO: RGBA 4 bytes per pixel?
    139 static      uint8_t Bitssource[160 * BUF_HEIGHT * 3];
    140 //
    141 static      uint8_t *pBitsdest = Bitsdest;
    142             uint8_t *pBitssource = Bitssource;
    143 
    144 
    145 #define MAX_CONVERSION_TYPES    83
    146 #define MAX_QUANTISER_TYPES     4
    147 
    148 pattern_entry named_patterns[] = {
    149     {.num = HICOLOR_PATTERN_ADAPTIVE_FAST, .name="adaptive-fast"},
    150     {.num = HICOLOR_PATTERN_ADAPTIVE_MED, .name="adaptive-medium"},
    151     {.num = HICOLOR_PATTERN_ADAPTIVE_BEST, .name="adaptive-best"}
    152 };
    153 
    154 static unsigned int image_y_min;
    155 static unsigned int image_y_max;
    156 static unsigned int image_height;
    157 static unsigned int y_region_count_left;
    158 static unsigned int y_region_count_right;
    159 static unsigned int y_region_count_lr_rndup;
    160 static unsigned int y_region_count_both_sides;
    161 static unsigned int y_height_in_tiles_left;
    162 static unsigned int y_height_in_tiles_right;
    163 static unsigned int y_height_in_tiles;
    164 static unsigned int y_height_in_tiles_lr_rndup;
    165 
    166 
    167 static void PrepareTileSet(void);
    168 static void PrepareMap(void);
    169 static void PrepareAttributes(void);
    170 
    171 static void DedupeTileset(void);
    172 
    173 static void ExportPalettes(const char * fname_base);
    174 static void ExportPalettesPrecompiled(const char * fname_base);
    175 static void ExportTileSet(const char * fname_base);
    176 static void ExportMap(const char * fname_base);
    177 static void ExportMapAttributes(const char * fname_base);
    178 
    179 
    180 void hicolor_init(void) {
    181     // Defaults
    182     LConversion = HICOLOR_PATTERN_ADAPTIVE_MED; // Default Conversion adaptive-medium Left Screen
    183     RConversion = HICOLOR_PATTERN_ADAPTIVE_MED; // Default Conversion adaptive-medium Righ Screen
    184     ConvertType = CONV_TYPE_MED_CUT_NO_DITHER; // Normal default is 1 ("Median cut - no dither")
    185 }
    186 
    187 
    188 static void hicolor_vars_prep(image_data * p_loaded_image) {
    189     DBG("hicolor_vars_prep()\n");
    190 
    191     image_height                = p_loaded_image->height;
    192     image_y_min                 = 0;
    193     image_y_max                 = p_loaded_image->height - 1;
    194 
    195     // // Screen palette region updates are 80 pixels wide and 2 pixels tall
    196     // // since palette 0-3 allocated to left side, 4-7 allocated to right side
    197     // // and only 4 palettes are updated per scanline, so Left and Right alternate in gettig udpates
    198     // // 73(L) & 72(R) for standard GB screen
    199 
    200     // One extra region due to starting at -1 Y offset from screen grid, and so there is a last extra entry that "hangs off" the bottom of the screen
    201     y_region_count_left         = ((image_height / PAL_REGION_HEIGHT_PX) + 1);
    202     y_region_count_right        =  (image_height / PAL_REGION_HEIGHT_PX);
    203     // Use larger size[side] for rounded up amount
    204     y_region_count_lr_rndup     =  (y_region_count_left);
    205     y_region_count_both_sides   =  (y_region_count_left + y_region_count_right);
    206 
    207     // 19(L) & 18(R) for standard GB Full screen height
    208     // One extra region due to starting at -1 Y offset from screen grid, and so there is a last extra entry that "hangs off" the bottom of the screen
    209     y_height_in_tiles_left      = ((image_height / TILE_HEIGHT_PX) + 1);
    210     y_height_in_tiles_right     =  (image_height / TILE_HEIGHT_PX);
    211     y_height_in_tiles           =  (image_height / TILE_HEIGHT_PX);
    212     // Use larger size[side] for rounded up amount
    213     y_height_in_tiles_lr_rndup  =  (y_height_in_tiles_left);
    214 }
    215 
    216 
    217 // Look up user specified L/R pattern by name if possible
    218 unsigned int hicolor_get_pattern_by_name(const char * opt_str) {
    219     char opt_str_lower[MAX_STR_LEN];
    220     unsigned int c;
    221 
    222     // Convert user input to lowercase first
    223     for(c = 0; (opt_str[c] != '\0') && (c < MAX_STR_LEN); c++)
    224         opt_str_lower[c] = tolower(opt_str[c]);
    225     opt_str_lower[c] = '\0';
    226 
    227     // Return if it matches any names in the named pattern list
    228     for (c = 0; c < ARRAY_LEN(named_patterns); c++) {
    229         if (strcmp(opt_str_lower, named_patterns[c].name) == 0)
    230             return named_patterns[c].num;
    231     }
    232 
    233     // If there was no match, return if it contained any non-digit characters,
    234     // meaning it should not later be converted as a raw numeric value for the option
    235     while (*opt_str != '\0') {
    236         if (isdigit(*opt_str) == 0) {
    237             return HICOLOR_PATTERN_NOT_FOUND_HAS_CHARS;
    238         }
    239         opt_str++;
    240     }
    241 
    242     return HICOLOR_PATTERN_NOT_FOUND;
    243 }
    244 
    245 
    246 void hicolor_set_convert_left_pattern(uint8_t new_value) {
    247     // IDC_CONVERTLEFT
    248     LConversion = new_value;
    249     VERBOSE("HiColor: Left pattern set to %d\n", new_value);
    250 }
    251 
    252 
    253 void hicolor_set_convert_right_pattern(uint8_t new_value) {
    254     // IDC_CONVERTRIGHT
    255     RConversion = new_value;
    256     VERBOSE("HiColor: Right pattern set to %d\n", new_value);
    257 }
    258 
    259 
    260 void hicolor_set_type(uint8_t new_value) {
    261     // IDC_CONVERTTYPE
    262     ConvertType = new_value;
    263     VERBOSE("HiColor: Convert type set to %d\n", new_value);
    264 }
    265 
    266 
    267 ///////////////////////////////////
    268 
    269 // Equivalent of former file loading
    270 static void hicolor_image_import(image_data * p_loaded_image) {
    271     DBG("hicolor_image_import()\n");
    272 
    273     // TODO: input guarding
    274     // TODO: deduplicate some of the array copying around
    275     uint8_t * p_input_img = p_loaded_image->p_img_data;
    276 
    277     for (unsigned int y=0; y< image_height; y++) {
    278         for (unsigned int x=0; x< 160; x++) {
    279 
    280             // Clamp to CGB max R/G/B value in RGB 888 mode (31u << 3)
    281             // png_image[].rgb -> pic2[].rgb -> pBitssource[].bgr??
    282             pic2[x][y][0] = (p_input_img[RGB_RED]   & 0xf8u);
    283             pic2[x][y][1] = (p_input_img[RGB_GREEN] & 0xf8u);
    284             pic2[x][y][2] = (p_input_img[RGB_BLUE]  & 0xf8u);
    285 
    286             p_input_img += RGB_24SZ; // Move to next pixel of source image
    287         }
    288     }
    289 
    290     // TODO: Eventually clean up data pathway to remove some vestigial former display rendering buffers
    291     // It's convoluted, but pBitssource & pBitsdest are used for:
    292     // - display as windows DIBs (formerly)
    293     // - and for some calculations at the end of ConvertRegions()
    294     for (unsigned int y=0; y<image_height; y++) {
    295         for (unsigned int x=0; x<160; x++) {
    296             for (unsigned int z=0; z<3; z++) {
    297                 // TODO: (2-z) seems to be swapping RGB for BGR?
    298                 *(pBitssource+(image_y_max-y)*3*160+x*3+z) = pic2[x][y][2-z];            // Invert the dib, cos windows likes it like that !!
    299             }
    300         }
    301     }
    302 
    303 }
    304 
    305 
    306 // TODO: fix
    307 // TODO: Operates on RGB data in pic[] copied from RGB data in pic2
    308 static void hicolor_convert(void) {
    309     DBG("hicolor_convert()\n");
    310 
    311     for(unsigned int x=0; x<160; x++)
    312     {
    313         for(unsigned int y=0; y<image_height; y++)
    314         {
    315             pic[x][y][0] = pic2[x][y][0];
    316             pic[x][y][1] = pic2[x][y][1];
    317             pic[x][y][2] = pic2[x][y][2];
    318 
    319             for(unsigned int i=0; i<3; i++)
    320             {
    321                 *(Data + y*160*3+x*3+i) = pic[x][y][i];
    322             }
    323         }
    324     }
    325 
    326     ConvertToHiColor(ConvertType-1);
    327 }
    328 
    329 
    330 static void hicolor_save(const char * fname_base, const char * varname) {
    331 
    332     // Default tile count to non-deduplicated number
    333     int tile_count = y_height_in_tiles * (160 / TILE_WIDTH_PX);
    334 
    335     DBG("hicolor_save()\n");
    336     PrepareTileSet();
    337     PrepareMap();
    338     PrepareAttributes();
    339 
    340     if (opt_get_tile_dedupe()) {
    341         DedupeTileset();
    342         tile_count = TileCountDeduped;
    343     }
    344 
    345     ExportTileSet(fname_base);
    346     if (opt_get_precompiled_palette())
    347         ExportPalettesPrecompiled(fname_base);
    348     else
    349         ExportPalettes(fname_base);
    350 
    351     ExportMap(fname_base);
    352     ExportMapAttributes(fname_base);
    353 
    354     if (opt_get_c_file_output()) {
    355         file_c_output_write(fname_base, varname, opt_get_bank_num(), tile_count, y_height_in_tiles);
    356     }
    357 }
    358 
    359 
    360 // Currently expects width x height x 3(RGB888)
    361 void hicolor_process_image(image_data * p_loaded_image, const char * fname_base, const char *varname) {
    362     DBG("hicolor_process_image(), fname_base: \"%s\"\n", fname_base);
    363 
    364     hicolor_vars_prep(p_loaded_image);
    365     hicolor_image_import(p_loaded_image);
    366     hicolor_convert();
    367     hicolor_save(fname_base, varname);
    368 }
    369 
    370 
    371 
    372 
    373 
    374 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    375 
    376 
    377 
    378 static void DedupeTileset(void)
    379 {
    380     unsigned int map_tile_id;
    381     uint8_t new_tile_id, new_attribs;
    382 
    383     TileCountDeduped = 0;
    384     // Traverse all tiles in the image/map
    385     for (unsigned int mapy = 0; mapy < y_height_in_tiles; mapy++) {
    386         for (unsigned int mapx = 0; mapx < 20; mapx++) {
    387 
    388             map_tile_id = MapTileIDs[mapx][mapy];
    389             map_tile_id += (MapAttributes[mapx][mapy] & CGB_ATTR_TILES_BANK) ? CGB_TILES_START_BANK_1 : CGB_TILES_START_BANK_0;
    390 
    391             if (!tileset_find_matching_tile(&TileSet[map_tile_id * TILE_SZ], &TileSetDeduped[0], TileCountDeduped, &new_tile_id, &new_attribs)) {
    392                 // If no match, copy tile to new tile set and save new index for remapping
    393                 new_tile_id = TileCountDeduped;
    394                 new_attribs = (TileCountDeduped < CGB_TILES_START_BANK_1) ? CGB_ATTR_TILES_BANK_0 : CGB_ATTR_TILES_BANK_1;
    395                 memcpy(&TileSetDeduped[TileCountDeduped * TILE_SZ], &TileSet[map_tile_id * TILE_SZ], TILE_SZ);
    396                 TileCountDeduped++;
    397             }
    398 
    399             // Update map data and attributes to new index
    400             // Mask out everything except palettes and then apply the new attribs (bank, vflip, hflip)
    401             MapTileIDs[mapx][mapy]    = new_tile_id;
    402             MapAttributes[mapx][mapy] = (MapAttributes[mapx][mapy] & CGB_ATTR_PALETTES_ONLY) | new_attribs;
    403         }
    404     }
    405     VERBOSE("DedupeTileset(): Reduced tiles from %d (%d bytes) to %d (%d bytes) = %d bytes saved. %%%d of original size\n",
    406                 map_tile_id + 1, (map_tile_id + 1) * TILE_SZ, TileCountDeduped, TileCountDeduped * TILE_SZ,
    407                 ((map_tile_id + 1) * TILE_SZ) - (TileCountDeduped * TILE_SZ), (TileCountDeduped * 100) / (map_tile_id + 1));
    408 }
    409 
    410 
    411 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    412 
    413 
    414 static void PrepareTileSet(void) {
    415     u32      x, y;
    416     u8       c1,c2;
    417     u8       dx,dy;
    418     u8       c;
    419 
    420     uint8_t * p_buf = TileSet;
    421 
    422     // Write out tilemap data, Left -> Right, Top -> Bottom, 16 bytes per tile
    423     for (y=0; y<image_height; y=y+8)
    424     {
    425         for (x=0; x<160; x=x+8)
    426         {
    427             for (dy=0; dy<8; dy++)
    428             {
    429                 c1 = 0;
    430                 c2 = 0;
    431                 for (dx=0; dx<8; dx++)
    432                 {
    433                     c1 = (u8)(c1 << 1);
    434                     c2 = (u8)(c2 << 1);
    435                     c = out[x+dx][y+dy];
    436                     if (c & 2) c1++;
    437                     if (c & 1) c2++;
    438                 }
    439 
    440                 *p_buf++ = c2;
    441                 *p_buf++ = c1;
    442             }
    443         }
    444     }
    445 }
    446 
    447 
    448 static void PrepareMap(void) {
    449     // Set up export Map Tile IDs
    450     // Note: The indexes are clipped to 0-255 (instead of 0-512),
    451     // the attribute tile index+256 bit is auto-calculated in the attribute map in PrepareAttributes()
    452     uint8_t tile_id = 0;
    453     for (unsigned int mapy = 0; mapy < y_height_in_tiles; mapy++) {
    454         for (unsigned int mapx = 0; mapx < 20; mapx++) {
    455 
    456             MapTileIDs[mapx][mapy] = tile_id;
    457             tile_id++;
    458         }
    459     }
    460 }
    461 
    462 
    463 static void PrepareAttributes(void) {
    464     // Set up the Map Attributes table
    465     unsigned int tile_id = 0;
    466     for(unsigned int MastY=0;MastY<y_height_in_tiles_right;MastY++)
    467     {
    468         for(unsigned int MastX=0;MastX<2;MastX++)
    469         {
    470             int Line=Best[MastX][MastY];
    471             int width=0;
    472             for(int i=0;i<4;i++)
    473             {
    474                 TileOffset[i]=width;
    475                 TileWidth[i]=SplitData[Line][i];
    476                 width+=TileWidth[i];
    477             }
    478 
    479             for(int x=0;x<4;x++) {
    480                 for(int z=TileOffset[x];z<(TileOffset[x]+TileWidth[x]);z++) {
    481                     MapAttributes[MastX*10+z][MastY]=x+MastX*4;
    482                     // Mask in second CGB Tile Bank flag if tile index is over 256 tiles
    483                     if (tile_id++ >= 256)
    484                         MapAttributes[MastX*10+z][MastY] |= CGB_ATTR_TILES_BANK_1;
    485                 }
    486             }
    487         }
    488     }
    489 }
    490 
    491 
    492 static void ExportTileSet(const char * fname_base)
    493 {
    494     char filename[MAX_PATH*2];
    495 
    496     strcpy(filename, fname_base);
    497     strcat(filename, ".til");
    498     VERBOSE("Writing Tile Patterns to: %s\n", filename);
    499 
    500     if (opt_get_tile_dedupe()) {
    501 
    502         int outbuf_sz_tiles = TileCountDeduped * TILE_SZ;
    503         if (!file_write_from_buffer(filename, TileSetDeduped, outbuf_sz_tiles))
    504             set_exit_error();
    505     } else {
    506 
    507         int outbuf_sz_tiles = ((image_height / TILE_HEIGHT_PX) * (160 / TILE_WIDTH_PX) * 8 * 2);
    508         if (!file_write_from_buffer(filename, TileSet, outbuf_sz_tiles))
    509             set_exit_error();
    510     }
    511 }
    512 
    513 
    514 static void ExportPalettes(const char * fname_base)
    515 {
    516     char filename[MAX_PATH * 2];
    517     unsigned int      i, j, k;
    518     s32      r,g,b,v;
    519 
    520     uint16_t pal_end_color_bgr555 = 0x0000u;
    521     int pal_end_color_count = 0;
    522 
    523     strcpy(filename, fname_base);
    524     strcat(filename, ".pal");
    525     VERBOSE("Writing Palette to: %s\n", filename);
    526 
    527     // No longer +1 for the trailing 0x2D
    528     int outbuf_sz_pals = (y_region_count_both_sides * PALS_PER_SIDE * COLORS_PER_PAL * BYTES_PER_COLOR);
    529 
    530     // Handle resize if trailing end colors have been appended
    531     if (opt_get_enable_pal_end_color()) {
    532         opt_load_pal_end_color(&pal_end_color_bgr555, &pal_end_color_count);
    533         outbuf_sz_pals += (pal_end_color_count * BYTES_PER_COLOR);
    534     }
    535 
    536     uint8_t output_buf[outbuf_sz_pals];
    537     uint8_t * p_buf = output_buf;
    538 
    539 
    540     for (i = 0; i < (y_region_count_both_sides); i++) // Number of palette sets (left side updates + right side updates)
    541     {
    542         for (j = 0; j < 4; j++) // Each palette in the set
    543         {
    544             for(k=0; k<4;k++) // Each color in the palette
    545             {
    546                 r = IdealPal[(i%2)*4+j][i/2][k][0];
    547                 g = IdealPal[(i%2)*4+j][i/2][k][1];
    548                 b = IdealPal[(i%2)*4+j][i/2][k][2];
    549 
    550                 // Converting to BGR555
    551                 v = ((b/8)*32*32) + ((g/8)*32) + (r/8);
    552 
    553                 // 2 bytes per color
    554                 *p_buf++ = (u8)(v & 255);
    555                 *p_buf++ = (u8)(v / 256);
    556             }
    557         }
    558     }
    559 
    560     // Add trailing 32 colors to clear BG if enabled
    561     if (opt_get_enable_pal_end_color()) {
    562         for (int c = 0; c < pal_end_color_count; c++) {
    563             *p_buf++ = (u8)(pal_end_color_bgr555 & 255);
    564             *p_buf++ = (u8)(pal_end_color_bgr555 / 256);
    565         }
    566     }
    567 
    568     // Set unused bit .15 = 1 for last u16 palette entry
    569     // to indicate it's the final one
    570     if (opt_get_pal_end_bit())
    571         output_buf[outbuf_sz_pals - 1] |= 0x80u;
    572 
    573     // This has an unknown purpose and was present in
    574     // the original source code, but doesn't appear to be needed.
    575     // *p_buf++ = 0x2d;
    576 
    577     if (!file_write_from_buffer(filename, output_buf, outbuf_sz_pals))
    578         set_exit_error();
    579 
    580 }
    581 
    582 
    583 #define LDHL_2x_SZ            2 // Scale factor for pal color bytes loaded via `ld [hl], <byte>`
    584 #define RET_SZ                1 // Size of ret opcode
    585 #define VBLANK_LOAD_LINE_CNT  2 // Number of lines loaded in vblank
    586 #define HALT_LOAD_SZ          5 // Size of Halt + LD HL, B/C/D/E on non-vblank scanlines 
    587 #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>
    588 #define PAL_BYTES_PER_LINE    (PALS_PER_SIDE * COLORS_PER_PAL * BYTES_PER_COLOR)
    589 
    590 static void ExportPalettesPrecompiled(const char * fname_base)
    591 {
    592     char filename[MAX_PATH * 2];
    593     unsigned int  line, pal, col;
    594     s32      r,g,b,v;
    595     size_t outbuf_sz_pals = 0;
    596 
    597     strcpy(filename, fname_base);
    598     strcat(filename, ".pal");
    599     VERBOSE("Writing Pre-compiled Palette to: %s\n", filename);
    600 
    601     // How to calculate output size:
    602     //
    603     // VBLANK ISR (2 Lines)
    604     // ~ No wait + load header code
    605     // + Always uses LD [HL] (so 2x num pal bytes)
    606     // + 1 ret shared by the 2 lines
    607     // = (Pal bytes per line x 2) x (2 lines) + 1 ret 
    608     //   (((4 x 4 x 2)       x 2)  x 2)       + 1 = 129
    609     outbuf_sz_pals = ((PAL_BYTES_PER_LINE * LDHL_2x_SZ) * VBLANK_LOAD_LINE_CNT) + RET_SZ;
    610 
    611     // Then...
    612     // STAT ISR (Num Lines - 2) 
    613     // - 4 bytes preload in STAT ISR without LD [HL] (so: 4 pal bytes without 2x sizing)
    614     // + Then wait + load header code (so +5 bytes)
    615     // + Then remainder of pal bytes get LD [HL] (so 2x num pal bytes)
    616     // + 1 ret per line
    617     // = (( (Pal bytes per line x 2) - 4 preload bytes + 5 header + 1 ret)  x (num lines - 2 vblank lines)
    618     //   ( ( (4 x 4 x 2)        x 2) - 4)              + 5        + 1) = 66 x (num lines - 2)
    619     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);
    620 
    621     uint8_t output_buf[outbuf_sz_pals];
    622     uint8_t * p_buf = output_buf;
    623 
    624     // Note: "line" 0 is equivalent to something like scanline -1
    625     // (due to left side region starting 1 scanline before line 0)
    626     for (line = 0; line < (y_region_count_both_sides); line++) // Number of palette sets (left side updates + right side updates)
    627     {
    628         for (pal = 0; pal < 4; pal++) // Each palette in the line
    629         {
    630             for(col = 0; col < 4;col++) // Each color in the palette
    631             {
    632                 // Precompiled mode has a "header" inserted after the first two colours of palette 0,
    633                 // except for the first two scanline lines (which are during VBlank so can load directly without a preload + wait)
    634                 if (line >= 2 && pal == 0 && col == 2) {
    635                     *p_buf++ = SM83_OPCODE_HALT;
    636                     *p_buf++ = SM83_OPCODE_LD_HL_B;
    637                     *p_buf++ = SM83_OPCODE_LD_HL_C;
    638                     *p_buf++ = SM83_OPCODE_LD_HL_D;
    639                     *p_buf++ = SM83_OPCODE_LD_HL_E;
    640                 }
    641 
    642                 r = IdealPal[(line % 2)*4 + pal][line / 2][col][0];
    643                 g = IdealPal[(line % 2)*4 + pal][line / 2][col][1];
    644                 b = IdealPal[(line % 2)*4 + pal][line / 2][col][2];
    645 
    646                 // Converting to BGR555
    647                 v = ((b/8)*32*32) + ((g/8)*32) + (r/8);
    648 
    649                 // Load 2 bytes per color
    650 
    651                 // Insert LD [HL] opcode before pal data bytes... when:
    652                 // -  Any time during first two lines (i.e for all pal bytes in vblank)
    653                 // -  Or is the Second Palette or more (of each Line)
    654                 // -  Or is the Third Color or more (of each Palette. the STAT isr has pre-load code for the first two pal colors)
    655                 if (line < 2 || pal >= 1 || col >= 2) {
    656                     *p_buf++ = SM83_OPCODE_LD_HL_IMM8; // ld [hl], <imm8>
    657                 }
    658                 *p_buf++ = (u8)(v & 255);
    659 
    660                 if (line < 2 || pal >= 1 || col >= 2) {
    661                     *p_buf++ = SM83_OPCODE_LD_HL_IMM8; // ld [hl], <imm8>
    662                 }
    663                 *p_buf++ = (u8)(v / 256);
    664             }
    665         }
    666 
    667         // Skip return for the first palette line (during vblank)
    668         if (line >= 1)
    669             *p_buf++ = SM83_OPCODE_RET;
    670     }
    671 
    672     // This has an unknown purpose and was present in
    673     // the original source code, but doesn't appear to be needed.
    674     // *p_buf++ = 0x2d;
    675 
    676     if (!file_write_from_buffer(filename, output_buf, outbuf_sz_pals))
    677         set_exit_error();
    678 }
    679 
    680 
    681 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    682 
    683 
    684 static void ExportMap(const char * fname_base)
    685 {
    686     char      filename[MAX_PATH*2];
    687 
    688     strcpy(filename, fname_base);
    689     strcat(filename, ".map");
    690     VERBOSE("Writing Tile Map to: %s\n", filename);
    691 
    692     int outbuf_sz_map = (20 * y_height_in_tiles);
    693     uint8_t output_buf_map[outbuf_sz_map];
    694 
    695     int tile_id = 0;
    696     for (unsigned int y = 0; y < y_height_in_tiles; y++) {
    697         for (unsigned int x = 0; x < 20; x++) {
    698             uint8_t tile_num = MapTileIDs[x][y];
    699 
    700             // This needs to happen here, after optional deduplication stage
    701             // since that may rewrite the tile pattern order and indexes
    702             if (opt_get_map_tile_order() != OPT_MAP_TILE_SEQUENTIAL_ORDER) // implied: OPT_MAP_TILE_ORDER_BY_VRAM_ID
    703                 tile_num = ((tile_num < 128) ? (tile_num) + 128 : (tile_num) - 128); // Previous ordering that was: 128 -> 255 -> 0 -> 127
    704 
    705             output_buf_map[tile_id] = tile_num;
    706             tile_id++;
    707         }
    708     }
    709 
    710     if (!file_write_from_buffer(filename, output_buf_map, outbuf_sz_map))
    711         set_exit_error();
    712 }
    713 
    714 
    715 static void ExportMapAttributes(const char * fname_base)
    716 {
    717     char    filename[MAX_PATH*2];
    718 
    719     strcpy(filename, fname_base);
    720     strcat(filename, ".atr");
    721     VERBOSE("Writing Attribute Map to: %s\n", filename);
    722 
    723     int outbuf_sz_map = (20 * y_height_in_tiles);
    724     uint8_t output_buf_map[outbuf_sz_map];
    725 
    726     int tile_id = 0;
    727     for (unsigned int y = 0; y < y_height_in_tiles; y++)
    728     {
    729         for (unsigned int x = 0; x < 20; x++)
    730         {
    731             output_buf_map[tile_id++] = MapAttributes[x][y];
    732         }
    733     }
    734 
    735     if (!file_write_from_buffer(filename, output_buf_map, outbuf_sz_map))
    736         set_exit_error();
    737 }
    738 
    739 
    740 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    741 
    742 
    743 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    744 
    745 
    746 // This section of code is used to convert an RGB (pc) triplet into a RGB (gameboy)
    747 // triplet. This section of code was kindly donated by Brett Bibby (GameBrains).
    748 
    749 uint8_t intensity[32] =
    750 {
    751  0x00,0x10,0x20,0x30,0x40,0x50,0x5e,0x6c,0x7a,0x88,0x94,0xa0,0xae,0xb7,0xbf,0xc6,
    752  0xce,0xd3,0xd9,0xdf,0xe3,0xe7,0xeb,0xef,0xf3,0xf6,0xf9,0xfb,0xfd,0xfe,0xff,0xff
    753 };
    754 
    755 unsigned char influence[3][3] =
    756 {
    757     {16,4,4},
    758     {8,16,8},
    759     {0,8,16}
    760 };
    761 
    762 RGBQUAD translate(uint8_t rgb[3])
    763 {
    764     RGBQUAD color;
    765     uint8_t    tmp[3];
    766     uint8_t    m[3][3];
    767     uint8_t    i,j;
    768 
    769     for (i=0;i<3;i++)
    770         for (j=0;j<3;j++)
    771             m[i][j] = (intensity[rgb[i]>>3]*influence[i][j]) >> 5;
    772 
    773     for (i=0;i<3;i++)
    774     {
    775         if (m[0][i]>m[1][i])
    776         {
    777             j=m[0][i];
    778             m[0][i]=m[1][i];
    779             m[1][i]=j;
    780         }
    781 
    782         if (m[1][i]>m[2][i])
    783         {
    784             j=m[1][i];
    785             m[1][i]=m[2][i];
    786             m[2][i]=j;
    787         }
    788 
    789         if (m[0][i]>m[1][i])
    790         {
    791             j=m[0][i];
    792             m[0][i]=m[1][i];
    793             m[1][i]=j;
    794         }
    795 
    796         tmp[i]=(((m[0][i]+m[1][i]*2+m[2][i]*4)*5) >> 4)+32;
    797     }
    798 
    799     color.rgbRed    = tmp[0];
    800     color.rgbGreen    = tmp[1];
    801     color.rgbBlue    = tmp[2];
    802 
    803     return color;
    804 }
    805 
    806 
    807 
    808 
    809 
    810 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    811 
    812 
    813 // Data table containing all of the possible combinations of attribute blocks
    814 // for one side of the screen.
    815 
    816 // The higher the adaptive level, the more combinations of attributes are tested.
    817 
    818 u8    SplitData[HICOLOR_PATTERN_FIXED_COUNT][4]=
    819 {
    820     {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},
    821     {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},
    822     {1,3,2,4},{1,3,3,3},{1,3,4,2},{1,3,5,1},{1,4,1,4},{1,4,2,3},{1,4,3,2},{1,4,4,1},{1,5,1,3},{1,5,2,2},{1,5,3,1},
    823     {1,6,1,2},{1,6,2,1},{2,1,1,6},{2,1,2,5},{2,1,3,4},{2,1,4,3},{2,1,5,2},{2,1,6,1},{2,2,1,5},{2,2,5,1},{2,3,1,4},
    824     {2,3,4,1},{2,4,1,3},{2,4,3,1},{2,5,1,2},{2,5,2,1},{2,6,1,1},{3,1,1,5},{3,1,2,4},{3,1,3,3},{3,1,4,2},{3,1,5,1},
    825     {3,2,1,4},{3,2,4,1},{3,3,1,3},{3,3,3,1},{3,4,1,2},{3,4,2,1},{3,5,1,1},{4,1,1,4},{4,1,2,3},{4,1,3,2},{4,1,4,1},
    826     {4,2,1,3},{4,2,3,1},{4,3,1,2},{4,3,2,1},{4,4,1,1},{5,1,1,3},{5,1,2,2},{5,1,3,1},{5,2,1,2},{5,2,2,1},{5,3,1,1},
    827     {6,1,1,2},{6,1,2,1},{6,2,1,1}
    828 };
    829 
    830 
    831 
    832 unsigned int ImageRating(u8 *src, u8 *dest, int StartX, int StartY, int Width, int Height)
    833 {
    834     DBG("ImageRating()\n");
    835     unsigned int    tot;
    836     int                x,y;
    837     unsigned int    accum=0;
    838     int                scradd;
    839 
    840     for(y=StartY;y<(StartY+Height);y++)
    841     {
    842         for(x=StartX;x<(StartX+Width);x++)
    843         {
    844             scradd=(image_y_max-y)*(160*3)+x*3;
    845             tot=(*(src+scradd)-*(dest+scradd)) * (*(src+scradd)-*(dest+scradd));
    846             tot+=(*(src+scradd+1)-*(dest+scradd+1)) * (*(src+scradd+1)-*(dest+scradd+1));
    847             tot+=(*(src+scradd+2)-*(dest+scradd+2)) * (*(src+scradd+2)-*(dest+scradd+2));
    848             accum+=tot;
    849         }
    850     }
    851 
    852     return accum;
    853 }
    854 
    855 
    856 // TODO: rename to something that aligns with other convert functions
    857 void ConvertToHiColor(int ConvertType)
    858 {
    859     DBG("ConvertToHiColor()\n");
    860     int        res;
    861     unsigned int        x,y;
    862     // TODO: Change "Adaptive Pattern" settings to be a separate variable so StartSplit doesn't have to be offset by -3
    863     //       Just set these directly:
    864     //       * StartSplit (first pattern to start checking with)
    865     //       * NumSplit   (number of patterns to iterate through for testing, 1 = just use the one in StartSplit)
    866     int        StartSplit=0;
    867     int        NumSplit=1;
    868 
    869     switch(LConversion)
    870     {
    871         case 0:
    872 
    873             StartSplit=0;
    874             NumSplit=6;
    875             break;
    876 
    877         case 1:
    878 
    879             StartSplit=0;
    880             NumSplit=10;
    881             break;
    882 
    883         case 2:
    884 
    885             StartSplit=0;
    886             NumSplit=80;
    887             break;
    888 
    889         default:
    890 
    891             StartSplit=LConversion-3;
    892             NumSplit=1;
    893             break;
    894     }
    895 
    896     // Convert left side with one extra tile of height to fix
    897     // the glitching where the last scanline on left bottom region
    898     // lacks tile and palette data
    899     res=ConvertRegions(0,1,0,y_height_in_tiles_left,StartSplit,NumSplit,ConvertType);        // Step through all options
    900     ConvertRegions(0,1,0,y_height_in_tiles_left,res,1,ConvertType);
    901 
    902     // Formerly: for(y=0;y<189;y++)
    903     // Treating it as a typo (intended a "18") since 189 would be out of bounds for the original array
    904     for(y=0;y<y_height_in_tiles_left;y++)
    905         Best[0][y]=res;
    906 
    907 
    908     switch(RConversion)
    909     {
    910         case 0:
    911 
    912             StartSplit=0;
    913             NumSplit=6;
    914             break;
    915 
    916         case 1:
    917 
    918             StartSplit=0;
    919             NumSplit=10;
    920             break;
    921 
    922         case 2:
    923 
    924             StartSplit=0;
    925             NumSplit=80;
    926             break;
    927 
    928         default:
    929 
    930             StartSplit=RConversion-3;
    931             NumSplit=1;
    932             break;
    933     }
    934 
    935     for(y=0;y<y_height_in_tiles_right;y++)
    936     {
    937         res=ConvertRegions(1,1,y,1,StartSplit,NumSplit,ConvertType);        // Step through all options
    938         ConvertRegions(1,1,y,1,res,1,ConvertType);
    939         Best[1][y]=res;
    940     }
    941 
    942 
    943     // TODO: fix me -> pBitsdest being used in conversion process
    944     for(y=0;y<image_height;y++)
    945     {
    946         for(x=0;x<160;x++)
    947         {
    948             raw[0][x][y][0] = *(pBitsdest+(image_y_max-y)*3*160+x*3+2);
    949             raw[0][x][y][1] = *(pBitsdest+(image_y_max-y)*3*160+x*3+1);
    950             raw[0][x][y][2] = *(pBitsdest+(image_y_max-y)*3*160+x*3);
    951 
    952             RGBQUAD GBView=translate(raw[0][x][y]);
    953 
    954             raw[1][x][y][0] = GBView.rgbRed;
    955             raw[1][x][y][1] = GBView.rgbGreen;
    956             raw[1][x][y][2] = GBView.rgbBlue;
    957         }
    958     }
    959     VERBOSE("\n");
    960 }
    961 
    962 
    963 
    964 // Start X = 0 for Left / 1 for Right
    965 // Width = 1 for half screen 2 = full screen
    966 // StartY = 0 - 17 : Starting attribute block
    967 // Height = Number of attribute blocks to check / process
    968 
    969 int ConvertRegions(unsigned int StartX, unsigned int Width, unsigned int StartY, unsigned int Height, unsigned int StartJ, unsigned int FinishJ, int ConvertType)
    970 {
    971     DBG("ConvertRegions()\n");
    972     u32        width,x1,ts,tw,y2,x2,y_offset;
    973     unsigned int        x,y;
    974     unsigned int        i,j;
    975     u8        col;
    976 
    977 
    978     BestQuantLine=0xffffffff;
    979 
    980     for(x=StartX;x<(StartX+Width);x++)
    981     {
    982         // Left side of screen is offset by -1 Y
    983         // (Left side calcs hang off top and bottom of screen
    984         // due to Left/Right palette update interleaving)
    985         if (x == CONV_SIDE_LEFT)
    986             y_offset = CONV_Y_SHIFT_UP_1;
    987         else
    988             y_offset = CONV_Y_SHIFT_NO;
    989 
    990         for(j=StartJ;j<(StartJ+FinishJ);j++)
    991         {
    992             width=0;
    993             for(i=0;i<4;i++)
    994             {
    995                 TileOffset[i]=width;
    996                 TileWidth[i]=SplitData[j][i]<<3;
    997                 width+=TileWidth[i];
    998             }
    999 
   1000             for(y=StartY*4;y<(StartY+Height)*4;y++)
   1001             {
   1002                 VERBOSE(".");
   1003 
   1004                 for(x1=0;x1<4;x1++)
   1005                 {
   1006                     ts=TileOffset[x1];
   1007                     tw=TileWidth[x1];
   1008 
   1009                     for(y2=0;y2<2;y2++)
   1010                     {
   1011                         // Skip case where y_line would evaluate to -1 to avoid unsigned wraparound)
   1012                         // (scanline 0, left side of the image where 80 x 2 pixel box goes from scanline -1 to 0)
   1013                         if (y_offset > ((y*2) + y2)) continue;
   1014 
   1015                         // Skip if Y line is outside image borders (prevents buffer overflow)
   1016                         // (Left side calcs hang off top and bottom of screen
   1017                         // due to Left/Right palette update interleaving)
   1018                         unsigned int y_line = (y*2+y2-y_offset);
   1019                         if ((y_line < image_y_min) || (y_line > image_y_max)) continue;
   1020 
   1021                         for(x2=0;x2<tw;x2++)
   1022                         {
   1023                             // i is iterating over r/g/b slots for the current pixel
   1024                             for(i=0;i<3;i++)
   1025                             {
   1026                                 *(Data+(tw*3*y2)+x2*3+i) = pic[x*80+ts+x2][y*2+y2-y_offset][i];
   1027                             }
   1028                         }
   1029                     }
   1030 
   1031                     switch(ConvertType)
   1032                     {
   1033                         case 0:
   1034                             to_indexed(Data,0,TileWidth[x1],2);            // Median Reduction No Dither
   1035                             break;
   1036 
   1037                         case 1:
   1038 
   1039                             to_indexed(Data,1,TileWidth[x1],2);            // Median Reduction With Dither
   1040                             break;
   1041 
   1042                         case 2:
   1043                             wuReduce(Data,4,TileWidth[x1]*2);                // Wu Reduction
   1044                             break;
   1045                     }
   1046 
   1047                     for(y2=0;y2<4;y2++)
   1048                     {
   1049                         // Skip if Y is outside allocated Palette size (prevents buffer overflow)
   1050                         // (Left side calcs hang off top and bottom of screen
   1051                         // due to Left/Right palette update interleaving)
   1052                         if (y >= y_region_count_lr_rndup) continue;
   1053 
   1054                         IdealPal[x*4+x1][y][y2][0]=QuantizedPalette[y2][2];
   1055                         IdealPal[x*4+x1][y][y2][1]=QuantizedPalette[y2][1];
   1056                         IdealPal[x*4+x1][y][y2][2]=QuantizedPalette[y2][0];
   1057                     }
   1058 
   1059                     for(y2=0;y2<2;y2++)
   1060                     {
   1061                         for(x2=0;x2<tw;x2++)
   1062                         {
   1063                             // Skip case where y_line would evaluate to -1 to avoid unsigned wraparound)
   1064                             // (scanline 0, left side of the image where 80 x 2 pixel box goes from scanline -1 to 0)
   1065                             if (y_offset > ((y*2) + y2)) continue;
   1066 
   1067                             // Skip if Y line is outside image borders (prevents buffer overflow)
   1068                             // since Left side calcs hang off top and bottom of image/screen
   1069                             unsigned int y_line = (y*2+y2-y_offset);
   1070                             if ((y_line < image_y_min) || (y_line > image_y_max)) continue;
   1071 
   1072                             col=Picture256[y2*tw+x2];
   1073                             out[x*80+x2+ts][y*2+y2-y_offset]=col;
   1074 
   1075                             for(i=0;i<3;i++)
   1076                             {
   1077                                 *(pBitsdest+(image_y_max-(y*2+y2-y_offset))*3*160+(x*80+ts+x2)*3+i)=QuantizedPalette[col][i];
   1078                             }
   1079                         }
   1080                     }
   1081                 }
   1082             }
   1083 
   1084             TempD=ImageRating(pBitssource,pBitsdest,StartX*80,StartY*8,Width*80,Height*8);
   1085 
   1086             if(TempD<BestQuantLine)
   1087             {
   1088                 BestLine=j;
   1089                 BestQuantLine=TempD;
   1090             }
   1091         }
   1092     }
   1093     return BestLine;
   1094 }
   1095 
   1096 
   1097 
   1098 

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