working demo of what the metdata exporter is supposed to do

This commit is contained in:
Stefan Stefanov 2024-04-23 19:17:38 +03:00
parent 194550335d
commit 67d7b3e227
10 changed files with 150 additions and 54 deletions

4
.vscode/tasks.json vendored
View file

@ -70,7 +70,7 @@
},
"group": {
"kind": "build",
"isDefault": true
"isDefault": false
},
},
{
@ -92,7 +92,7 @@
},
"group": {
"kind": "build",
"isDefault": false
"isDefault": true
},
}
]

6
odinfmt.json Normal file
View file

@ -0,0 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/DanielGavin/ols/master/misc/odinfmt.schema.json",
"character_width": 100,
"sort_imports": true,
"tabs": true
}

View file

@ -1,14 +1,17 @@
package generator
import ase "../aseprite"
import "core:encoding/json"
import "core:fmt"
import "core:mem"
import "core:os"
import fp "core:path/filepath"
import "core:slice"
import "core:strings"
import s "core:strings"
import "core:testing"
import rl "vendor:raylib"
import stbrp "vendor:stb/rect_pack"
import gen ".."
@ -24,14 +27,17 @@ main :: proc() {
}
cwd := os.get_current_directory()
target_dir := strings.concatenate({cwd, "\\src\\aseprite_odin_generator\\"})
target_dir := s.concatenate({cwd, "\\src\\aseprite_odin_generator\\"})
atlas: rl.Image = rl.GenImageColor(ATLAS_SIZE, ATLAS_SIZE, rl.BLANK)
atlas_entries: [dynamic]gen.AtlasEntry
gen.unmarshall_aseprite_dir(target_dir, &atlas_entries)
metadata := gen.pack_atlas_entries(atlas_entries[:], &atlas, 10, 10)
json_bytes, jerr := json.marshal(metadata)
os.write_entire_file("src/aseprite_odin_generator/metadata.json", json_bytes)
gen.pack_atlas_entries(atlas_entries[:], &atlas, 10, 10)
rl.ExportImage(atlas, EXPORT_PATH)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before After
Before After

View file

@ -0,0 +1 @@
[{"name":"Edinica","location":[95,10],"size":[58,57]},{"name":"Dvoika_0","location":[234,10],"size":[55,31]},{"name":"Troika","location":[10,10],"size":[75,75]},{"name":"Dvoika_1","location":[163,10],"size":[61,33]}]

View file

@ -1,15 +1,41 @@
package game
import ase "./aseprite"
import "core:encoding/json"
import "core:fmt"
import "core:mem"
import "core:os"
import fp "core:path/filepath"
import "core:strings"
import rl "vendor:raylib"
import stbrp "vendor:stb/rect_pack"
CellData :: struct {
layer_index: u16,
opacity: u8,
frame_index: i32,
img: rl.Image,
}
AtlasEntry :: struct {
path: string,
cells: [dynamic]rl.Image,
path: string,
cells: [dynamic]CellData,
frames: i32,
layer_names: [dynamic]string,
layer_cell_count: [dynamic]i32,
}
SingleFrameSprite :: distinct rl.Rectangle
AnimatedSprite :: struct {
x, y: i32,
width, height: i32,
}
SpriteAtlasMetadata :: struct {
name: string,
location: [2]i32,
size: [2]i32,
}
unmarshall_aseprite_dir :: proc(path: string, atlas_entries: ^[dynamic]AtlasEntry) {
@ -27,14 +53,22 @@ unmarshall_aseprite_files_file_info :: proc(
files: []os.File_Info,
atlas_entries: ^[dynamic]AtlasEntry,
) {
paths: [dynamic]string
for f in files {
append(&paths, f.fullpath)
if len(files) == 0 do return
paths := make([]string, len(files))
defer delete(paths)
for f, fi in files {
paths[fi] = f.fullpath
}
unmarshall_aseprite_files(paths[:], atlas_entries)
}
unmarshall_aseprite_files :: proc(file_paths: []string, atlas_entries: ^[dynamic]AtlasEntry) {
if len(file_paths) == 0 do return
aseprite_document: ase.Document
for file in file_paths {
extension := fp.ext(file)
@ -43,6 +77,7 @@ unmarshall_aseprite_files :: proc(file_paths: []string, atlas_entries: ^[dynamic
fmt.println("Unmarshalling file: ", file)
ase.unmarshal_from_filename(file, &aseprite_document)
atlas_entry := atlas_entry_from_compressed_cells(aseprite_document)
atlas_entry.path = file
append(atlas_entries, atlas_entry)
}
@ -52,26 +87,34 @@ unmarshall_aseprite_files :: proc(file_paths: []string, atlas_entries: ^[dynamic
Goes through all the chunks in an aseprite document & copies the `Com_Image_Cel` cells in a separate image
*/
atlas_entry_from_compressed_cells :: proc(document: ase.Document) -> (atlas_entry: AtlasEntry) {
for frame in document.frames {
atlas_entry.frames = auto_cast len(document.frames)
fmt.println("N Frames: ", len(document.frames))
for frame, frameIdx in document.frames {
fmt.printfln("Frame_{0} Chunks: ", frameIdx, len(frame.chunks))
for chunk in frame.chunks {
cel_chunk, cok := chunk.(ase.Cel_Chunk)
if !cok {
continue
}
if cel_chunk, ok := chunk.(ase.Cel_Chunk); ok {
cel_img, ci_ok := cel_chunk.cel.(ase.Com_Image_Cel)
if !ci_ok do continue
cel_img, ci_ok := cel_chunk.cel.(ase.Com_Image_Cel)
if !ci_ok {
continue
fmt.println(cel_chunk.layer_index)
cell := CellData {
img = rl.Image {
data = rawptr(&cel_img.pixel[0]),
width = auto_cast cel_img.width,
height = auto_cast cel_img.height,
format = .UNCOMPRESSED_R8G8B8A8,
},
frame_index = auto_cast frameIdx,
opacity = cel_chunk.opacity_level,
layer_index = cel_chunk.layer_index,
}
append(&atlas_entry.cells, cell)
}
if layer_chunk, ok := chunk.(ase.Layer_Chunk); ok {
fmt.println("Layer chunk: ", layer_chunk)
append(&atlas_entry.layer_names, layer_chunk.name)
}
append(
&atlas_entry.cells,
rl.Image {
data = rawptr(&cel_img.pixel[0]),
width = auto_cast cel_img.width,
height = auto_cast cel_img.height,
format = .UNCOMPRESSED_R8G8B8A8,
},
)
}
}
return
@ -80,26 +123,33 @@ atlas_entry_from_compressed_cells :: proc(document: ase.Document) -> (atlas_entr
/*
Takes in a slice of entries, an output texture and offsets (offset_x/y)
*/
pack_atlas_entries :: proc(entries: []AtlasEntry, atlas: ^rl.Image, offset_x: i32, offset_y: i32) {
assert(atlas.width != 0, "This shouldn't be 0!")
assert(atlas.height != 0, "This shouldn't be 0!")
pack_atlas_entries :: proc(
entries: []AtlasEntry,
atlas: ^rl.Image,
offset_x: i32,
offset_y: i32,
allocator := context.allocator,
) -> [dynamic]SpriteAtlasMetadata {
assert(atlas.width != 0, "Atlas width shouldn't be 0!")
assert(atlas.height != 0, "Atlas height shouldn't be 0!")
all_entries: [dynamic]rl.Image // it's fine to store it like this, rl.Image just stores a pointer to the data
{
for entry in entries {
append(&all_entries, ..entry.cells[:])
all_cell_images := make([dynamic]rl.Image, allocator) // it's fine to store it like this, rl.Image just stores a pointer to the data
for &entry in entries {
for cell in entry.cells {
append(&all_cell_images, cell.img)
}
entry.layer_cell_count = make([dynamic]i32, len(entry.cells), allocator)
}
num_entries := len(all_entries)
nodes := make([]stbrp.Node, num_entries)
rects := make([]stbrp.Rect, num_entries)
num_entries := len(all_cell_images)
nodes := make([]stbrp.Node, num_entries, allocator)
rects := make([]stbrp.Rect, num_entries, allocator)
EntryAndCell :: struct {
entry: ^AtlasEntry,
cell_of_entry: ^rl.Image,
cell_of_entry: ^CellData,
}
rect_idx_to_entry_and_cell: map[int]EntryAndCell
rect_idx_to_entry_and_cell := make(map[int]EntryAndCell, 100, allocator)
// Set the custom IDs
cellIdx: int
@ -109,13 +159,15 @@ pack_atlas_entries :: proc(entries: []AtlasEntry, atlas: ^rl.Image, offset_x: i3
map_insert(&rect_idx_to_entry_and_cell, cellIdx, EntryAndCell{&entry, &cell})
rects[cellIdx].id = auto_cast entryIdx
cellIdx += 1
entry.layer_cell_count[cell.layer_index] += 1
}
}
for entry, entryIdx in all_entries {
entry_stb_rect := &rects[entryIdx]
entry_stb_rect.w = stbrp.Coord(entry.width + offset_x)
entry_stb_rect.h = stbrp.Coord(entry.height + offset_y)
for cell_image, cell_index in all_cell_images {
entry_stb_rect := &rects[cell_index]
entry_stb_rect.w = stbrp.Coord(cell_image.width + offset_x)
entry_stb_rect.h = stbrp.Coord(cell_image.height + offset_y)
}
ctx: stbrp.Context
@ -131,22 +183,53 @@ pack_atlas_entries :: proc(entries: []AtlasEntry, atlas: ^rl.Image, offset_x: i3
for rect, rectIdx in rects {
entry_and_cell := rect_idx_to_entry_and_cell[auto_cast rectIdx]
cell := entry_and_cell.cell_of_entry
// We're grabbing the whole cell (the image itself)
src_rect := rl.Rectangle {
x = 0,
y = 0,
width = auto_cast cell.width,
height = auto_cast cell.height,
width = auto_cast cell.img.width,
height = auto_cast cell.img.height,
}
// Placing it in the atlas in the calculated offsets (in the packing step)
dst_rect := rl.Rectangle {
auto_cast rect.x + auto_cast offset_x,
auto_cast rect.y + auto_cast offset_y,
auto_cast cell.width,
auto_cast cell.height,
auto_cast cell.img.width,
auto_cast cell.img.height,
}
fmt.printfln("Src rect: {0}\nDst rect:{1}", src_rect, dst_rect)
rl.ImageDraw(atlas, cell^, src_rect, dst_rect, rl.WHITE)
// note(stefan): drawing the sprite in the atlas in the packed coordinates
rl.ImageDraw(atlas, cell.img, src_rect, dst_rect, rl.WHITE)
fmt.printfln("Src rect: {0}\nDst rect:{1}", src_rect, dst_rect)
}
metadata := make([dynamic]SpriteAtlasMetadata, allocator)
for rect, rectIdx in rects {
entry_and_cell := rect_idx_to_entry_and_cell[auto_cast rectIdx]
entry := entry_and_cell.entry
cell := entry_and_cell.cell_of_entry
cell_name : string
if entry.layer_cell_count[cell.layer_index] > 1 {
cell_name = fmt.aprintf(
"{0}_%d",
entry.layer_names[cell.layer_index],
cell.frame_index,
allocator,
)
} else {
cell_name = entry.layer_names[cell.layer_index]
}
cell_metadata := SpriteAtlasMetadata {
name = cell_name,
location = {
auto_cast rect.x + auto_cast offset_x,
auto_cast rect.y + auto_cast offset_y,
},
size = {auto_cast cell.img.width, auto_cast cell.img.height},
}
append(&metadata, cell_metadata)
}
return metadata
}

View file

@ -34,7 +34,6 @@ PackerSettings :: struct {
pixel_padding_x_int: i32,
pixel_padding_y_int: i32,
padding_enabled: bool,
fix_pixel_bleeding: bool,
output_json: bool,
output_odin: bool,
}

View file

@ -22,5 +22,7 @@ save_output :: proc() {
output_path := strings.concatenate({output_path, atlas_path})
cstring_output_path := strings.clone_to_cstring(output_path)
rl.ExportImage(image, cstring_output_path)
}
} else {
fmt.println("Output path is empty!")
}
}

View file

@ -13,7 +13,6 @@ game_update :: proc() -> bool {
game_init_window :: proc() {
rl.SetConfigFlags({.WINDOW_RESIZABLE})
rl.InitWindow(1400, 800, "YAAP - Yet Another Atlas Packer")
rl.SetWindowPosition(200, 200)
rl.SetWindowMinSize(1400, 800)
}