Overhaul of the program
|
|
@ -31,17 +31,21 @@ jobs:
|
||||||
- name: Rust Cache # cache the rust build artefacts
|
- name: Rust Cache # cache the rust build artefacts
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
- name: Download and install Trunk binary
|
- name: Download and install Trunk binary
|
||||||
|
working-directory: crates/rpack
|
||||||
run: wget -qO- https://github.com/thedodd/trunk/releases/latest/download/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf-
|
run: wget -qO- https://github.com/thedodd/trunk/releases/latest/download/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf-
|
||||||
- name: Update file
|
- name: Update file
|
||||||
|
working-directory: crates/rpack
|
||||||
run: sed -i '15d' index.html
|
run: sed -i '15d' index.html
|
||||||
- name: Build
|
- name: Build
|
||||||
|
working-directory: crates/rpack
|
||||||
run: ./trunk build --release
|
run: ./trunk build --release
|
||||||
- name: Update result file
|
- name: Update result file
|
||||||
|
working-directory: crates/rpack
|
||||||
run: sed -i 's|/rpack|./rpack|g' dist/index.html
|
run: sed -i 's|/rpack|./rpack|g' dist/index.html
|
||||||
- name: Itch.io - Publish
|
- name: Itch.io - Publish
|
||||||
uses: KikimoraGames/itch-publish@v0.0.3
|
uses: KikimoraGames/itch-publish@v0.0.3
|
||||||
with:
|
with:
|
||||||
gameData: ./dist
|
gameData: ./crates/rpack/dist
|
||||||
itchUsername: mevlyshkin
|
itchUsername: mevlyshkin
|
||||||
itchGameId: rpack
|
itchGameId: rpack
|
||||||
buildChannel: wasm
|
buildChannel: wasm
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,10 @@ jobs:
|
||||||
- name: Rust Cache # cache the rust build artefacts
|
- name: Rust Cache # cache the rust build artefacts
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@v2
|
||||||
- name: Download and install Trunk binary
|
- name: Download and install Trunk binary
|
||||||
|
working-directory: crates/rpack
|
||||||
run: wget -qO- https://github.com/thedodd/trunk/releases/latest/download/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf-
|
run: wget -qO- https://github.com/thedodd/trunk/releases/latest/download/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf-
|
||||||
- name: Build # build
|
- name: Build # build
|
||||||
|
working-directory: crates/rpack
|
||||||
# "${GITHUB_REPOSITORY#*/}" evaluates into the name of the repository
|
# "${GITHUB_REPOSITORY#*/}" evaluates into the name of the repository
|
||||||
# using --public-url something will allow trunk to modify all the href paths like from favicon.ico to repo_name/favicon.ico .
|
# using --public-url something will allow trunk to modify all the href paths like from favicon.ico to repo_name/favicon.ico .
|
||||||
# this is necessary for github pages where the site is deployed to username.github.io/repo_name and all files must be requested
|
# this is necessary for github pages where the site is deployed to username.github.io/repo_name and all files must be requested
|
||||||
|
|
@ -42,7 +44,7 @@ jobs:
|
||||||
- name: Deploy
|
- name: Deploy
|
||||||
uses: JamesIves/github-pages-deploy-action@v4
|
uses: JamesIves/github-pages-deploy-action@v4
|
||||||
with:
|
with:
|
||||||
folder: dist
|
folder: crates/rpack/dist
|
||||||
# this option will not maintain any history of your previous pages deployment
|
# this option will not maintain any history of your previous pages deployment
|
||||||
# set to false if you want all page build to be committed to your gh-pages branch history
|
# set to false if you want all page build to be committed to your gh-pages branch history
|
||||||
single-commit: true
|
single-commit: true
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,8 @@ jobs:
|
||||||
target: wasm32-unknown-unknown
|
target: wasm32-unknown-unknown
|
||||||
override: true
|
override: true
|
||||||
- name: Download and install Trunk binary
|
- name: Download and install Trunk binary
|
||||||
|
working-directory: crates/rpack
|
||||||
run: wget -qO- https://github.com/thedodd/trunk/releases/latest/download/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf-
|
run: wget -qO- https://github.com/thedodd/trunk/releases/latest/download/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf-
|
||||||
- name: Build
|
- name: Build
|
||||||
|
working-directory: crates/rpack
|
||||||
run: ./trunk build
|
run: ./trunk build
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
/target
|
/target
|
||||||
|
/crates/*/target
|
||||||
|
/crates/rpack/dist
|
||||||
/dist
|
/dist
|
||||||
skyline-packer-output.png
|
skyline-packer-output.png
|
||||||
result.png
|
result.png
|
||||||
|
|
|
||||||
70
Cargo.toml
|
|
@ -1,64 +1,18 @@
|
||||||
[package]
|
[workspace]
|
||||||
name = "rpack"
|
resolver = "2"
|
||||||
version = "0.1.0"
|
members = [
|
||||||
authors = ["Piotr Siuszko <siuszko@zoho.com>"]
|
"crates/bevy_rpack",
|
||||||
edition = "2021"
|
"crates/rpack",
|
||||||
rust-version = "1.81"
|
"crates/rpack_cli",
|
||||||
repository = "https://github.com/Leinnan/rpack.git"
|
]
|
||||||
homepage = "https://github.com/Leinnan/rpack"
|
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
egui = "0.30"
|
|
||||||
eframe = { version = "0.30", default-features = false, features = [
|
|
||||||
"accesskit", # Make egui comptaible with screen readers. NOTE: adds a lot of dependencies.
|
|
||||||
"default_fonts", # Embed the default egui fonts.
|
|
||||||
"glow", # Use the glow rendering backend. Alternative: "wgpu".
|
|
||||||
"persistence", # Enable restoring app state when restarting the app.
|
|
||||||
] }
|
|
||||||
log = "0.4"
|
|
||||||
serde_json = "1"
|
|
||||||
egui_json_tree = "0.10"
|
|
||||||
|
|
||||||
# You only need serde if you want app persistence:
|
|
||||||
serde = { version = "1", features = ["derive"] }
|
|
||||||
texture_packer = { version = "0.27.0", features = ["common"] }
|
|
||||||
image = { version = "0.24", features = ["jpeg", "png"] }
|
|
||||||
egui_extras = { version = "*", features = ["all_loaders"] }
|
|
||||||
rfd = { version = "0.15", features = [] }
|
|
||||||
wasm-bindgen-futures = "0.4"
|
|
||||||
|
|
||||||
# native:
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
|
||||||
env_logger = "0.11"
|
|
||||||
|
|
||||||
# web:
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
|
||||||
wasm-bindgen-futures = "0.4"
|
|
||||||
wasm-bindgen = "0.2"
|
|
||||||
web-sys = { version = "0.3", features = [
|
|
||||||
"Url",
|
|
||||||
"HtmlAnchorElement",
|
|
||||||
"Blob",
|
|
||||||
"BlobPropertyBag",
|
|
||||||
] }
|
|
||||||
js-sys = "0.3"
|
|
||||||
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = 2 # fast and small wasm
|
opt-level = 'z'
|
||||||
|
panic = 'abort'
|
||||||
|
lto = true
|
||||||
|
strip = true
|
||||||
|
|
||||||
|
|
||||||
# Optimize all dependencies even in debug builds:
|
# Optimize all dependencies even in debug builds:
|
||||||
[profile.dev.package."*"]
|
[profile.dev.package."*"]
|
||||||
opt-level = 2
|
opt-level = 2
|
||||||
|
|
||||||
|
|
||||||
[patch.crates-io]
|
|
||||||
|
|
||||||
# If you want to use the bleeding edge version of egui and eframe:
|
|
||||||
# egui = { git = "https://github.com/emilk/egui", branch = "master" }
|
|
||||||
# eframe = { git = "https://github.com/emilk/egui", branch = "master" }
|
|
||||||
|
|
||||||
# If you fork https://github.com/emilk/egui you can test with:
|
|
||||||
# egui = { path = "../egui/crates/egui" }
|
|
||||||
# eframe = { path = "../egui/crates/eframe" }
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
[package]
|
||||||
|
name = "bevy_rpack"
|
||||||
|
description = "Bevy plugin with rpack atlas support"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
repository = "https://github.com/Leinnan/rpack"
|
||||||
|
authors = ["Piotr Siuszko <siuszko@zoho.com>"]
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
keywords = ["bevy", "2d", "plugin"]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["bevy"]
|
||||||
|
bevy = ["dep:bevy"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bevy = { version = "0.15", optional = true, default-features = false, features = [
|
||||||
|
"bevy_asset",
|
||||||
|
"bevy_sprite",
|
||||||
|
"bevy_image",
|
||||||
|
] }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
thiserror = "2.0"
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
use crate::{AtlasAsset, SerializableRect};
|
||||||
|
use bevy::asset::{AssetLoader, AsyncReadExt};
|
||||||
|
use bevy::image::ImageSampler;
|
||||||
|
use bevy::{prelude::*, utils::HashMap};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// This is an asset containing the texture atlas image, the texture atlas layout, and a map of the original file names to their corresponding indices in the texture atlas.
|
||||||
|
#[derive(Asset, Debug, Reflect)]
|
||||||
|
pub struct RpackAtlasAsset {
|
||||||
|
/// The texture atlas image.
|
||||||
|
pub image: Handle<Image>,
|
||||||
|
/// The texture atlas layout.
|
||||||
|
pub atlas: Handle<TextureAtlasLayout>,
|
||||||
|
/// The map of the original file names to indices of the texture atlas.
|
||||||
|
pub files: HashMap<String, usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SerializableRect> for URect {
|
||||||
|
fn from(val: SerializableRect) -> Self {
|
||||||
|
URect {
|
||||||
|
min: UVec2 { x: val.x, y: val.y },
|
||||||
|
max: UVec2 {
|
||||||
|
x: val.x + val.w,
|
||||||
|
y: val.y + val.h,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RpackAtlasAsset {
|
||||||
|
// When atlas contains the given key returns a copy of TextureAtlas and Image
|
||||||
|
pub fn get_atlas_data<T: AsRef<str>>(&self, key: T) -> Option<(TextureAtlas, Handle<Image>)> {
|
||||||
|
self.files.get(key.as_ref()).map(|s| {
|
||||||
|
(
|
||||||
|
TextureAtlas {
|
||||||
|
index: *s,
|
||||||
|
layout: self.atlas.clone(),
|
||||||
|
},
|
||||||
|
self.image.clone(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// When atlas contains the given key creates a Sprite component
|
||||||
|
pub fn make_sprite<T: AsRef<str>>(&self, key: T) -> Option<Sprite> {
|
||||||
|
if let Some((atlas, image)) = self.get_atlas_data(key) {
|
||||||
|
Some(Sprite {
|
||||||
|
image,
|
||||||
|
texture_atlas: Some(atlas),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RpackAssetPlugin;
|
||||||
|
|
||||||
|
impl Plugin for RpackAssetPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.register_type::<RpackAtlasAsset>();
|
||||||
|
app.init_asset::<RpackAtlasAsset>();
|
||||||
|
app.init_asset_loader::<RpackAtlasAssetLoader>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum RpackAtlasAssetError {
|
||||||
|
/// An [IO](std::io) Error that occured
|
||||||
|
/// during parsing of a `.rpack.json` file.
|
||||||
|
#[error("could not load asset: {0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
#[error("could not parse asset: {0}")]
|
||||||
|
ParsinError(#[from] serde_json::Error),
|
||||||
|
/// A Bevy [`LoadDirectError`](bevy::asset::LoadDirectError) that occured
|
||||||
|
/// while loading a [`RpackAtlasAsset::image`](crate::RpackAtlasAsset::image).
|
||||||
|
#[error("could not load asset: {0}")]
|
||||||
|
LoadDirect(Box<bevy::asset::LoadDirectError>),
|
||||||
|
/// An error that can occur if there is
|
||||||
|
/// trouble loading the image asset of
|
||||||
|
/// an atlas.
|
||||||
|
#[error("missing image asset: {0}")]
|
||||||
|
LoadingImageAsset(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bevy::asset::LoadDirectError> for RpackAtlasAssetError {
|
||||||
|
fn from(value: bevy::asset::LoadDirectError) -> Self {
|
||||||
|
Self::LoadDirect(Box::new(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct RpackAtlasAssetLoader;
|
||||||
|
|
||||||
|
impl AssetLoader for RpackAtlasAssetLoader {
|
||||||
|
type Asset = RpackAtlasAsset;
|
||||||
|
type Settings = ();
|
||||||
|
type Error = RpackAtlasAssetError;
|
||||||
|
|
||||||
|
fn extensions(&self) -> &[&str] {
|
||||||
|
&["rpack.json"]
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn load(
|
||||||
|
&self,
|
||||||
|
reader: &mut dyn bevy::asset::io::Reader,
|
||||||
|
_settings: &(),
|
||||||
|
load_context: &mut bevy::asset::LoadContext<'_>,
|
||||||
|
) -> Result<Self::Asset, Self::Error> {
|
||||||
|
let mut file = String::new();
|
||||||
|
reader.read_to_string(&mut file).await?;
|
||||||
|
let asset: AtlasAsset = serde_json::from_str(&file)?;
|
||||||
|
|
||||||
|
let path = load_context
|
||||||
|
.asset_path()
|
||||||
|
.path()
|
||||||
|
.parent()
|
||||||
|
.unwrap_or(&std::path::Path::new(""))
|
||||||
|
.join(asset.name);
|
||||||
|
|
||||||
|
let mut image: Image = load_context
|
||||||
|
.loader()
|
||||||
|
.immediate()
|
||||||
|
.with_unknown_type()
|
||||||
|
.load(path)
|
||||||
|
.await?
|
||||||
|
.take()
|
||||||
|
.ok_or(RpackAtlasAssetError::LoadingImageAsset(
|
||||||
|
"failed to load image asset, does it exist".to_string(),
|
||||||
|
))?;
|
||||||
|
image.sampler = ImageSampler::nearest();
|
||||||
|
|
||||||
|
let mut layout = TextureAtlasLayout::new_empty(UVec2::new(asset.size[0], asset.size[1]));
|
||||||
|
let mut files = HashMap::new();
|
||||||
|
|
||||||
|
for frame in asset.frames {
|
||||||
|
let id = layout.add_texture(frame.frame.into());
|
||||||
|
files.insert(frame.key, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
let atlas = load_context.add_labeled_asset("atlas_layout".into(), layout);
|
||||||
|
let image = load_context.add_labeled_asset("atlas_texture".into(), image);
|
||||||
|
|
||||||
|
Ok(RpackAtlasAsset {
|
||||||
|
image,
|
||||||
|
atlas,
|
||||||
|
files,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
#[cfg(feature = "bevy")]
|
||||||
|
pub mod bevy;
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct AtlasFrame {
|
||||||
|
pub key: String,
|
||||||
|
pub frame: SerializableRect,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct AtlasAsset {
|
||||||
|
pub size: [u32; 2],
|
||||||
|
pub name: String,
|
||||||
|
pub frames: Vec<AtlasFrame>,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
[package]
|
||||||
|
name = "rpack"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Piotr Siuszko <siuszko@zoho.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.81"
|
||||||
|
repository = "https://github.com/Leinnan/rpack.git"
|
||||||
|
homepage = "https://github.com/Leinnan/rpack"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
egui = "0.30"
|
||||||
|
eframe = { version = "0.30", default-features = false, features = [
|
||||||
|
"accesskit", # Make egui comptaible with screen readers. NOTE: adds a lot of dependencies.
|
||||||
|
"default_fonts", # Embed the default egui fonts.
|
||||||
|
"glow", # Use the glow rendering backend. Alternative: "wgpu".
|
||||||
|
"persistence", # Enable restoring app state when restarting the app.
|
||||||
|
] }
|
||||||
|
log = "0.4"
|
||||||
|
egui_json_tree = "0.10"
|
||||||
|
rpack_cli = { default-features = false, path = "../rpack_cli" }
|
||||||
|
|
||||||
|
# You only need serde if you want app persistence:
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
texture_packer = { version = "0.29", features = ["common"] }
|
||||||
|
image = { version = "0.25", features = ["jpeg", "png"] }
|
||||||
|
egui_extras = { version = "*", features = ["all_loaders"] }
|
||||||
|
rfd = { version = "0.15", features = [] }
|
||||||
|
wasm-bindgen-futures = "0.4"
|
||||||
|
|
||||||
|
# native:
|
||||||
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
|
env_logger = "0.11"
|
||||||
|
|
||||||
|
# web:
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
wasm-bindgen-futures = "0.4"
|
||||||
|
wasm-bindgen = "0.2"
|
||||||
|
web-sys = { version = "0.3", features = [
|
||||||
|
"Url",
|
||||||
|
"HtmlAnchorElement",
|
||||||
|
"Blob",
|
||||||
|
"BlobPropertyBag",
|
||||||
|
] }
|
||||||
|
js-sys = "0.3"
|
||||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 314 KiB After Width: | Height: | Size: 314 KiB |
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
|
|
@ -1,103 +1,13 @@
|
||||||
use std::{collections::HashMap, io::Cursor};
|
|
||||||
|
|
||||||
use egui::{CollapsingHeader, Color32, DroppedFile, FontFamily, FontId, Image, RichText, Vec2};
|
use egui::{CollapsingHeader, Color32, DroppedFile, FontFamily, FontId, Image, RichText, Vec2};
|
||||||
use image::DynamicImage;
|
use image::DynamicImage;
|
||||||
use serde_json::Value;
|
use rpack_cli::{ImageFile, Spritesheet};
|
||||||
use texture_packer::{importer::ImageImporter, TexturePacker, TexturePackerConfig};
|
use texture_packer::{importer::ImageImporter, TexturePackerConfig};
|
||||||
pub const MY_ACCENT_COLOR32: Color32 = Color32::from_rgb(230, 102, 1);
|
pub const MY_ACCENT_COLOR32: Color32 = Color32::from_rgb(230, 102, 1);
|
||||||
pub const TOP_SIDE_MARGIN: f32 = 10.0;
|
pub const TOP_SIDE_MARGIN: f32 = 10.0;
|
||||||
pub const HEADER_HEIGHT: f32 = 45.0;
|
pub const HEADER_HEIGHT: f32 = 45.0;
|
||||||
pub const TOP_BUTTON_WIDTH: f32 = 150.0;
|
pub const TOP_BUTTON_WIDTH: f32 = 150.0;
|
||||||
pub const GIT_HASH: &str = env!("GIT_HASH");
|
pub const GIT_HASH: &str = env!("GIT_HASH");
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
|
||||||
pub struct AtlasFrame {
|
|
||||||
pub key: String,
|
|
||||||
pub frame: SerializableRect,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
|
|
||||||
pub struct AtlasAsset {
|
|
||||||
pub size: [u32; 2],
|
|
||||||
pub name: String,
|
|
||||||
pub frames: Vec<AtlasFrame>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Spritesheet {
|
|
||||||
pub data: Vec<u8>,
|
|
||||||
pub frames: HashMap<String, texture_packer::Frame<String>>,
|
|
||||||
pub atlas_asset_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<texture_packer::Frame<String>> for SerializableFrame {
|
|
||||||
fn from(value: texture_packer::Frame<String>) -> 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<texture_packer::Rect> 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.
|
/// We derive Deserialize/Serialize so we can persist app state on shutdown.
|
||||||
#[derive(serde::Deserialize, serde::Serialize)]
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
#[serde(default)] // if we add new fields, give them default values when deserializing old state
|
#[serde(default)] // if we add new fields, give them default values when deserializing old state
|
||||||
|
|
@ -106,15 +16,14 @@ pub struct TemplateApp {
|
||||||
dropped_files: Vec<DroppedFile>,
|
dropped_files: Vec<DroppedFile>,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
config: TexturePackerConfig,
|
config: TexturePackerConfig,
|
||||||
|
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
image: Option<Image<'static>>,
|
image: Option<Image<'static>>,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
|
name: String,
|
||||||
|
#[serde(skip)]
|
||||||
counter: i32,
|
counter: i32,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
data: Option<Spritesheet>,
|
data: Option<Result<Spritesheet, String>>,
|
||||||
#[serde(skip)]
|
|
||||||
error: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TemplateApp {
|
impl Default for TemplateApp {
|
||||||
|
|
@ -132,7 +41,7 @@ impl Default for TemplateApp {
|
||||||
counter: 0,
|
counter: 0,
|
||||||
image: None,
|
image: None,
|
||||||
data: None,
|
data: None,
|
||||||
error: None,
|
name: String::from("Tilemap"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -171,103 +80,78 @@ impl TemplateApp {
|
||||||
|
|
||||||
prefix
|
prefix
|
||||||
}
|
}
|
||||||
|
pub fn image_from_dropped_file<P>(file: &DroppedFile, prefix: P) -> Option<ImageFile>
|
||||||
|
where
|
||||||
|
P: AsRef<str>,
|
||||||
|
{
|
||||||
|
let id;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
{
|
||||||
|
let path = file.path.as_ref().unwrap().clone();
|
||||||
|
id = path.to_str().unwrap().to_owned();
|
||||||
|
}
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
{
|
||||||
|
id = file.name.clone();
|
||||||
|
}
|
||||||
|
let base_id = id.replace(".png", "");
|
||||||
|
|
||||||
|
let id = base_id
|
||||||
|
.strip_prefix(prefix.as_ref())
|
||||||
|
.unwrap_or(&base_id)
|
||||||
|
.to_owned()
|
||||||
|
.replace("\\", "/");
|
||||||
|
|
||||||
|
let Some(image) = dynamic_image_from_file(file) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
Some(ImageFile { id, image })
|
||||||
|
}
|
||||||
|
|
||||||
fn build_atlas(&mut self, ctx: &egui::Context) {
|
fn build_atlas(&mut self, ctx: &egui::Context) {
|
||||||
self.error = None;
|
|
||||||
let mut packer = TexturePacker::new_skyline(self.config);
|
|
||||||
let prefix = Self::get_common_prefix(&self.dropped_files);
|
let prefix = Self::get_common_prefix(&self.dropped_files);
|
||||||
println!("Prefix: {}", prefix);
|
println!("Prefix: {}", prefix);
|
||||||
for file in &self.dropped_files {
|
let images: Vec<ImageFile> = self
|
||||||
let base_id = file_path(file);
|
.dropped_files
|
||||||
let id = base_id
|
.iter()
|
||||||
.strip_prefix(&prefix)
|
.flat_map(|f| Self::image_from_dropped_file(f, &prefix))
|
||||||
.unwrap_or(&base_id)
|
.collect();
|
||||||
.to_owned()
|
|
||||||
.replace("\\", "/");
|
|
||||||
println!("Base id: {}, ID: {}", &base_id, &id);
|
|
||||||
let texture = dynamic_image_from_file(file);
|
|
||||||
let can_pack = packer.can_pack(&texture);
|
|
||||||
|
|
||||||
if can_pack {
|
self.data = Some(Spritesheet::build(
|
||||||
packer.pack_own(id, texture).unwrap();
|
self.config,
|
||||||
} else {
|
&images,
|
||||||
self.error = Some(format!(
|
"name".to_owned(),
|
||||||
"Consider making atlas bigger. Could not make atlas, failed on: {}",
|
));
|
||||||
id
|
if let Some(Ok(data)) = &self.data {
|
||||||
));
|
ctx.include_bytes("bytes://output.png", data.image_data.clone());
|
||||||
return;
|
self.image =
|
||||||
}
|
Some(Image::from_uri("bytes://output.png").max_size(Vec2::new(256.0, 256.0)));
|
||||||
}
|
}
|
||||||
for (name, frame) in packer.get_frames() {
|
|
||||||
println!(" {:7} : {:?}", name, frame.frame);
|
|
||||||
}
|
|
||||||
let mut out_vec = vec![];
|
|
||||||
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 atlas = AtlasAsset {
|
|
||||||
size: [img.width(), img.height()],
|
|
||||||
name: "Atlas".to_owned(),
|
|
||||||
frames: packer
|
|
||||||
.get_frames()
|
|
||||||
.values()
|
|
||||||
.map(|v| -> AtlasFrame {
|
|
||||||
AtlasFrame {
|
|
||||||
key: v.key.clone(),
|
|
||||||
frame: SerializableRect {
|
|
||||||
x: v.frame.x,
|
|
||||||
y: v.frame.y,
|
|
||||||
w: v.frame.w,
|
|
||||||
h: v.frame.h,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
};
|
|
||||||
let frames_string = serde_json::to_string_pretty(&atlas).unwrap();
|
|
||||||
|
|
||||||
let atlas_asset_json = serde_json::from_str(&frames_string).unwrap();
|
|
||||||
self.data = Some(Spritesheet {
|
|
||||||
data: out_vec.clone(),
|
|
||||||
frames: packer.get_frames().clone(),
|
|
||||||
size: (img.width(), img.height()),
|
|
||||||
atlas_asset_json,
|
|
||||||
});
|
|
||||||
let id = format!("bytes://output_{}.png", self.counter);
|
|
||||||
self.image = None;
|
|
||||||
ctx.forget_image(&id);
|
|
||||||
self.counter += 1;
|
|
||||||
|
|
||||||
let id = format!("bytes://output_{}.png", self.counter);
|
|
||||||
ctx.include_bytes(id.clone(), out_vec.clone());
|
|
||||||
self.image = Some(Image::from_uri(id.clone()).max_size(Vec2::new(256.0, 256.0)));
|
|
||||||
ctx.request_repaint();
|
ctx.request_repaint();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn save_atlas(&mut self) {
|
fn save_atlas(&mut self) {
|
||||||
if self.data.is_none() {
|
let Some(Ok(data)) = &self.data else {
|
||||||
return;
|
return;
|
||||||
}
|
};
|
||||||
|
let data = data.image_data.clone();
|
||||||
|
let filename = format!("{}.png", self.name);
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
{
|
{
|
||||||
let data = self.data.clone().unwrap().data;
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
let path_buf = rfd::FileDialog::new()
|
let path_buf = rfd::FileDialog::new()
|
||||||
.set_directory(".")
|
.set_directory(".")
|
||||||
.add_filter("Image", &["png"])
|
.add_filter("Image", &["png"])
|
||||||
.set_file_name("output.png")
|
.set_file_name(filename)
|
||||||
.save_file();
|
.save_file();
|
||||||
if let Some(path) = path_buf {
|
if let Some(path) = path_buf {
|
||||||
let mut file = std::fs::File::create(path).unwrap();
|
let mut file = std::fs::File::create(path).unwrap();
|
||||||
let write_result = file.write_all(&data);
|
let write_result = file.write_all(&data);
|
||||||
if write_result.is_err() {
|
if write_result.is_err() {
|
||||||
self.error = Some(format!(
|
self.data = Some(Err(format!(
|
||||||
"Could not make atlas, error: {:?}",
|
"Could not make atlas, error: {:?}",
|
||||||
write_result.unwrap_err()
|
write_result.unwrap_err()
|
||||||
));
|
)));
|
||||||
} else {
|
} else {
|
||||||
println!("Output texture stored in {:?}", file);
|
println!("Output texture stored in {:?}", file);
|
||||||
}
|
}
|
||||||
|
|
@ -275,11 +159,10 @@ impl TemplateApp {
|
||||||
}
|
}
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
{
|
{
|
||||||
let data = self.data.clone().unwrap().data;
|
|
||||||
wasm_bindgen_futures::spawn_local(async move {
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
let file = rfd::AsyncFileDialog::new()
|
let file = rfd::AsyncFileDialog::new()
|
||||||
.set_directory(".")
|
.set_directory(".")
|
||||||
.set_file_name("output.png")
|
.set_file_name(filename)
|
||||||
.save_file()
|
.save_file()
|
||||||
.await;
|
.await;
|
||||||
match file {
|
match file {
|
||||||
|
|
@ -378,6 +261,8 @@ impl eframe::App for TemplateApp {
|
||||||
)
|
)
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
|
self.image = None;
|
||||||
|
ctx.forget_image("bytes://output.png");
|
||||||
self.build_atlas(ctx);
|
self.build_atlas(ctx);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -398,7 +283,7 @@ impl eframe::App for TemplateApp {
|
||||||
});
|
});
|
||||||
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
if let Some(error) = &self.error {
|
if let Some(Err(error)) = &self.data {
|
||||||
let text = egui::RichText::new(format!("Error: {}",&error))
|
let text = egui::RichText::new(format!("Error: {}",&error))
|
||||||
.font(FontId::new(20.0, FontFamily::Name("semibold".into())))
|
.font(FontId::new(20.0, FontFamily::Name("semibold".into())))
|
||||||
.color(Color32::RED)
|
.color(Color32::RED)
|
||||||
|
|
@ -413,6 +298,8 @@ impl eframe::App for TemplateApp {
|
||||||
CollapsingHeader::new("Settings")
|
CollapsingHeader::new("Settings")
|
||||||
.default_open(false)
|
.default_open(false)
|
||||||
.show(ui, |ui| {
|
.show(ui, |ui| {
|
||||||
|
ui.label("Tilemap id");
|
||||||
|
ui.text_edit_singleline(&mut self.name);
|
||||||
ui.add(
|
ui.add(
|
||||||
egui::Slider::new(&mut self.config.max_width, 64..=4096).text("Width"),
|
egui::Slider::new(&mut self.config.max_width, 64..=4096).text("Width"),
|
||||||
);
|
);
|
||||||
|
|
@ -435,9 +322,9 @@ impl eframe::App for TemplateApp {
|
||||||
ui.with_layout(egui::Layout::top_down_justified(egui::Align::Min), |ui|{
|
ui.with_layout(egui::Layout::top_down_justified(egui::Align::Min), |ui|{
|
||||||
|
|
||||||
egui::ScrollArea::vertical().auto_shrink(false).show(ui, |ui| {
|
egui::ScrollArea::vertical().auto_shrink(false).show(ui, |ui| {
|
||||||
if let Some(data) = &self.data {
|
if let Some(Ok(data)) = &self.data {
|
||||||
ui.horizontal_top(|ui|{
|
ui.horizontal_top(|ui|{
|
||||||
ui.label(format!("{} frames, size: {}x{}",data.frames.len(),data.size.0,data.size.1));
|
ui.label(format!("{} frames, size: {}x{}",data.atlas_asset.frames.len(),data.atlas_asset.size[0],data.atlas_asset.size[1]));
|
||||||
});
|
});
|
||||||
ui.label(RichText::new("Frames JSON").strong());
|
ui.label(RichText::new("Frames JSON").strong());
|
||||||
egui_json_tree::JsonTree::new("simple-tree", &data.atlas_asset_json).show(ui);
|
egui_json_tree::JsonTree::new("simple-tree", &data.atlas_asset_json).show(ui);
|
||||||
|
|
@ -506,20 +393,30 @@ fn file_path(file: &DroppedFile) -> String {
|
||||||
id.replace(".png", "")
|
id.replace(".png", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dynamic_image_from_file(file: &DroppedFile) -> DynamicImage {
|
fn dynamic_image_from_file(file: &DroppedFile) -> Option<DynamicImage> {
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
{
|
{
|
||||||
let bytes = file.bytes.as_ref().clone();
|
let Some(bytes) = file.bytes.as_ref().clone() else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
ImageImporter::import_from_memory(&bytes.unwrap())
|
if let Ok(r) = ImageImporter::import_from_memory(&bytes.unwrap()) {
|
||||||
.expect("Unable to import file. Run this example with --features=\"png\"")
|
Some(r.into())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
{
|
{
|
||||||
let path = file.path.as_ref().unwrap().clone();
|
let Some(path) = file.path.as_ref() else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
ImageImporter::import_from_file(&path)
|
if let Ok(r) = ImageImporter::import_from_file(path) {
|
||||||
.expect("Unable to import file. Run this example with --features=\"png\"")
|
Some(r)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "rpack_cli"
|
||||||
|
authors = ["Piotr Siuszko <siuszko@zoho.com>"]
|
||||||
|
license = "MIT"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["cli"]
|
||||||
|
cli = ["dep:clap", "dep:glob"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
clap = { version = "4", features = ["derive"], optional = true}
|
||||||
|
bevy_rpack = { default-features = false, path = "../bevy_rpack" }
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
texture_packer = { version = "0.29", features = ["common"] }
|
||||||
|
image = { version = "0.25", features = ["jpeg", "png"] }
|
||||||
|
glob = {version = "0.3", optional = true}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
use bevy_rpack::{AtlasFrame, SerializableRect};
|
||||||
|
use image::DynamicImage;
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::{io::Cursor, path::PathBuf};
|
||||||
|
use texture_packer::{importer::ImageImporter, TexturePacker, TexturePackerConfig};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Spritesheet {
|
||||||
|
pub image_data: Vec<u8>,
|
||||||
|
pub atlas_asset: bevy_rpack::AtlasAsset,
|
||||||
|
pub atlas_asset_json: Value,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ImageFile {
|
||||||
|
pub id: String,
|
||||||
|
pub image: DynamicImage,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImageFile {
|
||||||
|
pub fn at_path<P>(path: &PathBuf, id: P) -> Option<ImageFile>
|
||||||
|
where
|
||||||
|
P: AsRef<str>,
|
||||||
|
{
|
||||||
|
if let Ok(image) = ImageImporter::import_from_file(&path) {
|
||||||
|
Some(ImageFile {
|
||||||
|
image,
|
||||||
|
id: id.as_ref().to_owned().replace("\\", "/"),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Spritesheet {
|
||||||
|
pub fn build(
|
||||||
|
config: TexturePackerConfig,
|
||||||
|
images: &[ImageFile],
|
||||||
|
name: String,
|
||||||
|
) -> Result<Self, String> {
|
||||||
|
let mut packer = TexturePacker::new_skyline(config);
|
||||||
|
for image in images.iter() {
|
||||||
|
if !packer.can_pack(&image.image) {
|
||||||
|
return Err(format!(
|
||||||
|
"Consider making atlas bigger. Could not make atlas, failed on: {}",
|
||||||
|
image.id
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if let Err(err) = packer.pack_own(&image.id, image.image.clone()) {
|
||||||
|
return Err(format!(
|
||||||
|
"Could not make atlas, failed on: {}, {:?}",
|
||||||
|
image.id, err
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut out_vec = vec![];
|
||||||
|
let exported_image =
|
||||||
|
texture_packer::exporter::ImageExporter::export(&packer, None).unwrap();
|
||||||
|
let mut img = image::DynamicImage::new_rgba8(config.max_width, 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 atlas_asset = bevy_rpack::AtlasAsset {
|
||||||
|
size: [img.width(), img.height()],
|
||||||
|
name,
|
||||||
|
frames: packer
|
||||||
|
.get_frames()
|
||||||
|
.values()
|
||||||
|
.map(|v| -> AtlasFrame {
|
||||||
|
AtlasFrame {
|
||||||
|
key: v.key.clone(),
|
||||||
|
frame: SerializableRect {
|
||||||
|
x: v.frame.x,
|
||||||
|
y: v.frame.y,
|
||||||
|
w: v.frame.w,
|
||||||
|
h: v.frame.h,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
};
|
||||||
|
let Ok(atlas_asset_json) = serde_json::to_value(&atlas_asset) else {
|
||||||
|
return Err("Failed to deserialize".to_owned());
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Spritesheet {
|
||||||
|
image_data: out_vec.clone(),
|
||||||
|
atlas_asset,
|
||||||
|
atlas_asset_json,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use clap::Parser;
|
||||||
|
use rpack_cli::{ImageFile, Spritesheet};
|
||||||
|
|
||||||
|
/// Build rpack tilemaps with ease
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(version, about, long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
/// Name of the tilemap to build, when no value is provided uses 'tilemap'
|
||||||
|
#[arg(short, long)]
|
||||||
|
name: Option<String>,
|
||||||
|
/// size of the tilemap, default: 512
|
||||||
|
#[arg(long)]
|
||||||
|
size: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args = Args::parse();
|
||||||
|
let name = args.name.unwrap_or("tilemap".to_owned());
|
||||||
|
let size = args.size.unwrap_or(512);
|
||||||
|
|
||||||
|
let images: Vec<ImageFile> = glob::glob("**/*png")
|
||||||
|
.expect("Failed to find the png files")
|
||||||
|
.flatten()
|
||||||
|
.flat_map(|f| ImageFile::at_path(&f, f.to_str().unwrap_or_default()))
|
||||||
|
.collect();
|
||||||
|
let spritesheet = Spritesheet::build(
|
||||||
|
texture_packer::TexturePackerConfig {
|
||||||
|
max_width: size,
|
||||||
|
max_height: size,
|
||||||
|
allow_rotation: false,
|
||||||
|
force_max_dimensions: true,
|
||||||
|
border_padding: 2,
|
||||||
|
texture_padding: 2,
|
||||||
|
texture_extrusion: 2,
|
||||||
|
trim: false,
|
||||||
|
texture_outlines: false,
|
||||||
|
},
|
||||||
|
&images,
|
||||||
|
name.clone(),
|
||||||
|
)
|
||||||
|
.expect("Failed to build spritesheet");
|
||||||
|
|
||||||
|
let mut file = std::fs::File::create(format!("{}.png", name)).unwrap();
|
||||||
|
let write_result = file.write_all(&spritesheet.image_data);
|
||||||
|
if write_result.is_err() {
|
||||||
|
eprintln!(
|
||||||
|
"Could not make atlas, error: {:?}",
|
||||||
|
write_result.unwrap_err()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
println!("Output texture stored in {:?}", file);
|
||||||
|
}
|
||||||
|
let json = serde_json::to_string_pretty(&spritesheet.atlas_asset_json).unwrap();
|
||||||
|
let mut file = std::fs::File::create(format!("{}.rpack.json", name)).unwrap();
|
||||||
|
let write_result = file.write_all(&json.as_bytes());
|
||||||
|
if write_result.is_err() {
|
||||||
|
eprintln!(
|
||||||
|
"Could not make atlas, error: {:?}",
|
||||||
|
write_result.unwrap_err()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
println!("Output data stored in {:?}", file);
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
output.png
|
Before Width: | Height: | Size: 31 KiB |