summaryrefslogtreecommitdiff
path: root/extractor/sprite_format.gd
diff options
context:
space:
mode:
Diffstat (limited to 'extractor/sprite_format.gd')
-rw-r--r--extractor/sprite_format.gd233
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