From 0988ab832bfc7a1b1c851125b6172cf68c6d9cb9 Mon Sep 17 00:00:00 2001 From: Xander Swan <[hidden email]> Date: Wed, 7 Jan 2026 23:12:22 -0500 Subject: doesn't compile but i'm commiting anywya --- tools/compile_assets/aseprite/utils/image.odin | 443 +++++++++++++++++++++++++ 1 file changed, 443 insertions(+) create mode 100644 tools/compile_assets/aseprite/utils/image.odin (limited to 'tools/compile_assets/aseprite/utils/image.odin') 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.. (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..