diff options
Diffstat (limited to 'extractor/sprite_format.gd')
-rw-r--r-- | extractor/sprite_format.gd | 233 |
1 files changed, 233 insertions, 0 deletions
diff --git a/extractor/sprite_format.gd b/extractor/sprite_format.gd new file mode 100644 index 0000000..fab0873 --- /dev/null +++ b/extractor/sprite_format.gd @@ -0,0 +1,233 @@ +class_name SpriteFormat + + +## Byte Length: 2 [br] +## SP +var signature: String = "SP" + +## Byte Type: u8 [br] +## Byte Length: 2 +var version: Version + +## Byte Type: u16 [br] +## Byte Length: 2 +var palette_image_count: int + +## Byte Type: u16 [br] +## Byte Length: 2 +var rgba_image_count: int + +## Length: [member palette_image_count] +var palette_image_data: Array[PaletteImageData] + +## Byte Type: u8 [br] +## Byte Length: [member rgba_image_count] +var rgba_image_data: Array[RGBAImageData] + +## Byte Type: u16 [br] +## Byte Length: 256 * 4 (rgba u8) [br] +## The color with palette index 0 can be considered the "background color". [br] +## It must be cleared manually on load. +var palette: Array[PaletteColor] + +var filepath: String + + +static func from_file(path: String) -> SpriteFormat: + var sprite_format = from_bytes(FileAccess.get_file_as_bytes(path)) + + sprite_format.filepath = path + + return sprite_format + + +static func from_bytes(bytes: PackedByteArray) -> SpriteFormat: + var sprite_format = SpriteFormat.new() + + @warning_ignore("shadowed_variable") + var version = Version.new() + version.minor = bytes.decode_u8(2) + version.major = bytes.decode_u8(3) + sprite_format.version = version + + sprite_format.palette_image_count = bytes.decode_u16(4) + sprite_format.rgba_image_count = bytes.decode_u16(6) + + sprite_format.palette_image_data = [] as Array[PaletteImageData] + var palette_image_offset = 8 + var processed_palette_images = 0 + while processed_palette_images < sprite_format.palette_image_count: + var data = PaletteImageData.new() + data.width = bytes.decode_u16(palette_image_offset) + data.height = bytes.decode_u16(palette_image_offset + 2) + + data.data_size = bytes.decode_u16(palette_image_offset + 4) + data.encoded_data = bytes.slice( + palette_image_offset + 6, + palette_image_offset + 6 + data.data_size + ) + + data.decode() + + sprite_format.palette_image_data.append(data) + processed_palette_images += 1 + palette_image_offset += data.get_byte_length() + + sprite_format.rgba_image_data = [] as Array[RGBAImageData] + var rgba_image_offset = palette_image_offset + var processed_rgba_images = 0 + while processed_rgba_images < sprite_format.rgba_image_count: + var data = RGBAImageData.new() + data.width = bytes.decode_u16(rgba_image_offset) + data.height = bytes.decode_u16(rgba_image_offset + 2) + + data.data = [] as Array[RGBAImageData.Pixel] + for _idx in (data.width * data.height): + var pixel = RGBAImageData.Pixel.new() + pixel.r = bytes.decode_u8(rgba_image_offset + 4) + pixel.g = bytes.decode_u8(rgba_image_offset + 5) + pixel.b = bytes.decode_u8(rgba_image_offset + 6) + pixel.a = bytes.decode_u8(rgba_image_offset + 7) + data.data.append(pixel) + + sprite_format.rgba_image_data.append(data) + processed_rgba_images += 1 + rgba_image_offset += data.get_byte_length() + + sprite_format.palette = [] as Array[PaletteColor] + var palette_offset = rgba_image_offset + while palette_offset < bytes.size(): + var color = PaletteColor.new() + color.r = bytes.decode_u8(palette_offset) + color.g = bytes.decode_u8(palette_offset + 1) + color.b = bytes.decode_u8(palette_offset + 2) + color.a = bytes.decode_u8(palette_offset + 3) + sprite_format.palette.append(color) + palette_offset += 4 + + return sprite_format + + +func save_to_file(): + assert(filepath != "") + + var base_dir = filepath.substr(0, filepath.length() - 4) # cut off .spr + DirAccess.make_dir_recursive_absolute(base_dir) + + for idx in palette_image_data.size(): + var data: PaletteImageData = palette_image_data[idx] + var image = Image.create_from_data( + data.width, + data.height, + false, + Image.FORMAT_RGBA8, + data.get_rgba_data(palette) + ) + + image.save_png("%s/%s.png" % [base_dir, str(idx).pad_zeros(3)]) + + +func files_exist() -> bool: + assert(filepath != "") + + var base_dir = filepath.substr(0, filepath.length() - 4) # cut off .spr + + return DirAccess.dir_exists_absolute(base_dir) + + + +class PaletteImageData: + ## Byte Type: u16 + ## Byte Length: 2 + var width: int + + ## Byte Type: u16 + ## Byte Length: 2 + var height: int + + ## Byte Type: u16 + ## Byte Length: 2 + var data_size: int + + ## Byte Type: u8 + ## Byte Length: [member data_size] + var encoded_data: PackedByteArray + + var decoded_data: PackedByteArray + + + func get_byte_length() -> int: + return 6 + data_size + + + func decode(): + decoded_data = PackedByteArray([]) + + var offset = 0 + while offset < encoded_data.size(): + var byte = encoded_data.decode_u8(offset) + if byte == 0: + var length = encoded_data.decode_u8(offset + 1) + var padding = PackedByteArray([]) + padding.resize(length) + padding.fill(0) + decoded_data.append_array(padding) + + offset += 2 + else: + decoded_data.append(byte) + offset += 1 + + + func get_rgba_data(palette: Array[PaletteColor]) -> PackedByteArray: + var rgba := PackedByteArray([]) + var background_color := palette[0] + + for idx in decoded_data: + var color := palette[idx] + if color == background_color: + rgba.append_array(PackedByteArray([0, 0, 0, 0])) + else: + rgba.append_array(color.to_bytes()) + + return rgba + + +class PaletteColor: + const BYTE_LENGTH := 4 + + var r: int + var g: int + var b: int + var a: int + + + func to_bytes(): + return PackedByteArray([r, g, b, 255]) + + +class RGBAImageData: + ## Byte Type: u16 + ## Byte Length: 2 + var width: int + + ## Byte Type: u16 + ## Byte Length: 2 + var height: int + + ## Byte Type: u8 + ## Byte Length: width * height * RGBAPixel.byte_length + var data: Array[Pixel] + + + class Pixel: + const BYTE_LENGTH := 4 + + var r: int + var g: int + var b: int + var a: int + + + func get_byte_length() -> int: + return 4 + width * height * Pixel.BYTE_LENGTH |