5using Microsoft.Extensions.Logging;
13public sealed class Tilesheet : IDisposable
15 private SKImage? _atlasImage;
17 [Newtonsoft.Json.JsonIgnore]
18 internal DefinitionProvenance? DefinitionProvenance {
get;
set; }
24 internal SKImage? AtlasImage
31 return _atlasImage ??= SKImage.FromBitmap(
SkBitmap);
40 private Tilesheet() { }
42 internal Tilesheet(
string name, SKBitmap bitmap,
bool addDefaultRegion =
true)
45 SkBitmap = bitmap ??
throw new ArgumentNullException(nameof(bitmap));
51 internal Tilesheet(
string name, Stream stream,
bool addDefaultRegion =
true)
54 SKBitmap.Decode(stream) ?? throw new ArgumentException(
"Invalid image stream."),
59 internal Tilesheet(
string name,
string file,
bool addDefaultRegion =
true)
62 SKBitmap.Decode(file) ?? throw new ArgumentException($
"Invalid image file: {file}"),
68 internal Tilesheet(
AssetsFile resFile,
string entryName,
bool addDefaultRegion =
true)
70 ArgumentNullException.ThrowIfNull(resFile);
72 if (
string.IsNullOrWhiteSpace(entryName))
73 throw new ArgumentException(
"Asset entry name must be a non-empty string.", nameof(entryName));
77 using var assetStream = AssetIdentifier.Data
78 ??
throw new InvalidOperationException(
79 $
"Tilesheet asset '{entryName}' could not be loaded from AssetsFile '{resFile.FilePath}'. " +
80 "The asset entry does not exist or returned a null data stream.");
82 SkBitmap = SKBitmap.Decode(assetStream)
83 ??
throw new ArgumentException(
84 $
"Failed to decode tilesheet bitmap from asset '{entryName}' in AssetsFile '{resFile.FilePath}'. " +
85 "The asset data is corrupt or not a supported image format.");
100 internal Tilesheet(Tilesheet baseSheet,
string name,
string file)
102 ArgumentNullException.ThrowIfNull(baseSheet);
106 ??
throw new ArgumentException($
"Invalid image file: {file}");
111 foreach (var region
in baseSheet.Regions)
120 region.CollisionAdjust,
121 region.CollisionType);
123 for (
int y = 0; y < region.Rows; y++)
125 for (
int x = 0; x < region.Columns; x++)
127 if (region.TryGetFrameCollisionAdjustOverride(
130 out var frameCollisionAdjust))
132 copiedRegion.SetFrameCollisionAdjust(
135 frameCollisionAdjust);
138 if (region.TryGetFrameCollisionTypeOverride(
141 out var frameCollisionType))
143 copiedRegion.SetFrameCollisionType(
156 public SKBitmap
SkBitmap {
get;
private set; } =
null!;
164 public string Name {
get;
internal set; } =
string.Empty;
168 public List<TilesheetRegion>
Regions {
get;
private set; } =
new();
216 Spacing? overhangPixels =
null,
220 if (
string.IsNullOrWhiteSpace(name))
221 throw new ArgumentException(
"Region name must be a non-empty string.", nameof(name));
224 throw new ArgumentException($
"A tilesheet region named '{name}' already exists.", nameof(name));
233 overhangPixels ??
Spacing.None,
248 if (
string.IsNullOrWhiteSpace(name))
251 foreach (var region
in Regions)
253 if (
string.Equals(region.Name, name, StringComparison.OrdinalIgnoreCase))
288 new(
this, regionName, x, y);
304 public void ApplyMask(SKColor? maskColor =
null,
byte tolerance = 5)
307 throw new ArgumentException(
"Invalid bitmap.");
309 var targetColor = maskColor ?? SKColors.White;
315 InvalidateAtlasImage();
318 if (
SkBitmap.Info.AlphaType == SKAlphaType.Opaque)
320 var info =
new SKImageInfo(
326 var withAlpha =
new SKBitmap(info);
327 using (var canvas =
new SKCanvas(withAlpha))
337 SkBitmap.ApplyAlphaMask(targetColor, tolerance);
349 throw new ArgumentException(
"Invalid bitmap.");
352 InvalidateAtlasImage();
369 SKEncodedImageFormat format = SKEncodedImageFormat.Png,
373 throw new ArgumentException(
"Invalid bitmap.");
375 return SkBitmap.EncodeBitmapToBytes(format, quality);
391 string imageFilePath,
392 SKEncodedImageFormat format = SKEncodedImageFormat.Png,
396 throw new ObjectDisposedException(nameof(Tilesheet));
398 if (
string.IsNullOrWhiteSpace(imageFilePath))
400 throw new ArgumentException(
401 "Image file path must be a non-empty string.",
402 nameof(imageFilePath));
405 if (quality is < 0 or > 100)
407 throw new ArgumentOutOfRangeException(
410 "Image encoding quality must be between 0 and 100.");
415 throw new InvalidOperationException(
416 "Tilesheet does not contain a valid bitmap to persist.");
419 var fullPath = Path.GetFullPath(imageFilePath);
420 var directory = Path.GetDirectoryName(fullPath);
421 if (!
string.IsNullOrWhiteSpace(directory))
422 Directory.CreateDirectory(directory);
425 var imageBytes = sourceBitmap.EncodeBitmapToBytes(format, quality);
426 File.WriteAllBytes(fullPath, imageBytes);
439 public SKImage?
GetImage(
string regionName,
int x,
int y) =>
449 public SKBitmap?
GetBitmap(
string regionName,
int x,
int y) =>
456 public Dictionary<(
string regionName,
int x,
int y), SKBitmap>
GetAllBitmaps()
458 var tiles =
new Dictionary<(string regionName, int x, int y), SKBitmap>();
460 foreach (var region
in Regions)
462 foreach (var tile
in region.GetAllBitmaps())
463 tiles[(region.Name, tile.Key.x, tile.Key.y)] = tile.Value;
473 public Dictionary<(
string regionName,
int x,
int y), SKImage>
GetAllImages()
475 var tiles =
new Dictionary<(string regionName, int x, int y), SKImage>();
477 foreach (var region
in Regions)
479 foreach (var tile
in region.GetAllImages())
480 tiles[(region.Name, tile.Key.x, tile.Key.y)] = tile.Value;
492 ??
throw new ArgumentException(
493 $
"No tilesheet region named '{regionName}' exists.",
502 public Frame this[
string regionName,
int x,
int y] =>
512 private void BuildTileCache()
514 foreach (var region
in Regions)
515 region.BuildTileCache();
518 private void ClearTileCache()
520 foreach (var region
in Regions)
521 region.ClearTileCache();
524 private void InvalidateAtlasImage()
526 _atlasImage?.Dispose();
530 private void AddDefaultRegion(
531 Size? tileSize =
null,
534 Spacing? overhangPixels =
null,
539 TilesheetRegion.DefaultRegionName,
541 tileSize ?? Size.Empty,
544 overhangPixels ??
Spacing.None,
549 private bool _disposed;
557 GC.SuppressFinalize(
this);
560 private void Dispose(
bool disposing)
570 foreach (var region
in Regions)
575 InvalidateAtlasImage();
585 Engine.
Logger.LogError(ex,
"Error during Tilesheet Disposed event handling.");
Represents an identifier for a specific asset within a file, including its type and name.
Represents an asset file used by the engine, providing functionality to load, manage,...
Represents a rectangular region within a tilesheet that contains a grid of frames.
SKImage? GetImage(int x, int y)
Gets the cached image for the specified frame.
SKBitmap? GetBitmap(int x, int y)
Gets the cached bitmap for the specified frame.
static readonly string DefaultRegionName
The default name assigned to tilesheet regions when no name is specified.
Represents a tilesheet image and metadata for rendering tiles.
bool Premultiplied
Gets whether alpha premultiplication has been applied.
TilesheetRegion DefaultRegion
Gets the default tilesheet region.
TypedValueBag ValueBag
Gets or sets the custom typed values associated with this object.
void PersistImageToFile(string imageFilePath, SKEncodedImageFormat format=SKEncodedImageFormat.Png, int quality=100)
Persists this tilesheet's source image to a file and promotes the tilesheet from a runtime-only bitma...
AssetsFileIdentifier? AssetIdentifier
Gets the packed asset from which the image was loaded, if any.
Frame GetFrame(string regionName, int x, int y)
Creates a frame reference at the specified tile coordinates.
SKImage? GetImage(string regionName, int x, int y)
Gets the cached image for the specified frame.
Action< Tilesheet >? Disposed
Occurs after the object releases its resources.
void ApplyMask(SKColor? maskColor=null, byte tolerance=5)
Applies a color-key transparency mask and rebuilds the frame cache.
byte MaskTolerance
Gets the per-channel tolerance used for transparency masking.
void ApplyPremultiplyAlpha()
Premultiplies the bitmap alpha and rebuilds the frame cache.
Dictionary<(string regionName, int x, int y), SKImage > GetAllImages()
Collects the cached frame images indexed by their tile coordinates.
bool RemoveRegion(string name, bool dispose=true)
Removes the named tilesheet region and optionally disposes it.
SKBitmap? GetBitmap(string regionName, int x, int y)
Gets the cached bitmap for the specified frame.
void Dispose()
Releases the resources owned by this instance.
Dictionary<(string regionName, int x, int y), SKBitmap > GetAllBitmaps()
Collects the cached frame bitmaps indexed by their tile coordinates.
List< TilesheetRegion > Regions
Gets the named frame regions in the tilesheet.
SKColor? MaskColor
Gets the color used for transparency masking.
string Name
Gets the name.
byte[] ToByteArray(SKEncodedImageFormat format=SKEncodedImageFormat.Png, int quality=100)
Encodes the tilesheet bitmap using the requested image format and quality.
string ImageFilePath
Gets the loose source image path, when available.
SKBitmap? SkBitmapOriginal
Gets the source bitmap retained before masking or alpha conversion.
SKBitmap SkBitmap
Gets the current tilesheet bitmap used for rendering.
Frame GetFrame(int x, int y)
Creates a frame reference at the specified tile coordinates.
TilesheetRegion? GetRegion(string name)
Finds a tilesheet region by its case-insensitive name.
TilesheetRegion AddRegion(string name, Rectangle area, Size tileSize, Spacing? tilePadding=null, Spacing? regionMargin=null, Spacing? overhangPixels=null, CollisionAdjust? collisionAdjust=null, TileCollisionType collisionType=TileCollisionType.None)
Adds a region with the supplied default collision adjustment and collision type.
Represents the core game engine singleton responsible for managing the main update loop,...
static ILogger< Engine > Logger
Gets the logger instance used by the engine for diagnostic and informational messages.
A flexible, strongly-typed value container intended for runtime-attached metadata and ephemeral engin...
AssetTypes
Defines the types of assets that can be stored and managed by the engine.
record struct Spacing([property:JsonProperty("left")] int Left, [property:JsonProperty("top")] int Top, [property:JsonProperty("right")] int Right, [property:JsonProperty("bottom")] int Bottom)
Represents dimensions in pixels when spacing Tilesheets.
TileCollisionType
Defines the default collision behavior associated with a tilesheet region, frame, or runtime tile.
Represents the source Tilesheet and its coordinates to render on a destination.
Per-edge inset amounts applied to visual bounds to produce a collision rectangle.
static readonly CollisionAdjust None
Represents an adjustment with no pixel offsets.