diff options
| -rw-r--r-- | gfx-iso/asset/mkasset.py | 127 | ||||
| -rw-r--r-- | gfx-iso/src/isogfx.c | 65 |
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 | ||
| 168 | def get_sprite_sheet_rows(input_filepath, sprite_width, sprite_height): | 168 | def 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 | |||
| 206 | def 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 | ||
| 208 | def convert_sprite_sheet(input_file_paths, sprite_width, sprite_height, | 221 | def 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 | ||
| 240 | def main(): | 293 | def 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. | ||
| 103 | typedef struct Ss_Row { | 105 | typedef 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 | ||
| 110 | typedef 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. |
| 111 | typedef struct Ss_SpriteSheet { | 118 | typedef 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 | ||
| 118 | const Ss_Row* get_sprite_sheet_row(const Ss_SpriteSheet* sheet, int row) { | 126 | static 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 | |||
| 137 | static 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. | ||
| 735 | static void draw_rect( | 762 | static 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 | ||
| 773 | static void draw(IsoGfx* iso) { | 804 | static 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 | ||
| 821 | static void draw_sprites(IsoGfx* iso) { | 848 | static void draw_sprites(IsoGfx* iso) { |
