diff options
author | 3gg <3gg@shellblade.net> | 2023-07-26 08:39:37 -0700 |
---|---|---|
committer | 3gg <3gg@shellblade.net> | 2023-07-26 08:39:37 -0700 |
commit | cef3385c2bee0b098a7795548345a9281ace008e (patch) | |
tree | b594e9cc151a4ae7fd8b5732cf349eb01f37683d /gfx-iso/asset/mkasset.py | |
parent | 48cef82988d6209987ae27fe29b72d7d5e402b3c (diff) |
Add support for paletted sprites.
Diffstat (limited to 'gfx-iso/asset/mkasset.py')
-rw-r--r-- | gfx-iso/asset/mkasset.py | 127 |
1 files changed, 90 insertions, 37 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() |