diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
index 8ca698b87..6bfdfa3a2 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
@@ -168,7 +168,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp
break;
default:
- throw new NotSupportedException("Does not support this kind of bitmap files.");
+ BmpThrowHelper.ThrowNotSupportedException("Does not support this kind of bitmap files.");
+
+ break;
}
return image;
@@ -319,7 +321,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
if (this.stream.Read(cmd, 0, cmd.Length) != 2)
{
- throw new Exception("Failed to read 2 bytes from the stream");
+ BmpThrowHelper.ThrowImageFormatException("Failed to read 2 bytes from the stream while uncompressing RLE4 bitmap.");
}
if (cmd[0] == RleCommand)
@@ -429,7 +431,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
if (this.stream.Read(cmd, 0, cmd.Length) != 2)
{
- throw new Exception("Failed to read 2 bytes from stream");
+ BmpThrowHelper.ThrowImageFormatException("Failed to read 2 bytes from stream while uncompressing RLE8 bitmap.");
}
if (cmd[0] == RleCommand)
@@ -913,7 +915,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int headerSize = BinaryPrimitives.ReadInt32LittleEndian(buffer);
if (headerSize < BmpInfoHeader.CoreSize)
{
- throw new NotSupportedException($"ImageSharp does not support this BMP file. HeaderSize: {headerSize}.");
+ BmpThrowHelper.ThrowNotSupportedException($"ImageSharp does not support this BMP file. HeaderSize is '{headerSize}'.");
}
int skipAmount = 0;
@@ -926,23 +928,23 @@ namespace SixLabors.ImageSharp.Formats.Bmp
// read the rest of the header
this.stream.Read(buffer, BmpInfoHeader.HeaderSizeSize, headerSize - BmpInfoHeader.HeaderSizeSize);
- BmpInfoHeaderType inofHeaderType = BmpInfoHeaderType.WinVersion2;
+ BmpInfoHeaderType infoHeaderType = BmpInfoHeaderType.WinVersion2;
if (headerSize == BmpInfoHeader.CoreSize)
{
// 12 bytes
- inofHeaderType = BmpInfoHeaderType.WinVersion2;
+ infoHeaderType = BmpInfoHeaderType.WinVersion2;
this.infoHeader = BmpInfoHeader.ParseCore(buffer);
}
else if (headerSize == BmpInfoHeader.Os22ShortSize)
{
// 16 bytes
- inofHeaderType = BmpInfoHeaderType.Os2Version2Short;
+ infoHeaderType = BmpInfoHeaderType.Os2Version2Short;
this.infoHeader = BmpInfoHeader.ParseOs22Short(buffer);
}
else if (headerSize == BmpInfoHeader.SizeV3)
{
// == 40 bytes
- inofHeaderType = BmpInfoHeaderType.WinVersion3;
+ infoHeaderType = BmpInfoHeaderType.WinVersion3;
this.infoHeader = BmpInfoHeader.ParseV3(buffer);
// if the info header is BMP version 3 and the compression type is BITFIELDS,
@@ -960,24 +962,30 @@ namespace SixLabors.ImageSharp.Formats.Bmp
else if (headerSize == BmpInfoHeader.AdobeV3Size)
{
// == 52 bytes
- inofHeaderType = BmpInfoHeaderType.AdobeVersion3;
+ infoHeaderType = BmpInfoHeaderType.AdobeVersion3;
this.infoHeader = BmpInfoHeader.ParseAdobeV3(buffer, withAlpha: false);
}
else if (headerSize == BmpInfoHeader.AdobeV3WithAlphaSize)
{
// == 56 bytes
- inofHeaderType = BmpInfoHeaderType.AdobeVersion3WithAlpha;
+ infoHeaderType = BmpInfoHeaderType.AdobeVersion3WithAlpha;
this.infoHeader = BmpInfoHeader.ParseAdobeV3(buffer, withAlpha: true);
}
+ else if (headerSize == BmpInfoHeader.Os2v2Size)
+ {
+ // == 64 bytes
+ infoHeaderType = BmpInfoHeaderType.Os2Version2;
+ this.infoHeader = BmpInfoHeader.ParseOs2Version2(buffer);
+ }
else if (headerSize >= BmpInfoHeader.SizeV4)
{
// >= 108 bytes
- inofHeaderType = headerSize == BmpInfoHeader.SizeV4 ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion5;
+ infoHeaderType = headerSize == BmpInfoHeader.SizeV4 ? BmpInfoHeaderType.WinVersion4 : BmpInfoHeaderType.WinVersion5;
this.infoHeader = BmpInfoHeader.ParseV4(buffer);
}
else
{
- throw new NotSupportedException($"ImageSharp does not support this BMP file. HeaderSize: {headerSize}.");
+ BmpThrowHelper.ThrowNotSupportedException($"ImageSharp does not support this BMP file. HeaderSize '{headerSize}'.");
}
// Resolution is stored in PPM.
@@ -1001,7 +1009,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
short bitsPerPixel = this.infoHeader.BitsPerPixel;
this.bmpMetaData = this.metaData.GetFormatMetaData(BmpFormat.Instance);
- this.bmpMetaData.InfoHeaderType = inofHeaderType;
+ this.bmpMetaData.InfoHeaderType = infoHeaderType;
// We can only encode at these bit rates so far.
if (bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel24)
@@ -1027,6 +1035,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.stream.Read(buffer, 0, BmpFileHeader.Size);
this.fileHeader = BmpFileHeader.Parse(buffer);
+
+ if (this.fileHeader.Type != BmpConstants.TypeMarkers.Bitmap)
+ {
+ BmpThrowHelper.ThrowNotSupportedException($"ImageSharp does not support this BMP file. File header bitmap type marker '{this.fileHeader.Type}'.");
+ }
}
///
@@ -1080,7 +1093,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
// 256 * 4
if (colorMapSize > 1024)
{
- throw new ImageFormatException($"Invalid bmp colormap size '{colorMapSize}'");
+ BmpThrowHelper.ThrowImageFormatException($"Invalid bmp colormap size '{colorMapSize}'");
}
palette = new byte[colorMapSize];
diff --git a/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs b/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
index 316df4acc..6da5f73e3 100644
--- a/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
@@ -41,6 +41,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
public const int AdobeV3WithAlphaSize = 56;
+ ///
+ /// Size of a IBM OS/2 2.x bitmap header.
+ ///
+ public const int Os2v2Size = 64;
+
///
/// Defines the size of the BITMAPINFOHEADER (BMP Version 4) data structure in the bitmap file.
///
@@ -117,7 +122,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
///
- /// Gets or sets the size of this header
+ /// Gets or sets the size of this header.
///
public int HeaderSize { get; set; }
@@ -346,6 +351,53 @@ namespace SixLabors.ImageSharp.Formats.Bmp
alphaMask: withAlpha ? BinaryPrimitives.ReadInt32LittleEndian(data.Slice(52, 4)) : 0);
}
+ ///
+ /// Parses a OS/2 version 2 bitmap header (64 bytes). Only the first 40 bytes are parsed which are
+ /// very similar to the Bitmap v3 header. The other 24 bytes are ignored, but they do not hold any
+ /// useful information for decoding the image.
+ ///
+ /// The data to parse.
+ /// The parsed header.
+ ///
+ public static BmpInfoHeader ParseOs2Version2(ReadOnlySpan data)
+ {
+ var infoHeader = 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)));
+
+ int compression = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(16, 4));
+
+ // The compression value in OS/2 bitmap has a different meaning than in windows bitmaps.
+ // Map the OS/2 value to the windows values.
+ switch (compression)
+ {
+ case 0:
+ infoHeader.Compression = BmpCompression.RGB;
+ break;
+ case 1:
+ infoHeader.Compression = BmpCompression.RLE8;
+ break;
+ case 2:
+ infoHeader.Compression = BmpCompression.RLE4;
+ break;
+ default:
+ BmpThrowHelper.ThrowImageFormatException($"Compression type is not supported. ImageSharp only supports uncompressed, RLE4 and RLE8.");
+ break;
+ }
+
+ infoHeader.ImageSize = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(20, 4));
+ infoHeader.XPelsPerMeter = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(24, 4));
+ infoHeader.YPelsPerMeter = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(28, 4));
+ infoHeader.ClrUsed = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(32, 4));
+ infoHeader.ClrImportant = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(36, 4));
+
+ // The following 24 bytes of the header are omitted.
+ return infoHeader;
+ }
+
///
/// Parses the full BMP Version 4 BITMAPINFOHEADER header (108 bytes).
///
diff --git a/src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs b/src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs
new file mode 100644
index 000000000..dae044ddb
--- /dev/null
+++ b/src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Bmp
+{
+ internal static class BmpThrowHelper
+ {
+ ///
+ /// Cold path optimization for throwing -s
+ ///
+ /// The error message for the exception.
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void ThrowImageFormatException(string errorMessage)
+ {
+ throw new ImageFormatException(errorMessage);
+ }
+
+ ///
+ /// Cold path optimization for throwing -s
+ ///
+ /// The error message for the exception.
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ public static void ThrowNotSupportedException(string errorMessage)
+ {
+ throw new NotSupportedException(errorMessage);
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
index 0ebfbf311..5d7d35dd5 100644
--- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
@@ -214,12 +214,28 @@ namespace SixLabors.ImageSharp.Tests
{
using (Image image = provider.GetImage(new BmpDecoder()))
{
- image.DebugSave(provider, "png");
+ image.DebugSave(provider);
// TODO: Neither System.Drawing not MagickReferenceDecoder
// can correctly decode this file.
// image.CompareToOriginal(provider);
}
}
+
+ [Theory]
+ [WithFile(Os2v2, PixelTypes.Rgba32)]
+ public void BmpDecoder_CanDecode_Os2v2Header(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new BmpDecoder()))
+ {
+ image.DebugSave(provider);
+
+ // TODO: System.Drawing can not decode this image. MagickReferenceDecoder can decode it,
+ // but i think incorrectly. I have loaded the image with GIMP and exported as PNG.
+ // The results are the same as the image sharp implementation.
+ // image.CompareToOriginal(provider, new MagickReferenceDecoder());
+ }
+ }
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 6e6c7ce47..d83fe4907 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -216,6 +216,7 @@ namespace SixLabors.ImageSharp.Tests
public const string WinBmpv5 = "Bmp/pal8v5.bmp";
public const string Bit8Palette4 = "Bmp/pal8-0.bmp";
public const string Os2v2Short = "Bmp/pal8os2v2-16.bmp";
+ public const string Os2v2 = "Bmp/pal8os2v2.bmp";
// Bitmap images with compression type BITFIELDS
public const string Rgb32bfdef = "Bmp/rgb32bfdef.bmp";
diff --git a/tests/Images/Input/Bmp/pal8os2v2.bmp b/tests/Images/Input/Bmp/pal8os2v2.bmp
new file mode 100644
index 000000000..1324a40d0
Binary files /dev/null and b/tests/Images/Input/Bmp/pal8os2v2.bmp differ