Page 1 of 1

Custom Unity SpriteAtlas control.

Posted: 30 Jan 2025, 19:18
by BrandonReinhart
I'm attempting to override Image to provide support for Unity sprite atlases.

The inline usage looks like this:
<local:AtlasImageExt Atlas="WheatAtlas" Region="farm_wheat_01_lvl1" Width="64" Height="64" />
The backing xaml is mostly empty:
<Image
    x:Class="ArchitectOfRuin.AtlasImageExt"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ArchitectOfRuin"
    xmlns:noesis="clr-namespace:NoesisGUI">
</Image>
And when I update the sprite the method is:
        private void UpdateSprite()
        {
            if (!_spriteAtlas)
                return;

            if (string.IsNullOrEmpty(Region))
                return;

            _sprite = _spriteAtlas.GetSprite(Region);
            if (!_sprite)
            {
                Debug.LogError($"Sprite '{Region}' not found in Atlas '{Atlas}'");
                return;
            }

            // Get the sprite texture rect (in pixels).
            var spriteRect = _sprite.textureRect;
            // var atlasWidth = _sprite.texture.width;
            // var atlasHeight = _sprite.texture.height;

            // Convert pixel-based rect to normalized UV coordinates.
            var x = spriteRect.x;
            var y = spriteRect.y;
            var width = spriteRect.width;
            var height = spriteRect.height;

            Source = new ImageBrush
            {
                ImageSource = new TextureSource(_sprite.texture),
                Viewbox = new Rect(x, y, width, height),
                ViewboxUnits = BrushMappingMode.Absolute,
                TileMode = TileMode.None,
                Stretch = Stretch.None
            }.ImageSource;
        }
No matter what I provide for the viewbox I see the entire image. I have confirmed the ImageBrush is working, just that the Viewbox isn't being respected.

What am I missing?

Ah! I figure the mistake is in the assignment of .ImageSource. Not sure how to use the whole ImageBrush.

Re: Custom Unity SpriteAtlas control.

Posted: 30 Jan 2025, 19:34
by BrandonReinhart
I figured it out. I needed to have a UserControl that contained an ImageBrush:
using Noesis;
using UnityEngine;
using UnityEngine.U2D;
using Rect = Noesis.Rect;

namespace ArchitectOfRuin
{
    public class AtlasImageExt : UserControl
    {
        private SpriteAtlas _spriteAtlas;
        private Sprite _sprite;
        private ImageBrush _imageBrush;

        public static readonly DependencyProperty AtlasProperty =
            DependencyProperty.Register(
                "Atlas",
                typeof(string),
                typeof(AtlasImageExt),
                new PropertyMetadata(null, OnAtlasChanged));

        public string Atlas
        {
            get => (string) GetValue(AtlasProperty);
            set => SetValue(AtlasProperty, value);
        }

        public static readonly DependencyProperty RegionProperty =
            DependencyProperty.Register(
                "Region",
                typeof(string),
                typeof(AtlasImageExt),
                new PropertyMetadata(null, OnRegionChanged));

        public string Region
        {
            get => (string) GetValue(RegionProperty);
            set => SetValue(RegionProperty, value);
        }

        public AtlasImageExt()
        {
            InitializeComponent();
        }

        private void InitializeComponent()
        {
            NoesisUnity.LoadComponent(this);
            LoadAtlas();
            UpdateSprite();
        }

        private static void OnAtlasChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((AtlasImageExt) d).LoadAtlas();
            ((AtlasImageExt) d).UpdateSprite();
        }

        private static void OnRegionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((AtlasImageExt) d).UpdateSprite();
        }

        private void LoadAtlas()
        {
            if (string.IsNullOrEmpty(Atlas))
                return;

            _spriteAtlas = AssetStorage<SpriteAtlas>.Get(Atlas);
        }

        private void UpdateSprite()
        {
            if (!_spriteAtlas)
                return;

            if (string.IsNullOrEmpty(Region))
                return;

            _sprite = _spriteAtlas.GetSprite(Region);
            if (!_sprite)
            {
                Debug.LogError($"Sprite '{Region}' not found in Atlas '{Atlas}'");
                return;
            }

            if (_imageBrush == null)
                _imageBrush = new ImageBrush();

            var spriteRect = _sprite.textureRect;
            var atlasWidth = _sprite.texture.width;
            var atlasHeight = _sprite.texture.height;

            var x = spriteRect.x;
            var y = spriteRect.y;
            var width = spriteRect.width;
            var height = spriteRect.height;

            var flippedY = atlasHeight - y - height;

            _imageBrush.ImageSource = new TextureSource(_sprite.texture);
            _imageBrush.Viewbox = new Rect(x, flippedY, width, height);
            _imageBrush.ViewboxUnits = BrushMappingMode.Absolute;
            _imageBrush.TileMode = TileMode.None;
            _imageBrush.Stretch = Stretch.None;

            var rect = FindName("AtlasImageRect") as Rectangle;
            if (rect != null)
                rect.Fill = _imageBrush;
        }
    }
}
The XAML:
<UserControl
    x:Class="ArchitectOfRuin.AtlasImageExt"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:ArchitectOfRuin"
    xmlns:noesis="clr-namespace:NoesisGUI">
    <Rectangle x:Name="AtlasImageRect">
        <Rectangle.Fill>
            <ImageBrush x:Name="AtlasBrush" />
        </Rectangle.Fill>
    </Rectangle>
</UserControl>
The usage:
                <local:AtlasImageExt Atlas="WheatAtlas" Region="farm_wheat_01_lvl1" Width="64" Height="64" />
Excellent!

Re: Custom Unity SpriteAtlas control.

Posted: 30 Jan 2025, 21:28
by jsantos
Hey Brandon, sprites (and atlases) are natively supported in our plugin. Have you tried that?

Re: Custom Unity SpriteAtlas control.

Posted: 31 Jan 2025, 01:39
by BrandonReinhart
What's an example? Thanks!

Re: Custom Unity SpriteAtlas control.

Posted: 31 Jan 2025, 15:44
by BrandonReinhart
I see that sprites can be registered with the TextureProvider. How do I get an IconSource from that in code?

Ah! I see, I got this working. I was overthinking it (again). Thank you.

Re: Custom Unity SpriteAtlas control.

Posted: 04 Feb 2025, 11:26
by sfernandez
Our Inventory example uses an Sprite Atlas for showing the inventory icons.

Thanks for the update, I'm going to mark this as solved then.