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": { "group": {
"kind": "build", "kind": "build",
"isDefault": true "isDefault": false
}, },
}, },
{ {
@ -92,7 +92,7 @@
}, },
"group": { "group": {
"kind": "build", "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 package generator
import ase "../aseprite" import ase "../aseprite"
import "core:encoding/json"
import "core:fmt" import "core:fmt"
import "core:mem" import "core:mem"
import "core:os" import "core:os"
import fp "core:path/filepath" import fp "core:path/filepath"
import "core:slice" import "core:slice"
import "core:strings" import s "core:strings"
import "core:testing" import "core:testing"
import rl "vendor:raylib" import rl "vendor:raylib"
import stbrp "vendor:stb/rect_pack"
import gen ".." import gen ".."
@ -24,14 +27,17 @@ main :: proc() {
} }
cwd := os.get_current_directory() 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: rl.Image = rl.GenImageColor(ATLAS_SIZE, ATLAS_SIZE, rl.BLANK)
atlas_entries: [dynamic]gen.AtlasEntry atlas_entries: [dynamic]gen.AtlasEntry
gen.unmarshall_aseprite_dir(target_dir, &atlas_entries) 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) 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 package game
import ase "./aseprite" import ase "./aseprite"
import "core:encoding/json"
import "core:fmt" import "core:fmt"
import "core:mem"
import "core:os" import "core:os"
import fp "core:path/filepath" import fp "core:path/filepath"
import "core:strings"
import rl "vendor:raylib" import rl "vendor:raylib"
import stbrp "vendor:stb/rect_pack" import stbrp "vendor:stb/rect_pack"
CellData :: struct {
layer_index: u16,
opacity: u8,
frame_index: i32,
img: rl.Image,
}
AtlasEntry :: struct { AtlasEntry :: struct {
path: string, path: string,
cells: [dynamic]rl.Image, 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) { unmarshall_aseprite_dir :: proc(path: string, atlas_entries: ^[dynamic]AtlasEntry) {
@ -27,14 +53,22 @@ unmarshall_aseprite_files_file_info :: proc(
files: []os.File_Info, files: []os.File_Info,
atlas_entries: ^[dynamic]AtlasEntry, atlas_entries: ^[dynamic]AtlasEntry,
) { ) {
paths: [dynamic]string if len(files) == 0 do return
for f in files {
append(&paths, f.fullpath) 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(paths[:], atlas_entries)
} }
unmarshall_aseprite_files :: proc(file_paths: []string, atlas_entries: ^[dynamic]AtlasEntry) { unmarshall_aseprite_files :: proc(file_paths: []string, atlas_entries: ^[dynamic]AtlasEntry) {
if len(file_paths) == 0 do return
aseprite_document: ase.Document aseprite_document: ase.Document
for file in file_paths { for file in file_paths {
extension := fp.ext(file) extension := fp.ext(file)
@ -43,6 +77,7 @@ unmarshall_aseprite_files :: proc(file_paths: []string, atlas_entries: ^[dynamic
fmt.println("Unmarshalling file: ", file) fmt.println("Unmarshalling file: ", file)
ase.unmarshal_from_filename(file, &aseprite_document) ase.unmarshal_from_filename(file, &aseprite_document)
atlas_entry := atlas_entry_from_compressed_cells(aseprite_document) atlas_entry := atlas_entry_from_compressed_cells(aseprite_document)
atlas_entry.path = file
append(atlas_entries, atlas_entry) 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 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) { 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 { for chunk in frame.chunks {
cel_chunk, cok := chunk.(ase.Cel_Chunk) if cel_chunk, ok := chunk.(ase.Cel_Chunk); ok {
if !cok { cel_img, ci_ok := cel_chunk.cel.(ase.Com_Image_Cel)
continue if !ci_ok do continue
}
cel_img, ci_ok := cel_chunk.cel.(ase.Com_Image_Cel) fmt.println(cel_chunk.layer_index)
if !ci_ok {
continue 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 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) 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) { pack_atlas_entries :: proc(
assert(atlas.width != 0, "This shouldn't be 0!") entries: []AtlasEntry,
assert(atlas.height != 0, "This shouldn't be 0!") 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 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 entry in entries { for cell in entry.cells {
append(&all_entries, ..entry.cells[:]) append(&all_cell_images, cell.img)
} }
entry.layer_cell_count = make([dynamic]i32, len(entry.cells), allocator)
} }
num_entries := len(all_entries) num_entries := len(all_cell_images)
nodes := make([]stbrp.Node, num_entries) nodes := make([]stbrp.Node, num_entries, allocator)
rects := make([]stbrp.Rect, num_entries) rects := make([]stbrp.Rect, num_entries, allocator)
EntryAndCell :: struct { EntryAndCell :: struct {
entry: ^AtlasEntry, 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 // Set the custom IDs
cellIdx: int 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}) map_insert(&rect_idx_to_entry_and_cell, cellIdx, EntryAndCell{&entry, &cell})
rects[cellIdx].id = auto_cast entryIdx rects[cellIdx].id = auto_cast entryIdx
cellIdx += 1 cellIdx += 1
entry.layer_cell_count[cell.layer_index] += 1
} }
} }
for entry, entryIdx in all_entries { for cell_image, cell_index in all_cell_images {
entry_stb_rect := &rects[entryIdx] entry_stb_rect := &rects[cell_index]
entry_stb_rect.w = stbrp.Coord(entry.width + offset_x) entry_stb_rect.w = stbrp.Coord(cell_image.width + offset_x)
entry_stb_rect.h = stbrp.Coord(entry.height + offset_y) entry_stb_rect.h = stbrp.Coord(cell_image.height + offset_y)
} }
ctx: stbrp.Context ctx: stbrp.Context
@ -131,22 +183,53 @@ pack_atlas_entries :: proc(entries: []AtlasEntry, atlas: ^rl.Image, offset_x: i3
for rect, rectIdx in rects { for rect, rectIdx in rects {
entry_and_cell := rect_idx_to_entry_and_cell[auto_cast rectIdx] entry_and_cell := rect_idx_to_entry_and_cell[auto_cast rectIdx]
cell := entry_and_cell.cell_of_entry cell := entry_and_cell.cell_of_entry
// We're grabbing the whole cell (the image itself)
src_rect := rl.Rectangle { src_rect := rl.Rectangle {
x = 0, x = 0,
y = 0, y = 0,
width = auto_cast cell.width, width = auto_cast cell.img.width,
height = auto_cast cell.height, height = auto_cast cell.img.height,
} }
// Placing it in the atlas in the calculated offsets (in the packing step)
dst_rect := rl.Rectangle { dst_rect := rl.Rectangle {
auto_cast rect.x + auto_cast offset_x, auto_cast rect.x + auto_cast offset_x,
auto_cast rect.y + auto_cast offset_y, auto_cast rect.y + auto_cast offset_y,
auto_cast cell.width, auto_cast cell.img.width,
auto_cast cell.height, 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_x_int: i32,
pixel_padding_y_int: i32, pixel_padding_y_int: i32,
padding_enabled: bool, padding_enabled: bool,
fix_pixel_bleeding: bool,
output_json: bool, output_json: bool,
output_odin: bool, output_odin: bool,
} }

View file

@ -22,5 +22,7 @@ save_output :: proc() {
output_path := strings.concatenate({output_path, atlas_path}) output_path := strings.concatenate({output_path, atlas_path})
cstring_output_path := strings.clone_to_cstring(output_path) cstring_output_path := strings.clone_to_cstring(output_path)
rl.ExportImage(image, 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() { game_init_window :: proc() {
rl.SetConfigFlags({.WINDOW_RESIZABLE}) rl.SetConfigFlags({.WINDOW_RESIZABLE})
rl.InitWindow(1400, 800, "YAAP - Yet Another Atlas Packer") rl.InitWindow(1400, 800, "YAAP - Yet Another Atlas Packer")
rl.SetWindowPosition(200, 200)
rl.SetWindowMinSize(1400, 800) rl.SetWindowMinSize(1400, 800)
} }