gbdk-2020 | GameBoy Development Kit |
| download: https://git.y1.nz/archives/gbdk.tar.gz | |
| README | Files | Log | Refs | LICENSE |
gbdk-support/png2asset/image_data.cpp
1 #include <vector>
2 #include <string>
3 #include <algorithm>
4 #include <cstring>
5 #include <set>
6 #include <stdio.h>
7 #include <fstream>
8 #include <cstdint>
9 #include <cstdlib>
10
11 #include "lodepng.h"
12 #include "mttile.h"
13 #include "export.h"
14 #include "map_attributes.h"
15 #include "palettes.h"
16
17 #include "cmp_int_color.h"
18 #include "process_arguments.h"
19
20 using namespace std;
21
22 #include "png_image.h"
23 #include "png2asset.h"
24 #include "image_utils.h"
25 #include "maps.h"
26 #include "metasprites.h"
27 #include "png_image.h"
28
29 int decodePNG(vector<unsigned char>& out_image, unsigned long& image_width, unsigned long& image_height, const unsigned char* in_png, size_t in_size, bool convert_to_rgba32 = true);
30 void loadFile(vector<unsigned char>& buffer, const std::string& filename);
31
32 int ReadImageData_KeepPaletteOrder( PNG2AssetData* assetData, string input_filename) {
33
34 //load and decode png
35 vector<unsigned char> buffer;
36 lodepng::load_file(buffer, input_filename);
37 lodepng::State state;
38
39 //Calling with keep_palette_order means
40 //-The image should be png indexed (1-8 bits per pixel)
41 //-For CGB: Each 4 colors define a gbc palette, the first color is the transparent one
42 //-Each rectangle with dimension(tile_w, tile_h) in the image has colors from one of those palettes only
43 state.info_raw.colortype = LCT_PALETTE;
44 state.info_raw.bitdepth = 8;
45
46 // * Do *NOT* turn color_convert ON here.
47 // When source PNG is indexed with bit depth was less than 8 bits per pixel then
48 // color_convert may mangle the packed indexed values. Instead manually unpack them.
49 //
50 // This is necessary since some PNG encoders will use the minimum possible number of bits.
51 // For example 2 colors in the palette -> 1bpp -> 8 pixels per byte in the decoded image.
52 // Also see below about requirement to use palette from source image
53 state.decoder.color_convert = false;
54
55 // Clearing is needed to ensure loading the png works
56 // (in cases where a previous png was loaded for source or entity tilesets)
57 assetData->image.data.clear();
58
59 unsigned error = lodepng::decode(assetData->image.data, assetData->image.w, assetData->image.h, state, buffer);
60 if(error)
61 {
62 printf("decoder error %s\n", lodepng_error_text(error));
63 return EXIT_FAILURE;
64 }
65
66 // Unpack the image if needed. Also checks and errors on incompatible palette type if needed
67 if(!image_indexed_ensure_8bpp(assetData->image.data, (int)state.info_png.color.bitdepth, (int)state.info_png.color.colortype))
68 return EXIT_FAILURE;
69
70 // Use source image palette since lodepng conversion to indexed (LCT_PALETTE) won't create a palette
71 // So: state->info_png.color.palette/size instead of state->info_raw.palette/size
72 unsigned int palette_count = PaletteCountApplyMaxLimit((unsigned int)assetData->args->max_palettes, (unsigned int)((state.info_png.color.palettesize + assetData->image.colors_per_pal - 1) / assetData->image.colors_per_pal));
73 assetData->image.total_color_count = palette_count * assetData->image.colors_per_pal;
74 // Copy the palette data since the source buffer (state...) won't exist outside the scope of this function
75 assetData->image.palette = new unsigned char[assetData->image.total_color_count * RGBA32_SZ];
76 memcpy(assetData->image.palette, state.info_png.color.palette, assetData->image.total_color_count * RGBA32_SZ);
77
78 // Save a copy of the palette data if it's the source palette (free first if already allocated)
79 if (assetData->args->processing_mode == MODE_SOURCE_TILESET) {
80 if (assetData->image.source_tileset_palette) free(assetData->image.source_tileset_palette);
81 assetData->image.source_tileset_palette = new unsigned char[assetData->image.total_color_count * RGBA32_SZ];
82 memcpy(assetData->image.source_tileset_palette, state.info_png.color.palette, assetData->image.total_color_count * RGBA32_SZ);
83 }
84
85
86 if(assetData->args->repair_indexed_pal)
87 if(!image_indexed_repair_tile_palettes(assetData->image, assetData->args->use_2x2_map_attributes))
88 return EXIT_FAILURE;
89
90 // TODO: Enable dimension check
91 // // Validate image dimensions
92 // if (((image.w % tile_w) != 0) || ((image.h % tile_h) != 0))
93 // {
94 // printf("Error: Image size %d x %d isn't an even multiple of tile size %d x %d\n", image.w, image.h, tile_w, tile_h);
95 // return EXIT_FAILURE;
96 // }
97
98 // Only check this for the main image, not for source tilesets
99 if ((assetData->args->processing_mode == MODE_MAIN_IMAGE) && (assetData->args->has_source_tilesets)) {
100
101 // Make sure these two values match when keeping palette order
102 if(assetData->image.total_color_count != assetData->args->source_total_color_count) {
103
104 printf("error: The number of color palettes for your source tileset (%d) and target image (%d) do not match.",
105 (unsigned int)assetData->args->source_total_color_count, (unsigned int)assetData->image.total_color_count);
106 return EXIT_FAILURE;
107 }
108
109 size_t size = max(assetData->image.total_color_count, assetData->args->source_total_color_count);
110 // size_t size = max(assetData->image.total_color_count, assetData->source_tileset_image.total_color_count);
111
112 // Make sure these two values match when keeping palette order
113 if (memcmp(assetData->image.palette, assetData->image.source_tileset_palette, size) != 0) {
114
115 printf("error: The palettes for your source tileset and target image do not match.");
116 return EXIT_FAILURE;
117 }
118 }
119 return EXIT_SUCCESS;
120 }
121
122 int ReadImageData_Default(PNG2AssetData* assetData, string input_filename) {
123
124 //load and decode png
125 vector<unsigned char> buffer;
126 lodepng::load_file(buffer, input_filename);
127 lodepng::State state;
128
129 PNGImage image32;
130 image32.colors_per_pal = assetData->image.colors_per_pal;
131 image32.tile_w = assetData->image.tile_w;
132 image32.tile_h = assetData->image.tile_h;
133
134 unsigned error = lodepng::decode(image32.data, image32.w, image32.h, state, buffer); //decode as 32 bit
135 if(error)
136 {
137 printf("decoder error %s\n", lodepng_error_text(error));
138 return EXIT_FAILURE;
139 }
140
141 // Validate image dimensions
142 if(((image32.w % image32.tile_w) != 0) || ((image32.h % image32.tile_h) != 0))
143 {
144 printf("Error: Image size %d x %d isn't an even multiple of tile size %d x %d\n", image32.w, image32.h, image32.tile_w, image32.tile_h);
145 return EXIT_FAILURE;
146 }
147
148 int* palettes_per_tile = BuildPalettesAndAttributes(image32, assetData);
149
150 // Create the indexed image
151 // Clearing is needed to ensure loading the png works
152 // (in cases where a previous png was loaded for source or entity tilesets)
153 assetData->image.data.clear();
154 assetData->image.w = image32.w;
155 assetData->image.h = image32.h;
156
157 unsigned int palette_count = PaletteCountApplyMaxLimit((unsigned int)assetData->args->max_palettes, (unsigned int)assetData->palettes.size());
158
159 assetData->image.total_color_count = palette_count * assetData->image.colors_per_pal;
160 assetData->image.palette = new unsigned char[palette_count * assetData->image.colors_per_pal * RGBA32_SZ]; // total color count * 4 bytes each
161 // Pre-fill palette to all black. Prevents garbage in palette color slots that are unused (ex: only 3 colors when colors-per-pal is 4)
162 memset(assetData->image.palette, 0, palette_count * assetData->image.colors_per_pal * RGBA32_SZ);
163
164 // If using a source tileset and have more palettes than it defines
165 // (only check for main image data, not source tilesets)
166 if ((assetData->args->processing_mode == MODE_MAIN_IMAGE) && (assetData->args->has_source_tilesets)) {
167 if (assetData->image.total_color_count > assetData->args->source_total_color_count) {
168 printf("Found %d extra palette(s) for target tilemap.\n", (unsigned int)((assetData->image.total_color_count - assetData->args->source_total_color_count) / assetData->image.colors_per_pal));
169 }
170 }
171 for(size_t p = 0; p < palette_count; ++p)
172 {
173 int* color_ptr = (int*)&assetData->image.palette[p * assetData->image.colors_per_pal * RGBA32_SZ];
174
175 // When palettes[p].size() != image.colors_per_pal the unused colors are left as black (see memset above)
176 for(SetPal::iterator it = assetData->palettes[p].begin(); it != assetData->palettes[p].end(); ++it, color_ptr++)
177 {
178 unsigned char* c = (unsigned char*)&(*it);
179 *color_ptr = (c[0] << 24) | (c[1] << 16) | (c[2] << 8) | c[3];
180 }
181 }
182
183 for(size_t y = 0; y < image32.h; ++y)
184 {
185 for(size_t x = 0; x < image32.w; ++x)
186 {
187 unsigned char* c32ptr = &image32.data[(image32.w * y + x) * RGBA32_SZ];
188 int color32 = (c32ptr[0] << 24) | (c32ptr[1] << 16) | (c32ptr[2] << 8) | c32ptr[3];
189 unsigned char palette = palettes_per_tile[(y / image32.tile_h) * (image32.w / image32.tile_w) + (x / image32.tile_w)];
190 unsigned char index = (unsigned char)std::distance(assetData->palettes[palette].begin(), assetData->palettes[palette].find(color32));
191 assetData->image.data.push_back((palette << assetData->args->bpp) + index);
192 }
193 }
194
195 //Test: output png to see how it looks
196 //Export(image, "temp.png");
197 return EXIT_SUCCESS;
198 }
199
200 int ReadImageData( PNG2AssetData* assetData, string input_filename) {
201
202 assetData->image.colors_per_pal = static_cast<size_t>(1) << assetData->args->bpp;
203 // assetData->source_tileset_image.colors_per_pal = static_cast<size_t>(1) << assetData->args->bpp;
204
205 if(assetData->args->export_as_map)
206 {
207 assetData->image.tile_w = 8; //Force tiles_w to 8 on maps
208 assetData->image.tile_h = 8; //Force tiles_h to 8 on maps
209 // assetData->source_tileset_image.tile_w = 8; //Force tiles_w to 8 on maps
210 // assetData->source_tileset_image.tile_h = 8; //Force tiles_h to 8 on maps
211 assetData->args->sprite_mode = SPR_NONE;
212 }
213
214
215 int errorCode = EXIT_SUCCESS;
216
217 // Will the specified PNG have a palette provided with it?
218 if (assetData->args->keep_palette_order) {
219
220 // Save the error code
221 errorCode = ReadImageData_KeepPaletteOrder(assetData, input_filename);
222 }
223 else
224 {
225
226 // Save the error code
227 errorCode = ReadImageData_Default(assetData, input_filename);
228 }
229
230 if (errorCode != EXIT_SUCCESS) return errorCode;
231
232
233 // Only set this data when processing the main image
234 if (assetData->args->processing_mode == MODE_MAIN_IMAGE) {
235 if(assetData->args->spriteSize.width == 0) assetData->args->spriteSize.width = (int)assetData->image.w;
236 if(assetData->args->spriteSize.height == 0) assetData->args->spriteSize.height = (int)assetData->image.h;
237 if(assetData->args->pivot.x == 0xFFFFFF) assetData->args->pivot.x = (unsigned int)assetData->args->spriteSize.width / 2;
238 if(assetData->args->pivot.y == 0xFFFFFF) assetData->args->pivot.y = (unsigned int)assetData->args->spriteSize.height / 2;
239 if(assetData->args->pivot.width == 0xFFFFFF) assetData->args->pivot.width = assetData->args->spriteSize.width;
240 if(assetData->args->pivot.height == 0xFFFFFF) assetData->args->pivot.height = assetData->args->spriteSize.height;
241 }
242 return EXIT_SUCCESS;
243 }
244
This webpage is intended to be an accessible preview of this repository. To get a fuller picture, clone it and use the git CLI.