summaryrefslogtreecommitdiff
path: root/gfx-iso
diff options
context:
space:
mode:
author3gg <3gg@shellblade.net>2023-07-26 08:39:37 -0700
committer3gg <3gg@shellblade.net>2023-07-26 08:39:37 -0700
commitcef3385c2bee0b098a7795548345a9281ace008e (patch)
treeb594e9cc151a4ae7fd8b5732cf349eb01f37683d /gfx-iso
parent48cef82988d6209987ae27fe29b72d7d5e402b3c (diff)
Add support for paletted sprites.
Diffstat (limited to 'gfx-iso')
-rw-r--r--gfx-iso/asset/mkasset.py127
-rw-r--r--gfx-iso/src/isogfx.c65
2 files changed, 136 insertions, 56 deletions
diff --git a/gfx-iso/asset/mkasset.py b/gfx-iso/asset/mkasset.py
index b4e335f..3ca8a1d 100644
--- a/gfx-iso/asset/mkasset.py
+++ b/gfx-iso/asset/mkasset.py
@@ -165,44 +165,57 @@ def get_num_cols(image, sprite_width):
165 return 0 165 return 0
166 166
167 167
168def get_sprite_sheet_rows(input_filepath, sprite_width, sprite_height): 168def get_sprite_sheet_rows(im, sprite_width, sprite_height):
169 """Gets the individual rows of a sprite sheet. 169 """Gets the individual rows of a sprite sheet.
170 170
171 The input sprite sheet can have any number of rows. 171 The input sprite sheet can have any number of rows.
172 172
173 Returns a list of lists [[sprite bytes]], one inner list for the columns in 173 Returns a list of lists [[sprite]], one inner list for the columns in each
174 each row. 174 row.
175 """ 175 """
176 with Image.open(input_filepath) as im: 176 # Sprite sheet's width and height must be integer multiples of the
177 # Sprite sheet's width and height must be integer multiples of the 177 # sprite's width and height.
178 # sprite's width and height. 178 assert (im.width % sprite_width == 0)
179 assert (im.width % sprite_width == 0) 179 assert (im.height % sprite_height == 0)
180 assert (im.height % sprite_height == 0)
181 180
182 num_rows = im.height // sprite_height 181 num_rows = im.height // sprite_height
183 182
184 rows = [] 183 rows = []
185 for row in range(num_rows): 184 for row in range(num_rows):
186 # Get the number of columns. 185 # Get the number of columns.
187 upper = row * sprite_height 186 upper = row * sprite_height
188 lower = (row + 1) * sprite_height 187 lower = (row + 1) * sprite_height
189 whole_row = im.crop((0, upper, im.width, lower)) 188 whole_row = im.crop((0, upper, im.width, lower))
190 num_cols = get_num_cols(whole_row, sprite_width) 189 num_cols = get_num_cols(whole_row, sprite_width)
191 assert (num_cols > 0) 190 assert (num_cols > 0)
192 191
193 # Crop the row into N columns. 192 # Crop the row into N columns.
194 cols = [] 193 cols = []
195 for i in range(num_cols): 194 for i in range(num_cols):
196 left = i * sprite_width 195 left = i * sprite_width
197 right = (i + 1) * sprite_width 196 right = (i + 1) * sprite_width
198 sprite = im.crop((left, upper, right, lower)) 197 sprite = im.crop((left, upper, right, lower))
199 cols.append(sprite) 198 cols.append(sprite)
200 199
201 sprite_bytes = [sprite.convert('RGBA').tobytes() for sprite in cols] 200 assert (len(cols) == num_cols)
202 assert (len(sprite_bytes) == num_cols) 201 rows.append(cols)
203 rows.append(sprite_bytes) 202
204 203 return rows
205 return rows 204
205
206def make_image_from_rows(rows, sprite_width, sprite_height):
207 """Concatenate the rows into a single RGBA image."""
208 im_width = sprite_width * max(len(row) for row in rows)
209 im_height = len(rows) * sprite_height
210 im = Image.new('RGBA', (im_width, im_height))
211 y = 0
212 for row in rows:
213 x = 0
214 for sprite in row:
215 im.paste(sprite.convert('RGBA'), (x, y))
216 x += sprite_width
217 y += sprite_height
218 return im
206 219
207 220
208def convert_sprite_sheet(input_file_paths, sprite_width, sprite_height, 221def convert_sprite_sheet(input_file_paths, sprite_width, sprite_height,
@@ -217,25 +230,65 @@ def convert_sprite_sheet(input_file_paths, sprite_width, sprite_height,
217 sprite sheets. 230 sprite sheets.
218 """ 231 """
219 rows = [] 232 rows = []
233 for input_filepath in input_file_paths:
234 with Image.open(input_filepath) as sprite_sheet:
235 rows.extend(
236 get_sprite_sheet_rows(sprite_sheet, sprite_width,
237 sprite_height))
220 238
221 for sprite_sheet in input_file_paths: 239 im = make_image_from_rows(rows, sprite_width, sprite_height)
222 rows.extend( 240 im = im.convert(mode="P", palette=Image.ADAPTIVE, colors=256)
223 get_sprite_sheet_rows(sprite_sheet, sprite_width, sprite_height)) 241
242 # The sprite data in 'rows' is no longer needed.
243 # Keep just the number of columns per row.
244 rows = [len(row) for row in rows]
224 245
225 with open(output_filepath, 'bw') as output: 246 with open(output_filepath, 'bw') as output:
226 output.write(ctypes.c_uint16(sprite_width)) 247 output.write(ctypes.c_uint16(sprite_width))
227 output.write(ctypes.c_uint16(sprite_height)) 248 output.write(ctypes.c_uint16(sprite_height))
228 output.write(ctypes.c_uint16(len(rows))) 249 output.write(ctypes.c_uint16(len(rows)))
229 250
251 # Write palette.
252 # getpalette() returns 256 colors, but the palette might use less than
253 # that. getcolors() returns the number of unique colors.
254 # getpalette() also returns a flattened list, which is why we must *4.
255 num_colours = len(im.getcolors())
256 colours = im.getpalette(rawmode="RGBA")[:4 * num_colours]
257 palette = []
258 for i in range(0, 4 * num_colours, 4):
259 palette.append((colours[i], colours[i + 1], colours[i + 2],
260 colours[i + 3]))
261
262 output.write(ctypes.c_uint16(len(palette)))
263 output.write(bytearray(colours))
264
230 print(f"Sprite width: {sprite_width}") 265 print(f"Sprite width: {sprite_width}")
231 print(f"Sprite height: {sprite_height}") 266 print(f"Sprite height: {sprite_height}")
232 print(f"Rows: {len(rows)}") 267 print(f"Rows: {len(rows)}")
268 print(f"Colours: {len(palette)}")
269
270 # print("Palette")
271 # for i, colour in enumerate(palette):
272 # print(f"{i}: {colour}")
233 273
234 for sprites in rows: 274 for row, num_columns in enumerate(rows):
235 output.write(ctypes.c_uint16(len(sprites))) 275 output.write(ctypes.c_uint16(num_columns))
236 for sprite_bytes in sprites: 276 upper = row * sprite_height
277 lower = (row + 1) * sprite_height
278 for col in range(num_columns):
279 left = col * sprite_width
280 right = (col + 1) * sprite_width
281 sprite = im.crop((left, upper, right, lower))
282 sprite_bytes = sprite.tobytes()
283
284 assert (len(sprite_bytes) == sprite_width * sprite_height)
237 output.write(sprite_bytes) 285 output.write(sprite_bytes)
238 286
287 # if (row == 0) and (col == 0):
288 # print(f"Sprite: ({len(sprite_bytes)})")
289 # print(list(sprite_bytes))
290 # sprite.save("out.png")
291
239 292
240def main(): 293def main():
241 parser = argparse.ArgumentParser() 294 parser = argparse.ArgumentParser()
diff --git a/gfx-iso/src/isogfx.c b/gfx-iso/src/isogfx.c
index 9ba1bec..4568375 100644
--- a/gfx-iso/src/isogfx.c
+++ b/gfx-iso/src/isogfx.c
@@ -100,26 +100,49 @@ static inline const Ts_Tile* ts_tileset_get_next_tile(
100/// The pixels of the row follow a "sprite-major" order. It contains the 100/// The pixels of the row follow a "sprite-major" order. It contains the
101/// 'sprite_width * sprite_height' pixels for the first column/sprite, then the 101/// 'sprite_width * sprite_height' pixels for the first column/sprite, then the
102/// second column/sprite, etc. 102/// second column/sprite, etc.
103///
104/// Pixels are 8-bit indices into the sprite sheet's colour palette.
103typedef struct Ss_Row { 105typedef struct Ss_Row {
104 uint16_t num_cols; /// Number of columns in this row. 106 uint16_t num_cols; /// Number of columns in this row.
105 Pixel pixels[1]; /// Count: num_cols * sprite_width * sprite_height. 107 uint8_t pixels[1]; /// Count: num_cols * sprite_width * sprite_height.
106} Ss_Row; 108} Ss_Row;
107 109
110typedef struct Ss_Palette {
111 uint16_t num_colours;
112 Pixel colours[1]; /// Count: num_colors.
113} Ss_Palette;
114
108/// Sprite sheet top-level data definition. 115/// Sprite sheet top-level data definition.
109/// 116///
110/// Sprite width and height are assumed constant throughout the sprite sheet. 117/// Sprite width and height are assumed constant throughout the sprite sheet.
111typedef struct Ss_SpriteSheet { 118typedef struct Ss_SpriteSheet {
112 uint16_t sprite_width; /// Sprite width in pixels. 119 uint16_t sprite_width; /// Sprite width in pixels.
113 uint16_t sprite_height; /// Sprite height in pixels. 120 uint16_t sprite_height; /// Sprite height in pixels.
114 uint16_t num_rows; 121 uint16_t num_rows;
115 Ss_Row rows[1]; /// Count: num_rows. 122 Ss_Palette palette; /// Variable size.
123 Ss_Row rows[1]; /// Count: num_rows. Variable offset.
116} Ss_SpriteSheet; 124} Ss_SpriteSheet;
117 125
118const Ss_Row* get_sprite_sheet_row(const Ss_SpriteSheet* sheet, int row) { 126static inline const Ss_Row* get_sprite_sheet_row(
127 const Ss_SpriteSheet* sheet, int row) {
119 assert(sheet); 128 assert(sheet);
120 assert(row >= 0); 129 assert(row >= 0);
121 assert(row < sheet->num_rows); 130 assert(row < sheet->num_rows);
122 return &sheet->rows[row]; 131 // Skip over the palette.
132 const Ss_Row* rows =
133 (const Ss_Row*)(&sheet->palette.colours[0] + sheet->palette.num_colours);
134 return &rows[row];
135}
136
137static inline const uint8_t* get_sprite_sheet_sprite(
138 const Ss_SpriteSheet* sheet, const Ss_Row* row, int col) {
139 assert(sheet);
140 assert(row);
141 assert(col >= 0);
142 assert(col < row->num_cols);
143 const int sprite_offset = col * sheet->sprite_width * sheet->sprite_height;
144 const uint8_t* sprite = &row->pixels[sprite_offset];
145 return sprite;
123} 146}
124 147
125// ----------------------------------------------------------------------------- 148// -----------------------------------------------------------------------------
@@ -732,11 +755,19 @@ static Pixel alpha_blend(Pixel src, Pixel dst) {
732/// 755///
733/// The rectangle's pixels are assumed to be arranged in a linear, row-major 756/// The rectangle's pixels are assumed to be arranged in a linear, row-major
734/// fashion. 757/// fashion.
758///
759/// If indices are given, then the image is assumed to be colour-paletted, where
760/// 'pixels' is the palette and 'indices' the pixel indices. Otherwise, the
761/// image is assumed to be in plain RGBA format.
735static void draw_rect( 762static void draw_rect(
736 IsoGfx* iso, ivec2 origin, int rect_width, int rect_height, 763 IsoGfx* iso, ivec2 origin, int rect_width, int rect_height,
737 const Pixel* pixels) { 764 const Pixel* pixels, const uint8_t* indices) {
738 assert(iso); 765 assert(iso);
739 766
767#define rect_pixel(x, y) \
768 (indices ? pixels[indices[py * rect_width + px]] \
769 : pixels[py * rect_width + px])
770
740 // Rect can exceed screen bounds, so we must clip it. 771 // Rect can exceed screen bounds, so we must clip it.
741#define max(a, b) (a > b ? a : b) 772#define max(a, b) (a > b ? a : b)
742 const int py_offset = max(0, rect_height - origin.y); 773 const int py_offset = max(0, rect_height - origin.y);
@@ -748,7 +779,7 @@ static void draw_rect(
748 const int sy = origin.y + py - py_offset; 779 const int sy = origin.y + py - py_offset;
749 for (int px = 0; (px < rect_width) && (origin.x + px < iso->screen_width); 780 for (int px = 0; (px < rect_width) && (origin.x + px < iso->screen_width);
750 ++px) { 781 ++px) {
751 const Pixel colour = pixels[py * rect_width + px]; 782 const Pixel colour = rect_pixel(px, py);
752 if (colour.a > 0) { 783 if (colour.a > 0) {
753 const int sx = origin.x + px; 784 const int sx = origin.x + px;
754 const Pixel dst = screen_xy(iso, sx, sy); 785 const Pixel dst = screen_xy(iso, sx, sy);
@@ -767,7 +798,7 @@ static void draw_tile(IsoGfx* iso, ivec2 origin, Tile tile) {
767 798
768 const Pixel* pixels = tile_xy_const_ref(iso, tile_data, 0, 0); 799 const Pixel* pixels = tile_xy_const_ref(iso, tile_data, 0, 0);
769 800
770 draw_rect(iso, origin, tile_data->width, tile_data->height, pixels); 801 draw_rect(iso, origin, tile_data->width, tile_data->height, pixels, 0);
771} 802}
772 803
773static void draw(IsoGfx* iso) { 804static void draw(IsoGfx* iso) {
@@ -807,15 +838,11 @@ static void draw_sprite(
807 assert(sprite->animation < sheet->num_rows); 838 assert(sprite->animation < sheet->num_rows);
808 assert(sprite->frame >= 0); 839 assert(sprite->frame >= 0);
809 840
810 const SpriteSheetRow* ss_row = &sheet->rows[sprite->animation]; 841 const SpriteSheetRow* row = get_sprite_sheet_row(sheet, sprite->animation);
811 assert(sprite->frame < ss_row->num_cols); 842 const uint8_t* frame = get_sprite_sheet_sprite(sheet, row, sprite->frame);
812 843 draw_rect(
813 const int sprite_offset = 844 iso, origin, sheet->sprite_width, sheet->sprite_height,
814 sprite->frame * sheet->sprite_width * sheet->sprite_height; 845 sheet->palette.colours, frame);
815
816 const Pixel* frame = &ss_row->pixels[sprite_offset];
817
818 draw_rect(iso, origin, sheet->sprite_width, sheet->sprite_height, frame);
819} 846}
820 847
821static void draw_sprites(IsoGfx* iso) { 848static void draw_sprites(IsoGfx* iso) {