< Summary

Class:Imagini.Drawing.Texture
Assembly:Imagini.2D
File(s):/home/razer/vscode-projects/project-grove/imagini/Imagini.2D/Drawing/Texture.cs
Covered lines:141
Uncovered lines:25
Coverable lines:166
Total lines:438
Line coverage:84.9% (141 of 166)
Covered branches:36
Total branches:42
Branch coverage:85.7% (36 of 42)

Metrics

MethodCyclomatic complexity NPath complexity Sequence coverage Branch coverage
.ctor(...)1087.5%100%
Lock(...)6083.33%100%
SetPixels(...)12083.33%91.66%
SetPixels(...)16092.3%93.75%
SetPixelsInternal(...)1016.66%100%
GetPixelBufferSizeInBytes(...)10100%100%
GetPixelBufferSize(...)10100%100%
InternalGetPixelBufferSize(...)20100%100%
InternalGetPixelBufferSizeInBytes(...)10100%100%
Unlock()20100%50%
Destroy()20100%50%
Dispose()200%0%
.cctor()10100%100%

File(s)

/home/razer/vscode-projects/project-grove/imagini/Imagini.2D/Drawing/Texture.cs

#LineLine coverage
 1using System;
 2using System.Diagnostics.CodeAnalysis;
 3using System.Drawing;
 4using System.Runtime.InteropServices;
 5using static Imagini.ErrorHandler;
 6using static SDL2.SDL_blendmode;
 7using static SDL2.SDL_rect;
 8using static SDL2.SDL_render;
 9using static SDL2.SDL_surface;
 10
 11namespace Imagini.Drawing
 12{
 13    /// <summary>
 14    /// Defines texture access patterns.
 15    /// Static - changes rarely, not lockable.
 16    /// Streaming - changes frequently, lockable.
 17    /// Target - can be used as render target.
 18    /// </summary>
 19    public enum TextureAccess
 20    {
 21        Static = SDL_TextureAccess.SDL_TEXTUREACCESS_STATIC,
 22        Streaming = SDL_TextureAccess.SDL_TEXTUREACCESS_STREAMING,
 23        Target = SDL_TextureAccess.SDL_TEXTUREACCESS_TARGET
 24    }
 25
 26    /// <summary>
 27    /// Specifies texture scaling quality.
 28    /// </summary>
 29    public enum TextureScalingQuality
 30    {
 31        Nearest,
 32        Linear,
 33        Anisotropic
 34    }
 35
 36    [ExcludeFromCodeCoverage]
 37    internal static class TextureScalingQualityExtensions
 38    {
 39        public static string AsHint(this TextureScalingQuality quality)
 40        {
 41            switch (quality)
 42            {
 43                case TextureScalingQuality.Nearest: return "nearest";
 44                case TextureScalingQuality.Linear: return "linear";
 45                case TextureScalingQuality.Anisotropic: return "best";
 46                default: return "";
 47            }
 48        }
 49    }
 50
 51    [Flags]
 52    /// <summary>
 53    /// Enumeration of flags that can be used as flip parameter in rendering
 54    /// operations.
 55    /// </summary>
 56    public enum TextureFlip
 57    {
 58        None = SDL_RendererFlip.SDL_FLIP_NONE,
 59        Horizontal = SDL_RendererFlip.SDL_FLIP_HORIZONTAL,
 60        Vertical = SDL_RendererFlip.SDL_FLIP_VERTICAL,
 61        Both = Horizontal | Vertical
 62    }
 63
 64    /// <summary>
 65    /// Defines a texture which stores it's data on GPU.
 66    /// </summary>
 67    public sealed class Texture : Resource, IDisposable
 68    {
 69        internal IntPtr Handle;
 70        /// <summary>
 71        /// Returns the renderer which owns this texture.
 72        /// </summary>
 3873        public Graphics Owner { get; private set; }
 74        /// <summary>
 75        /// Gets the width of this texture in pixels.
 76        /// </summary>
 25877        public int Width { get; private set; }
 78        /// <summary>
 79        /// Gets the height of this texture in pixels.
 80        /// </summary>
 13581        public int Height { get; private set; }
 82        /// <summary>
 83        /// Returns the size of this texture in pixels.
 84        /// </summary>
 785        public Size Size => new Size(Width, Height);
 86        /// <summary>
 87        /// Returns the pixel count of this texture (width * height).
 88        /// </summary>
 589        public int PixelCount => Width * Height;
 90        /// <summary>
 91        /// Gets the pixel format of this texture.
 92        /// </summary>
 21993        public PixelFormat Format { get; private set; }
 94        /// <summary>
 95        /// Gets the <see cref="TextureAccess" /> for this texture.
 96        /// </summary>
 4697        public TextureAccess Access { get; private set; }
 98
 99        /// <summary>
 100        /// Indicates if this texture is locked.
 101        /// </summary>
 17102        public bool Locked { get; private set; }
 103
 104        /// <summary>
 105        /// Returns a rectangle positioned at (0, 0) with texture's width and height.
 106        /// </summary>
 107        /// <returns></returns>
 38108        public Rectangle Bounds { get; private set; }
 109
 110        /// <summary>
 111        /// Gets or sets the additional RGB color value multiplied into render operations.
 112        /// </summary>
 113        public Color ColorMod
 114        {
 115            get
 116            {
 12117                byte r = 0; byte g = 0; byte b = 0;
 0118                Try(() =>
 4119                    SDL_GetTextureColorMod(Handle, ref r, ref g, ref b),
 0120                    "SDL_GetTextureColorMod");
 4121                return Color.FromArgb(r, g, b);
 122            }
 123            set
 124            {
 0125                Try(() =>
 19126                    SDL_SetTextureColorMod(Handle, value.R, value.G, value.B),
 0127                    "SDL_SetTextureColorMod");
 19128            }
 129        }
 130
 131        /// <summary>
 132        /// Gets or sets an additional alpha value used in render operations.
 133        /// </summary>
 134        public byte AlphaMod
 135        {
 136            get
 137            {
 4138                byte a = 0;
 4139                Try(() => SDL_GetTextureAlphaMod(Handle, ref a),
 4140                    "SDL_GetTextureAlphaMod");
 4141                return a;
 142            }
 143            set
 144            {
 3145                Try(() => SDL_SetTextureAlphaMod(Handle, value),
 3146                    "SDL_SetTextureAlphaMod");
 3147            }
 148        }
 149
 150        /// <summary>
 151        /// Gets or sets the <see cref="ColorMod" /> and <see cref="AlphaMod" /> values.
 152        /// </summary>
 153        public Color Tint
 154        {
 155            get {
 2156                var color = ColorMod;
 2157                var alpha = AlphaMod;
 2158                return Color.FromArgb(alpha, color);
 159            }
 160            set {
 2161                ColorMod = value;
 2162                AlphaMod = value.A;
 2163            }
 164        }
 165
 166        /// <summary>
 167        /// Gets or sets the blend mode used for render operations.
 168        /// </summary>
 169        public BlendMode BlendMode
 170        {
 171            get
 172            {
 2173                var blendMode = SDL_BlendMode.SDL_BLENDMODE_NONE;
 2174                Try(() => SDL_GetTextureBlendMode(Handle, ref blendMode),
 2175                    "SDL_GetTextureBlendMode");
 2176                return (BlendMode)blendMode;
 177            }
 178            set
 179            {
 13180                var blendMode = (SDL_BlendMode)value;
 13181                Try(() => SDL_SetTextureBlendMode(Handle, blendMode),
 13182                    "SDL_SetTextureBlendMode");
 13183            }
 184        }
 185
 38186        internal Texture(IntPtr handle, Graphics owner)
 187        {
 38188            (this.Handle, this.Owner) = (handle, owner);
 38189            owner.Register(this);
 190
 38191            var format = 0u;
 38192            var access = 0;
 38193            var w = 0;
 38194            var h = 0;
 0195            Try(() =>
 38196                SDL_QueryTexture(Handle, ref format, ref access, ref w, ref h),
 0197                "SDL_QueryTexture");
 38198            Format = (PixelFormat)format;
 38199            Access = (TextureAccess)access;
 38200            Width = w;
 38201            Height = h;
 38202            Bounds = new Rectangle(0, 0, w, h);
 38203        }
 204
 205        /// <summary>
 206        /// Locks a portion or the whole texture for write-only access and
 207        /// returns a pointer to it.
 208        /// </summary>
 209        /// <param name="rect">Rectangle to be locked, or null to lock entire texture</param>
 210        /// <param name="length">Length of the byte array</param>
 211        /// <param name="stride">Stride of the pixel data (bytes per row)</param>
 212        public IntPtr Lock(out int length, out int stride, Rectangle? rect = null)
 213        {
 5214            if (Locked)
 1215                throw new ImaginiException("This texture is already locked");
 4216            if (Access != TextureAccess.Streaming)
 2217                throw new ImaginiException("Only texture with streaming access can be locked");
 218
 2219            var r = rect?.ToSDL();
 2220            var rectHandle = GCHandle.Alloc(r, GCHandleType.Pinned);
 2221            var pitch = 0;
 2222            var result = IntPtr.Zero;
 223            try
 224            {
 0225                Try(() => SDL_LockTexture(Handle, rectHandle.AddrOfPinnedObject(),
 0226                    out result, ref pitch),
 0227                    "SDL_LockTexture");
 2228                stride = pitch;
 2229                length = GetPixelBufferSizeInBytes(rect);
 2230                Locked = true;
 2231            }
 232            finally
 233            {
 2234                rectHandle.Free();
 2235            }
 2236            return result;
 237        }
 238
 239        /// <summary>
 240        /// Use this function to update the given texture rectangle with new
 241        /// pixel data.
 242        /// </summary>
 243        /// <param name="pixelData">Pixel data array</param>
 244        /// <remarks>
 245        /// The pixel data must be in the pixel format of the texture.
 246        /// This is a fairly slow function, intended for use with static textures that do not change often.
 247        /// If the texture is intended to be updated often, it is preferred to
 248        /// create the texture as streaming and use the locking functions.
 249        /// </remarks>
 250        public void SetPixels(ref byte[] pixelData, Rectangle? rect = null)
 251        {
 15252            if (GetPixelBufferSizeInBytes(rect) > pixelData.Length)
 0253                throw new ArgumentOutOfRangeException("Pixel array is too small");
 15254            var r = rect?.ToSDL();
 15255            var rectHandle = GCHandle.Alloc(r, GCHandleType.Pinned);
 15256            var pixelHandle = GCHandle.Alloc(pixelData, GCHandleType.Pinned);
 15257            var bpp = Format.GetBytesPerPixel();
 258            try
 259            {
 15260                var width = rect?.Width ?? Width;
 15261                var height = rect?.Height ?? Height;
 15262                if (width == Width)
 263                {
 264                    // fast path for full-width copy
 14265                    SetPixelsInternal(
 0266                        rectHandle.AddrOfPinnedObject(),
 0267                        pixelHandle.AddrOfPinnedObject());
 268                }
 269                else
 270                {
 271                    // update each row part
 1272                    var p = pixelHandle.AddrOfPinnedObject();
 273                    unsafe
 274                    {
 1275                        SDL_Rect rows = r.Value;
 1276                        var row = stackalloc int[4];
 1277                        *row = rows.x;
 1278                        *(row + 1) = rows.y;
 1279                        *(row + 2) = rows.w;
 1280                        *(row + 3) = 1;
 1281                        var index = 0;
 22282                        for (int y = rows.y; y < rows.y + rows.h; y++)
 283                        {
 10284                            *(row + 1) = y;
 0285                            SetPixelsInternal((IntPtr)row,
 0286                                (IntPtr)((byte*)p + index * rows.w * bpp));
 10287                            index++;
 288                        }
 289                    }
 290                }
 1291            }
 292            finally
 293            {
 15294                rectHandle.Free();
 15295                pixelHandle.Free();
 15296            }
 15297        }
 298
 299        /// <summary>
 300        /// Use this function to update the given texture rectangle with new
 301        /// pixel data.
 302        /// </summary>
 303        /// <param name="rect">Area to update, or null to update entire texture</param>
 304        /// <param name="pixelData">Pixel data array</param>
 305        /// <remarks>
 306        /// This is a fairly slow function, intended for use with static textures that do not change often.
 307        /// If the texture is intended to be updated often, it is preferred to
 308        /// create the texture as streaming and use the locking functions.
 309        /// </remarks>
 310        public void SetPixels<T>(ref T[] pixelData, Rectangle? rect = null)
 311            where T : struct, IColor
 312        {
 13313            var srcBpp = pixelData[0].Format.GetBytesPerPixel();
 13314            var dstBpp = Format.GetBytesPerPixel();
 13315            var sizeInBytes = GetPixelBufferSizeInBytes(rect);
 13316            if (GetPixelBufferSize<T>(rect) > pixelData.Length)
 0317                throw new ArgumentOutOfRangeException("Pixel array is too small");
 13318            var r = rect?.ToSDL();
 13319            var rectHandle = GCHandle.Alloc(r, GCHandleType.Pinned);
 13320            var pixelHandle = GCHandle.Alloc(pixelData, GCHandleType.Pinned);
 321            try
 322            {
 13323                var width = rect?.Width ?? Width;
 13324                var height = rect?.Height ?? Height;
 13325                var tmpBuffer = pixelHandle.AddrOfPinnedObject();
 13326                var shouldFree = false;
 13327                if (pixelData[0].Format != Format)
 328                {
 12329                    tmpBuffer = Marshal.AllocHGlobal(sizeInBytes);
 12330                    Pixels.Convert(width, height, width * srcBpp, width * dstBpp,
 0331                        pixelData[0].Format, Format,
 0332                        pixelHandle.AddrOfPinnedObject(), tmpBuffer);
 12333                    shouldFree = true;
 334                }
 335                // fast path for full-width update
 13336                if (Width == width)
 9337                    SetPixelsInternal(rectHandle.AddrOfPinnedObject(), tmpBuffer);
 338                else
 339                {
 340                    // update each row part
 341                    unsafe
 342                    {
 4343                        SDL_Rect rows = r.Value;
 4344                        var row = stackalloc int[4];
 4345                        *row = rows.x;
 4346                        *(row + 1) = rows.y;
 4347                        *(row + 2) = rows.w;
 4348                        *(row + 3) = 1;
 4349                        var index = 0;
 128350                        for (int y = rows.y; y < rows.y + rows.h; y++)
 351                        {
 60352                            *(row + 1) = y;
 60353                            SetPixelsInternal((IntPtr)row,
 60354                                (IntPtr)((byte*)tmpBuffer + index * rows.w * dstBpp));
 60355                            index++;
 356                        }
 357                    }
 358                }
 13359                if (shouldFree)
 12360                    Marshal.FreeHGlobal(tmpBuffer);
 13361            }
 362            finally
 363            {
 13364                rectHandle.Free();
 13365                pixelHandle.Free();
 13366            }
 13367        }
 368
 369        private void SetPixelsInternal(IntPtr rect, IntPtr pixels)
 370        {
 0371            Try(() => SDL_UpdateTexture(Handle,
 0372                rect,
 0373                pixels,
 0374                Width * Format.GetBytesPerPixel()),
 0375                "SDL_UpdateTexture");
 93376        }
 377
 378        /// <summary>
 379        /// Calculates the buffer size needed for pixel writing and reading
 380        /// operations.
 381        /// </summary>
 382        /// <param name="rectangle">Rectangle to read the data from, or null to read entire texture</param>
 383        /// <seealso cref="Lock" />
 384        public int GetPixelBufferSizeInBytes(Rectangle? rectangle = null) =>
 30385            InternalGetPixelBufferSizeInBytes(Width, Height, Format, rectangle);
 386
 387        /// <summary>
 388        /// Calculates the buffer size needed for pixel writing and reading
 389        /// operations.
 390        /// </summary>
 391        /// <param name="rectangle">Rectangle to read the data from, or null to read entire texture</param>
 392        /// <seealso cref="Lock" />
 393        public int GetPixelBufferSize<T>(Rectangle? rectangle = null)
 394            where T : struct, IColor =>
 23395            InternalGetPixelBufferSize(Width, Height, rectangle);
 396
 397        internal static int InternalGetPixelBufferSize(int width, int height, Rectangle? rectangle)
 398        {
 147399            if (!rectangle.HasValue) return width * height;
 57400            return rectangle.Value.Width * rectangle.Value.Height;
 401        }
 402
 403        internal static int InternalGetPixelBufferSizeInBytes(int width, int height,
 404            PixelFormat format, Rectangle? rectangle)
 405        {
 30406            var bpp = format.GetBytesPerPixel();
 30407            return InternalGetPixelBufferSize(width, height, rectangle) * bpp;
 408        }
 409
 410        /// <summary>
 411        /// Unlocks the texture.
 412        /// </summary>
 413        public void Unlock()
 414        {
 2415            if (!Locked) return;
 2416            SDL_UnlockTexture(Handle);
 2417            Locked = false;
 2418        }
 419
 420        internal override void Destroy()
 421        {
 38422            if (IsDisposed) return;
 38423            SDL_DestroyTexture(Handle);
 38424            base.Destroy();
 38425        }
 426
 427        /// <summary>
 428        /// Disposes the object.
 429        /// </summary>
 430        public void Dispose()
 431        {
 0432            if (IsDisposed) return;
 0433            Destroy();
 0434        }
 435
 1436        static Texture() => Lifecycle.TryInitialize();
 437    }
 438}