Files
MonoGame.Extended/source/MonoGame.Extended/Graphics/Texture2DRegion.cs
T
Andreas Loew de157c44e8 Texture2DRegion: support trimmed and rotated sprites optionally load origin from atlas data file (#1004)
* Added the following features to Texture2DRegion to improve sprite sheet packing efficiency:
- Regions on the texture can now be rotated.
- Transparent borders around sprites can be trimmed.

The SpriteBatch.Draw method will automatically rotate the region back to its original orientation and restore the transparent borders during rendering.

Additionally, since sprite sheet data files can define sprite origins, TextureRegion2D can now optionally store this origin. Sprites created from a Texture2DRegion will use this stored origin as their default.

* Adapted nine-patch sprite implementation for support rotated / trimmed sprites, too.

* Extended the TextureAtlas content pipeline to support trimmed and rotated sprites, as well as reading anchor points (origins) from the data file.
The JSON format has been cleaned up by removing unnecessary keys. The legacy format remains supported for backward compatibility.

* added build-time check to ensure that texture image file referenced by atlas data file exists
2025-08-04 12:04:25 -04:00

275 lines
11 KiB
C#

// Copyright (c) Craftwork Games. All rights reserved.
// Licensed under the MIT license.
// See LICENSE file in the project root for full license information.
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.Graphics;
/// <summary>
/// Represents a region of a texture.
/// </summary>
public class Texture2DRegion
{
/// <summary>
/// Gets the name assigned to this texture region when it was created.
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the texture associated with this texture region.
/// </summary>
public Texture2D Texture { get; }
/// <summary>
/// Gets the top-left x-coordinate of the texture region within the texture.
/// </summary>
public int X { get; }
/// <summary>
/// Gets the top-left y-coordinate of the texture region within the texture.
/// </summary>
public int Y { get; }
/// <summary>
/// Gets the width, in pixels, of the texture region.
/// </summary>
public int Width { get; }
/// <summary>
/// Gets the height, in pixels, of the texture region.
/// </summary>
public int Height { get; }
/// <summary>
/// Gets the size of the texture region.
/// </summary>
public Size Size { get; }
/// <summary>
/// Gets or sets the user-defined data associated with this texture region.
/// </summary>
public object Tag { get; set; }
/// <summary>
/// Gets the bounds of the texture region within the texture.
/// </summary>
public Rectangle Bounds { get; }
/// <summary>
/// Gets the top UV coordinate of the texture region.
/// </summary>
public float TopUV { get; }
/// <summary>
/// Gets the right UV coordinate of the texture region.
/// </summary>
public float RightUV { get; }
/// <summary>
/// Gets the bottom UV coordinate of the texture region.
/// </summary>
public float BottomUV { get; }
/// <summary>
/// Gets the left UV coordinate of the texture region.
/// </summary>
public float LeftUV { get; }
/// <summary>
/// Gets a value indicating whether this texture region is rotated 90 degrees clockwise in the atlas.
/// </summary>
public bool IsRotated { get; }
/// <summary>
/// Gets the original size of the texture region before trimming.
/// </summary>
public Size OriginalSize { get; }
/// <summary>
/// Gets the offset between the top-left corner of the original sprite and the top-left corner of the trimmed sprite.
/// </summary>
public Vector2 Offset { get; }
/// <summary>
/// Gets the normalized origin point of the texture region, or null if no origin is specified.
/// </summary>
public Vector2? OriginNormalized { get; }
/// <summary>
/// Initializes a new instance of the <see cref="Texture2DRegion"/> class representing the entire texture.
/// </summary>
/// <param name="texture">The texture to create the region from.</param>
/// <exception cref="ArgumentNullException">
/// Thrown if <paramref name="texture"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// Thrown if <paramref name="texture"/> has been disposed prior.
/// </exception>
public Texture2DRegion(Texture2D texture)
: this(texture, 0, 0, texture.Width, texture.Height, null) { }
/// <summary>
/// Initializes a new instance of the <see cref="Texture2DRegion"/> class representing the entire texture with the
/// specified name.
/// </summary>
/// <param name="texture">The texture to create the region from.</param>
/// <param name="name">The name of the texture region.</param>
/// <exception cref="ArgumentNullException">
/// Thrown if <paramref name="texture"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// Thrown if <paramref name="texture"/> has been disposed prior.
/// </exception>
public Texture2DRegion(Texture2D texture, string name)
: this(texture, 0, 0, texture.Bounds.Width, texture.Bounds.Height, null) { }
/// <summary>
/// Initializes a new instance of the <see cref="Texture2DRegion"/> class with the specified region of the texture.
/// </summary>
/// <param name="texture">The texture to create the region from.</param>
/// <param name="region">The region of the texture to use.</param>
/// <exception cref="ArgumentNullException">
/// Thrown if <paramref name="texture"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// Thrown if <paramref name="texture"/> has been disposed prior.
/// </exception>
public Texture2DRegion(Texture2D texture, Rectangle region)
: this(texture, region.X, region.Y, region.Width, region.Height, null) { }
/// <summary>
/// Initializes a new instance of the <see cref="Texture2DRegion"/> class with the specified region of the texture.
/// </summary>
/// <param name="texture">The texture to create the region from.</param>
/// <param name="x">The top-left x-coordinate of the region within the texture.</param>
/// <param name="y">The top-left y-coordinate of the region within the texture.</param>
/// <param name="width">The width, in pixels, of the region.</param>
/// <param name="height">The height, in pixels, of the region.</param>
/// <exception cref="ArgumentNullException">
/// Thrown if <paramref name="texture"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// Thrown if <paramref name="texture"/> has been disposed prior.
/// </exception>
public Texture2DRegion(Texture2D texture, int x, int y, int width, int height)
: this(texture, x, y, width, height, null) { }
/// <summary>
/// Initializes a new instance of the <see cref="Texture2DRegion"/> class with the specified region of the texture and
/// name.
/// </summary>
/// <param name="texture">The texture to create the region from.</param>
/// <param name="region">The region of the texture to use.</param>
/// <param name="name">The name of the texture region.</param>
/// <exception cref="ArgumentNullException">
/// Thrown if <paramref name="texture"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// Thrown if <paramref name="texture"/> has been disposed prior.
/// </exception>
public Texture2DRegion(Texture2D texture, Rectangle region, string name)
: this(texture, region.X, region.Y, region.Width, region.Height, name) { }
/// <summary>
/// Initializes a new instance of the <see cref="Texture2DRegion"/> class with the specified region of the texture and
/// name.
/// </summary>
/// <param name="texture">The texture to create the region from.</param>
/// <param name="x">The top-left x-coordinate of the region within the texture.</param>
/// <param name="y">The top-left y-coordinate of the region within the texture.</param>
/// <param name="width">The width, in pixels, of the region.</param>
/// <param name="height">The height, in pixels, of the region.</param>
/// <param name="name">The name of the texture region.</param>
/// <exception cref="ArgumentNullException">
/// Thrown if <paramref name="texture"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// Thrown if <paramref name="texture"/> has been disposed prior.
/// </exception>
public Texture2DRegion(Texture2D texture,int x, int y, int width, int height, string name)
: this(texture, x, y, width, height, false, new Size(width, height), Vector2.Zero, null, name) { }
/// <summary>
/// Initializes a new instance of the <see cref="Texture2DRegion"/> class with the specified region of the texture,
/// original size, offset, origin, and name.
/// </summary>
/// <param name="texture">The texture to create the region from.</param>
/// <param name="x">The top-left x-coordinate of the region within the texture.</param>
/// <param name="y">The top-left y-coordinate of the region within the texture.</param>
/// <param name="width">The width, in pixels, of the region.</param>
/// <param name="height">The height, in pixels, of the region.</param>
/// <param name="isRotated">A value indicating whether this texture region is rotated 90 degrees clockwise in the atlas.</param>
/// <param name="originalSize">The original size of the texture region before trimming.</param>
/// <param name="offset">The offset between the top-left corner of the original sprite and the top-left corner of the trimmed sprite.</param>
/// <param name="originNormalized">The origin point of the texture region, or null if no origin is specified.</param>
/// <param name="name">The name of the texture region.</param>
/// <exception cref="ArgumentNullException">
/// Thrown if <paramref name="texture"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ObjectDisposedException">
/// Thrown if <paramref name="texture"/> has been disposed prior.
/// </exception>
public Texture2DRegion(Texture2D texture, int x, int y, int width, int height, bool isRotated, Size originalSize, Vector2 offset, Vector2? originNormalized, string name)
{
ArgumentNullException.ThrowIfNull(texture);
if (texture.IsDisposed)
{
throw new ObjectDisposedException(nameof(texture));
}
if (string.IsNullOrEmpty(name))
{
name = $"{texture.Name}";
}
Name = name;
Texture = texture;
X = x;
Y = y;
Width = width;
Height = height;
IsRotated = isRotated;
OriginalSize = originalSize;
Offset = offset;
OriginNormalized = originNormalized;
Bounds = new Rectangle(x, y, width, height);
Size = new Size(width, height);
TopUV = Bounds.Top / (float)texture.Height;
RightUV = Bounds.Right / (float)texture.Width;
BottomUV = Bounds.Bottom / (float)texture.Height;
LeftUV = Bounds.Left / (float)texture.Width;
}
// Used for unit tests only
internal Texture2DRegion(string name, Rectangle bounds) : this(name, bounds.X, bounds.Y, bounds.Width, bounds.Height) { }
// Used for unit tests only
internal Texture2DRegion(string name, int x, int y, int width, int height)
{
Name = name;
Texture = null;
X = x;
Y = y;
Width = width;
Height = height;
Bounds = new Rectangle(x, y, width, height);
Size = new Size(width, height);
OriginalSize = Size;
Offset = Vector2.Zero;
OriginNormalized = null;
TopUV = Bounds.Top / 1.0f;
RightUV = Bounds.Right / 1.0f;
BottomUV = Bounds.Bottom / 1.0f;
LeftUV = Bounds.Left / 1.0f;
}
/// <inheritdoc/>
public override string ToString()
{
return $"{Name ?? string.Empty} {Bounds}";
}
}