// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Tests.Memory; // ReSharper disable InconsistentNaming namespace SixLabors.ImageSharp.Tests; /// /// Tests the class. /// public partial class ImageTests { public class Constructor { [Fact] public void Width_Height() { using (Image image = new(11, 23)) { Assert.Equal(11, image.Width); Assert.Equal(23, image.Height); Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); Assert.Equal(11 * 23, imageMem.Length); image.ComparePixelBufferTo(default(Rgba32)); Assert.Equal(Configuration.Default, image.Configuration); } } [Fact] public void Width_Height_SizeNotRepresentable_ThrowsInvalidImageOperationException() => Assert.Throws(() => new Image(int.MaxValue, int.MaxValue)); [Fact] public void Configuration_Width_Height() { Configuration configuration = Configuration.Default.Clone(); using (Image image = new(configuration, 11, 23)) { Assert.Equal(11, image.Width); Assert.Equal(23, image.Height); Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); Assert.Equal(11 * 23, imageMem.Length); image.ComparePixelBufferTo(default(Rgba32)); Assert.Equal(configuration, image.Configuration); } } [Fact] public void Configuration_Width_Height_BackgroundColor() { Configuration configuration = Configuration.Default.Clone(); Rgba32 color = Color.Aquamarine.ToPixel(); using (Image image = new(configuration, 11, 23, color)) { Assert.Equal(11, image.Width); Assert.Equal(23, image.Height); Assert.True(image.DangerousTryGetSinglePixelMemory(out Memory imageMem)); Assert.Equal(11 * 23, imageMem.Length); image.ComparePixelBufferTo(color); Assert.Equal(configuration, image.Configuration); } } [Fact] public void CreateUninitialized() { Configuration configuration = Configuration.Default.Clone(); byte dirtyValue = 123; configuration.MemoryAllocator = new TestMemoryAllocator(dirtyValue); ImageMetadata metadata = new(); using (Image image = Image.CreateUninitialized(configuration, 21, 22, metadata)) { Assert.Equal(21, image.Width); Assert.Equal(22, image.Height); Assert.Same(configuration, image.Configuration); Assert.Same(metadata, image.Metadata); Assert.Equal(dirtyValue, image[5, 5].PackedValue); } } } public class Indexer { private readonly Configuration configuration = Configuration.CreateDefaultInstance(); private void LimitBufferCapacity(int bufferCapacityInBytes) => this.configuration.MemoryAllocator = new TestMemoryAllocator { BufferCapacityInBytes = bufferCapacityInBytes }; [Theory] [InlineData(false)] [InlineData(true)] public void GetSet(bool enforceDisco) { if (enforceDisco) { this.LimitBufferCapacity(100); } using Image image = new(this.configuration, 10, 10); Rgba32 val = image[3, 4]; Assert.Equal(default(Rgba32), val); image[3, 4] = Color.Red.ToPixel(); val = image[3, 4]; Assert.Equal(Color.Red.ToPixel(), val); } public static TheoryData OutOfRangeData = new() { { false, -1 }, { false, 10 }, { true, -1 }, { true, 10 }, }; [Theory] [MemberData(nameof(OutOfRangeData))] public void Get_OutOfRangeX(bool enforceDisco, int x) { if (enforceDisco) { this.LimitBufferCapacity(100); } using Image image = new(this.configuration, 10, 10); ArgumentOutOfRangeException ex = Assert.Throws(() => _ = image[x, 3]); Assert.Equal("x", ex.ParamName); } [Theory] [MemberData(nameof(OutOfRangeData))] public void Set_OutOfRangeX(bool enforceDisco, int x) { if (enforceDisco) { this.LimitBufferCapacity(100); } using Image image = new(this.configuration, 10, 10); ArgumentOutOfRangeException ex = Assert.Throws(() => image[x, 3] = default); Assert.Equal("x", ex.ParamName); } [Theory] [MemberData(nameof(OutOfRangeData))] public void Set_OutOfRangeY(bool enforceDisco, int y) { if (enforceDisco) { this.LimitBufferCapacity(100); } using Image image = new(this.configuration, 10, 10); ArgumentOutOfRangeException ex = Assert.Throws(() => image[3, y] = default); Assert.Equal("y", ex.ParamName); } [Theory] [InlineData(false, false)] [InlineData(false, true)] [InlineData(true, false)] [InlineData(true, true)] public void CopyPixelDataTo_Success(bool disco, bool byteSpan) { if (disco) { this.LimitBufferCapacity(20); } using Image image = new(this.configuration, 10, 10); if (disco) { Assert.True(image.GetPixelMemoryGroup().Count > 1); } byte[] expected = TestUtils.FillImageWithRandomBytes(image); byte[] actual = new byte[expected.Length]; if (byteSpan) { image.CopyPixelDataTo(actual); } else { Span destination = MemoryMarshal.Cast(actual); image.CopyPixelDataTo(destination); } Assert.True(expected.AsSpan().SequenceEqual(actual)); } [Theory] [InlineData(false)] [InlineData(true)] public void CopyPixelDataTo_DestinationTooShort_Throws(bool byteSpan) { using Image image = new(this.configuration, 10, 10); Assert.ThrowsAny(() => { if (byteSpan) { image.CopyPixelDataTo(new byte[199]); } else { image.CopyPixelDataTo(new La16[99]); } }); } } public class ProcessPixelRows : ProcessPixelRowsTestBase { protected override void ProcessPixelRowsImpl( Image image, PixelAccessorAction processPixels) => image.ProcessPixelRows(processPixels); protected override void ProcessPixelRowsImpl( Image image1, Image image2, PixelAccessorAction processPixels) => image1.ProcessPixelRows(image2, processPixels); protected override void ProcessPixelRowsImpl( Image image1, Image image2, Image image3, PixelAccessorAction processPixels) => image1.ProcessPixelRows(image2, image3, processPixels); [Fact] public void NullReference_Throws() { using Image img = new(1, 1); Assert.Throws(() => img.ProcessPixelRows(null)); Assert.Throws(() => img.ProcessPixelRows((Image)null, (_, _) => { })); Assert.Throws(() => img.ProcessPixelRows(img, img, null)); Assert.Throws(() => img.ProcessPixelRows((Image)null, img, (_, _, _) => { })); Assert.Throws(() => img.ProcessPixelRows(img, (Image)null, (_, _, _) => { })); Assert.Throws(() => img.ProcessPixelRows(img, img, null)); } } public class Dispose { private readonly Configuration configuration = Configuration.CreateDefaultInstance(); public void MultipleDisposeCalls() { Image image = new(this.configuration, 10, 10); image.Dispose(); image.Dispose(); } [Fact] public void NonPrivateProperties_ObjectDisposedException() { Image image = new(this.configuration, 10, 10); Image genericImage = (Image)image; image.Dispose(); // Image Assert.Throws(() => { ImageFrameCollection prop = image.Frames; }); // Image Assert.Throws(() => { ImageFrameCollection prop = genericImage.Frames; }); } [Fact] public void Save_ObjectDisposedException() { using MemoryStream stream = new(); Image image = new(this.configuration, 10, 10); JpegEncoder encoder = new(); image.Dispose(); // Image Assert.Throws(() => image.Save(stream, encoder)); } [Fact] public void AcceptVisitor_ObjectDisposedException() { // This test technically should exist but it's impossible to write proper test case without reflection: // All visitor types are private and can't be created without context of some save/processing operation // Save_ObjectDisposedException test checks this method with AcceptVisitor(EncodeVisitor) anyway return; } [Fact] public void NonPrivateMethods_ObjectDisposedException() { Image image = new(this.configuration, 10, 10); Image genericImage = (Image)image; image.Dispose(); // Image Assert.Throws(() => { Image res = image.Clone(this.configuration); }); Assert.Throws(() => { Image res = image.CloneAs(this.configuration); }); Assert.Throws(() => { bool res = image.DangerousTryGetSinglePixelMemory(out Memory _); }); // Image Assert.Throws(() => { Image res = genericImage.CloneAs(this.configuration); }); } } public class DetectEncoder { [Fact] public void KnownExtension_ReturnsEncoder() { using Image image = new(1, 1); IImageEncoder encoder = image.DetectEncoder("dummy.png"); Assert.NotNull(encoder); Assert.IsType(encoder); } [Fact] public void UnknownExtension_ThrowsUnknownImageFormatException() { using Image image = new(1, 1); Assert.Throws(() => image.DetectEncoder("dummy.yolo")); } [Fact] public void NoDetectorRegisteredForKnownExtension_ThrowsUnknownImageFormatException() { Configuration configuration = new(); TestFormat format = new(); configuration.ImageFormatsManager.AddImageFormat(format); configuration.ImageFormatsManager.AddImageFormatDetector(new MockImageFormatDetector(format)); using Image image = new(configuration, 1, 1); Assert.Throws(() => image.DetectEncoder($"dummy.{format.Extension}")); } } }