diff --git a/src/ImageSharp/Formats/Bmp/BmpCompression.cs b/src/ImageSharp/Formats/Bmp/BmpCompression.cs
index ef063f010..5f14d2243 100644
--- a/src/ImageSharp/Formats/Bmp/BmpCompression.cs
+++ b/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.
///
RLE8 = 1,
@@ -42,7 +41,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
/// 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.
///
BitFields = 3,
diff --git a/src/ImageSharp/Formats/Bmp/BmpConstants.cs b/src/ImageSharp/Formats/Bmp/BmpConstants.cs
index 99799b619..5cbed4af2 100644
--- a/src/ImageSharp/Formats/Bmp/BmpConstants.cs
+++ b/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.
///
public static readonly IEnumerable FileExtensions = new[] { "bm", "bmp", "dip" };
+
+ ///
+ /// Valid magic bytes markers identifying a Bitmap file.
+ ///
+ internal static class TypeMarkers
+ {
+ ///
+ /// Single-image BMP file that may have been created under Windows or OS/2.
+ ///
+ public const int Bitmap = 0x4D42;
+
+ ///
+ /// OS/2 Bitmap Array.
+ ///
+ public const int BitmapArray = 0x4142;
+
+ ///
+ /// OS/2 Color Icon.
+ ///
+ public const int ColorIcon = 0x4943;
+
+ ///
+ /// OS/2 Color Pointer.
+ ///
+ public const int ColorPointer = 0x5043;
+
+ ///
+ /// OS/2 Icon.
+ ///
+ public const int Icon = 0x4349;
+
+ ///
+ /// OS/2 Pointer.
+ ///
+ public const int Pointer = 0x5450;
+ }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs
index 3d079cf61..82af2a671 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs
@@ -15,8 +15,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// - JPG
/// - PNG
/// - RLE4
- /// - RLE8
- /// - BitFields
///
/// Formats will be supported in a later releases. We advise always
/// to use only 24 Bit Windows bitmaps.
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
index 68528edcd..362fe6443 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
+++ b/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
{
///
- /// Performs the bmp decoding operation.
+ /// Performs the bitmap decoding operation.
///
///
/// A useful decoding source example can be found at
@@ -22,19 +24,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp
internal sealed class BmpDecoderCore
{
///
- /// 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.
///
- private const int Rgb16RMask = 0x7C00;
+ private const int DefaultRgb16RMask = 0x7C00;
///
- /// 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.
///
- private const int Rgb16GMask = 0x3E0;
+ private const int DefaultRgb16GMask = 0x3E0;
///
- /// 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.
///
- private const int Rgb16BMask = 0x1F;
+ private const int DefaultRgb16BMask = 0x1F;
///
/// RLE8 flag value that indicates following byte has special meaning.
@@ -62,10 +64,15 @@ namespace SixLabors.ImageSharp.Formats.Bmp
private Stream stream;
///
- /// The metadata
+ /// The metadata.
///
private ImageMetaData metaData;
+ ///
+ /// The bmp specific metadata.
+ ///
+ private BmpMetaData bmpMetaData;
+
///
/// 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 class.
///
/// The configuration.
- /// The options
+ /// The options.
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
}
///
- /// 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.
///
- /// The masked and shifted value
- /// The
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static byte GetBytesFrom5BitValue(int value) => (byte)((value << 3) | (value >> 2));
+ /// The pixel format.
+ /// The output pixel buffer containing the decoded image.
+ /// Whether the bitmap is inverted.
+ private void ReadBitFields(Buffer2D pixels, bool inverted)
+ where TPixel : struct, IPixel
+ {
+ 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);
+ }
+ }
///
/// Looks up color values and builds the image from de-compressed RLE8 data.
@@ -240,12 +287,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
///
- /// Produce uncompressed bitmap data from RLE8 stream
+ /// Produce uncompressed bitmap data from RLE8 stream.
///
///
- /// RLE8 is a 2-byte run-length encoding
- ///
If first byte is 0, the second byte may have special meaning
- ///
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.
+ ///
If first byte is 0, the second byte may have special meaning.
+ ///
Otherwise, first byte is the length of the run and second byte is the color for the run.
///
/// The width of the bitmap.
/// Buffer for uncompressed data.
@@ -382,20 +429,32 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
///
- /// Reads the 16 bit color palette from the stream
+ /// Reads the 16 bit color palette from the stream.
///
/// The pixel format.
/// The to assign the palette to.
/// The width of the bitmap.
/// The height of the bitmap.
/// Whether the bitmap is inverted.
- private void ReadRgb16(Buffer2D pixels, int width, int height, bool inverted)
+ /// The bitmask for the red channel.
+ /// The bitmask for the green channel.
+ /// The bitmask for the blue channel.
+ private void ReadRgb16(Buffer2D pixels, int width, int height, bool inverted, int redMask = DefaultRgb16RMask, int greenMask = DefaultRgb16GMask, int blueMask = DefaultRgb16BMask)
where TPixel : struct, IPixel
{
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
}
///
- /// Reads the 24 bit color palette from the stream
+ /// Performs final shifting from a 5bit value to an 8bit one.
+ ///
+ /// The masked and shifted value.
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static byte GetBytesFrom5BitValue(int value) => (byte)((value << 3) | (value >> 2));
+
+ ///
+ /// Performs final shifting from a 6bit value to an 8bit one.
+ ///
+ /// The masked and shifted value.
+ /// The
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static byte GetBytesFrom6BitValue(int value) => (byte)((value << 2) | (value >> 4));
+
+ ///
+ /// Reads the 24 bit color palette from the stream.
///
/// The pixel format.
/// The to assign the palette to.
@@ -452,14 +528,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
///
- /// Reads the 32 bit color palette from the stream
+ /// Reads the 32 bit color palette from the stream.
///
/// The pixel format.
/// The to assign the palette to.
/// The width of the bitmap.
/// The height of the bitmap.
/// Whether the bitmap is inverted.
- private void ReadRgb32(Buffer2D pixels, int width, int height, bool inverted)
+ private void ReadRgb32Fast(Buffer2D pixels, int width, int height, bool inverted)
where TPixel : struct, IPixel
{
int padding = CalculatePadding(width, 4);
@@ -480,6 +556,228 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
}
+ ///
+ /// 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.
+ ///
+ /// The pixel format.
+ /// The to assign the palette to.
+ /// The width of the bitmap.
+ /// The height of the bitmap.
+ /// Whether the bitmap is inverted.
+ private void ReadRgb32Slow(Buffer2D pixels, int width, int height, bool inverted)
+ where TPixel : struct, IPixel
+ {
+ int padding = CalculatePadding(width, 4);
+
+ using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding))
+ using (IMemoryOwner bgraRow = this.memoryAllocator.Allocate(width))
+ {
+ Span 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.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 pixelSpan = pixels.GetRowSpan(newY);
+
+ PixelOperations.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.Instance.FromBgra32Bytes(
+ this.configuration,
+ row.GetSpan(),
+ bgraRowSpan,
+ width);
+
+ int newY = Invert(y, height, inverted);
+ Span 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);
+ }
+ }
+ }
+ }
+
+ ///
+ /// Decode an 32 Bit Bitmap containing a bitmask for each color channel.
+ ///
+ /// The pixel format.
+ /// The output pixel buffer containing the decoded image.
+ /// The width of the image.
+ /// The height of the image.
+ /// Whether the bitmap is inverted.
+ /// The bitmask for the red channel.
+ /// The bitmask for the green channel.
+ /// The bitmask for the blue channel.
+ /// The bitmask for the alpha channel.
+ private void ReadRgb32BitFields(Buffer2D pixels, int width, int height, bool inverted, int redMask, int greenMask, int blueMask, int alphaMask)
+ where TPixel : struct, IPixel
+ {
+ 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 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;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Calculates the necessary right shifts for a given color bitmask (the 0 bits to the right).
+ ///
+ /// The color bit mask.
+ /// Number of bits to shift right.
+ private static int CalculateRightShift(uint n)
+ {
+ int count = 0;
+ while (n > 0)
+ {
+ if ((1 & n) == 0)
+ {
+ count++;
+ }
+ else
+ {
+ break;
+ }
+
+ n >>= 1;
+ }
+
+ return count;
+ }
+
+ ///
+ /// Counts none zero bits.
+ ///
+ /// A color mask.
+ /// The none zero bits.
+ private static int CountBits(uint n)
+ {
+ int count = 0;
+ while (n != 0)
+ {
+ count++;
+ n &= n - 1;
+ }
+
+ return count;
+ }
+
///
/// Reads the from the stream.
///
@@ -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 data = bitfieldsBuffer.AsSpan();
+ 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
diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
index a67c581eb..27a38bc0d 100644
--- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
@@ -23,6 +23,26 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
private int padding;
+ ///
+ /// The mask for the alpha channel of the color for a 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.
+ ///
+ private const int Rgba32RedMask = 0xFF << 16;
+
+ ///
+ /// The mask for the green part of the color for a 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.
+ ///
+ 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 buffer = stackalloc byte[40];
+ Span 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);
diff --git a/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs b/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
index 5177bc325..316df4acc 100644
--- a/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
@@ -16,11 +16,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct BmpInfoHeader
{
- ///
- /// Defines the size of the BITMAPINFOHEADER data structure in the bitmap file.
- ///
- public const int Size = 40;
-
///
/// Defines the size of the BITMAPCOREHEADER data structure in the bitmap file.
///
@@ -31,10 +26,30 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
public const int Os22ShortSize = 16;
+ ///
+ /// Defines the size of the BITMAPINFOHEADER (BMP Version 3) data structure in the bitmap file.
+ ///
+ public const int SizeV3 = 40;
+
+ ///
+ /// Special case of the BITMAPINFOHEADER V3 used by adobe where the color bitmasks are part of the info header instead of following it.
+ ///
+ public const int AdobeV3Size = 52;
+
+ ///
+ /// 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.
+ ///
+ public const int AdobeV3WithAlphaSize = 56;
+
+ ///
+ /// Defines the size of the BITMAPINFOHEADER (BMP Version 4) data structure in the bitmap file.
+ ///
+ public const int SizeV4 = 108;
+
///
/// Defines the size of the biggest supported header data structure in the bitmap file.
///
- public const int MaxHeaderSize = Size;
+ public const int MaxHeaderSize = SizeV4;
///
/// Defines the size of the 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;
}
///
@@ -130,23 +179,92 @@ namespace SixLabors.ImageSharp.Formats.Bmp
public int ClrImportant { get; set; }
///
- /// Parses the full BITMAPINFOHEADER header (40 bytes).
+ /// Gets or sets red color mask. This is used with the BITFIELDS decoding.
///
- /// The data to parse.
- /// Parsed header
- ///
- public static BmpInfoHeader Parse(ReadOnlySpan 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(data)[0];
- }
+ ///
+ /// Gets or sets green color mask. This is used with the BITFIELDS decoding.
+ ///
+ public int GreenMask { get; set; }
+
+ ///
+ /// Gets or sets blue color mask. This is used with the BITFIELDS decoding.
+ ///
+ public int BlueMask { get; set; }
+
+ ///
+ /// Gets or sets alpha color mask. This is not used yet.
+ ///
+ public int AlphaMask { get; set; }
+
+ ///
+ /// Gets or sets the Color space type. Not used yet.
+ ///
+ public int CsType { get; set; }
+
+ ///
+ /// Gets or sets the X coordinate of red endpoint. Not used yet.
+ ///
+ public int RedX { get; set; }
+
+ ///
+ /// Gets or sets the Y coordinate of red endpoint. Not used yet.
+ ///
+ public int RedY { get; set; }
+
+ ///
+ /// Gets or sets the Z coordinate of red endpoint. Not used yet.
+ ///
+ public int RedZ { get; set; }
+
+ ///
+ /// Gets or sets the X coordinate of green endpoint. Not used yet.
+ ///
+ public int GreenX { get; set; }
+
+ ///
+ /// Gets or sets the Y coordinate of green endpoint. Not used yet.
+ ///
+ public int GreenY { get; set; }
+
+ ///
+ /// Gets or sets the Z coordinate of green endpoint. Not used yet.
+ ///
+ public int GreenZ { get; set; }
+
+ ///
+ /// Gets or sets the X coordinate of blue endpoint. Not used yet.
+ ///
+ public int BlueX { get; set; }
///
- /// 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.
+ ///
+ public int BlueY { get; set; }
+
+ ///
+ /// Gets or sets the Z coordinate of blue endpoint. Not used yet.
+ ///
+ public int BlueZ { get; set; }
+
+ ///
+ /// Gets or sets the Gamma red coordinate scale value. Not used yet.
+ ///
+ public int GammaRed { get; set; }
+
+ ///
+ /// Gets or sets the Gamma green coordinate scale value. Not used yet.
+ ///
+ public int GammaGreen { get; set; }
+
+ ///
+ /// Gets or sets the Gamma blue coordinate scale value. Not used yet.
+ ///
+ public int GammaBlue { get; set; }
+
+ ///
+ /// Parses the BITMAPCOREHEADER (BMP Version 2) consisting of the headerSize, width, height, planes, and bitsPerPixel fields (12 bytes).
///
/// The data to parse.
/// Parsed header
@@ -163,7 +281,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
/// 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.
///
/// The data to parse.
/// Parsed header
@@ -178,7 +296,97 @@ namespace SixLabors.ImageSharp.Formats.Bmp
bitsPerPixel: BinaryPrimitives.ReadInt16LittleEndian(data.Slice(14, 2)));
}
- public unsafe void WriteTo(Span buffer)
+ ///
+ /// Parses the full BMP Version 3 BITMAPINFOHEADER header (40 bytes).
+ ///
+ /// The data to parse.
+ /// The parsed header.
+ ///
+ public static BmpInfoHeader ParseV3(ReadOnlySpan 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)));
+ }
+
+ ///
+ /// 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.
+ ///
+ /// The data to parse.
+ /// Indicates, if the alpha bitmask is present.
+ /// The parsed header.
+ ///
+ public static BmpInfoHeader ParseAdobeV3(ReadOnlySpan 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);
+ }
+
+ ///
+ /// Parses the full BMP Version 4 BITMAPINFOHEADER header (108 bytes).
+ ///
+ /// The data to parse.
+ /// The parsed header.
+ ///
+ public static BmpInfoHeader ParseV4(ReadOnlySpan data)
+ {
+ if (data.Length != SizeV4)
+ {
+ throw new ArgumentException(nameof(data), $"Must be {SizeV4} bytes. Was {data.Length} bytes.");
+ }
+
+ return MemoryMarshal.Cast(data)[0];
+ }
+
+ ///
+ /// Writes a bitmap version 3 (Microsoft Windows NT) header to a buffer (40 bytes).
+ ///
+ /// The buffer to write to.
+ public void WriteV3Header(Span 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);
+ }
+
+ ///
+ /// Writes a complete Bitmap V4 header to a buffer.
+ ///
+ /// The buffer to write to.
+ public unsafe void WriteV4Header(Span buffer)
{
ref BmpInfoHeader dest = ref Unsafe.As(ref MemoryMarshal.GetReference(buffer));
diff --git a/src/ImageSharp/Formats/Bmp/BmpInfoHeaderType.cs b/src/ImageSharp/Formats/Bmp/BmpInfoHeaderType.cs
new file mode 100644
index 000000000..a4ce0115f
--- /dev/null
+++ b/src/ImageSharp/Formats/Bmp/BmpInfoHeaderType.cs
@@ -0,0 +1,48 @@
+namespace SixLabors.ImageSharp.Formats.Bmp
+{
+ ///
+ /// Enum value for the different bitmap info header types. The enum value is the number of bytes for the specific bitmap header.
+ ///
+ public enum BmpInfoHeaderType
+ {
+ ///
+ /// Bitmap Core or BMP Version 2 header (Microsoft Windows 2.x).
+ ///
+ WinVersion2 = 12,
+
+ ///
+ /// Short variant of the OS/2 Version 2 bitmap header.
+ ///
+ Os2Version2Short = 16,
+
+ ///
+ /// BMP Version 3 header (Microsoft Windows 3.x or Microsoft Windows NT).
+ ///
+ WinVersion3 = 40,
+
+ ///
+ /// Adobe variant of the BMP Version 3 header.
+ ///
+ AdobeVersion3 = 52,
+
+ ///
+ /// Adobe variant of the BMP Version 3 header with an alpha mask.
+ ///
+ AdobeVersion3WithAlpha = 56,
+
+ ///
+ /// BMP Version 2.x header (IBM OS/2 2.x).
+ ///
+ Os2Version2 = 64,
+
+ ///
+ /// BMP Version 4 header (Microsoft Windows 95).
+ ///
+ WinVersion4 = 108,
+
+ ///
+ /// BMP Version 5 header (Windows NT 5.0, 98 or later).
+ ///
+ WinVersion5 = 124,
+ }
+}
diff --git a/src/ImageSharp/Formats/Bmp/BmpMetaData.cs b/src/ImageSharp/Formats/Bmp/BmpMetaData.cs
index 8b33e30fa..2d8685617 100644
--- a/src/ImageSharp/Formats/Bmp/BmpMetaData.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpMetaData.cs
@@ -19,7 +19,16 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// Initializes a new instance of the class.
///
/// The metadata to create an instance from.
- private BmpMetaData(BmpMetaData other) => this.BitsPerPixel = other.BitsPerPixel;
+ private BmpMetaData(BmpMetaData other)
+ {
+ this.BitsPerPixel = other.BitsPerPixel;
+ this.InfoHeaderType = other.InfoHeaderType;
+ }
+
+ ///
+ /// Gets or sets the bitmap info header type.
+ ///
+ public BmpInfoHeaderType InfoHeaderType { get; set; }
///
/// Gets or sets the number of bits per pixel.
diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
index 251475567..60e45ae35 100644
--- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
+++ b/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 RatioFiles =
new TheoryData
{
@@ -34,7 +39,7 @@ namespace SixLabors.ImageSharp.Tests
{
using (Image 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(TestImageProvider provider)
+ [WithFileCollection(nameof(BitfieldsBmpFiles), PixelTypes.Rgba32)]
+ public void BmpDecoder_CanDecodeBitfields(TestImageProvider provider)
where TPixel : struct, IPixel
{
using (Image 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(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new BmpDecoder()))
+ {
+ image.DebugSave(provider);
+ image.CompareToOriginal(provider, new MagickReferenceDecoder());
+ }
+ }
+
+ [Theory]
+ [WithFile(Rgba321010102, PixelTypes.Rgba32)]
+ public void BmpDecoder_CanDecodeBitfields_WithUnusualBitmasks(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image 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(TestImageProvider provider)
@@ -62,7 +97,67 @@ namespace SixLabors.ImageSharp.Tests
{
using (Image image = provider.GetImage(new BmpDecoder()))
{
- image.DebugSave(provider, "png");
+ image.DebugSave(provider);
+ image.CompareToOriginal(provider);
+ }
+ }
+
+ [Theory]
+ [WithFile(WinBmpv3, PixelTypes.Rgba32)]
+ public void BmpDecoder_CanDecodeBmpv3(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new BmpDecoder()))
+ {
+ image.DebugSave(provider);
+ image.CompareToOriginal(provider);
+ }
+ }
+
+ [Theory]
+ [WithFile(Rgba32bf56, PixelTypes.Rgba32)]
+ public void BmpDecoder_CanDecodeAdobeBmpv3(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new BmpDecoder()))
+ {
+ image.DebugSave(provider);
+ image.CompareToOriginal(provider, new MagickReferenceDecoder());
+ }
+ }
+
+ [Theory]
+ [WithFile(WinBmpv4, PixelTypes.Rgba32)]
+ public void BmpDecoder_CanDecodeBmpv4(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new BmpDecoder()))
+ {
+ image.DebugSave(provider);
+ image.CompareToOriginal(provider);
+ }
+ }
+
+ [Theory]
+ [WithFile(WinBmpv5, PixelTypes.Rgba32)]
+ public void BmpDecoder_CanDecodeBmpv5(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new BmpDecoder()))
+ {
+ image.DebugSave(provider);
+ image.CompareToOriginal(provider);
+ }
+ }
+
+ [Theory]
+ [WithFile(F, CommonNonDefaultPixelTypes)]
+ public void BmpDecoder_IsNotBoundToSinglePixelType(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new BmpDecoder()))
+ {
+ image.DebugSave(provider);
image.CompareToOriginal(provider);
}
}
@@ -74,7 +169,7 @@ namespace SixLabors.ImageSharp.Tests
{
using (Image image = provider.GetImage(new BmpDecoder()))
{
- image.DebugSave(provider, "png");
+ image.DebugSave(provider);
image.CompareToOriginal(provider);
}
}
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 8d8a32fba..5ecc26657 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/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
};
}
diff --git a/tests/Images/Input/Bmp/issue735.bmp b/tests/Images/Input/Bmp/issue735.bmp
new file mode 100644
index 000000000..139e73830
--- /dev/null
+++ b/tests/Images/Input/Bmp/issue735.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2d4b076196ca2559581b1e64f448b3dcefc78ba38a6f91953b2d7cfa95465d11
+size 1334298
diff --git a/tests/Images/Input/Bmp/pal8v4.bmp b/tests/Images/Input/Bmp/pal8v4.bmp
new file mode 100644
index 000000000..87dad63bc
--- /dev/null
+++ b/tests/Images/Input/Bmp/pal8v4.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4dc4a9725f4c7b7b8ea1e181d1aee9355fe724dbd949ab562b3b364df9c90d64
+size 9322
diff --git a/tests/Images/Input/Bmp/pal8v5.bmp b/tests/Images/Input/Bmp/pal8v5.bmp
new file mode 100644
index 000000000..82c8b0ed7
--- /dev/null
+++ b/tests/Images/Input/Bmp/pal8v5.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:65ac4a579189d2fc30b3499b8246f30127e032f35d6cde90335dafe87a1b23c3
+size 9338
diff --git a/tests/Images/Input/Bmp/rgb16-565.bmp b/tests/Images/Input/Bmp/rgb16-565.bmp
new file mode 100644
index 000000000..d0d65bdbc
--- /dev/null
+++ b/tests/Images/Input/Bmp/rgb16-565.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c2ffadac9c1239fb397834415c7b5f85d5c6044bd9c31fa66e23056a19b82b1d
+size 16450
diff --git a/tests/Images/Input/Bmp/rgb16-565pal.bmp b/tests/Images/Input/Bmp/rgb16-565pal.bmp
new file mode 100644
index 000000000..5bf169162
--- /dev/null
+++ b/tests/Images/Input/Bmp/rgb16-565pal.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bcf485398db9c92235e5e9a7c20aaf3899ecf00ccdf25680f85e0797df610ea4
+size 17474
diff --git a/tests/Images/Input/Bmp/rgb16bfdef.bmp b/tests/Images/Input/Bmp/rgb16bfdef.bmp
new file mode 100644
index 000000000..5967db928
--- /dev/null
+++ b/tests/Images/Input/Bmp/rgb16bfdef.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3c5d1d67482b769d875b552e6012b80434880f763c9d56a45a94d07bbbe16fba
+size 16450
diff --git a/tests/Images/Input/Bmp/rgb24.bmp b/tests/Images/Input/Bmp/rgb24.bmp
new file mode 100644
index 000000000..2f54e2d5a
--- /dev/null
+++ b/tests/Images/Input/Bmp/rgb24.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a9c4fbfbf8cb6df8d2d9d1484359d037aebd25078b21137bfd6c69739fcbe2e1
+size 24630
diff --git a/tests/Images/Input/Bmp/rgb32bf.bmp b/tests/Images/Input/Bmp/rgb32bf.bmp
new file mode 100644
index 000000000..dc6d38f8d
--- /dev/null
+++ b/tests/Images/Input/Bmp/rgb32bf.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4c1189e3a039b1e5aae6347024684937c495a5736a3c309dc7c2e3c161f89751
+size 32578
diff --git a/tests/Images/Input/Bmp/rgb32bfdef.bmp b/tests/Images/Input/Bmp/rgb32bfdef.bmp
new file mode 100644
index 000000000..64598caf9
--- /dev/null
+++ b/tests/Images/Input/Bmp/rgb32bfdef.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:54d89660058b6ade17f54d0664b67ced375bbd67db9b89a4ada3addeaab7381a
+size 32578
diff --git a/tests/Images/Input/Bmp/rgba32-1010102.bmp b/tests/Images/Input/Bmp/rgba32-1010102.bmp
new file mode 100644
index 000000000..2eb610cbf
--- /dev/null
+++ b/tests/Images/Input/Bmp/rgba32-1010102.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3a784719de2d33f8678ea0efb8b52be662e0f126e723150010057cf8fd89e777
+size 32650
diff --git a/tests/Images/Input/Bmp/rgba32.bmp b/tests/Images/Input/Bmp/rgba32.bmp
new file mode 100644
index 000000000..e331b25bd
--- /dev/null
+++ b/tests/Images/Input/Bmp/rgba32.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:257e0127fa0ccdb6eb8381338dda0fb692b2ea4d38775f1aed56dfdccaed2ab4
+size 32650
diff --git a/tests/Images/Input/Bmp/rgba32h56.bmp b/tests/Images/Input/Bmp/rgba32h56.bmp
new file mode 100644
index 000000000..9efaa4e4e
--- /dev/null
+++ b/tests/Images/Input/Bmp/rgba32h56.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:12d76fd71a341c80a80a5c0116dea8d7338fe801d8483b594124d7b20ed1d782
+size 32582