From 3431ad321ed48e440712b60fe1ca227708e3ee58 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 7 May 2019 19:58:14 +0200 Subject: [PATCH 1/8] Add support for encoding 8-bit bitmaps --- src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs | 7 +- src/ImageSharp/Formats/Bmp/BmpEncoder.cs | 1 - src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 60 +++++++++++++++++- .../Formats/Bmp/IBmpEncoderOptions.cs | 1 - .../Quantization/QuantizedFrame{TPixel}.cs | 2 +- .../Formats/Bmp/BmpEncoderTests.cs | 9 ++- tests/ImageSharp.Tests/TestImages.cs | 1 + .../TestUtilities/PixelTypes.cs | 2 + tests/Images/Input/Bmp/pal8gs.bmp | Bin 0 -> 9254 bytes 9 files changed, 76 insertions(+), 7 deletions(-) create mode 100644 tests/Images/Input/Bmp/pal8gs.bmp diff --git a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs index 38f5c1d662..6e1145beb7 100644 --- a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs +++ b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs @@ -4,10 +4,15 @@ namespace SixLabors.ImageSharp.Formats.Bmp { /// - /// Enumerates the available bits per pixel for bitmap. + /// Enumerates the available bits per pixel the bitmap encoder supports. /// public enum BmpBitsPerPixel : short { + /// + /// 8 bits per pixel. Each pixel consists of 1 byte. + /// + Pixel8 = 8, + /// /// 16 bits per pixel. Each pixel consists of 2 bytes. /// diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index 4efdedb349..f3eb9282ce 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -10,7 +10,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// Image encoder for writing an image to a stream as a Windows bitmap. /// - /// The encoder can currently only write 24-bit rgb images to streams. public sealed class BmpEncoder : IImageEncoder, IBmpEncoderOptions { /// diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index 82483e3902..530bcf8cd2 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -9,6 +9,7 @@ using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.Memory; namespace SixLabors.ImageSharp.Formats.Bmp @@ -43,6 +44,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// private const int Rgba32BlueMask = 0xFF; + /// + /// The color palette for an 8 bit image will have 256 entry's with 4 bytes for each entry. + /// + private const int ColorPaletteSize8Bit = 1024; + private readonly MemoryAllocator memoryAllocator; private Configuration configuration; @@ -142,11 +148,13 @@ namespace SixLabors.ImageSharp.Formats.Bmp infoHeader.Compression = BmpCompression.BitFields; } + int colorPaletteSize = this.bitsPerPixel == BmpBitsPerPixel.Pixel8 ? ColorPaletteSize8Bit : 0; + var fileHeader = new BmpFileHeader( type: BmpConstants.TypeMarkers.Bitmap, fileSize: BmpFileHeader.Size + infoHeaderSize + infoHeader.ImageSize, reserved: 0, - offset: BmpFileHeader.Size + infoHeaderSize); + offset: BmpFileHeader.Size + infoHeaderSize + colorPaletteSize); #if NETCOREAPP2_1 Span buffer = stackalloc byte[infoHeaderSize]; @@ -198,6 +206,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp case BmpBitsPerPixel.Pixel16: this.Write16Bit(stream, pixels); break; + + case BmpBitsPerPixel.Pixel8: + this.Write8Bit(stream, image); + break; } } @@ -276,5 +288,51 @@ namespace SixLabors.ImageSharp.Formats.Bmp } } } + + /// + /// Writes an 8 Bit 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. + private void Write8Bit(Stream stream, ImageFrame image) + where TPixel : struct, IPixel + { +#if NETCOREAPP2_1 + Span colorPalette = stackalloc byte[ColorPaletteSize8Bit]; +#else + byte[] colorPalette = new byte[ColorPaletteSize8Bit]; +#endif + + var quantizer = new WuQuantizer(256); + QuantizedFrame quantized = quantizer.CreateFrameQuantizer(this.configuration).QuantizeFrame(image); + + int idx = 0; + var color = default(Rgba32); + foreach (TPixel quantizedColor in quantized.Palette) + { + 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, 0, ColorPaletteSize8Bit); + + for (int y = image.Height - 1; y >= 0; y--) + { + Span pixelSpan = quantized.GetRowSpan(y); + stream.Write(pixelSpan); + + for (int i = 0; i < this.padding; i++) + { + stream.WriteByte(0); + } + } + } } } diff --git a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs index 96ec423e7b..92ff092705 100644 --- a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs +++ b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs @@ -6,7 +6,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// Configuration options for use during bmp encoding /// - /// The encoder can currently only write 16-bit, 24-bit and 32-bit rgb images to streams. internal interface IBmpEncoderOptions { /// diff --git a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs index 38862ef446..30b8bc8469 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/QuantizedFrame{TPixel}.cs @@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Gets the representation of the pixels as a of contiguous memory - /// at row beginning from the the first pixel on that row. + /// at row beginning from the first pixel on that row. /// /// The row. /// The diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index 7e054734e3..42ec3088d9 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -114,6 +114,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] [WithFile(Rgb16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)] [WithFile(Bit16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)] + [WithFile(Bit8Palette4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)] + [WithFile(Bit8Gs, PixelTypes.Gray8, BmpBitsPerPixel.Pixel8)] public void Encode_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) // if supportTransparency is false, a v3 bitmap header will be written where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); @@ -126,11 +128,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] [WithFile(Rgb16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)] [WithFile(Bit16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)] + [WithFile(Bit8Palette4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)] + [WithFile(Bit8Gs, PixelTypes.Gray8, BmpBitsPerPixel.Pixel8)] public void Encode_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); [Theory] [WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] + [WithFile(Bit32Rgba, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] public void Encode_PreservesAlpha(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); @@ -139,8 +144,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp { using (Image image = provider.GetImage()) { - // There is no alpha in bmp with 24 bits per pixels, so the reference image will be made opaque. - if (bitsPerPixel == BmpBitsPerPixel.Pixel24) + // There is no alpha in bmp with less then 32 bits per pixels, so the reference image will be made opaque. + if (bitsPerPixel != BmpBitsPerPixel.Pixel32) { image.Mutate(c => c.MakeOpaque()); } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index f82278ef5d..62b7ae2ec0 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -236,6 +236,7 @@ namespace SixLabors.ImageSharp.Tests public const string Bit1Pal1 = "Bmp/pal1p1.bmp"; public const string Bit4 = "Bmp/pal4.bmp"; public const string Bit8 = "Bmp/test8.bmp"; + public const string Bit8Gs = "Bmp/pal8gs.bmp"; public const string Bit8Inverted = "Bmp/test8-inverted.bmp"; public const string Bit16 = "Bmp/test16.bmp"; public const string Bit16Inverted = "Bmp/test16-inverted.bmp"; diff --git a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs index e4a7572d60..78431f31aa 100644 --- a/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs +++ b/tests/ImageSharp.Tests/TestUtilities/PixelTypes.cs @@ -60,6 +60,8 @@ namespace SixLabors.ImageSharp.Tests Bgra5551 = 1 << 22, + Gray8 = 1 << 23, + // TODO: Add multi-flag entries by rules defined in PackedPixelConverterHelper // "All" is handled as a separate, individual case instead of using bitwise OR diff --git a/tests/Images/Input/Bmp/pal8gs.bmp b/tests/Images/Input/Bmp/pal8gs.bmp new file mode 100644 index 0000000000000000000000000000000000000000..66a0d70dc34f6c25554ea15d779f6114bd042f74 GIT binary patch literal 9254 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 Date: Tue, 7 May 2019 20:43:12 +0200 Subject: [PATCH 2/8] Changed WuQuantizer to OctreeQuantizer --- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index 530bcf8cd2..0ef2046a84 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -304,7 +304,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp byte[] colorPalette = new byte[ColorPaletteSize8Bit]; #endif - var quantizer = new WuQuantizer(256); + var quantizer = new OctreeQuantizer(256); QuantizedFrame quantized = quantizer.CreateFrameQuantizer(this.configuration).QuantizeFrame(image); int idx = 0; From 6ce4efe1783d576b5bd59de25dbdef397146142c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 9 May 2019 20:35:19 +0200 Subject: [PATCH 3/8] Trying MagickReferenceDecoder for the 8bit greyscale image encoder tests --- .../Formats/Bmp/BmpEncoderTests.cs | 50 +++++++++++++++---- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index 42ec3088d9..a56cee47d8 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -2,10 +2,13 @@ // Licensed under the Apache License, Version 2.0. 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.Tests.TestUtilities.ReferenceCodecs; using Xunit; using Xunit.Abstractions; @@ -110,13 +113,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(Bit32Rgba, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] [WithFile(WinBmpv4, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] [WithFile(WinBmpv5, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] - // WinBmpv3 is a 24 bits per pixel image - [WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] - [WithFile(Rgb16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)] - [WithFile(Bit16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)] - [WithFile(Bit8Palette4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)] - [WithFile(Bit8Gs, PixelTypes.Gray8, BmpBitsPerPixel.Pixel8)] - public void Encode_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + public void Encode_32Bit_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) // if supportTransparency is false, a v3 bitmap header will be written where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); @@ -125,13 +122,44 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(Bit32Rgba, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] [WithFile(WinBmpv4, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] [WithFile(WinBmpv5, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + public void Encode_32Bit_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + + [Theory] + // WinBmpv3 is a 24 bits per pixel image + [WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] + public void Encode_24Bit_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); + + [Theory] [WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] + public void Encode_24Bit_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + + + [Theory] + [WithFile(Rgb16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)] + [WithFile(Bit16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)] + public void Encode_16Bit_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); + + [Theory] [WithFile(Rgb16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)] [WithFile(Bit16, PixelTypes.Bgra5551, BmpBitsPerPixel.Pixel16)] + public void Encode_16Bit_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + + [Theory] [WithFile(Bit8Palette4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)] [WithFile(Bit8Gs, PixelTypes.Gray8, BmpBitsPerPixel.Pixel8)] - public void Encode_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + public void Encode_8Bit_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false, referenceDecoder: new MagickReferenceDecoder()); + + [Theory] + [WithFile(Bit8Palette4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)] + [WithFile(Bit8Gs, PixelTypes.Gray8, BmpBitsPerPixel.Pixel8)] + public void Encode_8Bit_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true, referenceDecoder: new MagickReferenceDecoder()); [Theory] [WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] @@ -139,7 +167,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void Encode_PreservesAlpha(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); - private static void TestBmpEncoderCore(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel, bool supportTransparency = true) + private static void TestBmpEncoderCore(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel, bool supportTransparency = true, IImageDecoder referenceDecoder = null) where TPixel : struct, IPixel { using (Image image = provider.GetImage()) @@ -153,7 +181,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp var encoder = new BmpEncoder { BitsPerPixel = bitsPerPixel, SupportTransparency = supportTransparency }; // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder); + image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder, referenceDecoder: referenceDecoder); } } } From b5df1c785c0929c117fcd2d28e0f68e0e838a1d3 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 9 May 2019 22:07:04 +0200 Subject: [PATCH 4/8] Setting dither to false in the OctreeQuantizer --- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 2 +- .../Processors/Quantization/OctreeQuantizer.cs | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index 0ef2046a84..be82e2cab3 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -304,7 +304,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp byte[] colorPalette = new byte[ColorPaletteSize8Bit]; #endif - var quantizer = new OctreeQuantizer(256); + var quantizer = new OctreeQuantizer(dither: false, maxColors: 256); QuantizedFrame quantized = quantizer.CreateFrameQuantizer(this.configuration).QuantizeFrame(image); int idx = 0; diff --git a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs index d49023886b..f5fa8c95d9 100644 --- a/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs +++ b/src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer.cs @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Initializes a new instance of the class. /// - /// Whether to apply dithering to the output image + /// Whether to apply dithering to the output image. public OctreeQuantizer(bool dither) : this(GetDiffuser(dither), QuantizerConstants.MaxColors) { @@ -44,7 +44,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Initializes a new instance of the class. /// - /// The error diffusion algorithm, if any, to apply to the output image + /// The maximum number of colors to hold in the color palette. + /// Whether to apply dithering to the output image. + public OctreeQuantizer(bool dither, int maxColors) + : this(GetDiffuser(dither), maxColors) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error diffusion algorithm, if any, to apply to the output image. public OctreeQuantizer(IErrorDiffuser diffuser) : this(diffuser, QuantizerConstants.MaxColors) { @@ -53,8 +63,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization /// /// Initializes a new instance of the class. /// - /// The error diffusion algorithm, if any, to apply to the output image - /// The maximum number of colors to hold in the color palette + /// The error diffusion algorithm, if any, to apply to the output image. + /// The maximum number of colors to hold in the color palette. public OctreeQuantizer(IErrorDiffuser diffuser, int maxColors) { this.Diffuser = diffuser; From e0afde9967924a0d54a98479ea99f42c224b32a8 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 10 May 2019 20:28:46 +0200 Subject: [PATCH 5/8] Switched back to the default reference decoder --- tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index a56cee47d8..f3d6e0fa8e 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -153,13 +153,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [WithFile(Bit8Palette4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)] [WithFile(Bit8Gs, PixelTypes.Gray8, BmpBitsPerPixel.Pixel8)] public void Encode_8Bit_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false, referenceDecoder: new MagickReferenceDecoder()); + where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); [Theory] [WithFile(Bit8Palette4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)] [WithFile(Bit8Gs, PixelTypes.Gray8, BmpBitsPerPixel.Pixel8)] public void Encode_8Bit_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true, referenceDecoder: new MagickReferenceDecoder()); + where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); [Theory] [WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] @@ -167,7 +167,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public void Encode_PreservesAlpha(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); - private static void TestBmpEncoderCore(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel, bool supportTransparency = true, IImageDecoder referenceDecoder = null) + private static void TestBmpEncoderCore(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel, bool supportTransparency = true) where TPixel : struct, IPixel { using (Image image = provider.GetImage()) @@ -181,7 +181,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp var encoder = new BmpEncoder { BitsPerPixel = bitsPerPixel, SupportTransparency = supportTransparency }; // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder, referenceDecoder: referenceDecoder); + image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder); } } } From e8695732b6c3367d30003cb2e563119312a58a36 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 11 May 2019 13:52:18 +0200 Subject: [PATCH 6/8] Using tolerant comparer for the Bit8Gs image --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 3 +- .../Formats/Bmp/BmpEncoderTests.cs | 37 ++++++++++++++++--- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 9d9c7b624a..0cbc4fca1b 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -1022,7 +1022,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.bmpMetadata.InfoHeaderType = infoHeaderType; // We can only encode at these bit rates so far. - if (bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel16) + if (bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel8) + || bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel16) || bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel24) || bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel32)) { diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index f3d6e0fa8e..1d489e69dc 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -3,12 +3,11 @@ 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.Tests.TestUtilities.ReferenceCodecs; +using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using Xunit; using Xunit.Abstractions; @@ -128,11 +127,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] // WinBmpv3 is a 24 bits per pixel image [WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] + [WithFile(F, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] public void Encode_24Bit_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); [Theory] [WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] + [WithFile(F, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] public void Encode_24Bit_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); @@ -150,24 +151,48 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); [Theory] + [WithFile(WinBmpv5, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)] [WithFile(Bit8Palette4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)] - [WithFile(Bit8Gs, PixelTypes.Gray8, BmpBitsPerPixel.Pixel8)] public void Encode_8Bit_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); [Theory] + [WithFile(WinBmpv5, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)] [WithFile(Bit8Palette4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel8)] - [WithFile(Bit8Gs, PixelTypes.Gray8, BmpBitsPerPixel.Pixel8)] public void Encode_8Bit_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + [Theory] + [WithFile(Bit8Gs, PixelTypes.Gray8, BmpBitsPerPixel.Pixel8)] + public void Encode_8BitGray_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : struct, IPixel => + TestBmpEncoderCore( + provider, + bitsPerPixel, + supportTransparency: false, + ImageComparer.TolerantPercentage(0.01f)); + + [Theory] + [WithFile(Bit8Gs, PixelTypes.Gray8, BmpBitsPerPixel.Pixel8)] + public void Encode_8BitGray_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : struct, IPixel => + TestBmpEncoderCore( + provider, + bitsPerPixel, + supportTransparency: true, + ImageComparer.TolerantPercentage(0.01f)); + [Theory] [WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] [WithFile(Bit32Rgba, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)] public void Encode_PreservesAlpha(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); - private static void TestBmpEncoderCore(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel, bool supportTransparency = true) + private static void TestBmpEncoderCore( + TestImageProvider provider, + BmpBitsPerPixel bitsPerPixel, + bool supportTransparency = true, + ImageComparer customComparer = null) where TPixel : struct, IPixel { using (Image image = provider.GetImage()) @@ -181,7 +206,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp var encoder = new BmpEncoder { BitsPerPixel = bitsPerPixel, SupportTransparency = supportTransparency }; // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder); + image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder, customComparer); } } } From 7b8f9ef14f45e95aafb76f52dfe7fcda0e8339f7 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 11 May 2019 16:27:51 +0200 Subject: [PATCH 7/8] Enabled dither again --- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index be82e2cab3..8d0e8578d1 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -304,7 +304,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp byte[] colorPalette = new byte[ColorPaletteSize8Bit]; #endif - var quantizer = new OctreeQuantizer(dither: false, maxColors: 256); + var quantizer = new OctreeQuantizer(dither: true, maxColors: 256); QuantizedFrame quantized = quantizer.CreateFrameQuantizer(this.configuration).QuantizeFrame(image); int idx = 0; From b1d29476d10eb9ddac35811cd687870ec41ed319 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 12 May 2019 18:38:22 +0200 Subject: [PATCH 8/8] Review changes --- src/ImageSharp/Formats/Bmp/BmpEncoder.cs | 7 +++ src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 61 ++++++++++--------- .../Formats/Bmp/IBmpEncoderOptions.cs | 9 ++- 3 files changed, 47 insertions(+), 30 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index f3eb9282ce..612675c33c 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -4,6 +4,7 @@ using System.IO; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Processing.Processors.Quantization; namespace SixLabors.ImageSharp.Formats.Bmp { @@ -25,6 +26,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// public bool SupportTransparency { get; set; } + /// + /// Gets or sets the quantizer for reducing the color count for 8-Bit images. + /// Defaults to OctreeQuantizer. + /// + public IQuantizer Quantizer { get; set; } + /// public void Encode(Image image, Stream stream) where TPixel : struct, IPixel diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index 8d0e8578d1..90ea673d3e 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Buffers; using System.IO; using SixLabors.ImageSharp.Advanced; @@ -62,6 +63,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// private readonly bool writeV4Header; + /// + /// The quantizer for reducing the color count for 8-Bit images. + /// + private readonly IQuantizer quantizer; + /// /// Initializes a new instance of the class. /// @@ -72,6 +78,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.memoryAllocator = memoryAllocator; this.bitsPerPixel = options.BitsPerPixel; this.writeV4Header = options.SupportTransparency; + this.quantizer = options.Quantizer ?? new OctreeQuantizer(dither: true, maxColors: 256); } /// @@ -298,39 +305,35 @@ namespace SixLabors.ImageSharp.Formats.Bmp private void Write8Bit(Stream stream, ImageFrame image) where TPixel : struct, IPixel { -#if NETCOREAPP2_1 - Span colorPalette = stackalloc byte[ColorPaletteSize8Bit]; -#else - byte[] colorPalette = new byte[ColorPaletteSize8Bit]; -#endif - - var quantizer = new OctreeQuantizer(dither: true, maxColors: 256); - QuantizedFrame quantized = quantizer.CreateFrameQuantizer(this.configuration).QuantizeFrame(image); - - int idx = 0; - var color = default(Rgba32); - foreach (TPixel quantizedColor in quantized.Palette) + using (IMemoryOwner colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize8Bit, AllocationOptions.Clean)) + using (QuantizedFrame quantized = this.quantizer.CreateFrameQuantizer(this.configuration, 256).QuantizeFrame(image)) { - 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, 0, ColorPaletteSize8Bit); + Span colorPalette = colorPaletteBuffer.GetSpan(); + int idx = 0; + var color = default(Rgba32); + foreach (TPixel quantizedColor in quantized.Palette) + { + 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 (int y = image.Height - 1; y >= 0; y--) - { - Span pixelSpan = quantized.GetRowSpan(y); - stream.Write(pixelSpan); + stream.Write(colorPalette); - for (int i = 0; i < this.padding; i++) + for (int y = image.Height - 1; y >= 0; y--) { - stream.WriteByte(0); + Span pixelSpan = quantized.GetRowSpan(y); + stream.Write(pixelSpan); + + for (int i = 0; i < this.padding; i++) + { + stream.WriteByte(0); + } } } } diff --git a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs index 92ff092705..59ad929df7 100644 --- a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs +++ b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs @@ -1,10 +1,12 @@ // Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using SixLabors.ImageSharp.Processing.Processors.Quantization; + namespace SixLabors.ImageSharp.Formats.Bmp { /// - /// Configuration options for use during bmp encoding + /// Configuration options for use during bmp encoding. /// internal interface IBmpEncoderOptions { @@ -20,5 +22,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// Instead a bitmap version 4 info header will be written with the BITFIELDS compression. /// bool SupportTransparency { get; } + + /// + /// Gets the quantizer for reducing the color count for 8-Bit images. + /// + IQuantizer Quantizer { get; } } } \ No newline at end of file