From fcc7e5b257dfd6ed58048e4635e5d5798468b9ca Mon Sep 17 00:00:00 2001
From: Brian Popow <38701097+brianpopow@users.noreply.github.com>
Date: Wed, 1 May 2019 16:03:50 +0200
Subject: [PATCH] Bitmap encoder writes V3 header as default (#889)
* 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.
* Add 4 and 16 bit images to the Identify test
* Add some useful links for bitmap documentation
* Add 32 bpp images to the Identify test
* Added further information on what will change for the encoder, if SupportTransparency is used.
---
src/ImageSharp/Formats/Bmp/BmpEncoder.cs | 8 ++++
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 45 ++++++++++++-------
.../Formats/Bmp/IBmpEncoderOptions.cs | 10 ++++-
src/ImageSharp/Formats/Bmp/README.md | 19 +++++---
.../Formats/Bmp/BmpDecoderTests.cs | 7 ++-
.../Formats/Bmp/BmpEncoderTests.cs | 38 ++++++++++++++--
6 files changed, 100 insertions(+), 27 deletions(-)
diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs
index b1a66accec..4efdedb349 100644
--- a/src/ImageSharp/Formats/Bmp/BmpEncoder.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpEncoder.cs
@@ -18,6 +18,14 @@ public sealed class BmpEncoder : IImageEncoder, IBmpEncoderOptions
///
public BmpBitsPerPixel? BitsPerPixel { get; set; }
+ ///
+ /// 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; }
+
///
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..a3a056bfa2 100644
--- a/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs
+++ b/src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs
@@ -6,12 +6,20 @@ 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.
+ /// 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; }
}
}
\ No newline at end of file
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
diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
index 05fadd4584..5f3cc7db47 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()))
@@ -213,11 +213,16 @@ public void BmpDecoder_CanDecode4BytePerEntryPalette(TestImageProvider 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);