diff options
Diffstat (limited to 'tools/compile_assets/aseprite/utils/image.odin')
| -rw-r--r-- | tools/compile_assets/aseprite/utils/image.odin | 443 |
1 files changed, 443 insertions, 0 deletions
diff --git a/tools/compile_assets/aseprite/utils/image.odin b/tools/compile_assets/aseprite/utils/image.odin new file mode 100644 index 0000000..ded33b7 --- /dev/null +++ b/tools/compile_assets/aseprite/utils/image.odin @@ -0,0 +1,443 @@ +package aseprite_file_handler_utility + +import "base:runtime" +import ir "base:intrinsics" + +import "core:slice" +import "core:mem" + +@(require) import "core:fmt" +@(require) import "core:log" + +import ase ".." + + +// Only Uses the first frame +get_image_from_doc :: proc(doc: ^ase.Document, frame := 0, alloc := context.allocator) -> (img: Image, err: Errors) { + context.allocator = alloc + + if len(doc.frames) <= frame { + return {}, Image_Error.Frame_Index_Out_Of_Bounds + } + + info: Info + get_info(doc, &info) or_return + defer destroy(&info) + + return get_image_from_frame(info.frames[frame], info) +} + +get_image_from_doc_frame :: proc( + frame: ase.Frame, info: Info, +) -> (img: Image, err: Errors) { + + raw_frame := get_frame(frame, info.allocator) or_return + defer delete(raw_frame.cels, info.allocator) + + return get_image_from_frame(raw_frame, info) +} + +get_image_from_cel :: proc( + cel: Cel, layer: Layer, info: Info, +) -> (img: Image, err: Errors) { + img.width = cel.width + img.height = cel.height + img.bpp = .RGBA + + if cel.tilemap.tiles != nil { + ts := info.tilesets[layer.tileset] + c := cel_from_tileset(cel, ts, info.md.bpp, info.allocator) or_return + defer delete(c.raw) + + img.data = make([]byte, c.width * c.height * 4, info.allocator) or_return + if !layer.is_background && info.md.bpp == .Indexed { + img_p := mem.slice_data_cast([]Pixel, img.data) + c := info.palette[info.md.trans_idx].color + c.a = 0 + slice.fill(img_p, c) + } + write_cel(img.data[:], c, layer, info.md, info.palette) or_return + + } else { + img.data = make([]byte, cel.width * cel.height * 4, info.allocator) or_return + if !layer.is_background && info.md.bpp == .Indexed { + img_p := mem.slice_data_cast([]Pixel, img.data) + c := info.palette[info.md.trans_idx].color + c.a = 0 + slice.fill(img_p, c) + } + write_cel(img.data[:], cel, layer, info.md, info.palette) or_return + } + + return +} + +get_image_from_frame :: proc( + frame: Frame, info: Info, +) -> (img: Image, err: Errors) { + + img.md = info.md + img.bpp = .RGBA + img.data = get_image_bytes(frame, info) or_return + return +} + +get_image :: proc { + get_image_from_doc, + get_image_from_doc_frame, + get_image_from_frame, + get_image_from_cel, +} + + +// Only Uses the First Frame +get_image_bytes_from_doc :: proc(doc: ^ase.Document, frame := 0, alloc := context.allocator) -> (img: []byte, err: Errors) { + context.allocator = alloc + md := get_metadata(doc.header) + + raw_frame := get_frame(doc.frames[frame]) or_return + defer delete(raw_frame.cels) + + layers := get_layers(doc) or_return + defer delete(layers) + + palette := get_palette(doc) or_return + defer delete(palette) + + ts := get_tileset(doc) or_return + defer delete(ts) + + info := Info{layers=layers, palette=palette, tilesets=ts, allocator=alloc, md=md} + + return get_image_bytes(raw_frame, info) +} + +get_image_bytes_from_doc_frame :: proc( + frame: ase.Frame, info: Info, +) -> (img: []byte, err: Errors) { + + raw_frame := get_frame(frame, info.allocator) or_return + defer destroy(raw_frame, info.allocator) + return get_image_bytes(raw_frame, info) +} + +get_image_bytes_from_cel :: proc ( + cel: Cel, layer: Layer, info: Info, +) -> (img: []byte, err: Errors) { + if cel.tilemap.tiles != nil { + ts := info.tilesets[layer.tileset] + c := cel_from_tileset(cel, ts, info.md.bpp, info.allocator) or_return + defer delete(c.raw) + + img = make([]byte, c.width * c.height * 4, info.allocator) or_return + if !layer.is_background && info.md.bpp == .Indexed { + img_p := mem.slice_data_cast([]Pixel, img) + c := info.palette[info.md.trans_idx].color + c.a = 0 + slice.fill(img_p, c) + } + write_cel(img, c, layer, info.md, info.palette) or_return + + } else { + img = make([]byte, cel.width * cel.height * 4, info.allocator) or_return + if !layer.is_background && info.md.bpp == .Indexed { + img_p := mem.slice_data_cast([]Pixel, img) + c := info.palette[info.md.trans_idx].color + c.a = 0 + slice.fill(img_p, c) + } + write_cel(img, cel, layer, info.md, info.palette) or_return + } + + return +} + +get_image_bytes_from_frame :: proc( + frame: Frame, info: Info, +) -> (img: []byte, err: Errors) { + context.allocator = info.allocator + + img = make([]byte, info.md.width * info.md.height * 4) or_return + if len(frame.cels) == 0 { return } + + if !slice.is_sorted_by(frame.cels, cel_less) { + slice.sort_by(frame.cels, cel_less) + } + + if !info.layers[0].is_background && info.md.bpp == .Indexed { + img_p := mem.slice_data_cast([]Pixel, img) + c := info.palette[info.md.trans_idx].color + c.a = 0 + slice.fill(img_p, c) + } + + for cel in frame.cels { + lay := info.layers[cel.layer] + if !lay.visiable { continue } + + if cel.tilemap.tiles != nil { + ts := info.tilesets[lay.tileset] + c := cel_from_tileset(cel, ts, info.md.bpp, info.allocator) or_return + defer delete(c.raw) + + write_cel(img, c, lay, info.md, info.palette) or_return + + } else { + write_cel(img, cel, lay, info.md, info.palette) or_return + } + } + + return +} + +get_image_bytes :: proc { + get_image_bytes_from_doc, + get_image_bytes_from_doc_frame, + get_image_bytes_from_cel, + get_image_bytes_from_frame, +} + + +get_all_images :: proc(doc: ^ase.Document, alloc := context.allocator) -> (imgs: []Image, err: Errors) { + context.allocator = alloc + imgs = make([]Image, len(doc.frames)) or_return + defer if err != nil { destroy(imgs)} + + info: Info + get_info(doc, &info) or_return + defer destroy(&info) + + for frame, p in info.frames { + imgs[p] = get_image(frame, info) or_return + } + + return +} + +get_all_images_bytes :: proc(doc: ^ase.Document, alloc := context.allocator) -> (imgs: [][]byte, err: Errors) { + context.allocator = alloc + imgs = make([][]byte, len(doc.frames)) or_return + defer if err != nil { destroy(imgs) } + + md := get_metadata(doc.header) + + layers := get_layers(doc) or_return + defer delete(layers) + + palette := get_palette(doc) or_return + defer delete(palette) + + ts := get_tileset(doc) or_return + defer delete(ts) + + info := Info{layers=layers, palette=palette, tilesets=ts, allocator=alloc, md=md} + + for frame, p in doc.frames { + imgs[p] = get_image_bytes(frame, info) or_return + } + + return +} + + +get_cels_as_imgs :: proc(doc: ^ase.Document, frame_idx := 0, alloc := context.allocator) -> (res: []Image, err: Errors) { + context.allocator = alloc + + info: Info + get_info(doc, &info) or_return + defer destroy(&info) + + if len(info.frames) < frame_idx { + return nil, .Frame_Index_Out_Of_Bounds + } + + frame := info.frames[frame_idx] + + res = make([]Image, len(frame.cels)) or_return + if len(frame.cels) == 0 { return } + + if !slice.is_sorted_by(frame.cels, cel_less) { + slice.sort_by(frame.cels, cel_less) + } + + for cel, i in frame.cels { + lay := info.layers[cel.layer] + if !lay.visiable { continue } + + res[i] = get_image_from_cel(cel, lay, info) or_return + } + + return +} + + +get_all_cels_as_imgs :: proc(doc: ^ase.Document, alloc := context.allocator) -> (res: []Image, err: Errors) { + context.allocator = alloc + + info: Info + get_info(doc, &info) or_return + defer destroy(&info) + + imgs := make([dynamic]Image) or_return + + for frame in info.frames { + if len(frame.cels) == 0 { continue } + + if !slice.is_sorted_by(frame.cels, cel_less) { + slice.sort_by(frame.cels, cel_less) + } + + for cel in frame.cels { + lay := info.layers[cel.layer] + if !lay.visiable { continue } + + img := get_image_from_cel(cel, lay, info) or_return + append(&imgs, img) + } + } + + return imgs[:], nil +} + +cel_from_tileset :: proc(cel: Cel, ts: Tileset, chans: Pixel_Depth, alloc: runtime.Allocator) -> (c: Cel, err: Errors) { + c = cel + c.width = cel.tilemap.width * ts.width + c.height = cel.tilemap.height * ts.height + ch := int(chans)/8 + + if (ts.height * ts.width * len(c.tilemap.tiles) * ch) != (c.width * c.height * ch) { + return {}, .Tileset_Cel_Sizes_Mismatch + } + + c.raw = make([]byte, ts.height * ts.width * len(c.tilemap.tiles) * ch, alloc) or_return + + for h in 0..<c.tilemap.height { + for w in 0..<c.tilemap.width { + s := (h * c.tilemap.width * ts.height + w) * ts.width * ch + s1 := c.tilemap.tiles[h * c.tilemap.width + w] * ts.width * ts.height * ch + + for y in 0..<ts.height { + copy(c.raw[s+(y*ts.width*c.tilemap.width*ch):][:ch*ts.width], ts.tiles[s1+(y*ts.width*ch):]) + } + } + } + + return +} + +// Write a cel to an image's data. Assumes tilemaps & linked cels have already been handled. +write_cel :: proc ( + buf: []byte, cel: Cel, layer: Layer, md: Metadata, + pal: Palette = nil, +) -> (err: Errors) { + // TODO: Allow for both arbitrary reads & writes, i.e. cropping. + + if len(cel.raw) <= 0 { + fast_log(.Debug, "No Cel data to write.") + return + } + + if len(buf) < (md.height * md.width * 4) { + fast_log(.Error, "Image buffer size is smaller than Metadata.") + return .Buffer_To_Small + } + + when UTILS_DEBUG_MODE { + switch md.bpp { + case .Indexed: + if pal == nil { + fast_log(.Error, "Indexed Color Mode. No Palette") + return .Indexed_BPP_No_Palette + } + fallthrough + case .Grayscale, .RGBA: + if len(cel.raw) % (int(md.bpp) / 8) != 0 { + fast_log(.Error, "Size of cel not a multipule of channels. ") + return .Cel_Size_Not_Of_BPP + } + case: + fast_log(.Error, "Invalid Color Mode: ", md.bpp) + return .Invalid_BPP + } + } + + /*offset := [2]int { + abs(cel.x) if cel.x < 0 else 0, + abs(cel.y) if cel.y < 0 else 0, + }*/ + offset := [2]int { + abs(min(0, cel.x)), + abs(min(0, cel.y)), + } + + bounds := Bounds { + x = clamp(cel.x, 0, md.width), + y = clamp(cel.y, 0, md.height), + width = clamp(cel.width, 0, md.width), + height = clamp(cel.height, 0, md.height), + } + + when UTILS_DEBUG_MODE { + if !( bounds.x <= md.width && bounds.y <= md.height \ + && bounds.width <= md.width && bounds.height <= md.height \ + && offset.x <= md.width && offset.y <= md.height \ + && bounds.x >= 0 && bounds.y >= 0 \ + && bounds.width >= 0 && bounds.height >= 0 \ + && offset.x >= 0 && offset.y >= 0 ) { + fast_log(.Error, "Cel out of bounds of Image bounds.") + return .Cel_Out_Of_Bounds, + } + } + + y_loop: for y in 0..<bounds.height { + for x in 0..<bounds.width { + pix: [4]byte + idx := (y + offset.y) * cel.width + x + offset.x + + // Convert to RGBA + switch md.bpp { + case .Indexed: + if cel.raw[idx] == md.trans_idx { + continue + } + pix = pal[cel.raw[idx]].color + + case .Grayscale: + pix.rgb = cel.raw[idx * 2] + pix.a = cel.raw[idx * 2 + 1] + + case .RGBA: + // Note(blob): + // This is comparable to slice casting before & idxing in to that. + // `mem.slice_data_cast()` or `slice.reinterpret()` + // On average is slightly faster but more variable in optimized builds + pix = ir.unaligned_load((^[4]byte)(&cel.raw[idx * 4])) + } + + if pix.a != 0 { + iidx := ((y + bounds.y) * md.width + x + bounds.x) * 4 + if len(buf) <= iidx { + break y_loop + } + + ipix := (^[4]byte)(&buf[iidx]) + + if ipix.a != 0 { + // Blend pixels) + p := ir.unaligned_load(ipix) + a := alpha(cel.opacity, layer.opacity) + pix = blend(p, pix, a, layer.blend_mode) or_return + + } else { + // Merge Alpha & Opacities + pix.a = u8(alpha(i32(pix.a), alpha(cel.opacity, layer.opacity))) + } + + ir.unaligned_store(ipix, pix) + } + } + } + + return +} + |
