tex-packer-core
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.
Install
- Cargo.toml
[]
= { = "https://example.com/your/repo", = "tex-packer-core" }
= "0.25"
Note: If using crates.io, set the version accordingly after publish.
Quick Example
use ImageReader;
use ;
Configuration
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:
- Use
PackerConfig::builder()for fluent construction andtex_packer_core::prelude::*to import common types.
use *;
let cfg = builder
.with_max_dimensions
.allow_rotation
.texture_padding
.auto_mode
.build;
API Surface
pack_images(inputs, cfg) -> PackOutput- Inputs:
Vec<InputImage { key: String, image: DynamicImage }> - Output:
PackOutput { atlas: Atlas, pages: Vec<OutputPage> } OutputPage { page: Page, rgba: RgbaImage }
- Inputs:
- Data model (serde):
Rect,Frame,Page,Atlas,Meta. - Export helpers: JSON (hash/array) and Plist string builders are available; the CLI crate covers file writing.
Metadata schema:
meta.schema_versionis currently "1" for JSON outputs. Future additive fields may bump this.
Runtime Usage
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:
- Layout-only, single shot (batch):
pack_layout/pack_layout_items - Incremental session (append/evict, multi‑shot):
runtime::AtlasSession
Recommended runtime config
- Algorithm: Skyline MinWaste (good occupancy + steady latency) or BottomLeft for even steadier times
- Trim: off (assume inputs are pre‑trimmed) to avoid alpha scans on hot paths
- Rotation: by engine needs; Padding/Extrude: 2/2 are safe defaults
- Waste map (Skyline): off for steadier perf; on for higher occupancy
Layout-only (sizes only)
use *;
let items = vec!;
let cfg = builder
.with_max_dimensions
.allow_rotation
.square
.pow2
.build;
let atlas = pack_layout?;
for page in &atlas.pages
Layout-only (with source/source_size)
use *;
let items = vec!;
let cfg = builder.with_max_dimensions.build;
let atlas = pack_layout_items?;
Incremental (append/evict)
use *;
let cfg = builder
.with_max_dimensions
.allow_rotation
.build;
let mut sess = new;
let = sess.append?; // place
let = sess.append?;
// 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!;
let removed = sess.evict; // free the slot
Shelf runtime strategy
use *;
let cfg = builder
.with_max_dimensions
.allow_rotation
.texture_padding
.texture_extrusion
.build;
// NextFit keeps filling the last shelf; FirstFit scans from the top.
let mut sess = new;
let = sess.append?;
let = sess.append?;
assert!;
let = sess.append?; // tends to reuse A's freed segment
let snap = sess.snapshot_atlas; // geometry only
Runtime strategy guidance
- Shelf(NextFit/FirstFit): lower variance, simple and fast; great for online append/evict with many similarly tall items. Use NextFit for fewer scans; FirstFit to reduce top‑area fragmentation.
- Guillotine: higher packing quality under fragmentation; good for heterogeneous sizes; costlier per update.
- Both strategies place content inside reserved slots with offset
extrude + padding/2, so extrusion never bleeds across neighbors.
Notes
- Frames are positioned inside reserved slots with an offset
extrude + padding/2, so extrusion stays inside the slot and won’t bleed into neighbors. frame.rotatedspecifies a 90° clockwise rotation at placement time; adjust your sampling/upload accordingly.- For composited PNGs at runtime, you can still call
pack_imagesin a background task, but layout-only is preferred for latency.
Notes
mr_reference(MaxRects split/prune): enables reference-style splitting (SplitFreeNode) with staged pruning; improves packing on large sets at higher CPU cost.- Skyline Waste Map is aligned with the reference implementation; no-Overlap guarantees are enforced by internal subtractive splitting and pruning.
- Auto thresholds:
auto_mr_ref_time_ms_threshold/auto_mr_ref_input_thresholdlet you tune when quality mode auto-enablesmr_reference.
For CLI usage, templates, and exporters, see crates/tex-packer-cli/README.md.
Wasm
- The core crate is designed to compile to
wasm32-unknown-unknown(no filesystem, no threads by default). - Build check:
rustup target add wasm32-unknown-unknowncargo build -p tex-packer-core --target wasm32-unknown-unknown
- Usage model in wasm:
- Provide decoded RGBA buffers and wrap as
image::DynamicImage(e.g.,DynamicImage::ImageRgba8(RgbaImage::from_raw(w, h, rgba).unwrap())). - Call
pack_imageswith in-memory images and use the returned page RGBA buffers to render to<canvas>or export.
- Provide decoded RGBA buffers and wrap as
- Parallel portfolio is behind the
parallelfeature; keep it disabled for wasm.
Auto Portfolio & mr_reference
family = Autotries a small portfolio and picks the best (pages first, then total area).time_budget_mscan limit evaluation;parallelmay evaluate candidates in parallel (when feature is enabled).- In
auto_mode = Quality, the core auto-enablesmr_referencefor MaxRects candidates whentime_budget_ms >= 200or the number of inputs>= 800.
Benchmark Summary
- kenney-ui-pack (release, 1024x1024, trim on, rotate on, padding=2):
- Skyline(MW): 1 page, 88.69%
- MaxRects(BAF/BL/CP): 1 page, 83.32% / 82.76% / 74.23%
- Guillotine(BAF+SLAS): 5 pages, 80.58%
- MaxRects split/prune (single 2048x2048, random 4..96 px):
- N=1000: mr_ref=false → 22.82% (~8ms); mr_ref=true → 58.68% (~304ms)
- N=5000: mr_ref=false → 24.55% (~9ms); mr_ref=true → 95.91% (~1241ms)