From a2c4b2d5d81a5ee8368431e2d5578715cc645bb7 Mon Sep 17 00:00:00 2001 From: Brian Popow <38701097+brianpopow@users.noreply.github.com> Date: Mon, 11 Feb 2019 05:33:03 +0100 Subject: [PATCH] Add support for Decoding BI_ALPHABITFIELDS (#832) * Adds support for BI_ALPHABITFIELDS * Fix for decoding bitmaps with a less than full sized palette --- src/ImageSharp/Formats/Bmp/BmpCompression.cs | 10 +++++-- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 17 ++++++++++-- .../Formats/Bmp/BmpDecoderTests.cs | 26 +++++++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 2 ++ tests/Images/Input/Bmp/pal8os2sp.bmp | 3 +++ tests/Images/Input/Bmp/rgba32abf.bmp | 3 +++ 6 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 tests/Images/Input/Bmp/pal8os2sp.bmp create mode 100644 tests/Images/Input/Bmp/rgba32abf.bmp diff --git a/src/ImageSharp/Formats/Bmp/BmpCompression.cs b/src/ImageSharp/Formats/Bmp/BmpCompression.cs index 5f14d2243..be275019e 100644 --- a/src/ImageSharp/Formats/Bmp/BmpCompression.cs +++ b/src/ImageSharp/Formats/Bmp/BmpCompression.cs @@ -34,7 +34,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. /// RLE4 = 2, @@ -54,6 +53,13 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The bitmap contains a PNG image. /// Not supported at the moment. /// - PNG = 5 + PNG = 5, + + /// + /// Introduced with Windows CE. + /// Specifies that the bitmap is not compressed and that the color table consists of four DWORD color + /// masks that specify the red, green, blue, and alpha components of each pixel. + /// + BI_ALPHABITFIELDS = 6 } } \ No newline at end of file diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 496a6df4a..d33ada286 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -75,7 +75,6 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// The file header containing general information. - /// TODO: Why is this not used? We advance the stream but do not use the values parsed. /// private BmpFileHeader fileHeader; @@ -163,6 +162,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp break; case BmpCompression.BitFields: + case BmpCompression.BI_ALPHABITFIELDS: this.ReadBitFields(pixels, inverted); break; @@ -947,7 +947,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp infoHeaderType = BmpInfoHeaderType.WinVersion3; this.infoHeader = BmpInfoHeader.ParseV3(buffer); - // if the info header is BMP version 3 and the compression type is BITFIELDS, + // 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) { @@ -958,6 +958,16 @@ namespace SixLabors.ImageSharp.Formats.Bmp this.infoHeader.GreenMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(4, 4)); this.infoHeader.BlueMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(8, 4)); } + else if (this.infoHeader.Compression == BmpCompression.BI_ALPHABITFIELDS) + { + byte[] bitfieldsBuffer = new byte[16]; + this.stream.Read(bitfieldsBuffer, 0, 16); + 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)); + this.infoHeader.AlphaMask = BinaryPrimitives.ReadInt32LittleEndian(data.Slice(12, 4)); + } } else if (headerSize == BmpInfoHeader.AdobeV3Size) { @@ -1078,6 +1088,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp int colorMapSizeBytes = this.fileHeader.Offset - BmpFileHeader.Size - this.infoHeader.HeaderSize; int colorCountForBitDepth = ImageMaths.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel); bytesPerColorMapEntry = colorMapSizeBytes / colorCountForBitDepth; + + // Edge case for less-than-full-sized palette: bytesPerColorMapEntry should be at least 3. + bytesPerColorMapEntry = Math.Max(bytesPerColorMapEntry, 3); colorMapSize = colorMapSizeBytes; } } diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index e7b5aeaea..ad47ed1e7 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -60,6 +60,20 @@ namespace SixLabors.ImageSharp.Tests } } + [Theory] + [WithFile(RgbaAlphaBitfields, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeAlphaBitfields(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + + // TODO: Neither System.Drawing nor MagickReferenceDecoder decode this file. + // image.CompareToOriginal(provider); + } + } + [Theory] [WithFile(Bit32Rgba, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeBitmap_WithAlphaChannel(TestImageProvider provider) @@ -114,6 +128,18 @@ namespace SixLabors.ImageSharp.Tests } } + [Theory] + [WithFile(LessThanFullSizedPalette, PixelTypes.Rgba32)] + public void BmpDecoder_CanDecodeLessThanFullPalete(TestImageProvider provider) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage(new BmpDecoder())) + { + image.DebugSave(provider); + image.CompareToOriginal(provider, new MagickReferenceDecoder()); + } + } + [Theory] [WithFile(Rgba32bf56, PixelTypes.Rgba32)] public void BmpDecoder_CanDecodeAdobeBmpv3(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 51e2d084c..3a497683a 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -221,6 +221,7 @@ namespace SixLabors.ImageSharp.Tests public const string Bit8Palette4 = "Bmp/pal8-0.bmp"; public const string Os2v2Short = "Bmp/pal8os2v2-16.bmp"; public const string Os2v2 = "Bmp/pal8os2v2.bmp"; + public const string LessThanFullSizedPalette = "Bmp/pal8os2sp.bmp"; public const string Pal8Offset = "Bmp/pal8offs.bmp"; // Bitmap images with compression type BITFIELDS @@ -232,6 +233,7 @@ namespace SixLabors.ImageSharp.Tests public const string Issue735 = "Bmp/issue735.bmp"; public const string Rgba32bf56 = "Bmp/rgba32h56.bmp"; public const string Rgba321010102 = "Bmp/rgba32-1010102.bmp"; + public const string RgbaAlphaBitfields = "Bmp/rgba32abf.bmp"; public static readonly string[] BitFields = { diff --git a/tests/Images/Input/Bmp/pal8os2sp.bmp b/tests/Images/Input/Bmp/pal8os2sp.bmp new file mode 100644 index 000000000..7d3e89d84 --- /dev/null +++ b/tests/Images/Input/Bmp/pal8os2sp.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e58eb53dcbf1c0920ad5af8d07f576ee794c1c5d051e1d950060ab6f6f253f50 +size 8974 diff --git a/tests/Images/Input/Bmp/rgba32abf.bmp b/tests/Images/Input/Bmp/rgba32abf.bmp new file mode 100644 index 000000000..0f4c4b76e --- /dev/null +++ b/tests/Images/Input/Bmp/rgba32abf.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1d2183466e05c768b5a9d2ce5d132d72db2272bb331c7b27560d24f197f4165e +size 32582