package aseprite_file_handler import "base:intrinsics" import "core:io" import "core:log" import "core:bytes" import "core:compress/zlib" read_file_header :: proc(r: io.Reader, rt: ^int) -> (h: File_Header, err: Unmarshal_Error) { h.size = read_dword(r, rt) or_return if io.Stream_Mode.Size in io.query(r) { stream_size := io.size(r) or_return if stream_size != i64(h.size) { return {}, .Data_Size_Not_Equal_To_Header } } magic := read_word(r, rt) or_return if magic != FILE_MAGIC_NUM { return {}, .Bad_File_Magic_Number } h.frames = read_word(r, rt) or_return h.width = read_word(r, rt) or_return h.height = read_word(r, rt) or_return h.color_depth = Color_Depth(read_word(r, rt) or_return) h.flags = transmute(File_Flags)read_dword(r, rt) or_return h.speed = read_word(r, rt) or_return read_skip(r, 4+4, rt) or_return h.transparent_index = read_byte(r, rt) or_return read_skip(r, 3, rt) or_return h.num_of_colors = read_word(r, rt) or_return h.ratio_width = read_byte(r, rt) or_return h.ratio_height = read_byte(r, rt) or_return h.x = read_short(r, rt) or_return h.y = read_short(r, rt) or_return h.grid_width = read_word(r, rt) or_return h.grid_height = read_word(r, rt) or_return read_skip(r, 84, rt) or_return return } read_old_palette_256 :: proc(r: io.Reader, rt: ^int, allocator := context.allocator) -> (chunk: Old_Palette_256_Chunk, err: Unmarshal_Error) { op_size := cast(int)read_word(r, rt) or_return chunk = make(Old_Palette_256_Chunk, op_size, allocator) or_return for &packet in chunk { packet.entries_to_skip = read_byte(r, rt) or_return packet.num_colors = read_byte(r, rt) or_return count := int(packet.num_colors) if count == 0 { count = 256 } packet.colors = make([]Color_RGB, count, allocator) or_return for &c in packet.colors { read_bytes(r, c[:], rt) or_return } } return } read_old_palette_64 :: proc(r: io.Reader, rt: ^int, allocator := context.allocator) -> (chunk: Old_Palette_64_Chunk, err: Unmarshal_Error) { op_size := cast(int)read_word(r, rt) or_return chunk = make(Old_Palette_64_Chunk, op_size, allocator) or_return for &packet in chunk { packet.entries_to_skip = read_byte(r, rt) or_return packet.num_colors = read_byte(r, rt) or_return count := int(packet.num_colors) if count == 0 { count = 256 } packet.colors = make([]Color_RGB, count, allocator) or_return for &c in packet.colors { read_bytes(r, c[:], rt) or_return } } return } read_layer :: proc(r: io.Reader, rt: ^int, has_uuid: bool, allocator := context.allocator) -> (chunk: Layer_Chunk, err: Unmarshal_Error) { chunk.flags = transmute(Layer_Chunk_Flags)read_word(r, rt) or_return chunk.type = Layer_Types(read_word(r, rt) or_return) chunk.child_level = read_word(r, rt) or_return chunk.default_width = read_word(r, rt) or_return chunk.default_height = read_word(r, rt) or_return chunk.blend_mode = Layer_Blend_Mode(read_word(r, rt) or_return) chunk.opacity = read_byte(r, rt) or_return read_skip(r, 3, rt) or_return chunk.name = read_string(r, rt, allocator) or_return if chunk.type == .Tilemap { chunk.tileset_index = read_dword(r, rt) or_return } if has_uuid { chunk.uuid = read_uuid(r, rt) or_return } return } read_cel :: proc(r: io.Reader, rt: ^int, color_depth: int, c_size: int, allocator := context.allocator) -> (chunk: Cel_Chunk, err: Unmarshal_Error) { context.allocator = allocator chunk.layer_index = read_word(r, rt) or_return chunk.x = read_short(r, rt) or_return chunk.y = read_short(r, rt) or_return chunk.opacity_level = read_byte(r, rt) or_return chunk.type = Cel_Types(read_word(r, rt) or_return) chunk.z_index = read_short(r, rt) or_return read_skip(r, 5, rt) or_return switch chunk.type { case .Raw: cel: Raw_Cel cel.width = read_word(r, rt) or_return cel.height = read_word(r, rt) or_return cel.pixels = make([]PIXEL, int(cel.width * cel.height)) or_return read_bytes(r, cel.pixels[:], rt) or_return chunk.cel = cel case .Linked_Cel: chunk.cel = Linked_Cel(read_word(r, rt) or_return) case .Compressed_Image: cel: Com_Image_Cel cel.width = read_word(r, rt) or_return cel.height = read_word(r, rt) or_return com_size := c_size-26 if com_size <= 0 { err = .Invalid_Compression_Size return } buf: bytes.Buffer defer bytes.buffer_destroy(&buf) data := make([]byte, com_size) or_return defer delete(data) read_bytes(r, data[:], rt) or_return exp_size := color_depth / 8 * int(cel.height) * int(cel.width) zlib.inflate(data[:], &buf, expected_output_size=exp_size) or_return cel.pixels = make([]byte, exp_size) or_return copy(cel.pixels[:], buf.buf[:]) chunk.cel = cel case .Compressed_Tilemap: cel: Com_Tilemap_Cel cel.width = read_word(r, rt) or_return cel.height = read_word(r, rt) or_return cel.bits_per_tile = read_word(r, rt) or_return cel.bitmask_id = Tile_ID(read_dword(r, rt) or_return) cel.bitmask_x = read_dword(r, rt) or_return cel.bitmask_y = read_dword(r, rt) or_return cel.bitmask_diagonal = read_dword(r, rt) or_return read_skip(r, 10, rt) or_return buf: bytes.Buffer defer bytes.buffer_destroy(&buf) // size_of(DWORD*5, WORD*6, SHORT*3, BYTE, SKIPED*15)-1 com_size := c_size-54 if com_size <= 0 { err = .Invalid_Compression_Size return } data := make([]byte, com_size) or_return defer delete(data) read_bytes(r, data[:], rt) or_return exp_size := color_depth / 8 * int(cel.height) * int(cel.width) zlib.inflate(data[:], &buf, expected_output_size=exp_size) or_return br: bytes.Reader bytes.reader_init(&br, buf.buf[:]) rr, ok := io.to_reader(bytes.reader_to_stream(&br)) if !ok { err = .Unable_Make_Reader; return } cel.tiles = make([]TILE, cel.height * cel.width) or_return read_tiles(rr, cel.tiles[:], cel.bitmask_id, rt) or_return chunk.cel = cel case: err = .Invalid_Cel_Type return } return } read_cel_extra :: proc(r: io.Reader, rt: ^int) -> (chunk: Cel_Extra_Chunk, err: Unmarshal_Error) { chunk.flags = transmute(Cel_Extra_Flags)read_word(r, rt) or_return chunk.x = read_fixed(r, rt) or_return chunk.y = read_fixed(r, rt) or_return chunk.width = read_fixed(r, rt) or_return chunk.height = read_fixed(r, rt) or_return return } read_color_profile :: proc(r: io.Reader, rt: ^int, warned: ^bool, allocator := context.allocator) -> (chunk: Color_Profile_Chunk, err: Unmarshal_Error) { chunk.type = Color_Profile_Type(read_word(r, rt) or_return) chunk.flags = transmute(Color_Profile_Flags)read_word(r, rt) or_return chunk.fixed_gamma = read_fixed(r, rt) or_return read_skip(r, 8, rt) or_return if chunk.type == .ICC { icc_size := cast(int)read_dword(r, rt) or_return chunk.icc = make(ICC_Profile, icc_size, allocator) or_return read_bytes(r, cast([]u8)chunk.icc.(ICC_Profile)[:], rt) or_return if !warned^ { log.warn("Embedded ICC Color Profiles are currently not supported.") warned^ = true } } return } read_external_files :: proc(r: io.Reader, rt: ^int, allocator := context.allocator) -> (chunk: External_Files_Chunk, err: Unmarshal_Error) { entries := read_dword(r, rt) or_return chunk = make([]External_Files_Entry, entries, allocator) or_return read_skip(r, 8, rt) or_return for &entry in chunk { entry.id = read_dword(r, rt) or_return entry.type = ExF_Entry_Type(read_byte(r, rt) or_return) read_skip(r, 7, rt) or_return entry.file_name_or_id = read_string(r, rt, allocator) or_return } return } read_mask :: proc(r: io.Reader, rt: ^int, allocator := context.allocator) -> (chunk: Mask_Chunk, err: Unmarshal_Error) { chunk.x = read_short(r, rt) or_return chunk.y = read_short(r, rt) or_return chunk.width = read_word(r, rt) or_return chunk.height = read_word(r, rt) or_return read_skip(r, 8, rt) or_return chunk.name = read_string(r, rt, allocator) or_return size := int(chunk.height) * ((int(chunk.width) + 7) / 8) chunk.bit_map_data = make([]BYTE, size, allocator) or_return read_bytes(r, chunk.bit_map_data[:], rt) or_return return } read_path :: proc() -> (chunk: Path_Chunk) { return } read_tags :: proc(r: io.Reader, rt: ^int, allocator := context.allocator) -> (chunk: Tags_Chunk, err: Unmarshal_Error) { size := cast(int)read_word(r, rt) or_return chunk = make([]Tag, size, allocator) or_return read_skip(r, 8, rt) or_return for &tag in chunk { tag.from_frame = read_word(r, rt) or_return tag.to_frame = read_word(r, rt) or_return tag.loop_direction = Tag_Loop_Dir(read_byte(r, rt) or_return) tag.repeat = read_word(r, rt) or_return read_skip(r, 6, rt) or_return read_bytes(r, tag.tag_color[:], rt) or_return read_byte(r, rt) or_return tag.name = read_string(r, rt, allocator) or_return } return } read_palette :: proc(r: io.Reader, rt: ^int, allocator := context.allocator) -> (chunk: Palette_Chunk, err: Unmarshal_Error) { chunk.size = read_dword(r, rt) or_return chunk.first_index = read_dword(r, rt) or_return chunk.last_index = read_dword(r, rt) or_return size := int(chunk.last_index - chunk.first_index + 1) chunk.entries = make([]Palette_Entry, size, allocator) or_return read_skip(r, 8, rt) or_return for &entry in chunk.entries { pf := transmute(Pal_Flags)read_word(r, rt) or_return read_bytes(r, entry.color[:], rt) or_return if .Has_Name in pf { entry.name = read_string(r, rt, allocator) or_return } } return } read_user_data :: proc(r: io.Reader, rt: ^int, allocator := context.allocator) -> (chunk: User_Data_Chunk, err: Unmarshal_Error) { flags := transmute(UD_Flags)read_dword(r, rt) or_return if .Text in flags { chunk.text = read_string(r, rt) or_return } if .Color in flags { colour: Color_RGBA read_bytes(r, colour[:], rt) or_return chunk.color = colour } if .Properties in flags { //map_size := read_dword(r, rt) or_return read_skip(r, 4, rt) or_return map_num := read_dword(r, rt) or_return maps := make(Properties_Map, map_num) or_return for _ in 0.. (chunk: Slice_Chunk, err: Unmarshal_Error) { context.allocator = alloc keys := int(read_dword(r, rt) or_return) chunk.flags = transmute(Slice_Flags)read_dword(r, rt) or_return read_dword(r, rt) or_return chunk.name = read_string(r, rt) or_return chunk.keys = make([]Slice_Key, keys) or_return for &key in chunk.keys { key.frame_num = read_dword(r, rt) or_return key.x = read_long(r, rt) or_return key.y = read_long(r, rt) or_return key.width = read_dword(r, rt) or_return key.height = read_dword(r, rt) or_return if .Patched_slice in chunk.flags { cen: Slice_Center cen.x = read_long(r, rt) or_return cen.y = read_long(r, rt) or_return cen.width = read_dword(r, rt) or_return cen.height = read_dword(r, rt) or_return key.center = cen } if .Pivot_Information in chunk.flags { p: Slice_Pivot p.x = read_long(r, rt) or_return p.y = read_long(r, rt) or_return key.pivot = p } } return } read_tileset :: proc(r: io.Reader, rt: ^int, allocator := context.allocator) -> (chunk: Tileset_Chunk, err: Unmarshal_Error) { chunk.id = read_dword(r, rt) or_return chunk.flags = transmute(Tileset_Flags)read_dword(r, rt) or_return chunk.num_of_tiles = read_dword(r, rt) or_return chunk.width = read_word(r, rt) or_return chunk.height = read_word(r, rt) or_return chunk.base_index = read_short(r, rt) or_return read_skip(r, 14, rt) chunk.name = read_string(r, rt) or_return if .Include_Link_To_External_File in chunk.flags { ex: Tileset_External ex.file_id = read_dword(r, rt) or_return ex.tileset_id = read_dword(r, rt) or_return chunk.external = ex } if .Include_Tiles_Inside_This_File in chunk.flags { size := int(read_dword(r, rt) or_return) buf: bytes.Buffer data := make([]byte, size, allocator) or_return defer delete(data) read_bytes(r, data, rt) or_return zlib.inflate_from_byte_array(data, &buf) or_return res := len(buf.buf)-buf.off exp_size := int(chunk.width) * int(chunk.height) * int(chunk.num_of_tiles) if (res != exp_size) && (res != exp_size*2) && (res != exp_size*4) { fast_log(.Error, "Expected size not equal to uncompressed size") return chunk, Read_Error(Read_Errors.Comp_Tileset_Not_Expected_Size) } chunk.compressed = (Tileset_Compressed)(buf.buf[buf.off:]) } return }