From 667637746ab2bd0e1c9f069c8cf1a06c78855da9 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sat, 20 Apr 2019 11:17:59 +0200 Subject: [PATCH 1/5] Bitmap encoder will now write a V3 header as default. Introduces a new encoder option `SupportTransparency`: With this option a V4 header will be written with BITFIELDS compression. --- src/ImageSharp/Formats/Bmp/BmpEncoder.cs | 5 +++ src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 45 ++++++++++++------- .../Formats/Bmp/IBmpEncoderOptions.cs | 7 ++- .../Formats/Bmp/BmpEncoderTests.cs | 38 ++++++++++++++-- 4 files changed, 74 insertions(+), 21 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index b1a66accec..2ae5ca78a4 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -18,6 +18,11 @@ public sealed class BmpEncoder : IImageEncoder, IBmpEncoderOptions /// public BmpBitsPerPixel? BitsPerPixel { get; set; } + /// + /// Gets or sets a value indicating whether the encoder should support transparency. + /// + public bool SupportTransparency { 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 ad526f6689..c4c51b78e3 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs @@ -24,22 +24,22 @@ internal sealed class BmpEncoderCore private int padding; /// - /// The mask for the alpha channel of the color for a 32 bit rgba bitmaps. + /// The mask for the alpha channel of the color for 32 bit rgba bitmaps. /// private const int Rgba32AlphaMask = 0xFF << 24; /// - /// The mask for the red part of the color for a 32 bit rgba bitmaps. + /// The mask for the red part of the color for 32 bit rgba bitmaps. /// private const int Rgba32RedMask = 0xFF << 16; /// - /// The mask for the green part of the color for a 32 bit rgba bitmaps. + /// The mask for the green part of the color for 32 bit rgba bitmaps. /// private const int Rgba32GreenMask = 0xFF << 8; /// - /// The mask for the blue part of the color for a 32 bit rgba bitmaps. + /// The mask for the blue part of the color for 32 bit rgba bitmaps. /// private const int Rgba32BlueMask = 0xFF; @@ -49,15 +49,23 @@ internal sealed class BmpEncoderCore private BmpBitsPerPixel? bitsPerPixel; + /// + /// A bitmap v4 header will only be written, if the user explicitly wants support for transparency. + /// In this case the compression type BITFIELDS will be used. + /// Otherwise a bitmap v3 header will be written, which is supported by almost all decoders. + /// + private readonly bool writeV4Header; + /// /// Initializes a new instance of the class. /// - /// The encoder options - /// The memory manager + /// The encoder options. + /// The memory manager. public BmpEncoderCore(IBmpEncoderOptions options, MemoryAllocator memoryAllocator) { this.memoryAllocator = memoryAllocator; this.bitsPerPixel = options.BitsPerPixel; + this.writeV4Header = options.SupportTransparency; } /// @@ -112,7 +120,7 @@ public void Encode(Image image, Stream stream) } } - int infoHeaderSize = BmpInfoHeader.SizeV4; + int infoHeaderSize = this.writeV4Header ? BmpInfoHeader.SizeV4 : BmpInfoHeader.SizeV3; var infoHeader = new BmpInfoHeader( headerSize: infoHeaderSize, height: image.Height, @@ -123,17 +131,15 @@ public void Encode(Image image, Stream stream) clrUsed: 0, clrImportant: 0, xPelsPerMeter: hResolution, - yPelsPerMeter: vResolution) - { - RedMask = Rgba32RedMask, - GreenMask = Rgba32GreenMask, - BlueMask = Rgba32BlueMask, - Compression = BmpCompression.BitFields - }; + yPelsPerMeter: vResolution); - if (this.bitsPerPixel == BmpBitsPerPixel.Pixel32) + if (this.writeV4Header && this.bitsPerPixel == BmpBitsPerPixel.Pixel32) { infoHeader.AlphaMask = Rgba32AlphaMask; + infoHeader.RedMask = Rgba32RedMask; + infoHeader.GreenMask = Rgba32GreenMask; + infoHeader.BlueMask = Rgba32BlueMask; + infoHeader.Compression = BmpCompression.BitFields; } var fileHeader = new BmpFileHeader( @@ -151,7 +157,14 @@ public void Encode(Image image, Stream stream) stream.Write(buffer, 0, BmpFileHeader.Size); - infoHeader.WriteV4Header(buffer); + if (this.writeV4Header) + { + infoHeader.WriteV4Header(buffer); + } + else + { + infoHeader.WriteV3Header(buffer); + } stream.Write(buffer, 0, infoHeaderSize); diff --git a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs index f62504d08f..cb91d8d8b5 100644 --- a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs +++ b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs @@ -6,12 +6,17 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// Configuration options for use during bmp encoding /// - /// The encoder can currently only write 24-bit rgb images to streams. + /// The encoder can currently only write 24-bit and 32-bit rgb images to streams. internal interface IBmpEncoderOptions { /// /// Gets the number of bits per pixel. /// BmpBitsPerPixel? BitsPerPixel { get; } + + /// + /// Gets a value indicating whether the encoder should support transparency. + /// + bool SupportTransparency { 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 c9745ef4f7..c7558ab422 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs @@ -11,6 +11,8 @@ namespace SixLabors.ImageSharp.Tests { + using static TestImages.Bmp; + public class BmpEncoderTests : FileTestBase { public static readonly TheoryData BitsPerPixel = @@ -102,15 +104,43 @@ public void Encode_IsNotBoundToSinglePixelType(TestImageProvider public void Encode_WorksWithDifferentSizes(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) where TPixel : struct, IPixel => TestBmpEncoderCore(provider, bitsPerPixel); - private static void TestBmpEncoderCore(TestImageProvider provider, BmpBitsPerPixel bitsPerPixel) + [Theory] + [WithFile(Bit32Rgb, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + [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)] + 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); + + [Theory] + [WithFile(Bit32Rgb, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + [WithFile(Bit32Rgba, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + [WithFile(WinBmpv4, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + [WithFile(WinBmpv5, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)] + [WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)] + 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)] + 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) where TPixel : struct, IPixel { using (Image image = provider.GetImage()) { - // there is no alpha in bmp! - image.Mutate(c => c.MakeOpaque()); + // There is no alpha in bmp with 24 bits per pixels, so the reference image will be made opaque. + if (bitsPerPixel == BmpBitsPerPixel.Pixel24) + { + image.Mutate(c => c.MakeOpaque()); + } - var encoder = new BmpEncoder { BitsPerPixel = bitsPerPixel }; + var encoder = new BmpEncoder { BitsPerPixel = bitsPerPixel, SupportTransparency = supportTransparency }; // Does DebugSave & load reference CompareToReferenceInput(): image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder); From e1244509729e9e1de0eec777be53e3a66e5fef0b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 30 Apr 2019 19:33:51 +0200 Subject: [PATCH 2/5] Add 4 and 16 bit images to the Identify test --- tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index 05fadd4584..f5c294c3b8 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -130,7 +130,7 @@ public void BmpDecoder_CanDecodeBmpv3(TestImageProvider provider [Theory] [WithFile(LessThanFullSizedPalette, PixelTypes.Rgba32)] - public void BmpDecoder_CanDecodeLessThanFullPalete(TestImageProvider provider) + public void BmpDecoder_CanDecodeLessThanFullPalette(TestImageProvider provider) where TPixel : struct, IPixel { using (Image image = provider.GetImage(new BmpDecoder())) @@ -218,6 +218,9 @@ public void BmpDecoder_CanDecode4BytePerEntryPalette(TestImageProvider Date: Tue, 30 Apr 2019 19:45:20 +0200 Subject: [PATCH 3/5] Add some useful links for bitmap documentation --- src/ImageSharp/Formats/Bmp/README.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/README.md b/src/ImageSharp/Formats/Bmp/README.md index d072838438..f418307335 100644 --- a/src/ImageSharp/Formats/Bmp/README.md +++ b/src/ImageSharp/Formats/Bmp/README.md @@ -1,8 +1,17 @@ -Encoder/Decoder adapted from: +### Encoder/Decoder adapted from: -https://github.com/yufeih/Nine.Imaging/ -https://imagetools.codeplex.com/ +- [Nine.Imaging](https://github.com/yufeih/Nine.Imaging/) +- [imagetools.codeplex](https://imagetools.codeplex.com/) -TODO: +### Some useful links for documentation about the bitmap format: -- Add support for all bitmap formats. +- [Microsoft Windows Bitmap File](http://www.fileformat.info/format/bmp/egff.htm) +- [OS/2 Bitmap File Format Summary](http://www.fileformat.info/format/os2bmp/egff.htm) +- [The DIB File Format](https://www-user.tu-chemnitz.de/~heha/viewchm.php/hs/petzold.chm/petzoldi/ch15b.htm) +- [Dr.Dobbs: The BMP File Format, Part 1](http://www.drdobbs.com/architecture-and-design/the-bmp-file-format-part-1/184409517) +- [Windows Bitmap File Format Specifications](ftp://ftp.nada.kth.se/pub/hacks/sgi/src/libwmf/doc/Bmpfrmat.html) + +### A set of bitmap test images: + +- [bmpsuite](http://entropymine.com/jason/bmpsuite/bmpsuite/html/bmpsuite.html) +- [eclecticgeek](http://eclecticgeek.com/dompdf/core_tests/image_bmp.html) \ No newline at end of file From 720977c18066af69cb5ca7069ecf95827bb643a7 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 30 Apr 2019 20:28:54 +0200 Subject: [PATCH 4/5] Add 32 bpp images to the Identify test --- tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index f5c294c3b8..5f3cc7db47 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -213,13 +213,15 @@ public void BmpDecoder_CanDecode4BytePerEntryPalette(TestImageProvider Date: Wed, 1 May 2019 11:02:10 +0200 Subject: [PATCH 5/5] Added further information on what will change for the encoder, if SupportTransparency is used. --- src/ImageSharp/Formats/Bmp/BmpEncoder.cs | 3 +++ src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs index 2ae5ca78a4..4efdedb349 100644 --- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs +++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs @@ -20,6 +20,9 @@ public sealed class BmpEncoder : IImageEncoder, IBmpEncoderOptions /// /// Gets or sets a value indicating whether the encoder should support transparency. + /// Note: Transparency support only works together with 32 bits per pixel. This option will + /// change the default behavior of the encoder of writing a bitmap version 3 info header with no compression. + /// Instead a bitmap version 4 info header will be written with the BITFIELDS compression. /// public bool SupportTransparency { get; set; } diff --git a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs index cb91d8d8b5..a3a056bfa2 100644 --- a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs +++ b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs @@ -16,6 +16,9 @@ internal interface IBmpEncoderOptions /// /// Gets a value indicating whether the encoder should support transparency. + /// Note: Transparency support only works together with 32 bits per pixel. This option will + /// change the default behavior of the encoder of writing a bitmap version 3 info header with no compression. + /// Instead a bitmap version 4 info header will be written with the BITFIELDS compression. /// bool SupportTransparency { get; } }