SpriteSheet¶
Namespace Nfty.Core.Publish in assembly Nfty.Core.
Stitches a cooked Set's assets into one image, left to right and top to bottom.
Remarks¶
The order is the Set's own numbering, which is what makes a sheet addressable: cell n is asset n + 1, so an engine indexing frames and a reader counting rows agree without anything having to be recorded. SetReader orders by number rather than by filename for the same reason.
It is built one asset at a time, never all of them at once. A Set is a folder of PNGs on disk and the sheet is the only large thing in memory here — decoding five hundred thousand-pixel assets to composite them in one pass would cost gigabytes for an operation whose output is a single image. Each asset is opened, drawn into its cell and disposed.
There is a hard pixel ceiling and it is not arbitrary. A sheet is one contiguous raster: a 500-asset Set at 1000px in a 23x23 grid is 23,000 square, which is 529 million pixels and about 2 GB of RGBA before the encoder sees it. The limit is stated, checked before anything is allocated, and reported with the numbers that would have to change — a refusal an author can act on beats an OutOfMemoryException half a minute in.
Fields¶
MaxPixels¶
The largest sheet this will build, in pixels.
Remarks
16,384 square. Chosen because it is the maximum texture dimension on essentially every GPU that would consume a sheet, so a sheet past it is not merely large but unusable by the thing it exists for — and because at RGBA it is about 1 GB, which is already the outer edge of what is reasonable to hold for one image.
MaxSide¶
The longest side this will build, in pixels.
Remarks
Checked separately from SpriteSheet.MaxPixels: a 1 x 400,000 strip is small in area and is still not an image anything can open.
Methods¶
Fit(int, int, int)¶
The grid a Set falls into when nobody has chosen one: as square as it gets.
Parameters
count(int) - How many assets.cellWidth(int) - The canvas width.cellHeight(int) - The canvas height.
Returns. A layout that holds every asset.
Exceptions
ArgumentOutOfRangeException- An argument is not positive.
Remarks
Square by COLUMN COUNT rather than by pixels, because a sheet is read as a grid of cells and a 4:1 canvas would otherwise produce a grid four cells wide. The last row is allowed to be short — that is what a spritesheet looks like — and the empty cells are transparent.
ProblemWith(SpriteSheetLayout, int)¶
Why a layout cannot be built, or null when it can.
Parameters
layout(SpriteSheetLayout) - The grid.count(int) - How many assets have to fit in it.
Returns. A sentence to show the user, or null.
Remarks
Returns a problem rather than throwing, the way Validator does and for the same reason: a dialog binds this live while someone is still typing into the row and column boxes, and a half-typed grid is a normal state to be in rather than an error to raise. The writer below throws on the same conditions, so a caller that skips the check still cannot produce a broken sheet.
Write(IReadOnlyList<SetItem>, SpriteSheetLayout, string, IProgress<SpriteSheetProgress>?, CancellationToken)¶
public static SpriteSheetLayout Write(IReadOnlyList<SetItem> items, SpriteSheetLayout layout, string destination, IProgress<SpriteSheetProgress>? progress = null, CancellationToken cancellationToken = default);
SpriteSheet.WriteAsync(), for a caller that is not on a UI thread.
Parameters
items(IReadOnlyList<SetItem>) - The Set's assets, in the order they should be placed.layout(SpriteSheetLayout) - The grid.destination(string) - The.pngto write.progress(IProgress<SpriteSheetProgress>?) - Reports one step per asset placed.cancellationToken(CancellationToken) - Stops between assets.
Returns. The layout that was written.
Exceptions
ArgumentException- The grid cannot hold the Set, or is too large to build.
Remarks
The async one is the real implementation and this blocks on it, which is the opposite of Generator.GenerateAsync and right for the opposite reason: generation is CPU-bound with nothing to await, and this is PNG codec work against the disk for every asset in the collection. Blocking is safe here because nothing in Core captures a synchronization context — every await inside is ConfigureAwait(false) — so there is no context for a continuation to deadlock against.
WriteAsync(IReadOnlyList<SetItem>, SpriteSheetLayout, string, IProgress<SpriteSheetProgress>?, CancellationToken)¶
public static Task<SpriteSheetLayout> WriteAsync(IReadOnlyList<SetItem> items, SpriteSheetLayout layout, string destination, IProgress<SpriteSheetProgress>? progress = null, CancellationToken cancellationToken = default);
Stitches a Set into a sheet and writes it as a PNG.
Parameters
items(IReadOnlyList<SetItem>) - The Set's assets, in the order they should be placed.layout(SpriteSheetLayout) - The grid.destination(string) - The.pngto write.progress(IProgress<SpriteSheetProgress>?) - Reports one step per asset placed.cancellationToken(CancellationToken) - Stops between assets.
Returns. The layout that was written.
Exceptions
ArgumentException- The grid cannot hold the Set, or is too large to build.
Remarks
A missing or unreadable asset leaves its cell EMPTY rather than failing the sheet. The Set browser already takes that position — a browser over a damaged Set shows the damage rather than refusing to open — and a sheet is more useful with a hole in it than not at all. The hole is visible, which is the point: a transparent cell among drawn ones is unmistakable, where a sheet that silently closed the gap would renumber every frame after it.