| Crates.io | tex-packer-core |
| lib.rs | tex-packer-core |
| version | 0.1.0 |
| created_at | 2025-10-07 12:20:57.833185+00 |
| updated_at | 2025-10-07 12:20:57.833185+00 |
| description | Core algorithms and API for packing images into texture atlases (Skyline / MaxRects / Guillotine). Returns pages (RGBA) and metadata (JSON/Plist/templates). |
| homepage | https://github.com/Latias94/tex-packer |
| repository | https://github.com/Latias94/tex-packer |
| max_upload_size | |
| id | 1871596 |
| size | 205,148 |
Core library for packing many textures into atlas pages. Provides algorithms, data model, and a pipeline to pack in-memory images and return pages + metadata for export.
[dependencies]
tex-packer-core = { git = "https://example.com/your/repo", package = "tex-packer-core" }
image = "0.25"
Note: If using crates.io, set the version accordingly after publish.
use image::ImageReader;
use tex_packer_core::{InputImage, PackerConfig, pack_images};
fn main() -> anyhow::Result<()> {
let img1 = ImageReader::open("a.png")?.decode()?;
let img2 = ImageReader::open("b.png")?.decode()?;
let inputs = vec![
InputImage { key: "a".into(), image: img1 },
InputImage { key: "b".into(), image: img2 },
];
let cfg = PackerConfig { max_width: 1024, max_height: 1024, ..Default::default() };
let out = pack_images(inputs, cfg)?;
for page in &out.pages {
println!("page {} ({}x{}), frames={}", page.page.id, page.page.width, page.page.height, page.page.frames.len());
}
Ok(())
}
PackerConfig controls dimensions, trim, rotation, sorting, and algorithm family/heuristics.
Key fields:
max_width, max_height: page limits.allow_rotation: allow 90° rotation for tighter packing.trim, trim_threshold: trim transparent borders (alpha ≤ threshold).texture_padding, border_padding, texture_extrusion.power_of_two, square.family: Skyline | MaxRects | Guillotine | Auto.skyline_heuristic: BottomLeft | MinWaste (+ use_waste_map).mr_heuristic: BestAreaFit | BestShortSideFit | BestLongSideFit | BottomLeft | ContactPoint.g_choice + g_split: Guillotine heuristics.sort_order: stable sorting mode.auto_mode: Fast | Quality.time_budget_ms, parallel: enables time-bounded portfolio and optional parallel evaluation for Auto.mr_reference: use reference-accurate MaxRects split/prune (higher quality, slower).Builder and prelude:
PackerConfig::builder() for fluent construction and tex_packer_core::prelude::* to import common types.use tex_packer_core::prelude::*;
let cfg = PackerConfig::builder()
.with_max_dimensions(1024, 1024)
.allow_rotation(true)
.texture_padding(2)
.auto_mode(AutoMode::Quality)
.build();
pack_images(inputs, cfg) -> PackOutput
Vec<InputImage { key: String, image: DynamicImage }>PackOutput { atlas: Atlas, pages: Vec<OutputPage> }OutputPage { page: Page, rgba: RgbaImage }Rect, Frame, Page, Atlas, Meta.Metadata schema:
meta.schema_version is currently "1" for JSON outputs. Future additive fields may bump this.When using the core in a game/runtime, prefer layout-only placement (no pixel compositing), then upload subimages to your GPU atlas. The core exposes two runtime‑friendly paths:
pack_layout / pack_layout_itemsruntime::AtlasSessionRecommended runtime config
Layout-only (sizes only)
use tex_packer_core::prelude::*;
let items = vec![("a", 32, 20), ("b", 64, 32)];
let cfg = PackerConfig::builder()
.with_max_dimensions(2048, 2048)
.allow_rotation(true)
.square(false)
.pow2(false)
.build();
let atlas = pack_layout(items, cfg)?;
for page in &atlas.pages {
// Upload subimages to your GPU texture using frame.xywh and rotated flag (if you keep it per item)
// Here, pack_layout returns only geometry; pixel uploads are handled by your renderer.
for f in &page.frames { println!("{} => {:?}", f.key, f.frame); }
}
Layout-only (with source/source_size)
use tex_packer_core::prelude::*;
let items = vec![
LayoutItem { key: "a".into(), w: 30, h: 18,
source: Some(Rect::new(2, 1, 30, 18)),
source_size: Some((32, 20)), trimmed: true },
LayoutItem { key: "b".into(), w: 64, h: 32, source: None, source_size: None, trimmed: false },
];
let cfg = PackerConfig::builder().with_max_dimensions(2048, 2048).build();
let atlas = pack_layout_items(items, cfg)?;
Incremental (append/evict)
use tex_packer_core::prelude::*;
let cfg = PackerConfig::builder()
.with_max_dimensions(2048, 2048)
.allow_rotation(true)
.build();
let mut sess = AtlasSession::new(cfg, RuntimeStrategy::Guillotine);
let (_page_a, frame_a) = sess.append("spriteA".into(), 64, 32)?; // place
let (_page_b, frame_b) = sess.append("spriteB".into(), 48, 64)?;
// Upload A/B to your GPU atlas using frame.frame (xywh) and frame.rotated
let snap = sess.snapshot_atlas(); // geometry snapshot (no RGBA pages)
assert!(!snap.pages.is_empty());
let removed = sess.evict(_page_a, "spriteA"); // free the slot
Shelf runtime strategy
use tex_packer_core::prelude::*;
let cfg = PackerConfig::builder()
.with_max_dimensions(2048, 2048)
.allow_rotation(true)
.texture_padding(2)
.texture_extrusion(1)
.build();
// NextFit keeps filling the last shelf; FirstFit scans from the top.
let mut sess = AtlasSession::new(cfg, RuntimeStrategy::Shelf(ShelfPolicy::NextFit));
let (_page_a, a) = sess.append("A".into(), 64, 32)?;
let (_page_b, b) = sess.append("B".into(), 80, 32)?;
assert!(sess.evict(0, "A"));
let (_page_c, c) = sess.append("C".into(), 64, 32)?; // tends to reuse A's freed segment
let snap = sess.snapshot_atlas(); // geometry only
Runtime strategy guidance
extrude + padding/2, so extrusion never bleeds across neighbors.Notes
extrude + padding/2, so extrusion stays inside the slot and won’t bleed into neighbors.frame.rotated specifies a 90° clockwise rotation at placement time; adjust your sampling/upload accordingly.pack_images in a background task, but layout-only is preferred for latency.mr_reference (MaxRects split/prune): enables reference-style splitting (SplitFreeNode) with staged pruning; improves packing on large sets at higher CPU cost.auto_mr_ref_time_ms_threshold / auto_mr_ref_input_threshold let you tune when quality mode auto-enables mr_reference.For CLI usage, templates, and exporters, see crates/tex-packer-cli/README.md.
wasm32-unknown-unknown (no filesystem, no threads by default).rustup target add wasm32-unknown-unknowncargo build -p tex-packer-core --target wasm32-unknown-unknownimage::DynamicImage (e.g., DynamicImage::ImageRgba8(RgbaImage::from_raw(w, h, rgba).unwrap())).pack_images with in-memory images and use the returned page RGBA buffers to render to <canvas> or export.parallel feature; keep it disabled for wasm.family = Auto tries a small portfolio and picks the best (pages first, then total area).time_budget_ms can limit evaluation; parallel may evaluate candidates in parallel (when feature is enabled).auto_mode = Quality, the core auto-enables mr_reference for MaxRects candidates when time_budget_ms >= 200 or the number of inputs >= 800.