From 89d09ee994b07ee751cea2461bf2c728fa14f414 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 15 May 2019 21:15:17 +0200 Subject: [PATCH 1/9] Not using quantization for Grey8 images when encoding 8Bit Bitmaps --- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 83 ++++++++++++++----- .../Formats/Bmp/BmpEncoderTests.cs | 6 +- 2 files changed, 62 insertions(+), 27 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index edde7dc893..532d20e000 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -305,37 +305,74 @@ private void Write16Bit(Stream stream, Buffer2D pixels) private void Write8Bit(Stream stream, ImageFrame image) where TPixel : struct, IPixel { + bool isGray8 = typeof(TPixel) == typeof(Gray8); using (IMemoryOwner colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize8Bit, AllocationOptions.Clean)) - using (IQuantizedFrame quantized = this.quantizer.CreateFrameQuantizer(this.configuration, 256).QuantizeFrame(image)) { Span colorPalette = colorPaletteBuffer.GetSpan(); - int idx = 0; - var color = default(Rgba32); - ReadOnlySpan paletteSpan = quantized.Palette.Span; - - // TODO: Use bulk conversion here for better perf - foreach (TPixel quantizedColor in paletteSpan) + if (isGray8) { - quantizedColor.ToRgba32(ref color); - colorPalette[idx] = color.B; - colorPalette[idx + 1] = color.G; - colorPalette[idx + 2] = color.R; - - // Padding byte, always 0 - colorPalette[idx + 3] = 0; - idx += 4; - } + for (byte i = 0; i <= 255; i++) + { + int idx = i * 4; + colorPalette[idx] = i; + colorPalette[idx + 1] = i; + colorPalette[idx + 2] = i; - stream.Write(colorPalette); + // Padding byte, always 0 + colorPalette[idx + 3] = 0; + } - for (int y = image.Height - 1; y >= 0; y--) - { - ReadOnlySpan pixelSpan = quantized.GetRowSpan(y); - stream.Write(pixelSpan); + stream.Write(colorPalette); - for (int i = 0; i < this.padding; i++) + using (IMemoryOwner rowSpanBuffer = this.memoryAllocator.AllocateManagedByteBuffer(image.Width, AllocationOptions.Clean)) + { + Span outputPixelRow = rowSpanBuffer.GetSpan(); + for (int y = image.Height - 1; y >= 0; y--) + { + Span inputPixelRow = image.GetPixelRowSpan(y); + PixelOperations.Instance.ToGray8Bytes(this.configuration, inputPixelRow, outputPixelRow, image.Width); + stream.Write(outputPixelRow); + + for (int i = 0; i < this.padding; i++) + { + stream.WriteByte(0); + } + } + } + } + else + { + using (IQuantizedFrame quantized = this.quantizer.CreateFrameQuantizer(this.configuration, 256).QuantizeFrame(image)) { - stream.WriteByte(0); + ReadOnlySpan quantizedColors = quantized.Palette.Span; + var color = default(Rgba32); + + // TODO: Use bulk conversion here for better perf + int idx = 0; + foreach (TPixel quantizedColor in quantizedColors) + { + quantizedColor.ToRgba32(ref color); + colorPalette[idx] = color.B; + colorPalette[idx + 1] = color.G; + colorPalette[idx + 2] = color.R; + + // Padding byte, always 0 + colorPalette[idx + 3] = 0; + idx += 4; + } + + stream.Write(colorPalette); + + for (int y = image.Height - 1; y >= 0; y--) + { + ReadOnlySpan pixelSpan = quantized.GetRowSpan(y); + stream.Write(pixelSpan); + + for (int i = 0; i < this.padding; i++) + { + stream.WriteByte(0); + } + } } } } diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index dd76e9443c..162d3f30b9 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -169,8 +169,7 @@ public void Encode_8BitGray_WithV3Header_Works(TestImageProvider TestBmpEncoderCore( provider, bitsPerPixel, - supportTransparency: false, - ImageComparer.TolerantPercentage(0.01f)); + supportTransparency: false); [Theory] [WithFile(Bit8Gs, PixelTypes.Gray8, BmpBitsPerPixel.Pixel8)] @@ -179,8 +178,7 @@ public void Encode_8BitGray_WithV4Header_Works(TestImageProvider TestBmpEncoderCore( provider, bitsPerPixel, - supportTransparency: true, - ImageComparer.TolerantPercentage(0.01f)); + supportTransparency: true); [Theory] [WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] From a1829bccc24d1da25b1059a986943d2a2e888811 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 16 May 2019 20:08:16 +0200 Subject: [PATCH 2/9] Refactor Write8Bit into two methods: one for gray and one for color --- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 140 ++++++++++++------- 1 file changed, 86 insertions(+), 54 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index 532d20e000..e9600f640d 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -311,68 +311,100 @@ private void Write8Bit(Stream stream, ImageFrame image) Span colorPalette = colorPaletteBuffer.GetSpan(); if (isGray8) { - for (byte i = 0; i <= 255; i++) - { - int idx = i * 4; - colorPalette[idx] = i; - colorPalette[idx + 1] = i; - colorPalette[idx + 2] = i; + this.Write8BitGray(stream, image, colorPalette); + } + else + { + this.Write8BitColor(stream, image, colorPalette); + } + } + } - // Padding byte, always 0 - colorPalette[idx + 3] = 0; - } + /// + /// Writes an 8 Bit color image with a color palette. The color palette has 256 entry's with 4 bytes for each entry. + /// + /// The type of the pixel. + /// The to write to. + /// The containing pixel data. + /// A byte span of size 1024 for the color palette. + private void Write8BitColor(Stream stream, ImageFrame image, Span colorPalette) + where TPixel : struct, IPixel + { + using (IQuantizedFrame quantized = this.quantizer.CreateFrameQuantizer(this.configuration, 256).QuantizeFrame(image)) + { + ReadOnlySpan quantizedColors = quantized.Palette.Span; + var color = default(Rgba32); - stream.Write(colorPalette); + // TODO: Use bulk conversion here for better perf + int idx = 0; + foreach (TPixel quantizedColor in quantizedColors) + { + quantizedColor.ToRgba32(ref color); + colorPalette[idx] = color.B; + colorPalette[idx + 1] = color.G; + colorPalette[idx + 2] = color.R; + + // Padding byte, always 0. + colorPalette[idx + 3] = 0; + idx += 4; + } - using (IMemoryOwner rowSpanBuffer = this.memoryAllocator.AllocateManagedByteBuffer(image.Width, AllocationOptions.Clean)) + stream.Write(colorPalette); + + for (int y = image.Height - 1; y >= 0; y--) + { + ReadOnlySpan pixelSpan = quantized.GetRowSpan(y); + stream.Write(pixelSpan); + + for (int i = 0; i < this.padding; i++) { - Span outputPixelRow = rowSpanBuffer.GetSpan(); - for (int y = image.Height - 1; y >= 0; y--) - { - Span inputPixelRow = image.GetPixelRowSpan(y); - PixelOperations.Instance.ToGray8Bytes(this.configuration, inputPixelRow, outputPixelRow, image.Width); - stream.Write(outputPixelRow); - - for (int i = 0; i < this.padding; i++) - { - stream.WriteByte(0); - } - } + stream.WriteByte(0); } } - else + } + } + + /// + /// Writes an 8 Bit gray image with a color palette. The color palette has 256 entry's with 4 bytes for each entry. + /// + /// The type of the pixel. + /// The to write to. + /// The containing pixel data. + /// A byte span of size 1024 for the color palette. + private void Write8BitGray(Stream stream, ImageFrame image, Span colorPalette) + where TPixel : struct, IPixel + { + // Create a color palette with 256 different gray values. + for (int i = 0; i <= 255; i++) + { + int idx = i * 4; + byte grayValue = (byte)i; + colorPalette[idx] = grayValue; + colorPalette[idx + 1] = grayValue; + colorPalette[idx + 2] = grayValue; + + // Padding byte, always 0. + colorPalette[idx + 3] = 0; + } + + stream.Write(colorPalette); + + using (IMemoryOwner rowSpanBuffer = this.memoryAllocator.AllocateManagedByteBuffer(image.Width, AllocationOptions.Clean)) + { + Span outputPixelRow = rowSpanBuffer.GetSpan(); + for (int y = image.Height - 1; y >= 0; y--) { - using (IQuantizedFrame quantized = this.quantizer.CreateFrameQuantizer(this.configuration, 256).QuantizeFrame(image)) + Span inputPixelRow = image.GetPixelRowSpan(y); + PixelOperations.Instance.ToGray8Bytes( + this.configuration, + inputPixelRow, + outputPixelRow, + image.Width); + stream.Write(outputPixelRow); + + for (int i = 0; i < this.padding; i++) { - ReadOnlySpan quantizedColors = quantized.Palette.Span; - var color = default(Rgba32); - - // TODO: Use bulk conversion here for better perf - int idx = 0; - foreach (TPixel quantizedColor in quantizedColors) - { - quantizedColor.ToRgba32(ref color); - colorPalette[idx] = color.B; - colorPalette[idx + 1] = color.G; - colorPalette[idx + 2] = color.R; - - // Padding byte, always 0 - colorPalette[idx + 3] = 0; - idx += 4; - } - - stream.Write(colorPalette); - - for (int y = image.Height - 1; y >= 0; y--) - { - ReadOnlySpan pixelSpan = quantized.GetRowSpan(y); - stream.Write(pixelSpan); - - for (int i = 0; i < this.padding; i++) - { - stream.WriteByte(0); - } - } + stream.WriteByte(0); } } } From ec0e91376f4e0a2297e55b0f6250d495e31cafcb Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 18 May 2019 11:44:47 +0200 Subject: [PATCH 3/9] Add tests for quantization of 32 bit bitmap to 8 bit color image --- .../Formats/Bmp/BmpEncoderTests.cs | 44 +++++++++++++++++++ tests/Images/External | 2 +- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index 162d3f30b9..36bd2bbdb3 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -3,10 +3,12 @@ using System.IO; +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; @@ -180,6 +182,48 @@ public void Encode_8BitGray_WithV4Header_Works(TestImageProvider bitsPerPixel, supportTransparency: true); + [Theory] + [WithFile(Bit32Rgb, PixelTypes.Rgba32)] + public void Encode_8BitColor_WithWuQuantizer(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + var encoder = new BmpEncoder + { + BitsPerPixel = BmpBitsPerPixel.Pixel8, + Quantizer = new WuQuantizer(256) + }; + string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); + IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); + using (var referenceImage = Image.Load(actualOutputFile, referenceDecoder)) + { + referenceImage.CompareToReferenceOutput(ImageComparer.Exact, provider, extension: "bmp", appendPixelTypeToFileName: false); + } + } + } + + [Theory] + [WithFile(Bit32Rgb, PixelTypes.Rgba32)] + public void Encode_8BitColor_WithOctreeQuantizer(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + { + var encoder = new BmpEncoder + { + BitsPerPixel = BmpBitsPerPixel.Pixel8, + Quantizer = new OctreeQuantizer(256) + }; + string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); + IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); + using (var referenceImage = Image.Load(actualOutputFile, referenceDecoder)) + { + referenceImage.CompareToReferenceOutput(ImageComparer.Exact, provider, extension: "bmp", appendPixelTypeToFileName: false); + } + } + } + [Theory] [WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] [WithFile(Bit32Rgba, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] diff --git a/tests/Images/External b/tests/Images/External index c057090b44..42b3b980ed 160000 --- a/tests/Images/External +++ b/tests/Images/External @@ -1 +1 @@ -Subproject commit c057090b4402120a83a8efe251aa5b691db9c0dc +Subproject commit 42b3b980ed07afd7b6603a5bfa6ffb91d6c8a124 From 18cda2ea480267c55a0ca469e851406bb3f9b7fb Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 18 May 2019 12:07:47 +0200 Subject: [PATCH 4/9] Renamed DrawImageTest to DrawImageTests, fixed namespace --- .../Drawing/{DrawImageTest.cs => DrawImageTests.cs} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename tests/ImageSharp.Tests/Drawing/{DrawImageTest.cs => DrawImageTests.cs} (99%) diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs similarity index 99% rename from tests/ImageSharp.Tests/Drawing/DrawImageTest.cs rename to tests/ImageSharp.Tests/Drawing/DrawImageTests.cs index 54b04390ec..0d0a821251 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs @@ -8,10 +8,10 @@ using SixLabors.Primitives; using Xunit; -namespace SixLabors.ImageSharp.Tests +namespace SixLabors.ImageSharp.Tests.Drawing { [GroupOutput("Drawing")] - public class DrawImageTest : FileTestBase + public class DrawImageTests : FileTestBase { private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32; From 5733661aca0e5ba69bf78a10db54d784fb93ac90 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 18 May 2019 12:49:35 +0200 Subject: [PATCH 5/9] Using tolerant comparer for the Encode_8BitColor tests --- tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index 36bd2bbdb3..f4c3154daf 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -198,7 +198,7 @@ public void Encode_8BitColor_WithWuQuantizer(TestImageProvider p IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); using (var referenceImage = Image.Load(actualOutputFile, referenceDecoder)) { - referenceImage.CompareToReferenceOutput(ImageComparer.Exact, provider, extension: "bmp", appendPixelTypeToFileName: false); + referenceImage.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.01f), provider, extension: "bmp", appendPixelTypeToFileName: false); } } } @@ -219,7 +219,7 @@ public void Encode_8BitColor_WithOctreeQuantizer(TestImageProvider(actualOutputFile, referenceDecoder)) { - referenceImage.CompareToReferenceOutput(ImageComparer.Exact, provider, extension: "bmp", appendPixelTypeToFileName: false); + referenceImage.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.01f), provider, extension: "bmp", appendPixelTypeToFileName: false); } } } From d98f003b91cbf0af787329622ad16a51e8aad0be Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 18 May 2019 19:06:44 +0200 Subject: [PATCH 6/9] Removed the Encode_8BitColor, because of the quantization differences with net472 and netcore --- .../Formats/Bmp/BmpEncoderTests.cs | 42 ------------------- 1 file changed, 42 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index f4c3154daf..9208de694d 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -182,48 +182,6 @@ public void Encode_8BitGray_WithV4Header_Works(TestImageProvider bitsPerPixel, supportTransparency: true); - [Theory] - [WithFile(Bit32Rgb, PixelTypes.Rgba32)] - public void Encode_8BitColor_WithWuQuantizer(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - var encoder = new BmpEncoder - { - BitsPerPixel = BmpBitsPerPixel.Pixel8, - Quantizer = new WuQuantizer(256) - }; - string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); - IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); - using (var referenceImage = Image.Load(actualOutputFile, referenceDecoder)) - { - referenceImage.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.01f), provider, extension: "bmp", appendPixelTypeToFileName: false); - } - } - } - - [Theory] - [WithFile(Bit32Rgb, PixelTypes.Rgba32)] - public void Encode_8BitColor_WithOctreeQuantizer(TestImageProvider provider) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - { - var encoder = new BmpEncoder - { - BitsPerPixel = BmpBitsPerPixel.Pixel8, - Quantizer = new OctreeQuantizer(256) - }; - string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); - IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); - using (var referenceImage = Image.Load(actualOutputFile, referenceDecoder)) - { - referenceImage.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.01f), provider, extension: "bmp", appendPixelTypeToFileName: false); - } - } - } - [Theory] [WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] [WithFile(Bit32Rgba, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] From e62ae795c76212018c872e14eab9c28d7552d1b5 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 19 May 2019 18:34:07 +0200 Subject: [PATCH 7/9] Re-enabled Encode_8BitColor tests, if its 64 bit process --- .../Formats/Bmp/BmpEncoderTests.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index 9208de694d..178e652ae7 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -182,6 +182,58 @@ public void Encode_8BitGray_WithV4Header_Works(TestImageProvider bitsPerPixel, supportTransparency: true); + [Theory] + [WithFile(Bit32Rgb, PixelTypes.Rgba32)] + public void Encode_8BitColor_WithWuQuantizer(TestImageProvider provider) + where TPixel : struct, IPixel + { + if (!TestEnvironment.Is64BitProcess) + { + return; + } + + using (Image image = provider.GetImage()) + { + var encoder = new BmpEncoder + { + BitsPerPixel = BmpBitsPerPixel.Pixel8, + Quantizer = new WuQuantizer(256) + }; + string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); + IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); + using (var referenceImage = Image.Load(actualOutputFile, referenceDecoder)) + { + referenceImage.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.01f), provider, extension: "bmp", appendPixelTypeToFileName: false); + } + } + } + + [Theory] + [WithFile(Bit32Rgb, PixelTypes.Rgba32)] + public void Encode_8BitColor_WithOctreeQuantizer(TestImageProvider provider) + where TPixel : struct, IPixel + { + if (!TestEnvironment.Is64BitProcess) + { + return; + } + + using (Image image = provider.GetImage()) + { + var encoder = new BmpEncoder + { + BitsPerPixel = BmpBitsPerPixel.Pixel8, + Quantizer = new OctreeQuantizer(256) + }; + string actualOutputFile = provider.Utility.SaveTestOutputFile(image, "bmp", encoder, appendPixelTypeToFileName: false); + IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile); + using (var referenceImage = Image.Load(actualOutputFile, referenceDecoder)) + { + referenceImage.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.01f), provider, extension: "bmp", appendPixelTypeToFileName: false); + } + } + } + [Theory] [WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] [WithFile(Bit32Rgba, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] From 88097655b8111ca2b9956d9e620dababa47c5b76 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 19 May 2019 20:56:20 +0200 Subject: [PATCH 8/9] Using MemoryMarshal.AsBytes in Write8BitGray to get the bytes of a row --- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 23 +++++++------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index e9600f640d..9fbd0b5adb 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -4,6 +4,7 @@ using System; using System.Buffers; using System.IO; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Common.Helpers; @@ -389,23 +390,15 @@ private void Write8BitGray(Stream stream, ImageFrame image, Span stream.Write(colorPalette); - using (IMemoryOwner rowSpanBuffer = this.memoryAllocator.AllocateManagedByteBuffer(image.Width, AllocationOptions.Clean)) + for (int y = image.Height - 1; y >= 0; y--) { - Span outputPixelRow = rowSpanBuffer.GetSpan(); - for (int y = image.Height - 1; y >= 0; y--) - { - Span inputPixelRow = image.GetPixelRowSpan(y); - PixelOperations.Instance.ToGray8Bytes( - this.configuration, - inputPixelRow, - outputPixelRow, - image.Width); - stream.Write(outputPixelRow); + ReadOnlySpan inputPixelRow = image.GetPixelRowSpan(y); + ReadOnlySpan outputPixelRow = MemoryMarshal.AsBytes(inputPixelRow); + stream.Write(outputPixelRow); - for (int i = 0; i < this.padding; i++) - { - stream.WriteByte(0); - } + for (int i = 0; i < this.padding; i++) + { + stream.WriteByte(0); } } } From bb8911dd392fa58e8c30b5e1b78bb24383df8993 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 19 May 2019 21:28:39 +0200 Subject: [PATCH 9/9] Fix merge mistake: Using DrawImageTests from current master branch --- .../Drawing/DrawImageTests.cs | 228 +++++++++--------- 1 file changed, 116 insertions(+), 112 deletions(-) diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs index 0d0a821251..b2d499c7fa 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawImageTests.cs @@ -2,58 +2,37 @@ // Licensed under the Apache License, Version 2.0. using System; + +using SixLabors.ImageSharp.Formats; +using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.Primitives; +using SixLabors.Shapes; + using Xunit; namespace SixLabors.ImageSharp.Tests.Drawing { [GroupOutput("Drawing")] - public class DrawImageTests : FileTestBase + public class DrawImageTests { - private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32; - - public static readonly string[] TestFiles = { - TestImages.Jpeg.Baseline.Calliphora, - TestImages.Bmp.Car, - TestImages.Png.Splash, - TestImages.Gif.Rings - }; - - [Theory] - [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Normal)] - [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Multiply)] - [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Add)] - [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Subtract)] - [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Screen)] - [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Darken)] - [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Lighten)] - [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Overlay)] - [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.HardLight)] - public void ImageShouldApplyDrawImage(TestImageProvider provider, PixelColorBlendingMode mode) - where TPixel : struct, IPixel - { - using (Image image = provider.GetImage()) - using (var blend = Image.Load(TestFile.Create(TestImages.Bmp.Car).Bytes)) + public static readonly TheoryData BlendingModes = new TheoryData { - blend.Mutate(x => x.Resize(image.Width / 2, image.Height / 2)); - image.Mutate(x => x.DrawImage(blend, new Point(image.Width / 4, image.Height / 4), mode, .75f)); - image.DebugSave(provider, new { mode }); - } - } + PixelColorBlendingMode.Normal, + PixelColorBlendingMode.Multiply, + PixelColorBlendingMode.Add, + PixelColorBlendingMode.Subtract, + PixelColorBlendingMode.Screen, + PixelColorBlendingMode.Darken, + PixelColorBlendingMode.Lighten, + PixelColorBlendingMode.Overlay, + PixelColorBlendingMode.HardLight, + }; [Theory] - [WithFile(TestImages.Png.Rainbow, PixelTypes, PixelColorBlendingMode.Normal)] - [WithFile(TestImages.Png.Rainbow, PixelTypes, PixelColorBlendingMode.Multiply)] - [WithFile(TestImages.Png.Rainbow, PixelTypes, PixelColorBlendingMode.Add)] - [WithFile(TestImages.Png.Rainbow, PixelTypes, PixelColorBlendingMode.Subtract)] - [WithFile(TestImages.Png.Rainbow, PixelTypes, PixelColorBlendingMode.Screen)] - [WithFile(TestImages.Png.Rainbow, PixelTypes, PixelColorBlendingMode.Darken)] - [WithFile(TestImages.Png.Rainbow, PixelTypes, PixelColorBlendingMode.Lighten)] - [WithFile(TestImages.Png.Rainbow, PixelTypes, PixelColorBlendingMode.Overlay)] - [WithFile(TestImages.Png.Rainbow, PixelTypes, PixelColorBlendingMode.HardLight)] + [WithFile(TestImages.Png.Rainbow, nameof(BlendingModes), PixelTypes.Rgba32)] public void ImageBlendingMatchesSvgSpecExamples(TestImageProvider provider, PixelColorBlendingMode mode) where TPixel : struct, IPixel { @@ -61,96 +40,139 @@ public void ImageBlendingMatchesSvgSpecExamples(TestImageProvider(TestFile.Create(TestImages.Png.Ducky).Bytes)) { background.Mutate(x => x.DrawImage(source, mode, 1F)); - VerifyImage(provider, mode, background); + background.DebugSave( + provider, + new { mode = mode }, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); + + var comparer = ImageComparer.TolerantPercentage(0.01F); + background.CompareToReferenceOutput(comparer, + provider, + new { mode = mode }, + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); } } [Theory] - [WithFileCollection(nameof(TestFiles), PixelTypes, PixelColorBlendingMode.Normal)] - public void ImageShouldDrawTransformedImage(TestImageProvider provider, PixelColorBlendingMode mode) + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Normal, 1f)] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Bgr24, TestImages.Png.Bike, PixelColorBlendingMode.Normal, 1f)] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Normal, 0.75f)] + [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Normal, 0.25f)] + + [WithTestPatternImages(400, 400, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Multiply, 0.5f)] + [WithTestPatternImages(400, 400, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Add, 0.5f)] + [WithTestPatternImages(400, 400, PixelTypes.Rgba32, TestImages.Png.Splash, PixelColorBlendingMode.Subtract, 0.5f)] + + [WithFile(TestImages.Png.Rgb48Bpp, PixelTypes.Rgba64, TestImages.Png.Splash, PixelColorBlendingMode.Normal, 1f)] + [WithFile(TestImages.Png.Rgb48Bpp, PixelTypes.Rgba64, TestImages.Png.Splash, PixelColorBlendingMode.Normal, 0.25f)] + public void WorksWithDifferentConfigurations( + TestImageProvider provider, + string brushImage, + PixelColorBlendingMode mode, + float opacity) where TPixel : struct, IPixel { using (Image image = provider.GetImage()) - using (var blend = Image.Load(TestFile.Create(TestImages.Bmp.Car).Bytes)) + using (var blend = Image.Load(TestFile.Create(brushImage).Bytes)) { - AffineTransformBuilder builder = new AffineTransformBuilder() - .AppendRotationDegrees(45F) - .AppendScale(new SizeF(.25F, .25F)) - .AppendTranslation(new PointF(10, 10)); + Size size = new Size(image.Width * 3 / 4, image.Height * 3 / 4); + Point position = new Point(image.Width / 8, image.Height / 8); + blend.Mutate(x => x.Resize(size.Width, size.Height, KnownResamplers.Bicubic)); + image.Mutate(x => x.DrawImage(blend, position, mode, opacity)); + FormattableString testInfo = $"{System.IO.Path.GetFileNameWithoutExtension(brushImage)}-{mode}-{opacity}"; - // Apply a background color so we can see the translation. - blend.Mutate(x => x.Transform(builder)); - blend.Mutate(x => x.BackgroundColor(Color.HotPink)); + PngEncoder encoder = new PngEncoder(); - // Lets center the matrix so we can tell whether any cut-off issues we may have belong to the drawing processor - var position = new Point((image.Width - blend.Width) / 2, (image.Height - blend.Height) / 2); - image.Mutate(x => x.DrawImage(blend, position, mode, .75F)); - image.DebugSave(provider, new[] { "Transformed" }); + if (provider.PixelType == PixelTypes.Rgba64) + { + encoder.BitDepth = PngBitDepth.Bit16; + } + + image.DebugSave(provider, testInfo, encoder: encoder); + image.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.01f), + provider, + testInfo); } } [Theory] - [WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32)] - public void ImageShouldHandleNegativeLocation(TestImageProvider provider) + [WithTestPatternImages(200, 200, PixelTypes.Rgba32 | PixelTypes.Bgra32)] + public void DrawImageOfDifferentPixelType(TestImageProvider provider) + where TPixel : struct, IPixel { - using (Image background = provider.GetImage()) - using (var overlay = new Image(50, 50)) - { - overlay.Mutate(x => x.Fill(Rgba32.Black)); - - const int xy = -25; - Rgba32 backgroundPixel = background[0, 0]; - Rgba32 overlayPixel = overlay[Math.Abs(xy) + 1, Math.Abs(xy) + 1]; + byte[] brushData = TestFile.Create(TestImages.Png.Ducky).Bytes; - background.Mutate(x => x.DrawImage(overlay, new Point(xy, xy), PixelColorBlendingMode.Normal, 1F)); - - Assert.Equal(Rgba32.White, backgroundPixel); - Assert.Equal(overlayPixel, background[0, 0]); + using (Image image = provider.GetImage()) + using (Image brushImage = provider.PixelType == PixelTypes.Rgba32 + ? (Image)Image.Load(brushData) + : Image.Load(brushData)) + { + image.Mutate(c => c.DrawImage(brushImage, 0.5f)); - background.DebugSave(provider, testOutputDetails: "Negative"); + image.DebugSave(provider, appendSourceFileOrDescription: false); + image.CompareToReferenceOutput( + ImageComparer.TolerantPercentage(0.01f), + provider, + appendSourceFileOrDescription: false); } } [Theory] - [WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32)] - public void ImageShouldHandlePositiveLocation(TestImageProvider provider) + [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 0, 0)] + [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 25, 25)] + [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, 75, 50)] + [WithSolidFilledImages(100, 100, "White", PixelTypes.Rgba32, -25, -30)] + public void WorksWithDifferentLocations(TestImageProvider provider, int x, int y) { using (Image background = provider.GetImage()) using (var overlay = new Image(50, 50)) { - overlay.Mutate(x => x.Fill(Rgba32.Black)); - - const int xy = 25; - Rgba32 backgroundPixel = background[xy - 1, xy - 1]; - Rgba32 overlayPixel = overlay[0, 0]; + overlay.Mutate(c => c.Fill(Rgba32.Black)); - background.Mutate(x => x.DrawImage(overlay, new Point(xy, xy), PixelColorBlendingMode.Normal, 1F)); + background.Mutate(c => c.DrawImage(overlay, new Point(x, y), PixelColorBlendingMode.Normal, 1F)); - Assert.Equal(Rgba32.White, backgroundPixel); - Assert.Equal(overlayPixel, background[xy, xy]); + background.DebugSave( + provider, + testOutputDetails: $"{x}_{y}", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); - background.DebugSave(provider, testOutputDetails: "Positive"); + background.CompareToReferenceOutput( + provider, + testOutputDetails: $"{x}_{y}", + appendPixelTypeToFileName: false, + appendSourceFileOrDescription: false); } } + [Theory] - [WithSolidFilledImages(100, 100, 255, 255, 255, PixelTypes.Rgba32)] - public void ImageShouldHandlePositiveLocationTruncatedOverlay(TestImageProvider provider) + [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] + public void DrawTransformed(TestImageProvider provider) + where TPixel : struct, IPixel { - using (Image background = provider.GetImage()) - using (var overlay = new Image(50, 50)) + using (Image image = provider.GetImage()) + using (var blend = Image.Load(TestFile.Create(TestImages.Bmp.Car).Bytes)) { - overlay.Mutate(x => x.Fill(Rgba32.Black)); - - const int xy = 75; - Rgba32 backgroundPixel = background[xy - 1, xy - 1]; - Rgba32 overlayPixel = overlay[0, 0]; + AffineTransformBuilder builder = new AffineTransformBuilder() + .AppendRotationDegrees(45F) + .AppendScale(new SizeF(.25F, .25F)) + .AppendTranslation(new PointF(10, 10)); - background.Mutate(x => x.DrawImage(overlay, new Point(xy, xy), PixelColorBlendingMode.Normal, 1F)); + // Apply a background color so we can see the translation. + blend.Mutate(x => x.Transform(builder)); + blend.Mutate(x => x.BackgroundColor(Color.HotPink)); - Assert.Equal(Rgba32.White, backgroundPixel); - Assert.Equal(overlayPixel, background[xy, xy]); + // Lets center the matrix so we can tell whether any cut-off issues we may have belong to the drawing processor + var position = new Point((image.Width - blend.Width) / 2, (image.Height - blend.Height) / 2); + image.Mutate(x => x.DrawImage(blend, position, .75F)); - background.DebugSave(provider, testOutputDetails: "PositiveTruncated"); + image.DebugSave(provider, appendSourceFileOrDescription: false, appendPixelTypeToFileName: false); + image.CompareToReferenceOutput(ImageComparer.TolerantPercentage(0.002f), + provider, + appendSourceFileOrDescription: false, + appendPixelTypeToFileName: false); } } @@ -175,24 +197,6 @@ void Test() } } - private static void VerifyImage( - TestImageProvider provider, - PixelColorBlendingMode mode, - Image img) - where TPixel : struct, IPixel - { - img.DebugSave( - provider, - new { mode }, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - - var comparer = ImageComparer.TolerantPercentage(0.01F, 3); - img.CompareFirstFrameToReferenceOutput(comparer, - provider, - new { mode }, - appendPixelTypeToFileName: false, - appendSourceFileOrDescription: false); - } + } } \ No newline at end of file