From c05d4ddeb5eef4e6c2a0fa3c192c4aa0394473b8 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 4 May 2021 20:12:00 +0200 Subject: [PATCH 1/5] Add support for encoding 4 bit per pixel bitmaps --- src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs | 9 +- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 98 ++++++++++++++++--- .../Formats/Bmp/BmpEncoderTests.cs | 16 ++- 3 files changed, 104 insertions(+), 19 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs index 6fdf8d6342..87f3b7e319 100644 --- a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs +++ b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. namespace SixLabors.ImageSharp.Formats.Bmp @@ -8,6 +8,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// public enum BmpBitsPerPixel : short { + /// + /// 4 bits per pixel. + /// + Pixel4 = 4, + /// /// 8 bits per pixel. Each pixel consists of 1 byte. /// @@ -28,4 +33,4 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// Pixel32 = 32 } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index 7819b1ebdb..7733b718ae 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -51,6 +51,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// private const int ColorPaletteSize8Bit = 1024; + /// + /// The color palette for an 4 bit image will have 16 entry's with 4 bytes for each entry. + /// + private const int ColorPaletteSize4Bit = 64; + /// /// Used for allocating memory during processing operations. /// @@ -107,7 +112,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.configuration = image.GetConfiguration(); ImageMetadata metadata = image.Metadata; BmpMetadata bmpMetadata = metadata.GetBmpMetadata(); - this.bitsPerPixel = this.bitsPerPixel ?? bmpMetadata.BitsPerPixel; + this.bitsPerPixel ??= bmpMetadata.BitsPerPixel; short bpp = (short)this.bitsPerPixel; int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32); @@ -166,7 +171,15 @@ namespace SixLabors.ImageSharp.Formats.Bmp infoHeader.Compression = BmpCompression.BitFields; } - int colorPaletteSize = this.bitsPerPixel == BmpBitsPerPixel.Pixel8 ? ColorPaletteSize8Bit : 0; + int colorPaletteSize = 0; + if (this.bitsPerPixel == BmpBitsPerPixel.Pixel8) + { + colorPaletteSize = ColorPaletteSize8Bit; + } + else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel4) + { + colorPaletteSize = ColorPaletteSize4Bit; + } var fileHeader = new BmpFileHeader( type: BmpConstants.TypeMarkers.Bitmap, @@ -224,6 +237,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp case BmpBitsPerPixel.Pixel8: this.Write8Bit(stream, image); break; + + case BmpBitsPerPixel.Pixel4: + this.Write4BitColor(stream, image); + break; } } @@ -344,16 +361,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration); using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); - ReadOnlySpan quantizedColors = quantized.Palette.Span; - var quantizedColorBytes = quantizedColors.Length * 4; - PixelOperations.Instance.ToBgra32(this.configuration, quantizedColors, MemoryMarshal.Cast(colorPalette.Slice(0, quantizedColorBytes))); - Span colorPaletteAsUInt = MemoryMarshal.Cast(colorPalette); - for (int i = 0; i < colorPaletteAsUInt.Length; i++) - { - colorPaletteAsUInt[i] = colorPaletteAsUInt[i] & 0x00FFFFFF; // Padding byte, always 0. - } - - stream.Write(colorPalette); + ReadOnlySpan quantizedColorPalette = quantized.Palette.Span; + this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); for (int y = image.Height - 1; y >= 0; y--) { @@ -404,5 +413,70 @@ namespace SixLabors.ImageSharp.Formats.Bmp } } } + + /// + /// Writes an 4 Bit color image with a color palette. The color palette has 16 entry's with 4 bytes for each entry. + /// + /// The type of the pixel. + /// The to write to. + /// The containing pixel data. + private void Write4BitColor(Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration, new QuantizerOptions() + { + MaxColors = 16 + }); + using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); + using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize4Bit, AllocationOptions.Clean); + + Span colorPalette = colorPaletteBuffer.GetSpan(); + ReadOnlySpan quantizedColorPalette = quantized.Palette.Span; + this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); + + ReadOnlySpan pixelRowSpan = quantized.GetPixelRowSpan(0); + int rowPadding = pixelRowSpan.Length % 2 != 0 ? this.padding - 1 : this.padding; + for (int y = image.Height - 1; y >= 0; y--) + { + pixelRowSpan = quantized.GetPixelRowSpan(y); + + int endIdx = pixelRowSpan.Length % 2 == 0 ? pixelRowSpan.Length : pixelRowSpan.Length - 1; + for (int i = 0; i < endIdx; i += 2) + { + stream.WriteByte((byte)((pixelRowSpan[i] << 4) | pixelRowSpan[i + 1])); + } + + if (pixelRowSpan.Length % 2 != 0) + { + stream.WriteByte((byte)((pixelRowSpan[pixelRowSpan.Length - 1] << 4) | 0)); + } + + for (int i = 0; i < rowPadding; i++) + { + stream.WriteByte(0); + } + } + } + + /// + /// Writes the color palette to the stream. The color palette has 4 bytes for each entry. + /// + /// The type of the pixel. + /// The to write to. + /// The color palette from the quantized image. + /// A temporary byte span to write the color palette to. + private void WriteColorPalette(Stream stream, ReadOnlySpan quantizedColorPalette, Span colorPalette) + where TPixel : unmanaged, IPixel + { + int quantizedColorBytes = quantizedColorPalette.Length * 4; + PixelOperations.Instance.ToBgra32(this.configuration, quantizedColorPalette, MemoryMarshal.Cast(colorPalette.Slice(0, quantizedColorBytes))); + Span colorPaletteAsUInt = MemoryMarshal.Cast(colorPalette); + for (int i = 0; i < colorPaletteAsUInt.Length; i++) + { + colorPaletteAsUInt[i] = colorPaletteAsUInt[i] & 0x00FFFFFF; // Padding byte, always 0. + } + + stream.Write(colorPalette); + } } } diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index fa63642bd2..35853413ea 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -13,7 +13,6 @@ using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; using Xunit; -using Xunit.Abstractions; using static SixLabors.ImageSharp.Tests.TestImages.Bmp; @@ -41,14 +40,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public static readonly TheoryData BmpBitsPerPixelFiles = new TheoryData { + { Rgb16, BmpBitsPerPixel.Pixel16 }, { Car, BmpBitsPerPixel.Pixel24 }, { Bit32Rgb, BmpBitsPerPixel.Pixel32 } }; - public BmpEncoderTests(ITestOutputHelper output) => this.Output = output; - - private ITestOutputHelper Output { get; } - [Theory] [MemberData(nameof(RatioFiles))] public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) @@ -175,6 +171,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp bitsPerPixel, supportTransparency: false); + [Theory] + [WithFile(Bit4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel4)] + public void Encode_4Bit_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); + + [Theory] + [WithFile(Bit4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel4)] + public void Encode_4Bit_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + [Theory] [WithFile(Bit8Gs, PixelTypes.L8, BmpBitsPerPixel.Pixel8)] public void Encode_8BitGray_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) From 04e6b4135a120019885b1a0e88d0ca020d05cc08 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 4 May 2021 20:26:28 +0200 Subject: [PATCH 2/5] Make sure bitmap encoder preserves 4 bits per pixel, when the input is 4 bit --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 5 +++-- tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 0be0385725..17ba96312a 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -1304,8 +1304,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.bmpMetadata = this.metadata.GetBmpMetadata(); this.bmpMetadata.InfoHeaderType = infoHeaderType; - // We can only encode at these bit rates so far (1 bit and 4 bit are still missing). - if (bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel8) + // We can only encode at these bit rates so far (1 bit per pixel is still missing). + if (bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel4) + || 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 35853413ea..82a93102d1 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -40,6 +40,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public static readonly TheoryData BmpBitsPerPixelFiles = new TheoryData { + { Bit4, BmpBitsPerPixel.Pixel4 }, + { Bit8, BmpBitsPerPixel.Pixel8 }, { Rgb16, BmpBitsPerPixel.Pixel16 }, { Car, BmpBitsPerPixel.Pixel24 }, { Bit32Rgb, BmpBitsPerPixel.Pixel32 } From 60bd3946dedd7fa293e4505d30a0166e0ee7be7b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 4 May 2021 20:38:58 +0200 Subject: [PATCH 3/5] Execute 4bit bitmap encoder tests only on windows --- .../Formats/Bmp/BmpEncoderTests.cs | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index 82a93102d1..f439b3b199 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -175,13 +175,31 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp [Theory] [WithFile(Bit4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel4)] - public void Encode_4Bit_WithV3Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); + public void Encode_4Bit_WithV3Header_Works( + TestImageProvider provider, + BmpBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel + { + // The Magick Reference Decoder can not decode 4-Bit bitmaps, so only execute this on windows. + if (TestEnvironment.IsWindows) + { + TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); + } + } [Theory] [WithFile(Bit4, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel4)] - public void Encode_4Bit_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) - where TPixel : unmanaged, IPixel => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + public void Encode_4Bit_WithV4Header_Works( + TestImageProvider provider, + BmpBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel + { + // The Magick Reference Decoder can not decode 4-Bit bitmaps, so only execute this on windows. + if (TestEnvironment.IsWindows) + { + TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); + } + } [Theory] [WithFile(Bit8Gs, PixelTypes.L8, BmpBitsPerPixel.Pixel8)] From ea8bef4321325a2331436239f76ad3edb944745c Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 5 May 2021 12:28:38 +0200 Subject: [PATCH 4/5] Add support for encoding 1 bit per pixel bitmaps --- src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs | 5 ++ src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 11 +-- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 89 +++++++++++++++++-- .../Formats/Bmp/IBmpEncoderOptions.cs | 7 +- .../Formats/Bmp/BmpEncoderTests.cs | 47 +++++++++- 5 files changed, 139 insertions(+), 20 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs index 87f3b7e319..7801e48a91 100644 --- a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs +++ b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs @@ -8,6 +8,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// public enum BmpBitsPerPixel : short { + /// + /// 1 bit per pixel. + /// + Pixel1 = 1, + /// /// 4 bits per pixel. /// diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 17ba96312a..f6fefda485 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -1303,16 +1303,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp short bitsPerPixel = this.infoHeader.BitsPerPixel; this.bmpMetadata = this.metadata.GetBmpMetadata(); this.bmpMetadata.InfoHeaderType = infoHeaderType; - - // We can only encode at these bit rates so far (1 bit per pixel is still missing). - if (bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel4) - || bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel8) - || bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel16) - || bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel24) - || bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel32)) - { - this.bmpMetadata.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel; - } + this.bmpMetadata.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel; } /// diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index 7733b718ae..b407ad221f 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -56,6 +56,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// private const int ColorPaletteSize4Bit = 64; + /// + /// The color palette for an 1 bit image will have 2 entry's with 4 bytes for each entry. + /// + private const int ColorPaletteSize1Bit = 8; + /// /// Used for allocating memory during processing operations. /// @@ -79,7 +84,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp private readonly bool writeV4Header; /// - /// The quantizer for reducing the color count for 8-Bit images. + /// The quantizer for reducing the color count for 8-Bit, 4-Bit and 1-Bit images. /// private readonly IQuantizer quantizer; @@ -180,6 +185,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp { colorPaletteSize = ColorPaletteSize4Bit; } + else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel1) + { + colorPaletteSize = ColorPaletteSize1Bit; + } var fileHeader = new BmpFileHeader( type: BmpConstants.TypeMarkers.Bitmap, @@ -241,6 +250,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp case BmpBitsPerPixel.Pixel4: this.Write4BitColor(stream, image); break; + + case BmpBitsPerPixel.Pixel1: + this.Write1BitColor(stream, image); + break; } } @@ -325,7 +338,7 @@ 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. + /// 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. @@ -349,7 +362,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp } /// - /// Writes an 8 Bit color image with a color palette. The color palette has 256 entry's with 4 bytes for each entry. + /// 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. @@ -377,7 +390,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp } /// - /// Writes an 8 Bit gray image with a color palette. The color palette has 256 entry's with 4 bytes for each entry. + /// 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. @@ -415,7 +428,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp } /// - /// Writes an 4 Bit color image with a color palette. The color palette has 16 entry's with 4 bytes for each entry. + /// Writes an 4 bit color image with a color palette. The color palette has 16 entry's with 4 bytes for each entry. /// /// The type of the pixel. /// The to write to. @@ -458,6 +471,52 @@ namespace SixLabors.ImageSharp.Formats.Bmp } } + /// + /// Writes a 1 bit image with a color palette. The color palette has 2 entry's with 4 bytes for each entry. + /// + /// The type of the pixel. + /// The to write to. + /// The containing pixel data. + private void Write1BitColor(Stream stream, ImageFrame image) + where TPixel : unmanaged, IPixel + { + using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration, new QuantizerOptions() + { + MaxColors = 2 + }); + using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds()); + using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize1Bit, AllocationOptions.Clean); + + Span colorPalette = colorPaletteBuffer.GetSpan(); + ReadOnlySpan quantizedColorPalette = quantized.Palette.Span; + this.WriteColorPalette(stream, quantizedColorPalette, colorPalette); + + ReadOnlySpan quantizedPixelRow = quantized.GetPixelRowSpan(0); + int rowPadding = quantizedPixelRow.Length % 8 != 0 ? this.padding - 1 : this.padding; + for (int y = image.Height - 1; y >= 0; y--) + { + quantizedPixelRow = quantized.GetPixelRowSpan(y); + + int endIdx = quantizedPixelRow.Length % 8 == 0 ? quantizedPixelRow.Length : quantizedPixelRow.Length - 8; + for (int i = 0; i < endIdx; i += 8) + { + Write1BitPalette(stream, i, i + 8, quantizedPixelRow); + } + + if (quantizedPixelRow.Length % 8 != 0) + { + int startIdx = quantizedPixelRow.Length - 7; + endIdx = quantizedPixelRow.Length; + Write1BitPalette(stream, startIdx, endIdx, quantizedPixelRow); + } + + for (int i = 0; i < rowPadding; i++) + { + stream.WriteByte(0); + } + } + } + /// /// Writes the color palette to the stream. The color palette has 4 bytes for each entry. /// @@ -478,5 +537,25 @@ namespace SixLabors.ImageSharp.Formats.Bmp stream.Write(colorPalette); } + + /// + /// Writes a 1-bit palette. + /// + /// The stream to write the palette to. + /// The start index. + /// The end index. + /// A quantized pixel row. + private static void Write1BitPalette(Stream stream, int startIdx, int endIdx, ReadOnlySpan quantizedPixelRow) + { + int shift = 7; + byte indices = 0; + for (int j = startIdx; j < endIdx; j++) + { + indices = (byte)(indices | ((byte)(quantizedPixelRow[j] & 1) << shift)); + shift--; + } + + stream.WriteByte(indices); + } } } diff --git a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs index d4a22d66ea..ca1fbd0ded 100644 --- a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs +++ b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Six Labors. +// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -24,8 +24,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp bool SupportTransparency { get; } /// - /// Gets the quantizer for reducing the color count for 8-Bit images. + /// Gets the quantizer for reducing the color count for 8-Bit, 4-Bit, and 1-Bit images. + /// Defaults to the Octree Quantizer. /// IQuantizer Quantizer { get; } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index f439b3b199..31e66896d0 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -40,6 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp public static readonly TheoryData BmpBitsPerPixelFiles = new TheoryData { + { Bit1, BmpBitsPerPixel.Pixel1 }, { Bit4, BmpBitsPerPixel.Pixel4 }, { Bit8, BmpBitsPerPixel.Pixel8 }, { Rgb16, BmpBitsPerPixel.Pixel16 }, @@ -201,6 +202,42 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp } } + [Theory] + [WithFile(Bit1, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel1)] + public void Encode_1Bit_WithV3Header_Works( + TestImageProvider provider, + BmpBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel + { + // The Magick Reference Decoder can not decode 1-Bit bitmaps, so only execute this on windows. + if (TestEnvironment.IsWindows) + { + TestBmpEncoderCore( + provider, + bitsPerPixel, + supportTransparency: false, + quantizer: KnownQuantizers.Wu); // using the wu quantizer, because the octree seems to have problems with just two colors. + } + } + + [Theory] + [WithFile(Bit1, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel1)] + public void Encode_1Bit_WithV4Header_Works( + TestImageProvider provider, + BmpBitsPerPixel bitsPerPixel) + where TPixel : unmanaged, IPixel + { + // The Magick Reference Decoder can not decode 1-Bit bitmaps, so only execute this on windows. + if (TestEnvironment.IsWindows) + { + TestBmpEncoderCore( + provider, + bitsPerPixel, + supportTransparency: true, + quantizer: KnownQuantizers.Wu); // using the wu quantizer, because the octree seems to have problems with just two colors. + } + } + [Theory] [WithFile(Bit8Gs, PixelTypes.L8, BmpBitsPerPixel.Pixel8)] public void Encode_8BitGray_WithV4Header_Works(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) @@ -297,7 +334,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp private static void TestBmpEncoderCore( TestImageProvider provider, BmpBitsPerPixel bitsPerPixel, - bool supportTransparency = true, + bool supportTransparency = true, // if set to true, will write a V4 header, otherwise a V3 header. + IQuantizer quantizer = null, ImageComparer customComparer = null) where TPixel : unmanaged, IPixel { @@ -309,7 +347,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp image.Mutate(c => c.MakeOpaque()); } - var encoder = new BmpEncoder { BitsPerPixel = bitsPerPixel, SupportTransparency = supportTransparency }; + var encoder = new BmpEncoder + { + BitsPerPixel = bitsPerPixel, + SupportTransparency = supportTransparency, + Quantizer = quantizer ?? KnownQuantizers.Octree + }; // Does DebugSave & load reference CompareToReferenceInput(): image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder, customComparer); From 33192d396bf343c28faf26c8dcf7bd96aee0b3f0 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 5 May 2021 19:07:07 +0200 Subject: [PATCH 5/5] Switch default quantizer for the bitmap encoder to Wu-quantizer --- src/ImageSharp/Formats/Bmp/BmpEncoder.cs | 2 +- src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 2 +- src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs | 1 - .../Formats/Bmp/BmpEncoderTests.cs | 14 +++----------- 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index 2f5c4b7cf7..f256ed9f81 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// Gets or sets the quantizer for reducing the color count for 8-Bit images. - /// Defaults to OctreeQuantizer. + /// Defaults to Wu Quantizer. /// public IQuantizer Quantizer { get; set; } diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs index b407ad221f..5cf54388d3 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.memoryAllocator = memoryAllocator; this.bitsPerPixel = options.BitsPerPixel; this.writeV4Header = options.SupportTransparency; - this.quantizer = options.Quantizer ?? KnownQuantizers.Octree; + this.quantizer = options.Quantizer ?? KnownQuantizers.Wu; } /// diff --git a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs index ca1fbd0ded..30aa70452e 100644 --- a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs +++ b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs @@ -25,7 +25,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// Gets the quantizer for reducing the color count for 8-Bit, 4-Bit, and 1-Bit images. - /// Defaults to the Octree Quantizer. /// IQuantizer Quantizer { get; } } diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs index 31e66896d0..4eb3b900e1 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -212,11 +212,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp // The Magick Reference Decoder can not decode 1-Bit bitmaps, so only execute this on windows. if (TestEnvironment.IsWindows) { - TestBmpEncoderCore( - provider, - bitsPerPixel, - supportTransparency: false, - quantizer: KnownQuantizers.Wu); // using the wu quantizer, because the octree seems to have problems with just two colors. + TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false); } } @@ -230,11 +226,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp // The Magick Reference Decoder can not decode 1-Bit bitmaps, so only execute this on windows. if (TestEnvironment.IsWindows) { - TestBmpEncoderCore( - provider, - bitsPerPixel, - supportTransparency: true, - quantizer: KnownQuantizers.Wu); // using the wu quantizer, because the octree seems to have problems with just two colors. + TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true); } } @@ -351,7 +343,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp { BitsPerPixel = bitsPerPixel, SupportTransparency = supportTransparency, - Quantizer = quantizer ?? KnownQuantizers.Octree + Quantizer = quantizer ?? KnownQuantizers.Wu }; // Does DebugSave & load reference CompareToReferenceInput():