11#pragma warning(push, 0)
22#include < FL/fl_utf8.h>
33#include < FL/Fl_PNG_Image.H>
4+ #include < FL/Fl_BMP_Image.H>
5+ #include < FL/Fl_Image_Surface.H>
46#include < FL/fl_draw.H>
57#pragma warning(pop)
68
@@ -58,16 +60,168 @@ bool Tileset::draw_tile(const Tile_State *ts, int x, int y, bool active) const {
5860}
5961
6062Tileset::Result Tileset::read_tiles (const char *f) {
61- if (ends_with (f, " .png" ) || ends_with (f, " .PNG" )) { return read_png_graphics (f); }
62- // TODO: support BMP, GIF, 1BPP, 2BPP, 1BPP.LZ, 2BPP.LZ
63+ std::string s (f);
64+ if (ends_with (s, " .png" ) || ends_with (s, " .PNG" )) { return read_png_graphics (f); }
65+ if (ends_with (s, " .bmp" ) || ends_with (s, " .BMP" )) { return read_bmp_graphics (f); }
66+ if (ends_with (s, " .1bpp" ) || ends_with (s, " .1BPP" )) { return read_1bpp_graphics (f); }
67+ if (ends_with (s, " .2bpp" ) || ends_with (s, " .2BPP" )) { return read_2bpp_graphics (f); }
68+ if (ends_with (s, " .1bpp.lz" ) || ends_with (s, " .1BPP.LZ" )) { return read_1bpp_lz_graphics (f); }
69+ if (ends_with (s, " .2bpp.lz" ) || ends_with (s, " .2BPP.LZ" )) { return read_2bpp_lz_graphics (f); }
6370 return (_result = TILESET_BAD_EXT);
6471}
6572
6673Tileset::Result Tileset::read_png_graphics (const char *f) {
6774 Fl_PNG_Image png (f);
68- if (png.fail ()) { return (_result = TILESET_BAD_FILE); }
75+ return postprocess_graphics (&png);
76+ }
77+
78+ Tileset::Result Tileset::read_bmp_graphics (const char *f) {
79+ Fl_BMP_Image bmp (f);
80+ return postprocess_graphics (&bmp);
81+ }
82+
83+ Tileset::Result Tileset::read_1bpp_graphics (const char *f) {
84+ FILE *file = fl_fopen (f, " rb" );
85+ if (!file) { return (_result = TILESET_BAD_FILE); }
86+
87+ fseek (file, 0 , SEEK_END);
88+ long n = ftell (file);
89+ rewind (file);
90+ if (n % BYTES_PER_1BPP_TILE) { fclose (file); return (_result = TILESET_BAD_DIMS); }
91+
92+ uchar *data = new uchar[n];
93+ size_t r = fread (data, 1 , n, file);
94+ fclose (file);
95+ if (r != (size_t )n) { delete [] data; return (_result = TILESET_BAD_FILE); }
96+
97+ return parse_1bpp_data (n, data);
98+ }
99+
100+ Tileset::Result Tileset::read_2bpp_graphics (const char *f) {
101+ FILE *file = fl_fopen (f, " rb" );
102+ if (!file) { return (_result = TILESET_BAD_FILE); }
103+
104+ fseek (file, 0 , SEEK_END);
105+ long n = ftell (file);
106+ rewind (file);
107+ if (n % BYTES_PER_2BPP_TILE) { fclose (file); return (_result = TILESET_BAD_DIMS); }
108+
109+ uchar *data = new uchar[n];
110+ size_t r = fread (data, 1 , n, file);
111+ fclose (file);
112+ if (r != (size_t )n) { delete [] data; return (_result = TILESET_BAD_FILE); }
113+
114+ return parse_2bpp_data (n, data);
115+ }
116+
117+ static Tileset::Result decompress_lz_data (const char *f, uchar *data, size_t lim, size_t &len);
118+
119+ Tileset::Result Tileset::read_1bpp_lz_graphics (const char *f) {
120+ uchar *data = new uchar[NUM_TILES * BYTES_PER_1BPP_TILE];
121+ size_t n = 0 ;
122+ if (decompress_lz_data (f, data, NUM_TILES * BYTES_PER_1BPP_TILE, n) != TILESET_OK) {
123+ delete [] data;
124+ return _result;
125+ }
126+ return parse_1bpp_data (n, data);
127+ }
128+
129+ Tileset::Result Tileset::read_2bpp_lz_graphics (const char *f) {
130+ uchar *data = new uchar[NUM_TILES * BYTES_PER_2BPP_TILE];
131+ size_t n = 0 ;
132+ if (decompress_lz_data (f, data, NUM_TILES * BYTES_PER_2BPP_TILE, n) != TILESET_OK) {
133+ delete [] data;
134+ return _result;
135+ }
136+ return parse_2bpp_data (n, data);
137+ }
138+
139+ enum Hue { WHITE, DARK, LIGHT, BLACK };
140+
141+ Fl_Color hue_colors[NUM_HUES] = {
142+ fl_rgb_color (0xFF , 0xFF , 0xFF ), fl_rgb_color (0x55 , 0x55 , 0x55 ),
143+ fl_rgb_color (0xAA , 0xAA , 0xAA ), fl_rgb_color (0x00 , 0x00 , 0x00 ),
144+ };
145+
146+ static void convert_1bpp_row (uchar b, Hue *row) {
147+ // %ABCD_EFGH -> %A %B %C %D %E %F %G %H
148+ for (int i = 0 ; i < TILE_SIZE; i++) {
149+ int j = TILE_SIZE - i - 1 ;
150+ row[i] = b >> j & 1 ? BLACK : WHITE;
151+ }
152+ }
153+
154+ static void convert_2bpp_row (uchar b1, uchar b2, Hue *row) {
155+ // %ABCD_EFGH %abcd_efgh -> %Aa %Bb %Cc %Dd %Ee %Ff %GG %Hh
156+ for (int i = 0 ; i < TILE_SIZE; i++) {
157+ int j = TILE_SIZE - i - 1 ;
158+ row[i] = (Hue)((b1 >> j & 1 ) * 2 + (b2 >> j & 1 ));
159+ }
160+ }
161+
162+ Tileset::Result Tileset::parse_1bpp_data (size_t n, uchar *data) {
163+ n /= BYTES_PER_1BPP_TILE;
164+ _num_tiles = n;
165+ if (_start + _num_tiles > NUM_TILES) { delete [] data; return (_result = TILESET_TOO_LARGE); }
166+
167+ Fl_Image_Surface *surface = new Fl_Image_Surface (TILE_SIZE, (int )n * TILE_SIZE);
168+ surface->set_current ();
169+
170+ Hue row[TILE_SIZE] = {};
171+ for (size_t i = 0 ; i < _num_tiles; i++) {
172+ for (size_t j = 0 ; j < TILE_SIZE; j++) {
173+ uchar b = data[i * BYTES_PER_1BPP_TILE + j];
174+ convert_1bpp_row (b, row);
175+ for (int k = 0 ; k < TILE_SIZE; k++) {
176+ Hue hue = row[k];
177+ fl_color (hue_colors[hue]);
178+ fl_point (k, (int )(i * TILE_SIZE + j));
179+ }
180+ }
181+ }
182+
183+ Fl_RGB_Image *img = surface->image ();
184+ delete surface;
185+ Fl_Display_Device::display_device ()->set_current ();
186+
187+ delete [] data;
188+ return postprocess_graphics (img);
189+ }
190+
191+ Tileset::Result Tileset::parse_2bpp_data (size_t n, uchar *data) {
192+ n /= BYTES_PER_2BPP_TILE;
193+ _num_tiles = n;
194+ if (_start + _num_tiles > NUM_TILES) { delete [] data; return (_result = TILESET_TOO_LARGE); }
195+
196+ Fl_Image_Surface *surface = new Fl_Image_Surface (TILE_SIZE, (int )n * TILE_SIZE);
197+ surface->set_current ();
198+
199+ Hue row[TILE_SIZE] = {};
200+ for (size_t i = 0 ; i < _num_tiles; i++) {
201+ for (size_t j = 0 ; j < TILE_SIZE; j++) {
202+ uchar b1 = data[i * BYTES_PER_2BPP_TILE + j * 2 ];
203+ uchar b2 = data[i * BYTES_PER_2BPP_TILE + j * 2 + 1 ];
204+ convert_2bpp_row (b1, b2, row);
205+ for (int k = 0 ; k < TILE_SIZE; k++) {
206+ Hue hue = row[k];
207+ fl_color (hue_colors[hue]);
208+ fl_point (k, (int )(i * TILE_SIZE + j));
209+ }
210+ }
211+ }
69212
70- _image = (Fl_RGB_Image *)png.copy (png.w () * 2 , png.h () * 2 );
213+ Fl_RGB_Image *img = surface->image ();
214+ delete surface;
215+ Fl_Display_Device::display_device ()->set_current ();
216+
217+ delete [] data;
218+ return postprocess_graphics (img);
219+ }
220+
221+ Tileset::Result Tileset::postprocess_graphics (Fl_RGB_Image *img) {
222+ if (!img || img->fail ()) { return (_result = TILESET_BAD_FILE); }
223+
224+ _image = (Fl_RGB_Image *)img->copy (img->w () * 2 , img->h () * 2 );
71225 if (!_image || _image->fail ()) { return (_result = TILESET_BAD_FILE); }
72226
73227 if (!refresh_inactive_image ()) {
@@ -81,7 +235,7 @@ Tileset::Result Tileset::read_png_graphics(const char *f) {
81235 w /= TILE_SIZE_2X;
82236 h /= TILE_SIZE_2X;
83237 _num_tiles = w * h;
84- if (_num_tiles > NUM_TILES) { return (_result = TILESET_TOO_LARGE); }
238+ if (_start + _num_tiles > NUM_TILES) { return (_result = TILESET_TOO_LARGE); }
85239
86240 return (_result = TILESET_OK);
87241}
@@ -108,3 +262,143 @@ const char *Tileset::error_message(Result result) {
108262 return " Unspecified error." ;
109263 }
110264}
265+
266+ // A rundown of Pokemon Crystal's LZ compression scheme:
267+ enum Lz_Command {
268+ // Control commands occupy bits 5-7.
269+ // Bits 0-4 serve as the first parameter n for each command.
270+ LZ_LITERAL, // n values for n bytes
271+ LZ_ITERATE, // one value for n bytes
272+ LZ_ALTERNATE, // alternate two values for n bytes
273+ LZ_BLANK, // zero for n bytes
274+ // Repeater commands repeat any data that was just decompressed.
275+ // They take an additional signed parameter s to mark a relative starting point.
276+ // These wrap around (positive from the start, negative from the current position).
277+ LZ_REPEAT, // n bytes starting from s
278+ LZ_FLIP, // n bytes in reverse bit order starting from s
279+ LZ_REVERSE, // n bytes backwards starting from s
280+ // The long command is used when 5 bits aren't enough. Bits 2-4 contain a new control code.
281+ // Bits 0-1 are appended to a new byte as 8-9, allowing a 10-bit parameter.
282+ LZ_LONG // n is now 10 bits for a new control code
283+ };
284+
285+ // If 0xff is encountered instead of a command, decompression ends.
286+ #define LZ_END 0xff
287+
288+ // [sum(((b >> i) & 1) << (7 - i) for i in range(8)) for b in range(256)]
289+ static uchar bit_flipped[256 ] = {
290+ 0x00 , 0x80 , 0x40 , 0xc0 , 0x20 , 0xa0 , 0x60 , 0xe0 , 0x10 , 0x90 , 0x50 , 0xd0 , 0x30 , 0xb0 , 0x70 , 0xf0 ,
291+ 0x08 , 0x88 , 0x48 , 0xc8 , 0x28 , 0xa8 , 0x68 , 0xe8 , 0x18 , 0x98 , 0x58 , 0xd8 , 0x38 , 0xb8 , 0x78 , 0xf8 ,
292+ 0x04 , 0x84 , 0x44 , 0xc4 , 0x24 , 0xa4 , 0x64 , 0xe4 , 0x14 , 0x94 , 0x54 , 0xd4 , 0x34 , 0xb4 , 0x74 , 0xf4 ,
293+ 0x0c , 0x8c , 0x4c , 0xcc , 0x2c , 0xac , 0x6c , 0xec , 0x1c , 0x9c , 0x5c , 0xdc , 0x3c , 0xbc , 0x7c , 0xfc ,
294+ 0x02 , 0x82 , 0x42 , 0xc2 , 0x22 , 0xa2 , 0x62 , 0xe2 , 0x12 , 0x92 , 0x52 , 0xd2 , 0x32 , 0xb2 , 0x72 , 0xf2 ,
295+ 0x0a , 0x8a , 0x4a , 0xca , 0x2a , 0xaa , 0x6a , 0xea , 0x1a , 0x9a , 0x5a , 0xda , 0x3a , 0xba , 0x7a , 0xfa ,
296+ 0x06 , 0x86 , 0x46 , 0xc6 , 0x26 , 0xa6 , 0x66 , 0xe6 , 0x16 , 0x96 , 0x56 , 0xd6 , 0x36 , 0xb6 , 0x76 , 0xf6 ,
297+ 0x0e , 0x8e , 0x4e , 0xce , 0x2e , 0xae , 0x6e , 0xee , 0x1e , 0x9e , 0x5e , 0xde , 0x3e , 0xbe , 0x7e , 0xfe ,
298+ 0x01 , 0x81 , 0x41 , 0xc1 , 0x21 , 0xa1 , 0x61 , 0xe1 , 0x11 , 0x91 , 0x51 , 0xd1 , 0x31 , 0xb1 , 0x71 , 0xf1 ,
299+ 0x09 , 0x89 , 0x49 , 0xc9 , 0x29 , 0xa9 , 0x69 , 0xe9 , 0x19 , 0x99 , 0x59 , 0xd9 , 0x39 , 0xb9 , 0x79 , 0xf9 ,
300+ 0x05 , 0x85 , 0x45 , 0xc5 , 0x25 , 0xa5 , 0x65 , 0xe5 , 0x15 , 0x95 , 0x55 , 0xd5 , 0x35 , 0xb5 , 0x75 , 0xf5 ,
301+ 0x0d , 0x8d , 0x4d , 0xcd , 0x2d , 0xad , 0x6d , 0xed , 0x1d , 0x9d , 0x5d , 0xdd , 0x3d , 0xbd , 0x7d , 0xfd ,
302+ 0x03 , 0x83 , 0x43 , 0xc3 , 0x23 , 0xa3 , 0x63 , 0xe3 , 0x13 , 0x93 , 0x53 , 0xd3 , 0x33 , 0xb3 , 0x73 , 0xf3 ,
303+ 0x0b , 0x8b , 0x4b , 0xcb , 0x2b , 0xab , 0x6b , 0xeb , 0x1b , 0x9b , 0x5b , 0xdb , 0x3b , 0xbb , 0x7b , 0xfb ,
304+ 0x07 , 0x87 , 0x47 , 0xc7 , 0x27 , 0xa7 , 0x67 , 0xe7 , 0x17 , 0x97 , 0x57 , 0xd7 , 0x37 , 0xb7 , 0x77 , 0xf7 ,
305+ 0x0f , 0x8f , 0x4f , 0xcf , 0x2f , 0xaf , 0x6f , 0xef , 0x1f , 0x9f , 0x5f , 0xdf , 0x3f , 0xbf , 0x7f , 0xff
306+ };
307+
308+ static Tileset::Result decompress_lz_data (const char *f, uchar *data, size_t lim, size_t &len) {
309+ FILE *file = fl_fopen (f, " rb" );
310+ if (!file) { return Tileset::Result::TILESET_BAD_FILE; }
311+
312+ fseek (file, 0 , SEEK_END);
313+ long n = ftell (file);
314+ rewind (file);
315+ uchar *lz_data = new uchar[n];
316+ size_t r = fread (lz_data, 1 , n, file);
317+ fclose (file);
318+ if (r != (size_t )n) { delete [] lz_data; return Tileset::Result::TILESET_BAD_FILE; }
319+
320+ size_t address = 0 ;
321+ uchar q[2 ];
322+ int offset;
323+ for (;;) {
324+ uchar b = lz_data[address++];
325+ if (b == LZ_END) { break ; }
326+ if (len >= lim) {
327+ delete [] lz_data;
328+ return Tileset::Result::TILESET_TOO_LARGE;
329+ }
330+ Lz_Command cmd = (Lz_Command)((b & 0xe0 ) >> 5 );
331+ int length = 0 ;
332+ if (cmd == LZ_LONG) {
333+ cmd = (Lz_Command)((b & 0x1c ) >> 2 );
334+ length = (int )(b & 0x03 ) * 0x100 ;
335+ b = lz_data[address++];
336+ length += (int )b + 1 ;
337+ }
338+ else {
339+ length = (int )(b & 0x1f ) + 1 ;
340+ }
341+ switch (cmd) {
342+ case LZ_LITERAL:
343+ // Copy data directly.
344+ for (int i = 0 ; i < length; i++) {
345+ data[len++] = lz_data[address++];
346+ }
347+ break ;
348+ case LZ_ITERATE:
349+ // Write one byte repeatedly.
350+ b = lz_data[address++];
351+ for (int i = 0 ; i < length; i++) {
352+ data[len++] = b;
353+ }
354+ break ;
355+ case LZ_ALTERNATE:
356+ // Write alternating bytes.
357+ q[0 ] = lz_data[address++];
358+ q[1 ] = lz_data[address++];
359+ // Copy data directly.
360+ for (int i = 0 ; i < length; i++) {
361+ data[len++] = q[i & 1 ];
362+ }
363+ break ;
364+ case LZ_BLANK:
365+ // Write zeros.
366+ for (int i = 0 ; i < length; i++) {
367+ data[len++] = 0 ;
368+ }
369+ break ;
370+ case LZ_REPEAT:
371+ // Repeat bytes from output.
372+ b = lz_data[address++];
373+ offset = b >= 0x80 ? (int )len - (int )(b & 0x7f ) - 1 : (int )b * 0x100 + lz_data[address++];
374+ for (int i = 0 ; i < length; i++) {
375+ data[len++] = data[offset + i];
376+ }
377+ break ;
378+ case LZ_FLIP:
379+ // Repeat flipped bytes from output.
380+ b = lz_data[address++];
381+ offset = b >= 0x80 ? (int )len - (int )(b & 0x7f ) - 1 : (int )b * 0x100 + lz_data[address++];
382+ for (int i = 0 ; i < length; i++) {
383+ b = data[offset + i];
384+ data[len++] = bit_flipped[b];
385+ }
386+ break ;
387+ case LZ_REVERSE:
388+ // Repeat reversed bytes from output.
389+ b = lz_data[address++];
390+ offset = b >= 0x80 ? (int )len - (int )(b & 0x7f ) - 1 : (int )b * 0x100 + lz_data[address++];
391+ for (int i = 0 ; i < length; i++) {
392+ data[len++] = data[offset - i];
393+ }
394+ break ;
395+ case LZ_LONG:
396+ default :
397+ delete [] lz_data;
398+ return Tileset::Result::TILESET_BAD_CMD;
399+ }
400+ }
401+
402+ delete [] lz_data;
403+ return Tileset::Result::TILESET_OK;
404+ }
0 commit comments