Browse Source

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.
pull/903/head
Brian Popow 7 years ago
committed by James Jackson-South
parent
commit
fcc7e5b257
  1. 8
      src/ImageSharp/Formats/Bmp/BmpEncoder.cs
  2. 45
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  3. 10
      src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs
  4. 19
      src/ImageSharp/Formats/Bmp/README.md
  5. 7
      tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
  6. 38
      tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs

8
src/ImageSharp/Formats/Bmp/BmpEncoder.cs

@ -18,6 +18,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </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>

45
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

@ -24,22 +24,22 @@ namespace SixLabors.ImageSharp.Formats.Bmp
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;
@ -49,15 +49,23 @@ namespace SixLabors.ImageSharp.Formats.Bmp
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>
@ -112,7 +120,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
}
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 @@ namespace SixLabors.ImageSharp.Formats.Bmp
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 @@ namespace SixLabors.ImageSharp.Formats.Bmp
stream.Write(buffer, 0, BmpFileHeader.Size);
infoHeader.WriteV4Header(buffer);
if (this.writeV4Header)
{
infoHeader.WriteV4Header(buffer);
}
else
{
infoHeader.WriteV3Header(buffer);
}
stream.Write(buffer, 0, infoHeaderSize);

10
src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs

@ -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
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)

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

@ -130,7 +130,7 @@ namespace SixLabors.ImageSharp.Tests
[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()))
@ -213,11 +213,16 @@ namespace SixLabors.ImageSharp.Tests
}
[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);

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

@ -11,6 +11,8 @@ using Xunit.Abstractions;
namespace SixLabors.ImageSharp.Tests
{
using static TestImages.Bmp;
public class BmpEncoderTests : FileTestBase
{
public static readonly TheoryData<BmpBitsPerPixel> BitsPerPixel =
@ -102,15 +104,43 @@ namespace SixLabors.ImageSharp.Tests
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);

Loading…
Cancel
Save