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(base_dir: String = ""): if base_dir == "": assert(filepath != "") 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) ) var path = "%s/%s.png" % [base_dir, str(idx).pad_zeros(3)] image.save_png(path) 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