gbdk-2020 | GameBoy Development Kit |
| download: https://git.y1.nz/archives/gbdk.tar.gz | |
| README | Files | Log | Refs | LICENSE |
gbdk-support/png2asset/image_utils.cpp
1
2 // image_utils.cpp
3
4 #include <vector>
5 #include <stdio.h>
6 #include <stdint.h>
7 #include "lodepng.h"
8
9 using namespace std;
10
11 #include "png2asset.h"
12 #include "image_utils.h"
13
14 static void image_bitunpack(const vector<uint8_t> & src_image_data, vector<uint8_t> & unpacked_image_data, uint8_t bitdepth);
15
16 static int pixel_get_palette_num(const PNGImage& image, int x, int y);
17 static bool tile_palette_is_ok(const PNGImage& image, int x, int y, int tile_w, int tile_h);
18 static int pixel_find_color_in_palette(const PNGImage& image, int x, int y, int pal_num, uint8_t * match_color_idx);
19 static bool tile_palette_try_repair(PNGImage& image, int x, int y, int tile_w, int tile_h, int pal);
20 static bool tile_palette_repair(PNGImage& image, int x, int y, int tile_w, int tile_h);
21
22
23 // Un-bitpacks a source image into an 8-bit-per-pixel destination buffer
24 // Acceptable range is 1-8 bits per pixel
25 static void image_bitunpack(const vector<uint8_t> & src_image_data, vector<uint8_t> & unpacked_image_data, uint8_t bitdepth) {
26
27 int pixels_per_byte = (8 / bitdepth);
28
29 // Loop through all pixels in source image buffer
30 for (const uint8_t & it_src: src_image_data) {
31
32 // Unpack grouped pixel data
33 uint8_t packed_bits = it_src;
34 for (int b = 0; b < pixels_per_byte; b++) {
35 unpacked_image_data.push_back(packed_bits >> (8 - bitdepth)); // Save extracted pixel bits
36 packed_bits <<= bitdepth; // Shift out the extracted bits
37 }
38 }
39 }
40
41
42 // Converts indexed image with bit depths 1- 8 bpp to indexed 8 bits per pixel
43 // Replaces incoming image buffer with unpacked image buffer if successful
44 bool image_indexed_ensure_8bpp(vector<uint8_t> & src_image_data, int bitdepth, int colortype) {
45
46 vector<uint8_t> unpacked_image_data;
47
48 if (colortype != LCT_PALETTE) {
49 printf("png2asset: Error: keep_palette_order only works with indexed png images (pngtype: %d, bits:%d)\n",
50 colortype, bitdepth);
51 return false;
52 }
53 else if ((bitdepth < 1) || (bitdepth > 8)) {
54 printf("png2asset: Error: Indexed mode PNG requires between 1 - 8 bits per pixel (pngtype: %d, bits:%d)\n",
55 colortype, bitdepth);
56 return false;
57 }
58
59 image_bitunpack(src_image_data, unpacked_image_data, bitdepth);
60
61 // Replace source image pixel data with unpacked pixels
62 src_image_data.clear();
63 for (const uint8_t & it_unpacked: unpacked_image_data)
64 src_image_data.push_back(it_unpacked);
65
66 return true;
67 }
68
69
70
71
72 // Returns the sub-palette number for a pixel in a indexed 8bpp image
73 static int pixel_get_palette_num(const PNGImage& image, int x, int y) {
74
75 return image.data[(y * image.w) + x] / (unsigned int)image.colors_per_pal;
76 }
77
78
79 // Verifies that a tile uses only colors within it's sub-palette in a indexed 8bpp image
80 static bool tile_palette_is_ok(const PNGImage& image, int x, int y, int tile_w, int tile_h) {
81
82 // Get palette from first pixel in tile, use that as reference
83 int prev_palette_num = pixel_get_palette_num(image, x, y);
84
85 for (int tile_y = 0; tile_y < tile_h; tile_y++) {
86 for (int tile_x = 0; tile_x < tile_w; tile_x++) {
87
88 // If any pixels have a different palette than it's a failure
89 if (prev_palette_num != pixel_get_palette_num(image, x + tile_x, y + tile_y)) {
90 return false; // Return failure
91 }
92 }
93 }
94 return true; // Return success
95 }
96
97
98 // Finds a matching sub-palette RGB color for a pixel in a indexed 8bpp image
99 static int pixel_find_color_in_palette(const PNGImage& image, int x, int y, int pal_num, uint8_t * match_color_idx) {
100
101 // Get pixel palette index num, then get RGB value for it
102 uint8_t pixel_pal_idx = image.data[(y * image.w) + x];
103 uint8_t * p_pixel_color = &image.palette[pixel_pal_idx * RGBA32_SZ];
104
105 // Loop through available palette colors to see if an exact match exists
106 uint8_t * p_pal_color = &image.palette[pal_num * (image.colors_per_pal * RGBA32_SZ)];
107 for (int c = 0; c < (int)image.colors_per_pal; c++, p_pal_color += RGBA32_SZ) {
108
109 // Check palette entry RGB match against pixel RGB
110 if ((p_pal_color[0] == p_pixel_color[0]) &&
111 (p_pal_color[1] == p_pixel_color[1]) &&
112 (p_pal_color[2] == p_pixel_color[2])) {
113
114 *match_color_idx = (pal_num * (int)image.colors_per_pal) + c;
115 return true; // Return success
116 }
117 }
118
119 return false; // Return failure
120 }
121
122
123 // Tries to remap a tile to a new palette. Fails if any color in tile isn't in palette
124 // Note: For any color that does match, tile color indexed gets remapped even if the rest
125 // of the match fails. That's ok because the new color is visually identical to previous.
126 static bool tile_palette_try_repair(PNGImage& image, int x, int y, int tile_w, int tile_h, int pal_num) {
127
128 uint8_t new_color;
129
130 // For each pixel in the tile, try to find an exact color match in the palette
131 for (int tile_y = 0; tile_y < tile_h; tile_y++) {
132 for (int tile_x = 0; tile_x < tile_w; tile_x++) {
133
134 // Update pixel if exact match was found
135 // Otherwise quit as soon as possible to reduce processing
136 if (pixel_find_color_in_palette(image, x + tile_x, y + tile_y, pal_num, &new_color))
137 image.data[((y + tile_y) * image.w) + x + tile_x] = new_color;
138 else
139 return false; // Failed, tile color not present in palette
140 }
141 }
142
143 // printf(" + png2asset: Info: repaired tile at %d x %d to palette make%d\n", x, y, pal_num);
144 return true; // Success, found a perfect match
145 }
146
147
148 // Checks all palettes to see if the tile's colors can be remapped to them.
149 static bool tile_palette_repair(PNGImage& image, int x, int y, int tile_w, int tile_h) {
150
151 // Loop through all sub-palettes
152 for (int pal_num = 0; pal_num < (int)image.total_color_count / (int)image.colors_per_pal; pal_num++)
153 if (tile_palette_try_repair(image, x, y, tile_w, tile_h, pal_num))
154 return true; // Success
155
156 printf("png2asset: Error: tile (%dx%d) at %d,%d - %d,%d uses unique colors from different palettes, "
157 "cannot repair palette\n", x / tile_w, y / tile_h, x, y, x + tile_w - 1, y + tile_h - 1);
158
159 // If no exact palette match was found then signal failure
160 return false; // Failure
161 }
162
163
164 // Tries to repair indexed color sub-palette usage per attribute tile region
165 // Must find a palette that contains all colors in tile, fails otherwise
166 //
167 // Image passed in must be: indexed 8bpp
168 bool image_indexed_repair_tile_palettes(PNGImage & image, bool use_2x2_map_attributes) {
169
170 // TODO: Optional, but need to pass in vars
171 // if ((colortype != LCT_PALETTE) || (bitdepth != 8)) {
172 // printf("png2asset: Error: repair_tile_palettes only works with indexed png images in 8 bits per pixel mode");
173 // return false;
174 // }
175
176 int tile_w = image.tile_w;
177 int tile_h = image.tile_h;
178
179 if (use_2x2_map_attributes) {
180 tile_w *= 2;
181 tile_h *= 2;
182 }
183
184 for (int y = 0; y < (int)image.h; y += tile_h) {
185 for (int x = 0; x < (int)image.w; x += tile_w) {
186 // If tile has no palette errors leave it as is
187 // but if there are errors then try to repair it
188 if (!tile_palette_is_ok(image, x, y, tile_w, tile_h)) {
189 if (!tile_palette_repair(image, x, y, tile_w, tile_h)) {
190 return false; // Return Failure
191 }
192 }
193 }
194 }
195
196 return true; // Return success
197 }
198
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.