diff --git a/Cargo.lock b/Cargo.lock index 95ed3eb..f5b62b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -885,6 +885,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "egui_json_tree" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c549dbaae5f70f0d4a9ec068617d24051a4e74f8611298e484bf76a09f06d0" +dependencies = [ + "egui", + "serde_json", +] + [[package]] name = "ehttp" version = "0.3.1" @@ -1423,6 +1433,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + [[package]] name = "jni" version = "0.21.1" @@ -2173,11 +2189,13 @@ dependencies = [ "eframe", "egui", "egui_extras", + "egui_json_tree", "env_logger", "image", "js-sys", "log", "serde", + "serde_json", "texture_packer", "wasm-bindgen", "wasm-bindgen-futures", @@ -2220,6 +2238,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + [[package]] name = "same-file" version = "1.0.6" @@ -2253,24 +2277,35 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.163" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", "syn 2.0.39", ] +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_repr" version = "0.1.12" diff --git a/Cargo.toml b/Cargo.toml index 5383df5..6a46dfd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,8 @@ eframe = { version = "0.24.1", default-features = false, features = [ "persistence", # Enable restoring app state when restarting the app. ] } log = "0.4" +serde_json = "1" +egui_json_tree = "0.2" # You only need serde if you want app persistence: serde = { version = "1", features = ["derive"] } diff --git a/src/app.rs b/src/app.rs index 4966fb0..fb31b2c 100644 --- a/src/app.rs +++ b/src/app.rs @@ -3,9 +3,8 @@ use std::{collections::HashMap, io::Cursor}; use egui::{CollapsingHeader, Color32, DroppedFile, FontFamily, FontId, Image, RichText}; use image::DynamicImage; -use texture_packer::{ - importer::ImageImporter, texture::Texture, TexturePacker, TexturePackerConfig, -}; +use serde_json::Value; +use texture_packer::{importer::ImageImporter, TexturePacker, TexturePackerConfig}; pub const MY_ACCENT_COLOR32: Color32 = Color32::from_rgb(230, 102, 1); pub const TOP_SIDE_MARGIN: f32 = 10.0; pub const HEADER_HEIGHT: f32 = 45.0; @@ -16,9 +15,77 @@ pub const GIT_HASH: &str = env!("GIT_HASH"); pub struct Spritesheet { pub data: Vec, pub frames: HashMap>, + pub frames_json: Value, pub size: (u32, u32), } +/// Boundaries and properties of a packed texture. +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct SerializableFrame { + /// Key used to uniquely identify this frame. + pub key: String, + /// Rectangle describing the texture coordinates and size. + pub frame: SerializableRect, + /// True if the texture was rotated during packing. + /// If it was rotated, it was rotated 90 degrees clockwise. + pub rotated: bool, + /// True if the texture was trimmed during packing. + pub trimmed: bool, + + // (x, y) is the trimmed frame position at original image + // (w, h) is original image size + // + // w + // +--------------+ + // | (x, y) | + // | ^ | + // | | | + // | ********* | + // | * * | h + // | * * | + // | ********* | + // | | + // +--------------+ + /// Source texture size before any trimming. + pub source: SerializableRect, +} + +impl From> for SerializableFrame { + fn from(value: texture_packer::Frame) -> Self { + SerializableFrame { + key: value.key, + frame: value.frame.into(), + rotated: value.rotated, + trimmed: value.trimmed, + source: value.source.into(), + } + } +} + +/// Defines a rectangle in pixels with the origin at the top-left of the texture atlas. +#[derive(Copy, Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct SerializableRect { + /// Horizontal position the rectangle begins at. + pub x: u32, + /// Vertical position the rectangle begins at. + pub y: u32, + /// Width of the rectangle. + pub w: u32, + /// Height of the rectangle. + pub h: u32, +} + +impl From for SerializableRect { + fn from(value: texture_packer::Rect) -> Self { + SerializableRect { + h: value.h, + w: value.w, + x: value.x, + y: value.y, + } + } +} + /// We derive Deserialize/Serialize so we can persist app state on shutdown. #[derive(serde::Deserialize, serde::Serialize)] #[serde(default)] // if we add new fields, give them default values when deserializing old state @@ -105,15 +172,25 @@ impl TemplateApp { println!(" {:7} : {:?}", name, frame.frame); } let mut out_vec = vec![]; - let exporter = texture_packer::exporter::ImageExporter::export(&packer).unwrap(); - exporter - .write_to(&mut Cursor::new(&mut out_vec), image::ImageFormat::Png) + let exported_image = texture_packer::exporter::ImageExporter::export(&packer).unwrap(); + let mut img = image::DynamicImage::new_rgba8(self.config.max_width, self.config.max_height); + image::imageops::overlay(&mut img, &exported_image, 0, 0); + + img.write_to(&mut Cursor::new(&mut out_vec), image::ImageFormat::Png) .unwrap(); + let frames_info: Vec = packer + .get_frames() + .values() + .map(|v| -> SerializableFrame { (*v).clone().into() }) + .collect(); + let frames_string = serde_json::to_string_pretty(&frames_info).unwrap(); + let frames_json = serde_json::from_str(&frames_string).unwrap(); self.data = Some(Spritesheet { data: out_vec.clone(), frames: packer.get_frames().clone(), - size: (packer.width(), packer.height()), + size: (img.width(), img.height()), + frames_json, }); let id = format!("bytes://output_{}.png", self.counter); self.image = None; @@ -300,19 +377,22 @@ impl eframe::App for TemplateApp { }); ui.with_layout(egui::Layout::top_down_justified(egui::Align::Min), |ui|{ + egui::ScrollArea::vertical().auto_shrink(false).show(ui, |ui| { if let Some(image) = &self.image { ui.horizontal_top(|ui|{ let data = &self.data.clone().unwrap(); ui.label(format!("{} frames, size: {}x{}",data.frames.len(),data.size.0,data.size.1)); }); - CollapsingHeader::new("Preview") - .default_open(true) - .show(ui, |ui| { - ui.add(image.clone()); + CollapsingHeader::new("Preview") + .default_open(true) + .show(ui, |ui| { + ui.label(RichText::new("Frames JSON").strong()); + egui_json_tree::JsonTree::new("simple-tree", &self.data.clone().unwrap().frames_json).show(ui); + ui.label(RichText::new("Spritesheet").strong()); + ui.add(image.clone()); }); } ui.separator(); - egui::ScrollArea::vertical().auto_shrink(false).show(ui, |ui| { let mut index_to_remove : Option = None; for (i, file) in self.dropped_files.iter().enumerate() { let mut info = if let Some(path) = &file.path {