Skip to content

Commit

Permalink
Bitmap encoder writes V3 header as default (#889)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
brianpopow authored and JimBobSquarePants committed May 1, 2019
1 parent ff269db commit fcc7e5b
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 27 deletions.
8 changes: 8 additions & 0 deletions src/ImageSharp/Formats/Bmp/BmpEncoder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ public sealed class BmpEncoder : IImageEncoder, IBmpEncoderOptions
/// </summary>
public BmpBitsPerPixel? BitsPerPixel { get; set; }

/// <summary>
/// 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.
/// </summary>
public bool SupportTransparency { get; set; }

/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
Expand Down
45 changes: 29 additions & 16 deletions src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,22 @@ internal sealed class BmpEncoderCore
private int padding;

/// <summary>
/// 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.
/// </summary>
private const int Rgba32AlphaMask = 0xFF << 24;

/// <summary>
/// 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.
/// </summary>
private const int Rgba32RedMask = 0xFF << 16;

/// <summary>
/// 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.
/// </summary>
private const int Rgba32GreenMask = 0xFF << 8;

/// <summary>
/// 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.
/// </summary>
private const int Rgba32BlueMask = 0xFF;

Expand All @@ -49,15 +49,23 @@ internal sealed class BmpEncoderCore

private BmpBitsPerPixel? bitsPerPixel;

/// <summary>
/// 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.
/// </summary>
private readonly bool writeV4Header;

/// <summary>
/// Initializes a new instance of the <see cref="BmpEncoderCore"/> class.
/// </summary>
/// <param name="options">The encoder options</param>
/// <param name="memoryAllocator">The memory manager</param>
/// <param name="options">The encoder options.</param>
/// <param name="memoryAllocator">The memory manager.</param>
public BmpEncoderCore(IBmpEncoderOptions options, MemoryAllocator memoryAllocator)
{
this.memoryAllocator = memoryAllocator;
this.bitsPerPixel = options.BitsPerPixel;
this.writeV4Header = options.SupportTransparency;
}

/// <summary>
Expand Down Expand Up @@ -112,7 +120,7 @@ public void Encode<TPixel>(Image<TPixel> image, Stream stream)
}
}

int infoHeaderSize = BmpInfoHeader.SizeV4;
int infoHeaderSize = this.writeV4Header ? BmpInfoHeader.SizeV4 : BmpInfoHeader.SizeV3;
var infoHeader = new BmpInfoHeader(
headerSize: infoHeaderSize,
height: image.Height,
Expand All @@ -123,17 +131,15 @@ public void Encode<TPixel>(Image<TPixel> 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(
Expand All @@ -151,7 +157,14 @@ public void Encode<TPixel>(Image<TPixel> 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);

Expand Down
10 changes: 9 additions & 1 deletion src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,20 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary>
/// Configuration options for use during bmp encoding
/// </summary>
/// <remarks>The encoder can currently only write 24-bit rgb images to streams.</remarks>
/// <remarks>The encoder can currently only write 24-bit and 32-bit rgb images to streams.</remarks>
internal interface IBmpEncoderOptions
{
/// <summary>
/// Gets the number of bits per pixel.
/// </summary>
BmpBitsPerPixel? BitsPerPixel { get; }

/// <summary>
/// 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.
/// </summary>
bool SupportTransparency { get; }
}
}
19 changes: 14 additions & 5 deletions src/ImageSharp/Formats/Bmp/README.md
Original file line number Diff line number Diff line change
@@ -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)
7 changes: 6 additions & 1 deletion tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ public void BmpDecoder_CanDecodeBmpv3<TPixel>(TestImageProvider<TPixel> provider

[Theory]
[WithFile(LessThanFullSizedPalette, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecodeLessThanFullPalete<TPixel>(TestImageProvider<TPixel> provider)
public void BmpDecoder_CanDecodeLessThanFullPalette<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
Expand Down Expand Up @@ -213,11 +213,16 @@ public void BmpDecoder_CanDecode4BytePerEntryPalette<TPixel>(TestImageProvider<T
}

[Theory]
[InlineData(Bit32Rgb, 32)]
[InlineData(Bit32Rgba, 32)]
[InlineData(Car, 24)]
[InlineData(F, 24)]
[InlineData(NegHeight, 24)]
[InlineData(Bit16, 16)]
[InlineData(Bit16Inverted, 16)]
[InlineData(Bit8, 8)]
[InlineData(Bit8Inverted, 8)]
[InlineData(Bit4, 4)]
public void Identify(string imagePath, int expectedPixelSize)
{
var testFile = TestFile.Create(imagePath);
Expand Down
38 changes: 34 additions & 4 deletions tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

namespace SixLabors.ImageSharp.Tests
{
using static TestImages.Bmp;

public class BmpEncoderTests : FileTestBase
{
public static readonly TheoryData<BmpBitsPerPixel> BitsPerPixel =
Expand Down Expand Up @@ -102,15 +104,43 @@ public void Encode_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel>
public void Encode_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
where TPixel : struct, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel);

private static void TestBmpEncoderCore<TPixel>(TestImageProvider<TPixel> 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<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
// if supportTransparency is false, a v3 bitmap header will be written
where TPixel : struct, IPixel<TPixel> => 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<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
where TPixel : struct, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true);

[Theory]
[WithFile(TestImages.Png.GrayAlpha2BitInterlaced, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel32)]
public void Encode_PreservesAlpha<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
where TPixel : struct, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true);

private static void TestBmpEncoderCore<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel, bool supportTransparency = true)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> 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);
Expand Down

0 comments on commit fcc7e5b

Please sign in to comment.