Browse Source

Adds support for OS/2 version 2 bitmaps (#813)

* Added support for OS/2 version 2 bitmaps

* throw NotSupportedException, if the file header type is not BM

* renamed Os2v2 to Os2v2Size

* Added BmpThrowHelper similar to the JpegThrowHelper
pull/816/head
Brian Popow 7 years ago
committed by James Jackson-South
parent
commit
b03d41bbb9
  1. 41
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  2. 54
      src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs
  3. 28
      src/ImageSharp/Formats/Bmp/BmpThrowHelper.cs
  4. 18
      tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
  5. 1
      tests/ImageSharp.Tests/TestImages.cs
  6. BIN
      tests/Images/Input/Bmp/pal8os2v2.bmp

41
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}'.");
}
}
/// <summary>
@ -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];

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

@ -41,6 +41,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
public const int AdobeV3WithAlphaSize = 56;
/// <summary>
/// Size of a IBM OS/2 2.x bitmap header.
/// </summary>
public const int Os2v2Size = 64;
/// <summary>
/// Defines the size of the BITMAPINFOHEADER (BMP Version 4) data structure in the bitmap file.
/// </summary>
@ -117,7 +122,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Gets or sets the size of this header
/// Gets or sets the size of this header.
/// </summary>
public int HeaderSize { get; set; }
@ -346,6 +351,53 @@ namespace SixLabors.ImageSharp.Formats.Bmp
alphaMask: withAlpha ? BinaryPrimitives.ReadInt32LittleEndian(data.Slice(52, 4)) : 0);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="data">The data to parse.</param>
/// <returns>The parsed header.</returns>
/// <seealso href="https://www.fileformat.info/format/os2bmp/egff.htm"/>
public static BmpInfoHeader ParseOs2Version2(ReadOnlySpan<byte> 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;
}
/// <summary>
/// Parses the full BMP Version 4 BITMAPINFOHEADER header (108 bytes).
/// </summary>

28
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
{
/// <summary>
/// Cold path optimization for throwing <see cref="ImageFormatException"/>-s
/// </summary>
/// <param name="errorMessage">The error message for the exception.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowImageFormatException(string errorMessage)
{
throw new ImageFormatException(errorMessage);
}
/// <summary>
/// Cold path optimization for throwing <see cref="NotSupportedException"/>-s
/// </summary>
/// <param name="errorMessage">The error message for the exception.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
public static void ThrowNotSupportedException(string errorMessage)
{
throw new NotSupportedException(errorMessage);
}
}
}

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

@ -214,12 +214,28 @@ namespace SixLabors.ImageSharp.Tests
{
using (Image<TPixel> 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<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> 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());
}
}
}
}

1
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";

BIN
tests/Images/Input/Bmp/pal8os2v2.bmp

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Loading…
Cancel
Save