Skip to content

Commit fcc7e5b

Browse files
brianpopowJimBobSquarePants
authored andcommitted
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.
1 parent ff269db commit fcc7e5b

6 files changed

Lines changed: 100 additions & 27 deletions

File tree

src/ImageSharp/Formats/Bmp/BmpEncoder.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ public sealed class BmpEncoder : IImageEncoder, IBmpEncoderOptions
1818
/// </summary>
1919
public BmpBitsPerPixel? BitsPerPixel { get; set; }
2020

21+
/// <summary>
22+
/// Gets or sets a value indicating whether the encoder should support transparency.
23+
/// Note: Transparency support only works together with 32 bits per pixel. This option will
24+
/// change the default behavior of the encoder of writing a bitmap version 3 info header with no compression.
25+
/// Instead a bitmap version 4 info header will be written with the BITFIELDS compression.
26+
/// </summary>
27+
public bool SupportTransparency { get; set; }
28+
2129
/// <inheritdoc/>
2230
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
2331
where TPixel : struct, IPixel<TPixel>

src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,22 @@ internal sealed class BmpEncoderCore
2424
private int padding;
2525

2626
/// <summary>
27-
/// The mask for the alpha channel of the color for a 32 bit rgba bitmaps.
27+
/// The mask for the alpha channel of the color for 32 bit rgba bitmaps.
2828
/// </summary>
2929
private const int Rgba32AlphaMask = 0xFF << 24;
3030

3131
/// <summary>
32-
/// The mask for the red part of the color for a 32 bit rgba bitmaps.
32+
/// The mask for the red part of the color for 32 bit rgba bitmaps.
3333
/// </summary>
3434
private const int Rgba32RedMask = 0xFF << 16;
3535

3636
/// <summary>
37-
/// The mask for the green part of the color for a 32 bit rgba bitmaps.
37+
/// The mask for the green part of the color for 32 bit rgba bitmaps.
3838
/// </summary>
3939
private const int Rgba32GreenMask = 0xFF << 8;
4040

4141
/// <summary>
42-
/// The mask for the blue part of the color for a 32 bit rgba bitmaps.
42+
/// The mask for the blue part of the color for 32 bit rgba bitmaps.
4343
/// </summary>
4444
private const int Rgba32BlueMask = 0xFF;
4545

@@ -49,15 +49,23 @@ internal sealed class BmpEncoderCore
4949

5050
private BmpBitsPerPixel? bitsPerPixel;
5151

52+
/// <summary>
53+
/// A bitmap v4 header will only be written, if the user explicitly wants support for transparency.
54+
/// In this case the compression type BITFIELDS will be used.
55+
/// Otherwise a bitmap v3 header will be written, which is supported by almost all decoders.
56+
/// </summary>
57+
private readonly bool writeV4Header;
58+
5259
/// <summary>
5360
/// Initializes a new instance of the <see cref="BmpEncoderCore"/> class.
5461
/// </summary>
55-
/// <param name="options">The encoder options</param>
56-
/// <param name="memoryAllocator">The memory manager</param>
62+
/// <param name="options">The encoder options.</param>
63+
/// <param name="memoryAllocator">The memory manager.</param>
5764
public BmpEncoderCore(IBmpEncoderOptions options, MemoryAllocator memoryAllocator)
5865
{
5966
this.memoryAllocator = memoryAllocator;
6067
this.bitsPerPixel = options.BitsPerPixel;
68+
this.writeV4Header = options.SupportTransparency;
6169
}
6270

6371
/// <summary>
@@ -112,7 +120,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
112120
}
113121
}
114122

115-
int infoHeaderSize = BmpInfoHeader.SizeV4;
123+
int infoHeaderSize = this.writeV4Header ? BmpInfoHeader.SizeV4 : BmpInfoHeader.SizeV3;
116124
var infoHeader = new BmpInfoHeader(
117125
headerSize: infoHeaderSize,
118126
height: image.Height,
@@ -123,17 +131,15 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
123131
clrUsed: 0,
124132
clrImportant: 0,
125133
xPelsPerMeter: hResolution,
126-
yPelsPerMeter: vResolution)
127-
{
128-
RedMask = Rgba32RedMask,
129-
GreenMask = Rgba32GreenMask,
130-
BlueMask = Rgba32BlueMask,
131-
Compression = BmpCompression.BitFields
132-
};
134+
yPelsPerMeter: vResolution);
133135

134-
if (this.bitsPerPixel == BmpBitsPerPixel.Pixel32)
136+
if (this.writeV4Header && this.bitsPerPixel == BmpBitsPerPixel.Pixel32)
135137
{
136138
infoHeader.AlphaMask = Rgba32AlphaMask;
139+
infoHeader.RedMask = Rgba32RedMask;
140+
infoHeader.GreenMask = Rgba32GreenMask;
141+
infoHeader.BlueMask = Rgba32BlueMask;
142+
infoHeader.Compression = BmpCompression.BitFields;
137143
}
138144

139145
var fileHeader = new BmpFileHeader(
@@ -151,7 +157,14 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
151157

152158
stream.Write(buffer, 0, BmpFileHeader.Size);
153159

154-
infoHeader.WriteV4Header(buffer);
160+
if (this.writeV4Header)
161+
{
162+
infoHeader.WriteV4Header(buffer);
163+
}
164+
else
165+
{
166+
infoHeader.WriteV3Header(buffer);
167+
}
155168

156169
stream.Write(buffer, 0, infoHeaderSize);
157170

src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,20 @@ namespace SixLabors.ImageSharp.Formats.Bmp
66
/// <summary>
77
/// Configuration options for use during bmp encoding
88
/// </summary>
9-
/// <remarks>The encoder can currently only write 24-bit rgb images to streams.</remarks>
9+
/// <remarks>The encoder can currently only write 24-bit and 32-bit rgb images to streams.</remarks>
1010
internal interface IBmpEncoderOptions
1111
{
1212
/// <summary>
1313
/// Gets the number of bits per pixel.
1414
/// </summary>
1515
BmpBitsPerPixel? BitsPerPixel { get; }
16+
17+
/// <summary>
18+
/// Gets a value indicating whether the encoder should support transparency.
19+
/// Note: Transparency support only works together with 32 bits per pixel. This option will
20+
/// change the default behavior of the encoder of writing a bitmap version 3 info header with no compression.
21+
/// Instead a bitmap version 4 info header will be written with the BITFIELDS compression.
22+
/// </summary>
23+
bool SupportTransparency { get; }
1624
}
1725
}
Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
1-
Encoder/Decoder adapted from:
1+
### Encoder/Decoder adapted from:
22

3-
https://github.com/yufeih/Nine.Imaging/
4-
https://imagetools.codeplex.com/
3+
- [Nine.Imaging](https://github.com/yufeih/Nine.Imaging/)
4+
- [imagetools.codeplex](https://imagetools.codeplex.com/)
55

6-
TODO:
6+
### Some useful links for documentation about the bitmap format:
77

8-
- Add support for all bitmap formats.
8+
- [Microsoft Windows Bitmap File](http://www.fileformat.info/format/bmp/egff.htm)
9+
- [OS/2 Bitmap File Format Summary](http://www.fileformat.info/format/os2bmp/egff.htm)
10+
- [The DIB File Format](https://www-user.tu-chemnitz.de/~heha/viewchm.php/hs/petzold.chm/petzoldi/ch15b.htm)
11+
- [Dr.Dobbs: The BMP File Format, Part 1](http://www.drdobbs.com/architecture-and-design/the-bmp-file-format-part-1/184409517)
12+
- [Windows Bitmap File Format Specifications](ftp://ftp.nada.kth.se/pub/hacks/sgi/src/libwmf/doc/Bmpfrmat.html)
13+
14+
### A set of bitmap test images:
15+
16+
- [bmpsuite](http://entropymine.com/jason/bmpsuite/bmpsuite/html/bmpsuite.html)
17+
- [eclecticgeek](http://eclecticgeek.com/dompdf/core_tests/image_bmp.html)

tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ public void BmpDecoder_CanDecodeBmpv3<TPixel>(TestImageProvider<TPixel> provider
130130

131131
[Theory]
132132
[WithFile(LessThanFullSizedPalette, PixelTypes.Rgba32)]
133-
public void BmpDecoder_CanDecodeLessThanFullPalete<TPixel>(TestImageProvider<TPixel> provider)
133+
public void BmpDecoder_CanDecodeLessThanFullPalette<TPixel>(TestImageProvider<TPixel> provider)
134134
where TPixel : struct, IPixel<TPixel>
135135
{
136136
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
@@ -213,11 +213,16 @@ public void BmpDecoder_CanDecode4BytePerEntryPalette<TPixel>(TestImageProvider<T
213213
}
214214

215215
[Theory]
216+
[InlineData(Bit32Rgb, 32)]
217+
[InlineData(Bit32Rgba, 32)]
216218
[InlineData(Car, 24)]
217219
[InlineData(F, 24)]
218220
[InlineData(NegHeight, 24)]
221+
[InlineData(Bit16, 16)]
222+
[InlineData(Bit16Inverted, 16)]
219223
[InlineData(Bit8, 8)]
220224
[InlineData(Bit8Inverted, 8)]
225+
[InlineData(Bit4, 4)]
221226
public void Identify(string imagePath, int expectedPixelSize)
222227
{
223228
var testFile = TestFile.Create(imagePath);

tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace SixLabors.ImageSharp.Tests
1313
{
14+
using static TestImages.Bmp;
15+
1416
public class BmpEncoderTests : FileTestBase
1517
{
1618
public static readonly TheoryData<BmpBitsPerPixel> BitsPerPixel =
@@ -102,15 +104,43 @@ public void Encode_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel>
102104
public void Encode_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
103105
where TPixel : struct, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel);
104106

105-
private static void TestBmpEncoderCore<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
107+
[Theory]
108+
[WithFile(Bit32Rgb, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)]
109+
[WithFile(Bit32Rgba, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)]
110+
[WithFile(WinBmpv4, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)]
111+
[WithFile(WinBmpv5, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)]
112+
// WinBmpv3 is a 24 bits per pixel image
113+
[WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)]
114+
public void Encode_WithV3Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
115+
// if supportTransparency is false, a v3 bitmap header will be written
116+
where TPixel : struct, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: false);
117+
118+
[Theory]
119+
[WithFile(Bit32Rgb, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)]
120+
[WithFile(Bit32Rgba, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)]
121+
[WithFile(WinBmpv4, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)]
122+
[WithFile(WinBmpv5, PixelTypes.Rgba32 | PixelTypes.Rgb24, BmpBitsPerPixel.Pixel32)]
123+
[WithFile(WinBmpv3, PixelTypes.Rgb24, BmpBitsPerPixel.Pixel24)]
124+
public void Encode_WithV4Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
125+
where TPixel : struct, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true);
126+
127+
[Theory]
128+
[WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)]
129+
public void Encode_PreservesAlpha<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
130+
where TPixel : struct, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true);
131+
132+
private static void TestBmpEncoderCore<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel, bool supportTransparency = true)
106133
where TPixel : struct, IPixel<TPixel>
107134
{
108135
using (Image<TPixel> image = provider.GetImage())
109136
{
110-
// there is no alpha in bmp!
111-
image.Mutate(c => c.MakeOpaque());
137+
// There is no alpha in bmp with 24 bits per pixels, so the reference image will be made opaque.
138+
if (bitsPerPixel == BmpBitsPerPixel.Pixel24)
139+
{
140+
image.Mutate(c => c.MakeOpaque());
141+
}
112142

113-
var encoder = new BmpEncoder { BitsPerPixel = bitsPerPixel };
143+
var encoder = new BmpEncoder { BitsPerPixel = bitsPerPixel, SupportTransparency = supportTransparency };
114144

115145
// Does DebugSave & load reference CompareToReferenceInput():
116146
image.VerifyEncoder(provider, "bmp", bitsPerPixel, encoder);

0 commit comments

Comments
 (0)