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.