diff --git a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs index fd9f98ac98..ba50f176e1 100644 --- a/src/ImageSharp/Advanced/AdvancedImageExtensions.cs +++ b/src/ImageSharp/Advanced/AdvancedImageExtensions.cs @@ -96,7 +96,7 @@ namespace SixLabors.ImageSharp.Advanced IMemoryGroup mg = source.GetPixelMemoryGroup(); if (mg.Count > 1) { - throw new InvalidOperationException($"GetPixelSpan is invalid, since the backing buffer of this {source.Width}x{source.Height} sized image is discontiguos!"); + throw new InvalidOperationException($"GetPixelSpan is invalid, since the backing buffer of this {source.Width}x{source.Height} sized image is discontiguous!"); } return mg.Single().Span; diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs index a404ab418b..150ce243e6 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs @@ -1,7 +1,8 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System.IO; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Bmp @@ -32,7 +33,20 @@ namespace SixLabors.ImageSharp.Formats.Bmp { Guard.NotNull(stream, nameof(stream)); - return new BmpDecoderCore(configuration, this).Decode(stream); + var decoder = new BmpDecoderCore(configuration, this); + + try + { + return decoder.Decode(stream); + } + catch (InvalidMemoryOperationException ex) + { + Size dims = decoder.Dimensions; + + // TODO: use InvalidImageContentException here, if we decide to define it + // https://github.com/SixLabors/ImageSharp/issues/1110 + throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {dims.Width}x{dims.Height}.", ex); + } } /// diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 392697d150..960c2ecd22 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -114,6 +114,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.options = options; } + public Size Dimensions => new Size(this.infoHeader.Width, this.infoHeader.Height); + /// /// Decodes the image from the specified this._stream and sets /// the data to image. diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs index 187e432694..31085dbaaa 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoder.cs @@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // TODO: use InvalidImageContentException here, if we decide to define it // https://github.com/SixLabors/ImageSharp/issues/1110 - throw new ImageFormatException($"Can not decode the image having degenerate dimensions of {w}x{h}.", ex); + throw new ImageFormatException($"Can not decode image. Failed to allocate buffers for possibly degenerate dimensions: {w}x{h}.", ex); } } diff --git a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs index de69d7207b..d40d6ec4c2 100644 --- a/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs +++ b/tests/ImageSharp.Tests/Advanced/AdvancedImageExtensionsTests.cs @@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Advanced public void OwnedMemory_PixelDataIsCorrect(TestImageProvider provider) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity(); + provider.LimitAllocatorBufferCapacity().InPixels(200); using Image image = provider.GetImage(); @@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Tests.Advanced public void GetPixelRowMemory_PixelDataIsCorrect(TestImageProvider provider) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity(); + provider.LimitAllocatorBufferCapacity().InPixels(200); using Image image = provider.GetImage(); @@ -147,7 +147,7 @@ namespace SixLabors.ImageSharp.Tests.Advanced public void GetPixelRowSpan_ShouldReferenceSpanOfMemory(TestImageProvider provider) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity(); + provider.LimitAllocatorBufferCapacity().InPixels(200); using Image image = provider.GetImage(); diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index afe2648f56..5b8060f069 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp if (!string.IsNullOrEmpty(nonContiguousBuffersStr)) { - provider.LimitAllocatorBufferCapacity(); + provider.LimitAllocatorBufferCapacity().InPixels(100); } using Image image = provider.GetImage(BmpDecoder); @@ -77,7 +77,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void BmpDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity(100); + provider.LimitAllocatorBufferCapacity().InPixels(10); ImageFormatException ex = Assert.Throws(provider.GetImage); Assert.IsType(ex.InnerException); } @@ -253,11 +253,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp } [Theory] - [WithFile(RLE8, PixelTypes.Rgba32)] - [WithFile(RLE8Inverted, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit(TestImageProvider provider) + [WithFile(RLE8, PixelTypes.Rgba32, false)] + [WithFile(RLE8Inverted, PixelTypes.Rgba32, false)] + [WithFile(RLE8, PixelTypes.Rgba32, true)] + [WithFile(RLE8Inverted, PixelTypes.Rgba32, true)] + public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit(TestImageProvider provider, bool enforceDiscontiguousBuffers) where TPixel : struct, IPixel { + if (enforceDiscontiguousBuffers) + { + provider.LimitAllocatorBufferCapacity().InBytes(100); + } + using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette })) { image.DebugSave(provider); @@ -266,12 +273,20 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp } [Theory] - [WithFile(RLE24, PixelTypes.Rgba32)] - [WithFile(RLE24Cut, PixelTypes.Rgba32)] - [WithFile(RLE24Delta, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecode_RunLengthEncoded_24Bit(TestImageProvider provider) + [WithFile(RLE24, PixelTypes.Rgba32, false)] + [WithFile(RLE24Cut, PixelTypes.Rgba32, false)] + [WithFile(RLE24Delta, PixelTypes.Rgba32, false)] + [WithFile(RLE24, PixelTypes.Rgba32, true)] + [WithFile(RLE24Cut, PixelTypes.Rgba32, true)] + [WithFile(RLE24Delta, PixelTypes.Rgba32, true)] + public void BmpDecoder_CanDecode_RunLengthEncoded_24Bit(TestImageProvider provider, bool enforceNonContiguous) where TPixel : struct, IPixel { + if (enforceNonContiguous) + { + provider.LimitAllocatorBufferCapacity().InBytes(50); + } + using (Image image = provider.GetImage(new BmpDecoder { RleSkippedPixelHandling = RleSkippedPixelHandling.Black })) { image.DebugSave(provider); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs index e237c65c68..99fd6b221b 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Baseline.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg if (!string.IsNullOrEmpty(nonContiguousBuffersStr)) { - provider.LimitAllocatorBufferCapacity(); + provider.LimitAllocatorBufferCapacity().InBytes(200); } using Image image = provider.GetImage(JpegDecoder); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs index 0755f79d1b..5d94ed985e 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Progressive.cs @@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg if (!string.IsNullOrEmpty(nonContiguousBuffersStr)) { - provider.LimitAllocatorBufferCapacity(); + provider.LimitAllocatorBufferCapacity().InBytes(200); } using Image image = provider.GetImage(JpegDecoder); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs index 1bf01fd511..c083d308d6 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs @@ -111,7 +111,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity(100); + provider.LimitAllocatorBufferCapacity().InBytes(10); ImageFormatException ex = Assert.Throws(provider.GetImage); this.Output.WriteLine(ex.Message); Assert.IsType(ex.InnerException); diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 49ef7f8f88..56dd378745 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -93,7 +93,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg ? ImageComparer.TolerantPercentage(0.1f) : ImageComparer.TolerantPercentage(5f); - provider.LimitAllocatorBufferCapacity(); + provider.LimitAllocatorBufferCapacity().InBytes(200); TestJpegEncoderCore(provider, subsample, 100, comparer); } diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs index d87407ad85..429ff995ae 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs @@ -204,7 +204,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga public void TgaDecoder_DegenerateMemoryRequest_ShouldTranslateTo_ImageFormatException(TestImageProvider provider) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity(100); + provider.LimitAllocatorBufferCapacity().InPixels(10); ImageFormatException ex = Assert.Throws(provider.GetImage); Assert.IsType(ex.InnerException); } @@ -219,7 +219,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga { TestImageProvider provider = BasicSerializer.Deserialize>(providerDump); - provider.LimitAllocatorBufferCapacity(); + provider.LimitAllocatorBufferCapacity().InPixels(200); using Image image = provider.GetImage(new TgaDecoder()); image.DebugSave(provider, testOutputDetails: nonContiguousBuffersStr); diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs index 4144038e49..2bb49a93e5 100644 --- a/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tga/TgaEncoderTests.cs @@ -128,7 +128,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tga public void Encode_WorksWithDiscontiguousBuffers(TestImageProvider provider, TgaBitsPerPixel bitsPerPixel) where TPixel : struct, IPixel { - provider.LimitAllocatorBufferCapacity(10000); + provider.LimitAllocatorBufferCapacity().InPixels(100); TestTgaEncoderCore(provider, bitsPerPixel, TgaCompression.RunLength); } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs index fa5eab20a5..12321d38f3 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs @@ -666,13 +666,12 @@ namespace SixLabors.ImageSharp.Tests } } - internal static void LimitAllocatorBufferCapacity( - this TestImageProvider provider, - int bufferCapacityInPixels = 40000) // 200 x 200 + internal static AllocatorBufferCapacityConfigurator LimitAllocatorBufferCapacity( + this TestImageProvider provider) where TPixel : struct, IPixel { var allocator = (ArrayPoolMemoryAllocator)provider.Configuration.MemoryAllocator; - allocator.BufferCapacityInBytes = Unsafe.SizeOf() * bufferCapacityInPixels; + return new AllocatorBufferCapacityConfigurator(allocator, Unsafe.SizeOf()); } internal static Image ToGrayscaleImage(this Buffer2D buffer, float scale) @@ -734,4 +733,32 @@ namespace SixLabors.ImageSharp.Tests } } } + + internal class AllocatorBufferCapacityConfigurator + { + private readonly ArrayPoolMemoryAllocator allocator; + private readonly int pixelSizeInBytes; + + public AllocatorBufferCapacityConfigurator(ArrayPoolMemoryAllocator allocator, int pixelSizeInBytes) + { + this.allocator = allocator; + this.pixelSizeInBytes = pixelSizeInBytes; + } + + /// + /// Set the maximum buffer capacity to (areaDimensionBytes x areaDimensionBytes) bytes. + /// + public void InBytes(int areaDimensionBytes) + { + this.allocator.BufferCapacityInBytes = areaDimensionBytes * areaDimensionBytes; + } + + /// + /// Set the maximum buffer capacity to (areaDimensionPixels x areaDimensionPixels x size of the pixel) bytes. + /// + public void InPixels(int areaDimensionPixels) + { + this.allocator.BufferCapacityInBytes = areaDimensionPixels * areaDimensionPixels * this.pixelSizeInBytes; + } + } }