Browse Source

Decoding Bitmaps with BITFIELDS masks (#796)

* decoding bitmaps with Bitfields masks

* added testcases for Bitfields bitmaps

* added parsing of the complete bitmap V4 header to get the color masks infos for the BITFIELDS compression

* writing now explicitly a Bitamp v3 header (40 bytes)

* added test image for issue #735

* fixed rescaling 5-bit / 6-bit to 0 - 255 range

* BitmapEncoder now writes BMP v4 header

* using MemoryMarshal.Cast to simplify parsing v4 header

* added testcases for bitmap v3, v4, v5

* Bitmap encoder writes again V3 header instead of V4. Additional fields for V4 are zero anyway.

* added parsing of special case for 56 bytes headers

* using the alpha mask in ReadRgb32BitFields() when its present

* added support for bitmasks greater then 8 bits per channel

* using MagickReferenceDecoder reference decoder for the test with 10 bits pixel masks

* changed bitmap constants to hex

* added enum for the bitmap info header type

* added support for 52 bytes header (same as 56 bytes without the alpha mask)

* clarified comment with difference between imagesharp and magick decoder for Rgba321010102

* BmpEncoder now writes a V4 info header and uses BITFIELDS compression

* workaround for issue that the decoder does not decode the alpha channel correctly: For V3 bitmaps, the alpha channel will be ignored during encoding

* Fix #732
af/merge-core
Brian Popow 7 years ago
committed by James Jackson-South
parent
commit
84ba4bbf0d
  1. 2
      src/ImageSharp/Formats/Bmp/BmpCompression.cs
  2. 36
      src/ImageSharp/Formats/Bmp/BmpConstants.cs
  3. 2
      src/ImageSharp/Formats/Bmp/BmpDecoder.cs
  4. 399
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  5. 50
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  6. 252
      src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
  7. 48
      src/ImageSharp/Formats/Bmp/BmpInfoHeaderType.cs
  8. 11
      src/ImageSharp/Formats/Bmp/BmpMetaData.cs
  9. 107
      tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
  10. 29
      tests/ImageSharp.Tests/TestImages.cs
  11. 3
      tests/Images/Input/Bmp/issue735.bmp
  12. 3
      tests/Images/Input/Bmp/pal8v4.bmp
  13. 3
      tests/Images/Input/Bmp/pal8v5.bmp
  14. 3
      tests/Images/Input/Bmp/rgb16-565.bmp
  15. 3
      tests/Images/Input/Bmp/rgb16-565pal.bmp
  16. 3
      tests/Images/Input/Bmp/rgb16bfdef.bmp
  17. 3
      tests/Images/Input/Bmp/rgb24.bmp
  18. 3
      tests/Images/Input/Bmp/rgb32bf.bmp
  19. 3
      tests/Images/Input/Bmp/rgb32bfdef.bmp
  20. 3
      tests/Images/Input/Bmp/rgba32-1010102.bmp
  21. 3
      tests/Images/Input/Bmp/rgba32.bmp
  22. 3
      tests/Images/Input/Bmp/rgba32h56.bmp

2
src/ImageSharp/Formats/Bmp/BmpCompression.cs

@ -25,7 +25,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// If the first byte is zero, the record has different meanings, depending
/// on the second byte. If the second byte is zero, it is the end of the row,
/// if it is one, it is the end of the image.
/// Not supported at the moment.
/// </summary>
RLE8 = 1,
@ -42,7 +41,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary>
/// Each image row has a multiple of four elements. If the
/// row has less elements, zeros will be added at the right side.
/// Not supported at the moment.
/// </summary>
BitFields = 3,

36
src/ImageSharp/Formats/Bmp/BmpConstants.cs

@ -19,5 +19,41 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The list of file extensions that equate to a bmp.
/// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "bm", "bmp", "dip" };
/// <summary>
/// Valid magic bytes markers identifying a Bitmap file.
/// </summary>
internal static class TypeMarkers
{
/// <summary>
/// Single-image BMP file that may have been created under Windows or OS/2.
/// </summary>
public const int Bitmap = 0x4D42;
/// <summary>
/// OS/2 Bitmap Array.
/// </summary>
public const int BitmapArray = 0x4142;
/// <summary>
/// OS/2 Color Icon.
/// </summary>
public const int ColorIcon = 0x4943;
/// <summary>
/// OS/2 Color Pointer.
/// </summary>
public const int ColorPointer = 0x5043;
/// <summary>
/// OS/2 Icon.
/// </summary>
public const int Icon = 0x4349;
/// <summary>
/// OS/2 Pointer.
/// </summary>
public const int Pointer = 0x5450;
}
}
}

2
src/ImageSharp/Formats/Bmp/BmpDecoder.cs

@ -15,8 +15,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <item>JPG</item>
/// <item>PNG</item>
/// <item>RLE4</item>
/// <item>RLE8</item>
/// <item>BitFields</item>
/// </list>
/// Formats will be supported in a later releases. We advise always
/// to use only 24 Bit Windows bitmaps.

399
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -2,8 +2,10 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.IO;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Memory;
@ -14,7 +16,7 @@ using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Bmp
{
/// <summary>
/// Performs the bmp decoding operation.
/// Performs the bitmap decoding operation.
/// </summary>
/// <remarks>
/// A useful decoding source example can be found at <see href="https://dxr.mozilla.org/mozilla-central/source/image/decoders/nsBMPDecoder.cpp"/>
@ -22,19 +24,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp
internal sealed class BmpDecoderCore
{
/// <summary>
/// The mask for the red part of the color for 16 bit rgb bitmaps.
/// The default mask for the red part of the color for 16 bit rgb bitmaps.
/// </summary>
private const int Rgb16RMask = 0x7C00;
private const int DefaultRgb16RMask = 0x7C00;
/// <summary>
/// The mask for the green part of the color for 16 bit rgb bitmaps.
/// The default mask for the green part of the color for 16 bit rgb bitmaps.
/// </summary>
private const int Rgb16GMask = 0x3E0;
private const int DefaultRgb16GMask = 0x3E0;
/// <summary>
/// The mask for the blue part of the color for 16 bit rgb bitmaps.
/// The default mask for the blue part of the color for 16 bit rgb bitmaps.
/// </summary>
private const int Rgb16BMask = 0x1F;
private const int DefaultRgb16BMask = 0x1F;
/// <summary>
/// RLE8 flag value that indicates following byte has special meaning.
@ -62,10 +64,15 @@ namespace SixLabors.ImageSharp.Formats.Bmp
private Stream stream;
/// <summary>
/// The metadata
/// The metadata.
/// </summary>
private ImageMetaData metaData;
/// <summary>
/// The bmp specific metadata.
/// </summary>
private BmpMetaData bmpMetaData;
/// <summary>
/// The file header containing general information.
/// TODO: Why is this not used? We advance the stream but do not use the values parsed.
@ -85,7 +92,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// Initializes a new instance of the <see cref="BmpDecoderCore"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The options</param>
/// <param name="options">The options.</param>
public BmpDecoderCore(Configuration configuration, IBmpDecoderOptions options)
{
this.configuration = configuration;
@ -119,7 +126,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp
case BmpCompression.RGB:
if (this.infoHeader.BitsPerPixel == 32)
{
this.ReadRgb32(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
if (this.bmpMetaData.InfoHeaderType == BmpInfoHeaderType.WinVersion3)
{
this.ReadRgb32Slow(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
}
else
{
this.ReadRgb32Fast(pixels, this.infoHeader.Width, this.infoHeader.Height, inverted);
}
}
else if (this.infoHeader.BitsPerPixel == 24)
{
@ -146,6 +160,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.ReadRle8(pixels, palette, this.infoHeader.Width, this.infoHeader.Height, inverted);
break;
case BmpCompression.BitFields:
this.ReadBitFields(pixels, inverted);
break;
default:
throw new NotSupportedException("Does not support this kind of bitmap files.");
}
@ -199,12 +219,39 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Performs final shifting from a 5bit value to an 8bit one.
/// Decodes a bitmap containing BITFIELDS Compression type. For each color channel, there will be bitmask
/// which will be used to determine which bits belong to that channel.
/// </summary>
/// <param name="value">The masked and shifted value</param>
/// <returns>The <see cref="byte"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte GetBytesFrom5BitValue(int value) => (byte)((value << 3) | (value >> 2));
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The output pixel buffer containing the decoded image.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadBitFields<TPixel>(Buffer2D<TPixel> pixels, bool inverted)
where TPixel : struct, IPixel<TPixel>
{
if (this.infoHeader.BitsPerPixel == 16)
{
this.ReadRgb16(
pixels,
this.infoHeader.Width,
this.infoHeader.Height,
inverted,
this.infoHeader.RedMask,
this.infoHeader.GreenMask,
this.infoHeader.BlueMask);
}
else
{
this.ReadRgb32BitFields(
pixels,
this.infoHeader.Width,
this.infoHeader.Height,
inverted,
this.infoHeader.RedMask,
this.infoHeader.GreenMask,
this.infoHeader.BlueMask,
this.infoHeader.AlphaMask);
}
}
/// <summary>
/// Looks up color values and builds the image from de-compressed RLE8 data.
@ -240,12 +287,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Produce uncompressed bitmap data from RLE8 stream
/// Produce uncompressed bitmap data from RLE8 stream.
/// </summary>
/// <remarks>
/// RLE8 is a 2-byte run-length encoding
/// <br/>If first byte is 0, the second byte may have special meaning
/// <br/>Otherwise, first byte is the length of the run and second byte is the color for the run
/// RLE8 is a 2-byte run-length encoding.
/// <br/>If first byte is 0, the second byte may have special meaning.
/// <br/>Otherwise, first byte is the length of the run and second byte is the color for the run.
/// </remarks>
/// <param name="w">The width of the bitmap.</param>
/// <param name="buffer">Buffer for uncompressed data.</param>
@ -382,20 +429,32 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Reads the 16 bit color palette from the stream
/// Reads the 16 bit color palette from the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRgb16<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted)
/// <param name="redMask">The bitmask for the red channel.</param>
/// <param name="greenMask">The bitmask for the green channel.</param>
/// <param name="blueMask">The bitmask for the blue channel.</param>
private void ReadRgb16<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted, int redMask = DefaultRgb16RMask, int greenMask = DefaultRgb16GMask, int blueMask = DefaultRgb16BMask)
where TPixel : struct, IPixel<TPixel>
{
int padding = CalculatePadding(width, 2);
int stride = (width * 2) + padding;
TPixel color = default;
int rightShiftRedMask = CalculateRightShift((uint)redMask);
int rightShiftGreenMask = CalculateRightShift((uint)greenMask);
int rightShiftBlueMask = CalculateRightShift((uint)blueMask);
// Each color channel contains either 5 or 6 Bits values.
int redMaskBits = CountBits((uint)redMask);
int greenMaskBits = CountBits((uint)greenMask);
int blueMaskBits = CountBits((uint)blueMask);
using (IManagedByteBuffer buffer = this.memoryAllocator.AllocateManagedByteBuffer(stride))
{
for (int y = 0; y < height; y++)
@ -409,10 +468,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
short temp = BitConverter.ToInt16(buffer.Array, offset);
var rgb = new Rgb24(
GetBytesFrom5BitValue((temp & Rgb16RMask) >> 10),
GetBytesFrom5BitValue((temp & Rgb16GMask) >> 5),
GetBytesFrom5BitValue(temp & Rgb16BMask));
// Rescale values, so the values range from 0 to 255.
int r = (redMaskBits == 5) ? GetBytesFrom5BitValue((temp & redMask) >> rightShiftRedMask) : GetBytesFrom6BitValue((temp & redMask) >> rightShiftRedMask);
int g = (greenMaskBits == 5) ? GetBytesFrom5BitValue((temp & greenMask) >> rightShiftGreenMask) : GetBytesFrom6BitValue((temp & greenMask) >> rightShiftGreenMask);
int b = (blueMaskBits == 5) ? GetBytesFrom5BitValue((temp & blueMask) >> rightShiftBlueMask) : GetBytesFrom6BitValue((temp & blueMask) >> rightShiftBlueMask);
var rgb = new Rgb24((byte)r, (byte)g, (byte)b);
color.FromRgb24(rgb);
pixelRow[x] = color;
@ -423,7 +483,23 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Reads the 24 bit color palette from the stream
/// Performs final shifting from a 5bit value to an 8bit one.
/// </summary>
/// <param name="value">The masked and shifted value.</param>
/// <returns>The <see cref="byte"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte GetBytesFrom5BitValue(int value) => (byte)((value << 3) | (value >> 2));
/// <summary>
/// Performs final shifting from a 6bit value to an 8bit one.
/// </summary>
/// <param name="value">The masked and shifted value.</param>
/// <returns>The <see cref="byte"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte GetBytesFrom6BitValue(int value) => (byte)((value << 2) | (value >> 4));
/// <summary>
/// Reads the 24 bit color palette from the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
@ -452,14 +528,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Reads the 32 bit color palette from the stream
/// Reads the 32 bit color palette from the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRgb32<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted)
private void ReadRgb32Fast<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted)
where TPixel : struct, IPixel<TPixel>
{
int padding = CalculatePadding(width, 4);
@ -480,6 +556,228 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
}
/// <summary>
/// Reads the 32 bit color palette from the stream, checking the alpha component of each pixel.
/// This is a special case only used for 32bpp WinBMPv3 files, which could be in either BGR0 or BGRA format.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRgb32Slow<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted)
where TPixel : struct, IPixel<TPixel>
{
int padding = CalculatePadding(width, 4);
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding))
using (IMemoryOwner<Bgra32> bgraRow = this.memoryAllocator.Allocate<Bgra32>(width))
{
Span<Bgra32> bgraRowSpan = bgraRow.GetSpan();
long currentPosition = this.stream.Position;
bool hasAlpha = false;
// Loop though the rows checking each pixel. We start by assuming it's
// an BGR0 image. If we hit a non-zero alpha value, then we know it's
// actually a BGRA image, and change tactics accordingly.
for (int y = 0; y < height; y++)
{
this.stream.Read(row);
PixelOperations<Bgra32>.Instance.FromBgra32Bytes(
this.configuration,
row.GetSpan(),
bgraRowSpan,
width);
// Check each pixel in the row to see if it has an alpha value.
for (int x = 0; x < width; x++)
{
Bgra32 bgra = bgraRowSpan[x];
if (bgra.A > 0)
{
hasAlpha = true;
break;
}
}
if (hasAlpha)
{
break;
}
}
// Reset our stream for a second pass.
this.stream.Position = currentPosition;
// Process the pixels in bulk taking the raw alpha component value.
if (hasAlpha)
{
for (int y = 0; y < height; y++)
{
this.stream.Read(row);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(
this.configuration,
row.GetSpan(),
pixelSpan,
width);
}
return;
}
// Slow path. We need to set each alpha component value to fully opaque.
for (int y = 0; y < height; y++)
{
this.stream.Read(row);
PixelOperations<Bgra32>.Instance.FromBgra32Bytes(
this.configuration,
row.GetSpan(),
bgraRowSpan,
width);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
for (int x = 0; x < width; x++)
{
Bgra32 bgra = bgraRowSpan[x];
bgra.A = byte.MaxValue;
ref TPixel pixel = ref pixelSpan[x];
pixel.FromBgra32(bgra);
}
}
}
}
/// <summary>
/// Decode an 32 Bit Bitmap containing a bitmask for each color channel.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The output pixel buffer containing the decoded image.</param>
/// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
/// <param name="redMask">The bitmask for the red channel.</param>
/// <param name="greenMask">The bitmask for the green channel.</param>
/// <param name="blueMask">The bitmask for the blue channel.</param>
/// <param name="alphaMask">The bitmask for the alpha channel.</param>
private void ReadRgb32BitFields<TPixel>(Buffer2D<TPixel> pixels, int width, int height, bool inverted, int redMask, int greenMask, int blueMask, int alphaMask)
where TPixel : struct, IPixel<TPixel>
{
TPixel color = default;
int padding = CalculatePadding(width, 4);
int stride = (width * 4) + padding;
int rightShiftRedMask = CalculateRightShift((uint)redMask);
int rightShiftGreenMask = CalculateRightShift((uint)greenMask);
int rightShiftBlueMask = CalculateRightShift((uint)blueMask);
int rightShiftAlphaMask = CalculateRightShift((uint)alphaMask);
int bitsRedMask = CountBits((uint)redMask);
int bitsGreenMask = CountBits((uint)greenMask);
int bitsBlueMask = CountBits((uint)blueMask);
int bitsAlphaMask = CountBits((uint)alphaMask);
float invMaxValueRed = 1.0f / (0xFFFFFFFF >> (32 - bitsRedMask));
float invMaxValueGreen = 1.0f / (0xFFFFFFFF >> (32 - bitsGreenMask));
float invMaxValueBlue = 1.0f / (0xFFFFFFFF >> (32 - bitsBlueMask));
uint maxValueAlpha = 0xFFFFFFFF >> (32 - bitsAlphaMask);
float invMaxValueAlpha = 1.0f / maxValueAlpha;
bool unusualBitMask = false;
if (bitsRedMask > 8 || bitsGreenMask > 8 || bitsBlueMask > 8 || invMaxValueAlpha > 8)
{
unusualBitMask = true;
}
using (IManagedByteBuffer buffer = this.memoryAllocator.AllocateManagedByteBuffer(stride))
{
for (int y = 0; y < height; y++)
{
this.stream.Read(buffer.Array, 0, stride);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
int offset = 0;
for (int x = 0; x < width; x++)
{
uint temp = BitConverter.ToUInt32(buffer.Array, offset);
if (unusualBitMask)
{
uint r = (uint)(temp & redMask) >> rightShiftRedMask;
uint g = (uint)(temp & greenMask) >> rightShiftGreenMask;
uint b = (uint)(temp & blueMask) >> rightShiftBlueMask;
float alpha = alphaMask != 0 ? invMaxValueAlpha * ((uint)(temp & alphaMask) >> rightShiftAlphaMask) : 1.0f;
var vector4 = new Vector4(
r * invMaxValueRed,
g * invMaxValueGreen,
b * invMaxValueBlue,
alpha);
color.FromVector4(vector4);
}
else
{
byte r = (byte)((temp & redMask) >> rightShiftRedMask);
byte g = (byte)((temp & greenMask) >> rightShiftGreenMask);
byte b = (byte)((temp & blueMask) >> rightShiftBlueMask);
byte a = alphaMask != 0 ? (byte)((temp & alphaMask) >> rightShiftAlphaMask) : (byte)255;
color.FromRgba32(new Rgba32(r, g, b, a));
}
pixelRow[x] = color;
offset += 4;
}
}
}
}
/// <summary>
/// Calculates the necessary right shifts for a given color bitmask (the 0 bits to the right).
/// </summary>
/// <param name="n">The color bit mask.</param>
/// <returns>Number of bits to shift right.</returns>
private static int CalculateRightShift(uint n)
{
int count = 0;
while (n > 0)
{
if ((1 & n) == 0)
{
count++;
}
else
{
break;
}
n >>= 1;
}
return count;
}
/// <summary>
/// Counts none zero bits.
/// </summary>
/// <param name="n">A color mask.</param>
/// <returns>The none zero bits.</returns>
private static int CountBits(uint n)
{
int count = 0;
while (n != 0)
{
count++;
n &= n - 1;
}
return count;
}
/// <summary>
/// Reads the <see cref="BmpInfoHeader"/> from the stream.
/// </summary>
@ -508,20 +806,54 @@ namespace SixLabors.ImageSharp.Formats.Bmp
// read the rest of the header
this.stream.Read(buffer, BmpInfoHeader.HeaderSizeSize, headerSize - BmpInfoHeader.HeaderSizeSize);
BmpInfoHeaderType inofHeaderType = BmpInfoHeaderType.WinVersion2;
if (headerSize == BmpInfoHeader.CoreSize)
{
// 12 bytes
inofHeaderType = BmpInfoHeaderType.WinVersion2;
this.infoHeader = BmpInfoHeader.ParseCore(buffer);
}
else if (headerSize == BmpInfoHeader.Os22ShortSize)
{
// 16 bytes
inofHeaderType = BmpInfoHeaderType.Os2Version2Short;
this.infoHeader = BmpInfoHeader.ParseOs22Short(buffer);
}
else if (headerSize >= BmpInfoHeader.Size)
else if (headerSize == BmpInfoHeader.SizeV3)
{
// == 40 bytes
inofHeaderType = BmpInfoHeaderType.WinVersion3;
this.infoHeader = BmpInfoHeader.ParseV3(buffer);
// if the info header is BMP version 3 and the compression type is BITFIELDS,
// color masks for each color channel follow the info header.
if (this.infoHeader.Compression == BmpCompression.BitFields)
{
byte[] bitfieldsBuffer = new byte[12];
this.stream.Read(bitfieldsBuffer, 0, 12);
Span<byte> data = bitfieldsBuffer.AsSpan<byte>();
this.infoHeader.RedMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4));
this.infoHeader.GreenMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4));
this.infoHeader.BlueMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4));
}
}
else if (headerSize == BmpInfoHeader.AdobeV3Size)
{
// == 52 bytes
inofHeaderType = BmpInfoHeaderType.AdobeVersion3;
this.infoHeader = BmpInfoHeader.ParseAdobeV3(buffer, withAlpha: false);
}
else if (headerSize == BmpInfoHeader.AdobeV3WithAlphaSize)
{
// == 56 bytes
inofHeaderType = BmpInfoHeaderType.AdobeVersion3WithAlpha;
this.infoHeader = BmpInfoHeader.ParseAdobeV3(buffer, withAlpha: true);
}
else if (headerSize >= BmpInfoHeader.SizeV4)
{
// >= 40 bytes
this.infoHeader = BmpInfoHeader.Parse(buffer);
// >= 108 bytes
inofHeaderType = headerSize == BmpInfoHeader.SizeV4 ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion5;
this.infoHeader = BmpInfoHeader.ParseV4(buffer);
}
else
{
@ -548,13 +880,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.metaData = meta;
short bitsPerPixel = this.infoHeader.BitsPerPixel;
BmpMetaData bmpMetaData = this.metaData.GetFormatMetaData(BmpFormat.Instance);
this.bmpMetaData = this.metaData.GetFormatMetaData(BmpFormat.Instance);
this.bmpMetaData.InfoHeaderType = inofHeaderType;
// We can only encode at these bit rates so far.
if (bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel24)
|| bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel32))
{
bmpMetaData.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel;
this.bmpMetaData.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel;
}
// skip the remaining header because we can't read those parts

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

@ -23,6 +23,26 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
private int padding;
/// <summary>
/// The mask for the alpha channel of the color for a 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.
/// </summary>
private const int Rgba32RedMask = 0xFF << 16;
/// <summary>
/// The mask for the green part of the color for a 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.
/// </summary>
private const int Rgba32BlueMask = 0xFF;
private readonly MemoryAllocator memoryAllocator;
private Configuration configuration;
@ -92,8 +112,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
}
int infoHeaderSize = BmpInfoHeader.SizeV4;
var infoHeader = new BmpInfoHeader(
headerSize: BmpInfoHeader.Size,
headerSize: infoHeaderSize,
height: image.Height,
width: image.Width,
bitsPerPixel: bpp,
@ -102,26 +123,37 @@ namespace SixLabors.ImageSharp.Formats.Bmp
clrUsed: 0,
clrImportant: 0,
xPelsPerMeter: hResolution,
yPelsPerMeter: vResolution);
yPelsPerMeter: vResolution)
{
RedMask = Rgba32RedMask,
GreenMask = Rgba32GreenMask,
BlueMask = Rgba32BlueMask,
Compression = BmpCompression.BitFields
};
if (this.bitsPerPixel == BmpBitsPerPixel.Pixel32)
{
infoHeader.AlphaMask = Rgba32AlphaMask;
}
var fileHeader = new BmpFileHeader(
type: 19778, // BM
fileSize: 54 + infoHeader.ImageSize,
type: BmpConstants.TypeMarkers.Bitmap,
fileSize: BmpFileHeader.Size + infoHeaderSize + infoHeader.ImageSize,
reserved: 0,
offset: 54);
offset: BmpFileHeader.Size + infoHeaderSize);
#if NETCOREAPP2_1
Span<byte> buffer = stackalloc byte[40];
Span<byte> buffer = stackalloc byte[infoHeaderSize];
#else
byte[] buffer = new byte[40];
byte[] buffer = new byte[infoHeaderSize];
#endif
fileHeader.WriteTo(buffer);
stream.Write(buffer, 0, BmpFileHeader.Size);
infoHeader.WriteTo(buffer);
infoHeader.WriteV4Header(buffer);
stream.Write(buffer, 0, 40);
stream.Write(buffer, 0, infoHeaderSize);
this.WriteImage(stream, image.Frames.RootFrame);

252
src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs

@ -16,11 +16,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct BmpInfoHeader
{
/// <summary>
/// Defines the size of the BITMAPINFOHEADER data structure in the bitmap file.
/// </summary>
public const int Size = 40;
/// <summary>
/// Defines the size of the BITMAPCOREHEADER data structure in the bitmap file.
/// </summary>
@ -31,10 +26,30 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
public const int Os22ShortSize = 16;
/// <summary>
/// Defines the size of the BITMAPINFOHEADER (BMP Version 3) data structure in the bitmap file.
/// </summary>
public const int SizeV3 = 40;
/// <summary>
/// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks are part of the info header instead of following it.
/// </summary>
public const int AdobeV3Size = 52;
/// <summary>
/// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks (including the alpha channel) are part of the info header instead of following it.
/// </summary>
public const int AdobeV3WithAlphaSize = 56;
/// <summary>
/// Defines the size of the BITMAPINFOHEADER (BMP Version 4) data structure in the bitmap file.
/// </summary>
public const int SizeV4 = 108;
/// <summary>
/// Defines the size of the biggest supported header data structure in the bitmap file.
/// </summary>
public const int MaxHeaderSize = Size;
public const int MaxHeaderSize = SizeV4;
/// <summary>
/// Defines the size of the <see cref="HeaderSize"/> field.
@ -52,7 +67,24 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int xPelsPerMeter = 0,
int yPelsPerMeter = 0,
int clrUsed = 0,
int clrImportant = 0)
int clrImportant = 0,
int redMask = 0,
int greenMask = 0,
int blueMask = 0,
int alphaMask = 0,
int csType = 0,
int redX = 0,
int redY = 0,
int redZ = 0,
int greenX = 0,
int greenY = 0,
int greenZ = 0,
int blueX = 0,
int blueY = 0,
int blueZ = 0,
int gammeRed = 0,
int gammeGreen = 0,
int gammeBlue = 0)
{
this.HeaderSize = headerSize;
this.Width = width;
@ -65,6 +97,23 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.YPelsPerMeter = yPelsPerMeter;
this.ClrUsed = clrUsed;
this.ClrImportant = clrImportant;
this.RedMask = redMask;
this.GreenMask = greenMask;
this.BlueMask = blueMask;
this.AlphaMask = alphaMask;
this.CsType = csType;
this.RedX = redX;
this.RedY = redY;
this.RedZ = redZ;
this.GreenX = greenX;
this.GreenY = greenY;
this.GreenZ = greenZ;
this.BlueX = blueX;
this.BlueY = blueY;
this.BlueZ = blueZ;
this.GammaRed = gammeRed;
this.GammaGreen = gammeGreen;
this.GammaBlue = gammeBlue;
}
/// <summary>
@ -130,23 +179,92 @@ namespace SixLabors.ImageSharp.Formats.Bmp
public int ClrImportant { get; set; }
/// <summary>
/// Parses the full BITMAPINFOHEADER header (40 bytes).
/// Gets or sets red color mask. This is used with the BITFIELDS decoding.
/// </summary>
/// <param name="data">The data to parse.</param>
/// <returns>Parsed header</returns>
/// <seealso href="https://msdn.microsoft.com/en-us/library/windows/desktop/dd183376.aspx"/>
public static BmpInfoHeader Parse(ReadOnlySpan<byte> data)
{
if (data.Length != Size)
{
throw new ArgumentException(nameof(data), $"Must be {Size} bytes. Was {data.Length} bytes.");
}
public int RedMask { get; set; }
return MemoryMarshal.Cast<byte, BmpInfoHeader>(data)[0];
}
/// <summary>
/// Gets or sets green color mask. This is used with the BITFIELDS decoding.
/// </summary>
public int GreenMask { get; set; }
/// <summary>
/// Gets or sets blue color mask. This is used with the BITFIELDS decoding.
/// </summary>
public int BlueMask { get; set; }
/// <summary>
/// Gets or sets alpha color mask. This is not used yet.
/// </summary>
public int AlphaMask { get; set; }
/// <summary>
/// Gets or sets the Color space type. Not used yet.
/// </summary>
public int CsType { get; set; }
/// <summary>
/// Gets or sets the X coordinate of red endpoint. Not used yet.
/// </summary>
public int RedX { get; set; }
/// <summary>
/// Gets or sets the Y coordinate of red endpoint. Not used yet.
/// </summary>
public int RedY { get; set; }
/// <summary>
/// Gets or sets the Z coordinate of red endpoint. Not used yet.
/// </summary>
public int RedZ { get; set; }
/// <summary>
/// Gets or sets the X coordinate of green endpoint. Not used yet.
/// </summary>
public int GreenX { get; set; }
/// <summary>
/// Gets or sets the Y coordinate of green endpoint. Not used yet.
/// </summary>
public int GreenY { get; set; }
/// <summary>
/// Gets or sets the Z coordinate of green endpoint. Not used yet.
/// </summary>
public int GreenZ { get; set; }
/// <summary>
/// Gets or sets the X coordinate of blue endpoint. Not used yet.
/// </summary>
public int BlueX { get; set; }
/// <summary>
/// Parses the BITMAPCOREHEADER consisting of the headerSize, width, height, planes, and bitsPerPixel fields (12 bytes).
/// Gets or sets the Y coordinate of blue endpoint. Not used yet.
/// </summary>
public int BlueY { get; set; }
/// <summary>
/// Gets or sets the Z coordinate of blue endpoint. Not used yet.
/// </summary>
public int BlueZ { get; set; }
/// <summary>
/// Gets or sets the Gamma red coordinate scale value. Not used yet.
/// </summary>
public int GammaRed { get; set; }
/// <summary>
/// Gets or sets the Gamma green coordinate scale value. Not used yet.
/// </summary>
public int GammaGreen { get; set; }
/// <summary>
/// Gets or sets the Gamma blue coordinate scale value. Not used yet.
/// </summary>
public int GammaBlue { get; set; }
/// <summary>
/// Parses the BITMAPCOREHEADER (BMP Version 2) consisting of the headerSize, width, height, planes, and bitsPerPixel fields (12 bytes).
/// </summary>
/// <param name="data">The data to parse.</param>
/// <returns>Parsed header</returns>
@ -163,7 +281,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary>
/// Parses a short variant of the OS22XBITMAPHEADER. It is identical to the BITMAPCOREHEADER, except that the width and height
/// are 4 bytes instead of 2.
/// are 4 bytes instead of 2, resulting in 16 bytes total.
/// </summary>
/// <param name="data">The data to parse.</param>
/// <returns>Parsed header</returns>
@ -178,7 +296,97 @@ namespace SixLabors.ImageSharp.Formats.Bmp
bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2)));
}
public unsafe void WriteTo(Span<byte> buffer)
/// <summary>
/// Parses the full BMP Version 3 BITMAPINFOHEADER header (40 bytes).
/// </summary>
/// <param name="data">The data to parse.</param>
/// <returns>The parsed header.</returns>
/// <seealso href="http://www.fileformat.info/format/bmp/egff.htm"/>
public static BmpInfoHeader ParseV3(ReadOnlySpan<byte> data)
{
return new BmpInfoHeader(
headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)),
width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)),
height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)),
planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)),
bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2)),
compression: (BmpCompression)BinaryPrimitives.ReadInt32LittleEndian(data.Slice(16, 4)),
imageSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(20, 4)),
xPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(24, 4)),
yPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)),
clrUsed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)),
clrImportant: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4)));
}
/// <summary>
/// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks are part of the info header instead of following it.
/// 52 bytes without the alpha mask, 56 bytes with the alpha mask.
/// </summary>
/// <param name="data">The data to parse.</param>
/// <param name="withAlpha">Indicates, if the alpha bitmask is present.</param>
/// <returns>The parsed header.</returns>
/// <seealso href="https://forums.adobe.com/message/3272950#3272950"/>
public static BmpInfoHeader ParseAdobeV3(ReadOnlySpan<byte> data, bool withAlpha = true)
{
return new BmpInfoHeader(
headerSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(0, 4)),
width: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)),
height: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)),
planes: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(12, 2)),
bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2)),
compression: (BmpCompression)BinaryPrimitives.ReadInt32LittleEndian(data.Slice(16, 4)),
imageSize: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(20, 4)),
xPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(24, 4)),
yPelsPerMeter: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4)),
clrUsed: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4)),
clrImportant: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4)),
redMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(40, 4)),
greenMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(44, 4)),
blueMask: BinaryPrimitives.ReadInt32LittleEndian(data.Slice(48, 4)),
alphaMask: withAlpha ? BinaryPrimitives.ReadInt32LittleEndian(data.Slice(52, 4)) : 0);
}
/// <summary>
/// Parses the full BMP Version 4 BITMAPINFOHEADER header (108 bytes).
/// </summary>
/// <param name="data">The data to parse.</param>
/// <returns>The parsed header.</returns>
/// <seealso href="http://www.fileformat.info/format/bmp/egff.htm"/>
public static BmpInfoHeader ParseV4(ReadOnlySpan<byte> data)
{
if (data.Length != SizeV4)
{
throw new ArgumentException(nameof(data), $"Must be {SizeV4} bytes. Was {data.Length} bytes.");
}
return MemoryMarshal.Cast<byte, BmpInfoHeader>(data)[0];
}
/// <summary>
/// Writes a bitmap version 3 (Microsoft Windows NT) header to a buffer (40 bytes).
/// </summary>
/// <param name="buffer">The buffer to write to.</param>
public void WriteV3Header(Span<byte> buffer)
{
buffer.Clear();
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(0, 4), SizeV3);
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(4, 4), this.Width);
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(8, 4), this.Height);
BinaryPrimitives.WriteInt16LittleEndian(buffer.Slice(12, 2), this.Planes);
BinaryPrimitives.WriteInt16LittleEndian(buffer.Slice(14, 2), this.BitsPerPixel);
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(16, 4), (int)this.Compression);
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(20, 4), this.ImageSize);
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(24, 4), this.XPelsPerMeter);
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(28, 4), this.YPelsPerMeter);
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(32, 4), this.ClrUsed);
BinaryPrimitives.WriteInt32LittleEndian(buffer.Slice(36, 4), this.ClrImportant);
}
/// <summary>
/// Writes a complete Bitmap V4 header to a buffer.
/// </summary>
/// <param name="buffer">The buffer to write to.</param>
public unsafe void WriteV4Header(Span<byte> buffer)
{
ref BmpInfoHeader dest = ref Unsafe.As<byte, BmpInfoHeader>(ref MemoryMarshal.GetReference(buffer));

48
src/ImageSharp/Formats/Bmp/BmpInfoHeaderType.cs

@ -0,0 +1,48 @@
namespace SixLabors.ImageSharp.Formats.Bmp
{
/// <summary>
/// Enum value for the different bitmap info header types. The enum value is the number of bytes for the specific bitmap header.
/// </summary>
public enum BmpInfoHeaderType
{
/// <summary>
/// Bitmap Core or BMP Version 2 header (Microsoft Windows 2.x).
/// </summary>
WinVersion2 = 12,
/// <summary>
/// Short variant of the OS/2 Version 2 bitmap header.
/// </summary>
Os2Version2Short = 16,
/// <summary>
/// BMP Version 3 header (Microsoft Windows 3.x or Microsoft Windows NT).
/// </summary>
WinVersion3 = 40,
/// <summary>
/// Adobe variant of the BMP Version 3 header.
/// </summary>
AdobeVersion3 = 52,
/// <summary>
/// Adobe variant of the BMP Version 3 header with an alpha mask.
/// </summary>
AdobeVersion3WithAlpha = 56,
/// <summary>
/// BMP Version 2.x header (IBM OS/2 2.x).
/// </summary>
Os2Version2 = 64,
/// <summary>
/// BMP Version 4 header (Microsoft Windows 95).
/// </summary>
WinVersion4 = 108,
/// <summary>
/// BMP Version 5 header (Windows NT 5.0, 98 or later).
/// </summary>
WinVersion5 = 124,
}
}

11
src/ImageSharp/Formats/Bmp/BmpMetaData.cs

@ -19,7 +19,16 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// Initializes a new instance of the <see cref="BmpMetaData"/> class.
/// </summary>
/// <param name="other">The metadata to create an instance from.</param>
private BmpMetaData(BmpMetaData other) => this.BitsPerPixel = other.BitsPerPixel;
private BmpMetaData(BmpMetaData other)
{
this.BitsPerPixel = other.BitsPerPixel;
this.InfoHeaderType = other.InfoHeaderType;
}
/// <summary>
/// Gets or sets the bitmap info header type.
/// </summary>
public BmpInfoHeaderType InfoHeaderType { get; set; }
/// <summary>
/// Gets or sets the number of bits per pixel.

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

@ -4,6 +4,9 @@
using System.IO;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
using Xunit;
// ReSharper disable InconsistentNaming
@ -19,6 +22,8 @@ namespace SixLabors.ImageSharp.Tests
public static readonly string[] AllBmpFiles = All;
public static readonly string[] BitfieldsBmpFiles = BitFields;
public static readonly TheoryData<string, int, int, PixelResolutionUnit> RatioFiles =
new TheoryData<string, int, int, PixelResolutionUnit>
{
@ -34,7 +39,7 @@ namespace SixLabors.ImageSharp.Tests
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
{
image.DebugSave(provider, "bmp");
image.DebugSave(provider);
if (TestEnvironment.IsWindows)
{
@ -44,17 +49,47 @@ namespace SixLabors.ImageSharp.Tests
}
[Theory]
[WithFile(F, CommonNonDefaultPixelTypes)]
public void BmpDecoder_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
[WithFileCollection(nameof(BitfieldsBmpFiles), PixelTypes.Rgba32)]
public void BmpDecoder_CanDecodeBitfields<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
{
image.DebugSave(provider, "bmp");
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
}
[Theory]
[WithFile(Bit32Rgba, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecodeBitmap_WithAlphaChannel<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
}
[Theory]
[WithFile(Rgba321010102, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecodeBitfields_WithUnusualBitmasks<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
{
image.DebugSave(provider);
// Choosing large tolerance of 6.1 here, because for some reason with the MagickReferenceDecoder the alpha channel
// seems to be wrong. This bitmap has an alpha channel of two bits. In many cases this alpha channel has a value of 3,
// which should be remapped to 255 for RGBA32, but the magick decoder has a value of 191 set.
// The total difference without the alpha channel is still: 0.0204%
// Exporting the image as PNG with GIMP yields to the same result as the imagesharp implementation.
image.CompareToOriginal(provider, ImageComparer.TolerantPercentage(6.1f), new MagickReferenceDecoder());
}
}
[Theory]
[WithFile(WinBmpv2, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecodeBmpv2<TPixel>(TestImageProvider<TPixel> provider)
@ -62,7 +97,67 @@ namespace SixLabors.ImageSharp.Tests
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
{
image.DebugSave(provider, "png");
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
}
[Theory]
[WithFile(WinBmpv3, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecodeBmpv3<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
}
[Theory]
[WithFile(Rgba32bf56, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecodeAdobeBmpv3<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder());
}
}
[Theory]
[WithFile(WinBmpv4, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecodeBmpv4<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
}
[Theory]
[WithFile(WinBmpv5, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecodeBmpv5<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
}
[Theory]
[WithFile(F, CommonNonDefaultPixelTypes)]
public void BmpDecoder_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
{
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
}
@ -74,7 +169,7 @@ namespace SixLabors.ImageSharp.Tests
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
{
image.DebugSave(provider, "png");
image.DebugSave(provider);
image.CompareToOriginal(provider);
}
}

29
tests/ImageSharp.Tests/TestImages.cs

@ -205,12 +205,36 @@ namespace SixLabors.ImageSharp.Tests
public const string Bit16 = "Bmp/test16.bmp";
public const string Bit16Inverted = "Bmp/test16-inverted.bmp";
public const string Bit32Rgb = "Bmp/rgb32.bmp";
public const string Bit32Rgba = "Bmp/rgba32.bmp";
// Note: This format can be called OS/2 BMPv1, or Windows BMPv2
public const string WinBmpv2 = "Bmp/pal8os2v1_winv2.bmp";
public const string WinBmpv3 = "Bmp/rgb24.bmp";
public const string WinBmpv4 = "Bmp/pal8v4.bmp";
public const string WinBmpv5 = "Bmp/pal8v5.bmp";
public const string Bit8Palette4 = "Bmp/pal8-0.bmp";
public const string Os2v2Short = "Bmp/pal8os2v2-16.bmp";
// Bitmap images with compression type BITFIELDS
public const string Rgb32bfdef = "Bmp/rgb32bfdef.bmp";
public const string Rgb32bf = "Bmp/rgb32bf.bmp";
public const string Rgb16565 = "Bmp/rgb16-565.bmp";
public const string Rgb16bfdef = "Bmp/rgb16bfdef.bmp";
public const string Rgb16565pal = "Bmp/rgb16-565pal.bmp";
public const string Issue735 = "Bmp/issue735.bmp";
public const string Rgba32bf56 = "Bmp/rgba32h56.bmp";
public const string Rgba321010102 = "Bmp/rgba32-1010102.bmp";
public static readonly string[] BitFields
= {
Rgb32bfdef,
Rgb32bf,
Rgb16565,
Rgb16bfdef,
Rgb16565pal,
Issue735,
};
public static readonly string[] All
= {
Car,
@ -225,7 +249,8 @@ namespace SixLabors.ImageSharp.Tests
Bit8,
Bit8Inverted,
Bit16,
Bit16Inverted
Bit16Inverted,
Bit32Rgb
};
}

3
tests/Images/Input/Bmp/issue735.bmp

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2d4b076196ca2559581b1e64f448b3dcefc78ba38a6f91953b2d7cfa95465d11
size 1334298

3
tests/Images/Input/Bmp/pal8v4.bmp

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4dc4a9725f4c7b7b8ea1e181d1aee9355fe724dbd949ab562b3b364df9c90d64
size 9322

3
tests/Images/Input/Bmp/pal8v5.bmp

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:65ac4a579189d2fc30b3499b8246f30127e032f35d6cde90335dafe87a1b23c3
size 9338

3
tests/Images/Input/Bmp/rgb16-565.bmp

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c2ffadac9c1239fb397834415c7b5f85d5c6044bd9c31fa66e23056a19b82b1d
size 16450

3
tests/Images/Input/Bmp/rgb16-565pal.bmp

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bcf485398db9c92235e5e9a7c20aaf3899ecf00ccdf25680f85e0797df610ea4
size 17474

3
tests/Images/Input/Bmp/rgb16bfdef.bmp

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3c5d1d67482b769d875b552e6012b80434880f763c9d56a45a94d07bbbe16fba
size 16450

3
tests/Images/Input/Bmp/rgb24.bmp

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a9c4fbfbf8cb6df8d2d9d1484359d037aebd25078b21137bfd6c69739fcbe2e1
size 24630

3
tests/Images/Input/Bmp/rgb32bf.bmp

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4c1189e3a039b1e5aae6347024684937c495a5736a3c309dc7c2e3c161f89751
size 32578

3
tests/Images/Input/Bmp/rgb32bfdef.bmp

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:54d89660058b6ade17f54d0664b67ced375bbd67db9b89a4ada3addeaab7381a
size 32578

3
tests/Images/Input/Bmp/rgba32-1010102.bmp

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3a784719de2d33f8678ea0efb8b52be662e0f126e723150010057cf8fd89e777
size 32650

3
tests/Images/Input/Bmp/rgba32.bmp

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:257e0127fa0ccdb6eb8381338dda0fb692b2ea4d38775f1aed56dfdccaed2ab4
size 32650

3
tests/Images/Input/Bmp/rgba32h56.bmp

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:12d76fd71a341c80a80a5c0116dea8d7338fe801d8483b594124d7b20ed1d782
size 32582
Loading…
Cancel
Save