High-performance rectangle packing for .NET 10 and 11
Pack rectangles. Compose the atlas.
Tedd.RectanglePacker places rectangles with the
MaxRects or Skyline algorithm and
searches for a small atlas size in parallel. The atlas builder then
writes sprite pixels, with optional extrude, mirror or wrap padding,
into a buffer that the caller allocates. Inputs are raw pixel views
in seven byte layouts or Tedd.FIC containers.
MaxRects and SkylineFive MaxRects and two Skyline placement rules
Parallel size searchThe same layout for any thread count
Edge paddingTransparent, extrude, mirror and wrap
Caller-owned memoryLayouts and pixels written into lent buffers
Layout contract
Rectangles come back in input order, without padding.
The layout is a width, a height and one rectangle per input. It
carries no references to the images, so it can be stored, reused, or
computed before any pixels exist.
Ordering
rects[i] belongs to sizes[i]
Rectangles are returned in input order, whatever order the search
packed them in. Empty inputs are reported at (0, 0).
Rectangles are never rotated.
Content rectangle
PackedRect excludes padding
Each rectangle is the sprite's content area. Padding surrounds it,
Spacing separates padded cells, and
BorderPadding keeps the atlas edges free. Texture
coordinates can be taken from the rectangle directly.
Cell size
w + 2 × padding + spacing
Sprites are packed as cells of this width (and the corresponding
height). The trailing spacing of the last row and column falls
outside the atlas, so no spacing is wasted at the edges.
Pixel layout
Rgba32: red at byte 0
Format names list byte order in memory, independent of processor
endianness: Gray8, Rgb24,
Bgr24, Rgba32, Bgra32,
Argb32, Abgr32. Every image view has
its own stride.
Pipeline
Layout and pixels are separate steps.
Packing needs only sizes. Composition needs the layout, the source
pixels and a destination. The one-shot Build methods
run both steps and ask a callback for the destination once its size
is known.
01
Measure
RectSize / RawImage / FicImage
Supply sizes, raw pixel views, or FIC containers. A
FicImage reads its dimensions from the header without
decoding pixels.
→
02
Pack
AtlasBuilder.TryPack
Search candidate widths, sort orders and heuristics, and write the
winning rectangles into a caller-provided
Span<PackedRect>.
→
03
Compose
AtlasBuilder.Compose
Convert each source to the atlas format, write padding, and clear
the background, into a RawImageBuffer over any memory.
→
04
Encode (optional)
AtlasBuilder.BuildFic
Stage the composed atlas in pooled memory and encode it as a
Tedd.FIC container, RGB or RGBA.
Capabilities
Placement, size search, padding and memory control.
The low-level packers place rectangles in a bin of fixed size. The
atlas builder adds the size search, padding and pixel composition on
top of them.
▦
Placement algorithms
MaxRects and Skyline
MaxRectsPacker supports best short side, best long
side, best area, bottom-left and contact-point rules.
SkylinePacker supports bottom-left and minimum waste.
Both place into a fixed bin and are reused with Reset.
⌕
Atlas size search
Three effort levels
Fast runs one pass at an estimated width.
Balanced and Thorough try more widths,
sort orders and heuristics in parallel. The winner is chosen by a
fixed rule, so the result does not depend on thread scheduling.
⊡
Size constraints
Bounds, powers of two, squares
Limit the atlas with MaxWidth and
MaxHeight (16,384 by default, 65,536 at most), round
to powers of two, force a square, or pack into an exact
FixedSize.
⧉
Padding modes
Transparent, extrude, mirror, wrap
Extrude repeats edge pixels outward, which prevents colour
bleeding under bilinear filtering. Mirror reflects across the
edge. Wrap continues from the opposite edge for tiling textures.
⇆
Pixel formats
Per-image conversion
Sources may use different formats. Missing alpha becomes 255,
Gray8 expands to RGB, and colour converts to
Gray8 with integer Rec. 601 luma.
≡
Composition order
Scanline or per sprite
Scanline writes every atlas row once, left to right,
in parallel bands. PerSprite clears the background
and then copies each sprite as a block, in parallel by sprite.
Auto chooses a strategy.
⌁
Allocation control
Lent buffers for rectangles and pixels
TryPack writes into a caller span, and
Compose into a RawImageBuffer over pooled,
pinned, native or GPU-mapped memory. With lent buffers, repeated
calls allocate nothing apart from allocations inside Tedd.FIC's
decoder and encoder.
◫
FIC input and output
Decode during composition
FicImage inputs are decoded into pooled or lent
scratch memory while the atlas is written, with optional checksum
verification. BuildFic encodes the finished atlas.
⋮⋮
Threading
Bounded worker count
MaxDegreeOfParallelism bounds the threads used for
searching, decoding and composing. Builder and packer instances
keep scratch storage between calls and are not thread-safe; use
one per thread.
Two-phase use
Allocate the atlas after the size is known.
Pack returns the atlas size and the rectangles.
Compose then writes into any buffer with that size,
such as a pooled array or a mapped GPU upload buffer.
Mixed formats. Sources are converted to the
destination format while they are copied.
Background. Uncovered pixels are cleared to zero
unless ClearBackground is disabled for a buffer that
is already zeroed.
One-shot alternative.Build packs
and composes in one call and requests the buffer through an
AtlasPixelBufferProvider.
Atlas.csC#
var builder = newAtlasBuilder(newAtlasOptions
{
Padding = 2,
PaddingMode = PaddingMode.Extrude,
Spacing = 1,
PowerOfTwo = true,
});
// Views over decoded pixels; formats may differ per sprite.RawImage[] sprites = LoadSprites();
// 1. Layout: one rectangle per sprite, in input order.AtlasLayout layout = builder.Pack(sprites);
// 2. Pixels: compose into memory the caller owns.byte[] pixels = ArrayPool<byte>.Shared.Rent(
layout.Width * layout.Height * 4);
var atlas = newRawImageBuffer(
layout.Width, layout.Height, PixelFormat.Rgba32, pixels);
builder.Compose(sprites, layout.Rects.Span, atlas);
// Content rectangle of sprites[i], excluding padding.PackedRect r = layout.Rects.Span[0];
Benchmarks
Measured against other .NET packers.
4 other .NET libraries were compared on atlas size search,
7 on packing into the same fixed bin, and 2 image libraries on
pixel composition. Packing results were checked for overlaps; composition outputs matched the reference pixels.
4 of 5datasets where the default settings produced the densest atlas of all size-searching libraries tested
57.6×speed of Fast effort versus the quickest other size search, geometric mean over 5 datasets (range 19.8× to 347×); its atlas was at least as dense on 5 of 5
12.6×speed of MaxRects BestShortSideFit versus the quickest other implementation of that heuristic, one thread in the same fixed bin; geometric mean over 5 datasets
2.9×composition throughput with 32 threads versus SkiaSharp drawing the same layout; on one thread the ratio is 1.6×
AMD Ryzen 9 5950X 16-Core Processor, .NET 11.0.0-rc.1.26425.128. Results depend on the input set and hardware.
MaxRectsPackerPlaces rectangles one at a time into a bin of fixed size. Suits
online allocation, for example glyphs added to a cache texture as
they are first used.
SkylinePackerThe same interface with lower cost per insertion. Space below an
overhang is not reused.
AtlasBuilder.TryPackChooses the atlas size for a complete set of rectangles and
writes the layout into a caller span without allocating.
AtlasBuilder.ComposeWrites pixels for an existing layout into a caller buffer,
including padding, format conversion and background.
AtlasBuilder.BuildPacks and composes in one call. The destination comes from a
provider callback or a new array.
AtlasBuilder.BuildFicPacks, composes and encodes the atlas as a Tedd.FIC
container.
Operational constraints
What the library does not do.
Rectangles are never rotated. Output is a single atlas: when the
inputs do not fit within the configured maximum size,
Pack throws InvalidOperationException and
TryPack returns false. There is no
multi-page output.
The library does not decode image files such as PNG or JPEG.
Callers pass decoded pixels as RawImage views or
Tedd.FIC containers as FicImage. It does not trim
transparent borders, premultiply alpha, or merge identical sprites.
The package targets .NET 10 and 11. AtlasBuilder, MaxRectsPacker and
SkylinePacker instances are not thread-safe.