< Summary

Class:Imagini.Drawing.Surface
Assembly:Imagini.2D
File(s):/home/razer/vscode-projects/project-grove/imagini/Imagini.2D/Drawing/Surface.cs
Covered lines:181
Uncovered lines:34
Coverable lines:215
Total lines:604
Line coverage:84.1% (181 of 215)
Covered branches:54
Total branches:70
Branch coverage:77.1% (54 of 70)

Metrics

MethodCyclomatic complexity NPath complexity Sequence coverage Branch coverage
.ctor(...)10100%100%
.ctor(...)10100%100%
Lock()2080%50%
Unlock()20100%50%
SetRLEAcceleration(...)20100%100%
Create(...)10100%100%
Create(...)2060%100%
CreateFrom(...)1083.33%100%
CreateFrom(...)2085.71%50%
OptimizeFor(...)10100%100%
OptimizeFor(...)2075%50%
ConvertTo(...)2075%50%
ReadPixels(...)8073.68%75%
ReadPixels(...)4075%50%
SetPixelData(...)8091.3%75%
SetPixelData(...)4075%50%
Fill(...)20100%50%
Fill(...)2091.66%100%
BlitTo(...)4080%100%
Destroy()60100%83.33%
Dispose()10100%100%
.cctor()10100%100%

File(s)

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

#LineLine coverage
 1using System;
 2using System.Runtime.InteropServices;
 3
 4using static SDL2.SDL_blendmode;
 5using static SDL2.SDL_error;
 6using static SDL2.SDL_surface;
 7using static Imagini.ErrorHandler;
 8using System.Drawing;
 9using Imagini;
 10using static SDL2.SDL_rect;
 11using System.Diagnostics.CodeAnalysis;
 12using Imagini.Core.Internal;
 13
 14namespace Imagini.Drawing
 15{
 16    /// <summary>
 17    /// Defines surface blend modes.
 18    /// </summary>
 19    /// <remarks>
 20    /// None:
 21    /// <code>
 22    /// dstRGBA = srcRGBA
 23    /// </code>
 24    /// AlphaBlend:
 25    /// <code>
 26    /// dstRGB = (srcRGB * srcA) + (dstRGB * (1 - srcA))
 27    /// dstA = srcA + (dstA * (1 - srcA))
 28    /// </code>
 29    /// Add:
 30    /// <code>
 31    /// dstRGB = (srcRGB * srcA) + dstRGB
 32    /// dstA = dstA
 33    /// </code>
 34    /// Modulate:
 35    /// <code>
 36    /// dstRGB = srcRGB * dstRGB
 37    /// dstA = dstA
 38    /// </code>
 39    /// </remarks>
 40    public enum BlendMode
 41    {
 42        None = SDL_BlendMode.SDL_BLENDMODE_NONE,
 43        AlphaBlend = SDL_BlendMode.SDL_BLENDMODE_BLEND,
 44        Add = SDL_BlendMode.SDL_BLENDMODE_ADD,
 45        Modulate = SDL_BlendMode.SDL_BLENDMODE_MOD
 46    }
 47
 48    /// <summary>
 49    /// Defines a surface which stores it's pixel data in
 50    /// the RAM.
 51    /// </summary>
 52    public sealed class Surface : Resource, IDisposable
 53    {
 54        internal readonly IntPtr Handle;
 55        private readonly IntPtr _pixels;
 256        internal IntPtr PixelsHandle => _pixels;
 57        private readonly bool _shouldFreePixels;
 58
 59        /// <summary>
 60        /// Returns the surface's pixel format.
 61        /// </summary>
 62        /// <returns></returns>
 9563        public PixelFormatInfo PixelInfo { get; private set; }
 64        /// <summary>
 65        /// Returns the surface width in pixels.
 66        /// </summary>
 17067        public int Width { get; private set; }
 68        /// <summary>
 69        /// Returns the surface height in pixels.
 70        /// </summary>
 16171        public int Height { get; private set; }
 72        /// <summary>
 73        /// Returns the size of this surface in pixels.
 74        /// </summary>
 475        public Size Size => new Size(Width, Height);
 76        /// <summary>
 77        /// Returns the pixel count of the surface (width * height).
 78        /// </summary>
 2679        public int PixelCount => Width * Height;
 80        /// <summary>
 81        /// Returns the surface stride (aka pitch, or length of a pixel row) in bytes.
 82        /// </summary>
 83        /// <returns></returns>
 7384        public int Stride { get; private set; }
 85        /// <summary>
 86        /// Returns the size of the pixel data array in bytes (stride * height).
 87        /// </summary>
 1488        public int SizeInBytes => Stride * Height;
 89        /// <summary>
 90        /// Indicates if this surface should be locked in order to access
 91        /// the pixel data.
 92        /// </summary>
 93        /// <returns></returns>
 7394        public bool MustBeLocked { get; private set; }
 95        /// <summary>
 96        /// Indicates if this surface is currently locked or not.
 97        /// </summary>
 4798        public bool Locked { get; private set; }
 99        /// <summary>
 100        /// Indicates if this surface has RLE acceleration enabled.
 101        /// </summary>
 5102        public bool RLEEnabled { get; private set; }
 103
 104        /// <summary>
 105        /// Gets or sets the clipping rectangle.
 106        /// </summary>
 107        public Rectangle? ClipRect
 108        {
 109            get
 110            {
 111                unsafe
 112                {
 3113                    var rect = new SDL_Rect();
 3114                    var p = &rect;
 3115                    SDL_GetClipRect(Handle, &rect);
 3116                    if (rect.x == 0 && rect.y == 0 && rect.w == Width && rect.h == Height)
 2117                        return null;
 1118                    return rect.ToRectangle();
 119                }
 120            }
 121            set
 122            {
 123                unsafe
 124                {
 0125                    var rect = value.HasValue ? value.Value.ToSDL() :
 0126                        new SDL_Rect() { x = 0, y = 0, w = Width, h = Height };
 2127                    var p = &rect;
 4128                    Try(() => SDL_SetClipRect(Handle, p), "SDL_SetClipRect");
 129                }
 2130            }
 131        }
 132
 133        /// <summary>
 134        /// Gets or sets the additional color value multiplied into blit operations.
 135        /// Only R, G and B channels are used.
 136        /// </summary>
 137        /// <remarks>
 138        /// When this surface is blitted, during the blit operation each source color channel is modulated by the approp
 139        /// srcC = srcC * (color / 255)
 140        /// </remarks>
 141        public Color ColorMod
 142        {
 143            get
 144            {
 6145                byte r = 0; byte g = 0; byte b = 0;
 2146                Try(() =>
 2147                    SDL_GetSurfaceColorMod(Handle, ref r, ref g, ref b),
 2148                    "SDL_GetSurfaceColorMod");
 2149                return Color.FromArgb(r, g, b);
 150            }
 151            set
 152            {
 0153                Try(() =>
 1154                    SDL_SetSurfaceColorMod(Handle, value.R, value.G, value.B),
 0155                    "SDL_SetSurfaceColorMod");
 1156            }
 157        }
 158
 159        /// <summary>
 160        /// Gets or sets an additional alpha value used in blit operations.
 161        /// </summary>
 162        /// <remarks>
 163        /// When this surface is blitted, during the blit operation the source alpha value is modulated by this alpha va
 164        /// srcA = srcA * (alpha / 255)
 165        /// </remarks>
 166        public byte AlphaMod
 167        {
 168            get
 169            {
 2170                byte a = 0;
 2171                Try(() => SDL_GetSurfaceAlphaMod(Handle, ref a),
 2172                    "SDL_GetSurfaceAlphaMod");
 2173                return a;
 174            }
 175            set
 176            {
 1177                Try(() => SDL_SetSurfaceAlphaMod(Handle, value),
 0178                    "SDL_SetSurfaceAlphaMod");
 1179            }
 180        }
 181
 182        /// <summary>
 183        /// Gets or sets the <see cref="ColorMod" /> and <see cref="AlphaMod" /> values.
 184        /// </summary>
 185        public Color Tint
 186        {
 187            get {
 0188                var color = ColorMod;
 0189                var alpha = AlphaMod;
 0190                return Color.FromArgb(alpha, color);
 191            }
 192            set {
 0193                ColorMod = value;
 0194                AlphaMod = value.A;
 0195            }
 196        }
 197
 198
 199        /// <summary>
 200        /// Gets or sets the blend mode used for blit operations.
 201        /// </summary>
 202        public BlendMode BlendMode
 203        {
 204            get
 205            {
 2206                var blendMode = SDL_BlendMode.SDL_BLENDMODE_NONE;
 2207                Try(() => SDL_GetSurfaceBlendMode(Handle, ref blendMode),
 2208                    "SDL_GetSurfaceBlendMode");
 2209                return (BlendMode)blendMode;
 210            }
 211            set
 212            {
 1213                var blendMode = (SDL_BlendMode)value;
 1214                Try(() => SDL_SetSurfaceBlendMode(Handle, blendMode),
 0215                    "SDL_SetSurfaceBlendMode");
 1216            }
 217        }
 218
 219        /// <summary>
 220        /// Gets or sets the color key (transparent pixel).
 221        /// </summary>
 222        public Color? ColorKey
 223        {
 224            get
 225            {
 3226                var key = 0u;
 3227                var result = SDL_GetColorKey(Handle, ref key);
 4228                if (result == 0) return ColorExtensions.FromUint(key, PixelInfo);
 4229                if (result == -1) return null;
 0230                throw new ImaginiException($"Could not obtain color key: {SDL_GetError()}");
 231            }
 232            set
 233            {
 2234                var val = value?.AsUint(PixelInfo) ?? 0u;
 2235                Try(() => SDL_SetColorKey(Handle, value != null ? 1 : 0, val),
 0236                    "SDL_SetColorKey");
 2237            }
 238        }
 239
 40240        internal Surface(IntPtr handle)
 241        {
 40242            Handle = handle;
 40243            var data = Marshal.PtrToStructure<SDL_Surface>(handle);
 40244            PixelInfo = new PixelFormatInfo(data.format);
 40245            Width = data.w;
 40246            Height = data.h;
 40247            Stride = data.pitch;
 40248            MustBeLocked = SDL_MUSTLOCK(data);
 40249            _pixels = data.pixels;
 40250            _shouldFreePixels = false;
 40251            Locked = data.locked > 0;
 40252        }
 253
 254        internal Surface(IntPtr handle, IntPtr pixels)
 11255            : this(handle) =>
 11256            (this._pixels, this._shouldFreePixels) = (pixels, true);
 257
 258        /// <summary>
 259        /// Locks the surface
 260        /// </summary>
 261        public void Lock()
 262        {
 1263            if (Locked) return;
 1264            Try(() => SDL_LockSurface(Handle),
 0265                "SDL_LockSurface");
 1266            Locked = true;
 1267        }
 268
 269        /// <summary>
 270        /// Unlocks the surface.
 271        /// </summary>
 272        public void Unlock()
 273        {
 1274            if (!Locked) return;
 1275            SDL_UnlockSurface(Handle);
 1276            Locked = false;
 1277        }
 278
 279        /// <summary>
 280        /// Enabled or disables the RLE acceleration.
 281        /// </summary>
 282        public void SetRLEAcceleration(bool enable)
 283        {
 4284            Try(() => SDL_SetSurfaceRLE(Handle, enable ? 1 : 0), "SDL_SetSurfaceRLE");
 2285            RLEEnabled = enable;
 2286            var data = Marshal.PtrToStructure<SDL_Surface>(Handle);
 2287            MustBeLocked = SDL_MUSTLOCK(data);
 2288        }
 289
 290        /// <summary>
 291        /// Creates a new surface with the specified format.
 292        /// </summary>
 293        /// <param name="width">Surface width</param>
 294        /// <param name="height">Surface height</param>
 295        /// <param name="format">Surface format</param>
 296        public static Surface Create(int width, int height, PixelFormat format = PixelFormat.Format_ARGB8888)
 297        {
 22298            var fmt = new PixelFormatInfo(format);
 22299            var result = Create(width, height, fmt.BitsPerPixel,
 22300                fmt.MaskR, fmt.MaskG, fmt.MaskB, fmt.MaskA);
 22301            fmt.Dispose();
 22302            return result;
 303        }
 304
 305        /// <summary>
 306        /// Creates a new surface.
 307        /// </summary>
 308        /// <param name="width">Surface width</param>
 309        /// <param name="height">Surface height</param>
 310        /// <param name="depth">Surface depth in bits (defaults to 32)</param>
 311        /// <remarks>
 312        /// The mask parameters are the bitmasks used to extract that
 313        /// color from a pixel. For instance, Rmask being FF000000 means
 314        /// the red data is stored in the most significant byte. Uzing zeros for
 315        /// the RGB masks sets a default value, based on the depth. However,
 316        /// using zero for the Amask results in an Amask of 0.
 317        /// </remarks>
 318        public static Surface Create(int width, int height, int depth,
 319            int Rmask = 0, int Gmask = 0, int Bmask = 0, int Amask = 0)
 320        {
 321            unchecked
 322            {
 0323                var handle = SDL_CreateRGBSurface(0, width, height, depth,
 0324                    (uint)Rmask, (uint)Gmask, (uint)Bmask, (uint)Amask);
 24325                if (handle == IntPtr.Zero)
 1326                    throw new ImaginiException($"Could not create surface: {SDL_GetError()}");
 23327                return new Surface(handle);
 328            }
 329        }
 330
 331        /// <summary>
 332        /// Creates a surface with the specified format from the specified pixel data.
 333        /// </summary>
 334        /// <param name="data">The pixel data to create surface from</param>
 335        /// <param name="width">Surface width</param>
 336        /// <param name="height">Surface height</param>
 337        /// <param name="format">Surface format</param>
 338        public static Surface CreateFrom(byte[] data, int width, int height, PixelFormat format)
 339        {
 11340            var fmt = new PixelFormatInfo(format);
 11341            var result = CreateFrom(data, width, height,
 11342                fmt.BytesPerPixel * width, fmt.BitsPerPixel,
 0343                fmt.MaskR, fmt.MaskG, fmt.MaskB, fmt.MaskA);
 11344            fmt.Dispose();
 11345            return result;
 346        }
 347
 348        /// <summary>
 349        /// Creates a new surface from existing data. Data is copied.
 350        /// </summary>
 351        /// <param name="data">The pixel data to create surface from</param>
 352        /// <param name="width">Surface width</param>
 353        /// <param name="height">Surface height</param>
 354        /// <param name="stride">Length of pixel row in bytes. RGBA = 4 * width</param>
 355        /// <param name="depth">Surface depth in bits (defaults to 32)</param>
 356        /// <remarks>
 357        /// The mask parameters are the bitmasks used to extract that
 358        /// color from a pixel. For instance, Rmask being FF000000 means
 359        /// the red data is stored in the most significant byte. Uzing zeros for
 360        /// the RGB masks sets a default value, based on the depth. However,
 361        /// using zero for the Amask results in an Amask of 0.
 362        /// </remarks>
 363        public static Surface CreateFrom(byte[] data, int width, int height, int stride, int depth,
 364            int Rmask, int Gmask, int Bmask, int Amask)
 365        {
 11366            var allocated = Marshal.AllocHGlobal(data.Length);
 11367            Marshal.Copy(data, 0, allocated, data.Length);
 11368            var handle = SDL_CreateRGBSurfaceFrom(allocated, width, height, depth, stride,
 11369                (uint)Rmask, (uint)Gmask, (uint)Bmask, (uint)Amask);
 11370            if (handle == IntPtr.Zero)
 0371                throw new ImaginiException($"Could not create surface: {SDL_GetError()}");
 11372            return new Surface(handle, allocated);
 373        }
 374
 375        /// <summary>
 376        /// Copies the surface into a new one that is optimized for blitting to
 377        /// a surface of the specified pixel format.
 378        /// </summary>
 379        public Surface OptimizeFor(PixelFormat format)
 380        {
 1381            var fmt = new PixelFormatInfo(format);
 1382            return OptimizeFor(fmt);
 383        }
 384
 385        /// <summary>
 386        /// Copies the surface into a new one that is optimized for blitting to
 387        /// a surface of the specified pixel format.
 388        /// </summary>
 389        public Surface OptimizeFor(PixelFormatInfo format)
 390        {
 1391            var handle = SDL_ConvertSurface(Handle, format.Handle, 0);
 1392            if (handle == IntPtr.Zero)
 0393                throw new ImaginiException($"Could not create surface: {SDL_GetError()}");
 1394            return new Surface(handle);
 395        }
 396
 397        /// <summary>
 398        /// Copies the surface into a new one that has the specified pixel format.
 399        /// </summary>
 400        public Surface ConvertTo(PixelFormat format)
 401        {
 3402            var handle = SDL_ConvertSurfaceFormat(Handle, (uint)format, 0);
 3403            if (handle == IntPtr.Zero)
 0404                throw new ImaginiException($"Could not create surface: {SDL_GetError()}");
 3405            return new Surface(handle);
 406        }
 407
 408        [ExcludeFromCodeCoverage]
 409        /// <summary>
 410        /// Copies the surface into a new one that has the specified pixel format.
 411        /// </summary>
 412        public Surface ConvertTo(PixelFormatInfo format) => ConvertTo(format.Format);
 413
 414        /// <summary>
 415        /// Reads the pixel data to the specified pixel buffer, making automatic
 416        /// conversion if necessary.
 417        /// </summary>
 418        public void ReadPixels<T>(ref T[] target)
 419            where T : struct, IColor
 420        {
 24421            if (MustBeLocked && !Locked)
 0422                throw new ImaginiException("Surface must be locked before accessing");
 24423            var p = _pixels;
 24424            var shouldFree = false;
 24425            var targetFormat = target[0].Format;
 24426            var targetStride = targetFormat.GetBytesPerPixel() * Width;
 24427            var sizeInBytes = Util.SizeOf<T>() * (Width * Height);
 24428            if (PixelInfo.Format != targetFormat)
 429            {
 12430                var p2 = Marshal.AllocHGlobal(sizeInBytes);
 0431                Try(() => SDL_ConvertPixels(Width, Height,
 0432                    (uint)PixelInfo.Format, p, Stride,
 0433                    (uint)targetFormat, p2, targetStride),
 0434                    "SDL_ConvertPixels");
 12435                p = p2;
 12436                shouldFree = true;
 437            }
 24438            Util.CopyTo(target, from: p, count: Width * Height);
 24439            if (shouldFree)
 12440                Marshal.FreeHGlobal(p);
 24441        }
 442
 443        /// <summary>
 444        /// Copies the pixel data in the specified byte array.
 445        /// </summary>
 446        public void ReadPixels(ref byte[] target)
 447        {
 3448            if (MustBeLocked && !Locked)
 0449                throw new ImaginiException("Surface must be locked before accessing");
 3450            Marshal.Copy(_pixels, target, 0, Stride * Height);
 3451        }
 452
 453        /// <summary>
 454        /// Copies the pixel data from the specified pixel array, making a
 455        /// conversion if necessary.
 456        /// </summary>
 457        public void SetPixelData<T>(ref T[] source)
 458            where T : struct, IColor
 459        {
 3460            if (MustBeLocked && !Locked)
 0461                throw new ImaginiException("Surface must be locked before accessing");
 3462            var shouldFree = false;
 3463            var sourceFormat = source[0].Format;
 3464            var sourceStride = sourceFormat.GetBytesPerPixel() * Width;
 3465            var result = new T[Width * Height];
 3466            var srcHandle = GCHandle.Alloc(source, GCHandleType.Pinned);
 3467            var p = srcHandle.AddrOfPinnedObject();
 468            try
 469            {
 3470                if (PixelInfo.Format != sourceFormat)
 471                {
 1472                    var p2 = Marshal.AllocHGlobal(SizeInBytes);
 1473                    shouldFree = true;
 1474                    Try(() => SDL_ConvertPixels(Width, Height,
 0475                        (uint)sourceFormat, p, sourceStride,
 1476                        (uint)PixelInfo.Format, p2, Stride),
 1477                        "SDL_ConvertPixels");
 1478                    p = p2;
 479                }
 480                unsafe
 481                {
 3482                    Buffer.MemoryCopy((void*)p, (void*)_pixels, SizeInBytes, SizeInBytes);
 483                }
 3484            }
 485            finally
 486            {
 3487                srcHandle.Free();
 3488                if (shouldFree)
 1489                    Marshal.FreeHGlobal(p);
 3490            }
 3491        }
 492
 493        /// <summary>
 494        /// Copies the pixel data from the specified byte array to the surface.
 495        /// </summary>
 496        /// <param name="source"></param>
 497        public void SetPixelData(ref byte[] source)
 498        {
 1499            if (MustBeLocked && !Locked)
 0500                throw new ImaginiException("Surface must be locked before accessing");
 1501            Marshal.Copy(source, 0, _pixels, Stride * Height);
 1502        }
 503
 504        /// <summary>
 505        /// Performs a fast fill of a rectangle with the specified color. No
 506        /// alpha blending is performed if alpha channel data is present.
 507        /// </summary>
 508        /// <param name="rectangle">Rectangle to fill, or NULL to fill entire surface</param>
 509        /// <param name="color">Color to fill with</param>
 510        public void Fill(Color color, Rectangle? rectangle = null)
 511        {
 3512            SDL_Rect? rect = rectangle?.ToSDL();
 3513            var rectHandle = GCHandle.Alloc(rect, GCHandleType.Pinned);
 514            try
 515            {
 516                unsafe
 517                {
 3518                    Try(() => SDL_FillRect(Handle,
 3519                       (SDL_Rect*)rectHandle.AddrOfPinnedObject(), color.AsUint(PixelInfo)),
 3520                       "SDL_FillRect");
 521                }
 3522            }
 523            finally
 524            {
 3525                rectHandle.Free();
 3526            }
 3527        }
 528
 529        /// <summary>
 530        /// Performs a fast fill of rectangles with the specified color. No
 531        /// alpha blending is performed if alpha channel data is present.
 532        /// </summary>
 533        /// <param name="rectangle">Rectangles to fill</param>
 534        /// <param name="color">Color to fill with</param>
 535        public void Fill(Color color, params Rectangle[] rectangles)
 536        {
 3537            var rects = new SDL_Rect[rectangles.Length];
 14538            for (int i = 0; i < rects.Length; i++)
 4539                rects[i] = rectangles[i].ToSDL();
 3540            var rectsHandle = GCHandle.Alloc(rects, GCHandleType.Pinned);
 541            try
 542            {
 543                unsafe
 544                {
 3545                    Try(() => SDL_FillRects(Handle,
 0546                        rectsHandle.AddrOfPinnedObject(),
 3547                        rects.Length, color.AsUint(PixelInfo)),
 3548                        "SDL_FillRects");
 549                }
 3550            }
 551            finally
 552            {
 3553                rectsHandle.Free();
 3554            }
 3555        }
 556
 557        /// <summary>
 558        /// Performs a scaled surface copy to a destination surface.
 559        /// </summary>
 560        /// <param name="srcRect">Rectangle to be copied, or null to copy entire surface</param>
 561        /// <param name="destination">Blit target</param>
 562        /// <param name="dstRect">Rectangle that is copied into, or null to copy into the entire surface</param>
 563        public void BlitTo(Surface destination, Rectangle? srcRect = null, Rectangle? dstRect = null)
 564        {
 2565            SDL_Rect? src = srcRect?.ToSDL();
 2566            SDL_Rect? dst = dstRect?.ToSDL();
 2567            var srcHandle = GCHandle.Alloc(src, GCHandleType.Pinned);
 2568            var dstHandle = GCHandle.Alloc(dst, GCHandleType.Pinned);
 569            try
 570            {
 571                unsafe
 572                {
 2573                    Try(() =>
 0574                        SDL_BlitScaled(Handle,
 2575                            (SDL_Rect*)srcHandle.AddrOfPinnedObject(),
 2576                            destination.Handle,
 0577                            (SDL_Rect*)dstHandle.AddrOfPinnedObject()),
 0578                        "SDL_BlitScaled");
 579                }
 2580            }
 581            finally
 582            {
 2583                srcHandle.Free();
 2584                dstHandle.Free();
 2585            }
 2586        }
 587
 588        internal override void Destroy()
 589        {
 34590            if (IsDisposed) return;
 34591            SDL_FreeSurface(Handle);
 34592            if (_pixels != IntPtr.Zero && _shouldFreePixels)
 9593                Marshal.FreeHGlobal(_pixels);
 34594        }
 595
 596        /// <summary>
 597        /// Disposes the surface.
 598        /// </summary>
 34599        public void Dispose() => Destroy();
 600
 601
 1602        static Surface() => Lifecycle.TryInitialize();
 603    }
 604}