From 30a1b11f898d89722552bee14c2c7855ebcc78c5 Mon Sep 17 00:00:00 2001
From: Brian Popow <38701097+brianpopow@users.noreply.github.com>
Date: Mon, 10 Jun 2019 08:24:54 +0200
Subject: [PATCH] Feature: Bitmap RLE undefined pixel handling (#927)
* Add bitmap decoder option, how to treat skipped pixels for RLE
* Refactored bitmap tests into smaller tests, instead of just one test which goes through all bitmap files
* Add another adobe v3 header bitmap testcase
* Using the constant from BmpConstants to Identify bitmaps
* Bitmap decoder now can handle oversized palette's
* Add test for invalid palette size
* Renamed RleUndefinedPixelHandling to RleSkippedPixelHandling
* Explicitly using SystemDrawingReferenceDecoder in some BitmapDecoder tests
* Add test cases for unsupported bitmaps
* Comparing RLE test images to reference decoder only on windows
* Add test case for decoding winv4 fast path
* Add another 8 Bit RLE test with magick reference decoder
* Optimize RLE skipped pixel handling
* Refactor RLE decoding to eliminate code duplication
* Using MagickReferenceDecoder for the 8-Bit RLE test
---
src/ImageSharp/Formats/Bmp/BmpDecoder.cs | 7 +-
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 240 ++++++++++++++----
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs | 9 +
.../Formats/Bmp/BmpImageFormatDetector.cs | 6 +-
.../Formats/Bmp/IBmpDecoderOptions.cs | 5 +-
.../Formats/Bmp/RleSkippedPixelHandling.cs | 26 ++
tests/ImageSharp.Tests/FileTestBase.cs | 2 +-
.../Formats/Bmp/BmpDecoderTests.cs | 226 ++++++++++++++++-
.../JpegProfilingBenchmarks.cs | 2 +-
tests/ImageSharp.Tests/TestImages.cs | 57 +++--
tests/Images/Input/Bmp/invalidPaletteSize.bmp | 3 +
tests/Images/Input/Bmp/pal4rlecut.bmp | 3 +
tests/Images/Input/Bmp/pal4rletrns.bmp | 3 +
tests/Images/Input/Bmp/pal8oversizepal.bmp | 3 +
tests/Images/Input/Bmp/pal8rlecut.bmp | 3 +
tests/Images/Input/Bmp/pal8rletrns.bmp | 3 +
tests/Images/Input/Bmp/rgb24jpeg.bmp | 3 +
tests/Images/Input/Bmp/rgb24largepal.bmp | 3 +
tests/Images/Input/Bmp/rgb24png.bmp | 3 +
tests/Images/Input/Bmp/rgb32h52.bmp | 3 +
tests/Images/Input/Bmp/rgba32v4.bmp | 3 +
tests/Images/Input/Bmp/rle4-delta-320x240.bmp | 3 +
tests/Images/Input/Bmp/rle8-blank-160x120.bmp | 3 +
tests/Images/Input/Bmp/rle8-delta-320x240.bmp | 3 +
24 files changed, 537 insertions(+), 85 deletions(-)
create mode 100644 src/ImageSharp/Formats/Bmp/RleSkippedPixelHandling.cs
create mode 100644 tests/Images/Input/Bmp/invalidPaletteSize.bmp
create mode 100644 tests/Images/Input/Bmp/pal4rlecut.bmp
create mode 100644 tests/Images/Input/Bmp/pal4rletrns.bmp
create mode 100644 tests/Images/Input/Bmp/pal8oversizepal.bmp
create mode 100644 tests/Images/Input/Bmp/pal8rlecut.bmp
create mode 100644 tests/Images/Input/Bmp/pal8rletrns.bmp
create mode 100644 tests/Images/Input/Bmp/rgb24jpeg.bmp
create mode 100644 tests/Images/Input/Bmp/rgb24largepal.bmp
create mode 100644 tests/Images/Input/Bmp/rgb24png.bmp
create mode 100644 tests/Images/Input/Bmp/rgb32h52.bmp
create mode 100644 tests/Images/Input/Bmp/rgba32v4.bmp
create mode 100644 tests/Images/Input/Bmp/rle4-delta-320x240.bmp
create mode 100644 tests/Images/Input/Bmp/rle8-blank-160x120.bmp
create mode 100644 tests/Images/Input/Bmp/rle8-delta-320x240.bmp
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs
index ebb7ffdf3c..a404ab418b 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoder.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoder.cs
@@ -14,13 +14,18 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
/// - JPG
/// - PNG
- /// - RLE4
+ /// - Some OS/2 specific subtypes like: Bitmap Array, Color Icon, Color Pointer, Icon, Pointer.
///
/// Formats will be supported in a later releases. We advise always
/// to use only 24 Bit Windows bitmaps.
///
public sealed class BmpDecoder : IImageDecoder, IBmpDecoderOptions, IImageInfoDetector
{
+ ///
+ /// Gets or sets a value indicating how to deal with skipped pixels, which can occur during decoding run length encoded bitmaps.
+ ///
+ public RleSkippedPixelHandling RleSkippedPixelHandling { get; set; } = RleSkippedPixelHandling.Black;
+
///
public Image Decode(Configuration configuration, Stream stream)
where TPixel : struct, IPixel
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
index 0cbc4fca1b..294b49ed7e 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
@@ -69,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
private ImageMetadata metadata;
///
- /// The bmp specific metadata.
+ /// The bitmap specific metadata.
///
private BmpMetadata bmpMetadata;
@@ -83,10 +83,21 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
private BmpInfoHeader infoHeader;
+ ///
+ /// The global configuration.
+ ///
private readonly Configuration configuration;
+ ///
+ /// Used for allocating memory during processing operations.
+ ///
private readonly MemoryAllocator memoryAllocator;
+ ///
+ /// The bitmap decoder options.
+ ///
+ private readonly IBmpDecoderOptions options;
+
///
/// Initializes a new instance of the class.
///
@@ -96,6 +107,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
this.configuration = configuration;
this.memoryAllocator = configuration.MemoryAllocator;
+ this.options = options;
}
///
@@ -207,7 +219,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// The image width.
/// The pixel component count.
///
- /// The .
+ /// The padding.
///
private static int CalculatePadding(int width, int componentCount)
{
@@ -222,7 +234,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
///
- /// Decodes a bitmap containing BITFIELDS Compression type. For each color channel, there will be bitmask
+ /// Decodes a bitmap containing the BITFIELDS Compression type. For each color channel, there will be a bitmask
/// which will be used to determine which bits belong to that channel.
///
/// The pixel format.
@@ -258,8 +270,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
/// Looks up color values and builds the image from de-compressed RLE8 or RLE4 data.
- /// Compressed RLE8 stream is uncompressed by
- /// Compressed RLE4 stream is uncompressed by
+ /// Compressed RLE8 stream is uncompressed by
+ /// Compressed RLE4 stream is uncompressed by
///
/// The pixel format.
/// The compression type. Either RLE4 or RLE8.
@@ -273,14 +285,17 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
TPixel color = default;
using (Buffer2D buffer = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean))
+ using (Buffer2D undefinedPixels = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean))
+ using (IMemoryOwner rowsWithUndefinedPixels = this.memoryAllocator.Allocate(height, AllocationOptions.Clean))
{
+ Span rowsWithUndefinedPixelsSpan = rowsWithUndefinedPixels.Memory.Span;
if (compression == BmpCompression.RLE8)
{
- this.UncompressRle8(width, buffer.GetSpan());
+ this.UncompressRle8(width, buffer.GetSpan(), undefinedPixels.GetSpan(), rowsWithUndefinedPixelsSpan);
}
else
{
- this.UncompressRle4(width, buffer.GetSpan());
+ this.UncompressRle4(width, buffer.GetSpan(), undefinedPixels.GetSpan(), rowsWithUndefinedPixelsSpan);
}
for (int y = 0; y < height; y++)
@@ -289,10 +304,46 @@ namespace SixLabors.ImageSharp.Formats.Bmp
Span bufferRow = buffer.GetRowSpan(y);
Span pixelRow = pixels.GetRowSpan(newY);
- for (int x = 0; x < width; x++)
+ bool rowHasUndefinedPixels = rowsWithUndefinedPixelsSpan[y];
+ if (rowHasUndefinedPixels)
{
- color.FromBgr24(Unsafe.As(ref colors[bufferRow[x] * 4]));
- pixelRow[x] = color;
+ // Slow path with undefined pixels.
+ for (int x = 0; x < width; x++)
+ {
+ byte colorIdx = bufferRow[x];
+ if (undefinedPixels[x, y])
+ {
+ switch (this.options.RleSkippedPixelHandling)
+ {
+ case RleSkippedPixelHandling.FirstColorOfPalette:
+ color.FromBgr24(Unsafe.As(ref colors[colorIdx * 4]));
+ break;
+ case RleSkippedPixelHandling.Transparent:
+ color.FromVector4(Vector4.Zero);
+ break;
+
+ // Default handling for skipped pixels is black (which is what System.Drawing is also doing).
+ default:
+ color.FromVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f));
+ break;
+ }
+ }
+ else
+ {
+ color.FromBgr24(Unsafe.As(ref colors[colorIdx * 4]));
+ }
+
+ pixelRow[x] = color;
+ }
+ }
+ else
+ {
+ // Fast path without any undefined pixels.
+ for (int x = 0; x < width; x++)
+ {
+ color.FromBgr24(Unsafe.As(ref colors[bufferRow[x] * 4]));
+ pixelRow[x] = color;
+ }
}
}
}
@@ -308,7 +359,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
/// The width of the bitmap.
/// Buffer for uncompressed data.
- private void UncompressRle4(int w, Span buffer)
+ /// Keeps track over skipped and therefore undefined pixels.
+ /// Keeps track of rows, which have undefined pixels.
+ private void UncompressRle4(int w, Span buffer, Span undefinedPixels, Span rowsWithUndefinedPixels)
{
#if NETCOREAPP2_1
Span cmd = stackalloc byte[2];
@@ -329,21 +382,20 @@ namespace SixLabors.ImageSharp.Formats.Bmp
switch (cmd[1])
{
case RleEndOfBitmap:
+ int skipEoB = buffer.Length - count;
+ RleSkipEndOfBitmap(count, w, skipEoB, undefinedPixels, rowsWithUndefinedPixels);
+
return;
case RleEndOfLine:
- int extra = count % w;
- if (extra > 0)
- {
- count += w - extra;
- }
+ count += RleSkipEndOfLine(count, w, undefinedPixels, rowsWithUndefinedPixels);
break;
case RleDelta:
int dx = this.stream.ReadByte();
int dy = this.stream.ReadByte();
- count += (w * dy) + dx;
+ count += RleSkipDelta(count, w, dx, dy, undefinedPixels, rowsWithUndefinedPixels);
break;
@@ -374,7 +426,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
}
- // Absolute mode data is aligned to two-byte word-boundary
+ // Absolute mode data is aligned to two-byte word-boundary.
int padding = bytesToRead & 1;
this.stream.Skip(padding);
@@ -418,7 +470,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
/// The width of the bitmap.
/// Buffer for uncompressed data.
- private void UncompressRle8(int w, Span buffer)
+ /// Keeps track of skipped and therefore undefined pixels.
+ /// Keeps track of rows, which have undefined pixels.
+ private void UncompressRle8(int w, Span buffer, Span undefinedPixels, Span rowsWithUndefinedPixels)
{
#if NETCOREAPP2_1
Span cmd = stackalloc byte[2];
@@ -439,27 +493,26 @@ namespace SixLabors.ImageSharp.Formats.Bmp
switch (cmd[1])
{
case RleEndOfBitmap:
+ int skipEoB = buffer.Length - count;
+ RleSkipEndOfBitmap(count, w, skipEoB, undefinedPixels, rowsWithUndefinedPixels);
+
return;
case RleEndOfLine:
- int extra = count % w;
- if (extra > 0)
- {
- count += w - extra;
- }
+ count += RleSkipEndOfLine(count, w, undefinedPixels, rowsWithUndefinedPixels);
break;
case RleDelta:
int dx = this.stream.ReadByte();
int dy = this.stream.ReadByte();
- count += (w * dy) + dx;
+ count += RleSkipDelta(count, w, dx, dy, undefinedPixels, rowsWithUndefinedPixels);
break;
default:
- // If the second byte > 2, we are in 'absolute mode'
- // Take this number of bytes from the stream as uncompressed data
+ // If the second byte > 2, we are in 'absolute mode'.
+ // Take this number of bytes from the stream as uncompressed data.
int length = cmd[1];
byte[] run = new byte[length];
@@ -470,7 +523,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
count += run.Length;
- // Absolute mode data is aligned to two-byte word-boundary
+ // Absolute mode data is aligned to two-byte word-boundary.
int padding = length & 1;
this.stream.Skip(padding);
@@ -481,16 +534,105 @@ namespace SixLabors.ImageSharp.Formats.Bmp
else
{
int max = count + cmd[0]; // as we start at the current count in the following loop, max is count + cmd[0]
- byte cmd1 = cmd[1]; // store the value to avoid the repeated indexer access inside the loop
+ byte colorIdx = cmd[1]; // store the value to avoid the repeated indexer access inside the loop.
for (; count < max; count++)
{
- buffer[count] = cmd1;
+ buffer[count] = colorIdx;
}
}
}
}
+ ///
+ /// Keeps track of skipped / undefined pixels, when EndOfBitmap command occurs.
+ ///
+ /// The already processed pixel count.
+ /// The width of the image.
+ /// The skipped pixel count.
+ /// The undefined pixels.
+ /// Rows with undefined pixels.
+ private static void RleSkipEndOfBitmap(
+ int count,
+ int w,
+ int skipPixelCount,
+ Span undefinedPixels,
+ Span rowsWithUndefinedPixels)
+ {
+ for (int i = count; i < count + skipPixelCount; i++)
+ {
+ undefinedPixels[i] = true;
+ }
+
+ int skippedRowIdx = count / w;
+ int skippedRows = (skipPixelCount / w) - 1;
+ int lastSkippedRow = Math.Min(skippedRowIdx + skippedRows, rowsWithUndefinedPixels.Length - 1);
+ for (int i = skippedRowIdx; i <= lastSkippedRow; i++)
+ {
+ rowsWithUndefinedPixels[i] = true;
+ }
+ }
+
+ ///
+ /// Keeps track of undefined / skipped pixels, when the EndOfLine command occurs.
+ ///
+ /// The already processed pixel count.
+ /// The width of image.
+ /// The undefined pixels.
+ /// The rows with undefined pixels.
+ /// The number of skipped pixels.
+ private static int RleSkipEndOfLine(int count, int w, Span undefinedPixels, Span rowsWithUndefinedPixels)
+ {
+ rowsWithUndefinedPixels[count / w] = true;
+ int remainingPixelsInRow = count % w;
+ if (remainingPixelsInRow > 0)
+ {
+ int skipEoL = w - remainingPixelsInRow;
+ for (int i = count; i < count + skipEoL; i++)
+ {
+ undefinedPixels[i] = true;
+ }
+
+ return skipEoL;
+ }
+
+ return 0;
+ }
+
+ ///
+ /// Keeps track of undefined / skipped pixels, when the delta command occurs.
+ ///
+ /// The count.
+ /// The width of the image.
+ /// Delta skip in x direction.
+ /// Delta skip in y direction.
+ /// The undefined pixels.
+ /// The rows with undefined pixels.
+ /// The number of skipped pixels.
+ private static int RleSkipDelta(
+ int count,
+ int w,
+ int dx,
+ int dy,
+ Span undefinedPixels,
+ Span rowsWithUndefinedPixels)
+ {
+ int skipDelta = (w * dy) + dx;
+ for (int i = count; i < count + skipDelta; i++)
+ {
+ undefinedPixels[i] = true;
+ }
+
+ int skippedRowIdx = count / w;
+ int lastSkippedRow = Math.Min(skippedRowIdx + dy, rowsWithUndefinedPixels.Length - 1);
+ for (int i = skippedRowIdx; i <= lastSkippedRow; i++)
+ {
+ rowsWithUndefinedPixels[i] = true;
+ }
+
+ return skipDelta;
+ }
+
///
/// Reads the color palette from the stream.
///
@@ -506,7 +648,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
private void ReadRgbPalette(Buffer2D pixels, byte[] colors, int width, int height, int bitsPerPixel, int bytesPerColorMapEntry, bool inverted)
where TPixel : struct, IPixel
{
- // Pixels per byte (bits per pixel)
+ // Pixels per byte (bits per pixel).
int ppb = 8 / bitsPerPixel;
int arrayWidth = (width + ppb - 1) / ppb;
@@ -514,7 +656,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
// Bit mask
int mask = 0xFF >> (8 - bitsPerPixel);
- // Rows are aligned on 4 byte boundaries
+ // Rows are aligned on 4 byte boundaries.
int padding = arrayWidth % 4;
if (padding != 0)
{
@@ -810,11 +952,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
uint maxValueAlpha = 0xFFFFFFFF >> (32 - bitsAlphaMask);
float invMaxValueAlpha = 1.0f / maxValueAlpha;
- bool unusualBitMask = false;
- if (bitsRedMask > 8 || bitsGreenMask > 8 || bitsBlueMask > 8 || invMaxValueAlpha > 8)
- {
- unusualBitMask = true;
- }
+ bool unusualBitMask = bitsRedMask > 8 || bitsGreenMask > 8 || bitsBlueMask > 8 || invMaxValueAlpha > 8;
using (IManagedByteBuffer buffer = this.memoryAllocator.AllocateManagedByteBuffer(stride))
{
@@ -910,7 +1048,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp
#else
byte[] buffer = new byte[BmpInfoHeader.MaxHeaderSize];
#endif
- this.stream.Read(buffer, 0, BmpInfoHeader.HeaderSizeSize); // read the header size
+
+ // Read the header size.
+ this.stream.Read(buffer, 0, BmpInfoHeader.HeaderSizeSize);
int headerSize = BinaryPrimitives.ReadInt32LittleEndian(buffer);
if (headerSize < BmpInfoHeader.CoreSize)
@@ -925,7 +1065,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
headerSize = BmpInfoHeader.MaxHeaderSize;
}
- // read the rest of the header
+ // Read the rest of the header.
this.stream.Read(buffer, BmpInfoHeader.HeaderSizeSize, headerSize - BmpInfoHeader.HeaderSizeSize);
BmpInfoHeaderType infoHeaderType = BmpInfoHeaderType.WinVersion2;
@@ -953,7 +1093,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
byte[] bitfieldsBuffer = new byte[12];
this.stream.Read(bitfieldsBuffer, 0, 12);
- Span data = bitfieldsBuffer.AsSpan();
+ 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));
@@ -962,7 +1102,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
byte[] bitfieldsBuffer = new byte[16];
this.stream.Read(bitfieldsBuffer, 0, 16);
- Span data = bitfieldsBuffer.AsSpan();
+ 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));
@@ -1021,7 +1161,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.bmpMetadata = this.metadata.GetFormatMetadata(BmpFormat.Instance);
this.bmpMetadata.InfoHeaderType = infoHeaderType;
- // We can only encode at these bit rates so far.
+ // We can only encode at these bit rates so far (1 bit and 4 bit are still missing).
if (bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel8)
|| bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel16)
|| bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel24)
@@ -1030,7 +1170,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.bmpMetadata.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel;
}
- // skip the remaining header because we can't read those parts
+ // Skip the remaining header because we can't read those parts.
this.stream.Skip(skipAmount);
}
@@ -1105,10 +1245,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
if (colorMapSize > 0)
{
- // 256 * 4
- if (colorMapSize > 1024)
+ // Usually the color palette is 1024 byte (256 colors * 4), but the documentation does not mention a size limit.
+ // Make sure, that we will not read pass the bitmap offset (starting position of image data).
+ if ((this.stream.Position + colorMapSize) > this.fileHeader.Offset)
{
- BmpThrowHelper.ThrowImageFormatException($"Invalid bmp colormap size '{colorMapSize}'");
+ BmpThrowHelper.ThrowImageFormatException(
+ $"Reading the color map would read beyond the bitmap offset. Either the color map size of '{colorMapSize}' is invalid or the bitmap offset.");
}
palette = new byte[colorMapSize];
@@ -1121,7 +1263,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int skipAmount = this.fileHeader.Offset - (int)this.stream.Position;
if ((skipAmount + (int)this.stream.Position) > this.stream.Length)
{
- BmpThrowHelper.ThrowImageFormatException($"Invalid fileheader offset found. Offset is greater than the stream length.");
+ BmpThrowHelper.ThrowImageFormatException("Invalid fileheader offset found. Offset is greater than the stream length.");
}
if (skipAmount > 0)
@@ -1132,4 +1274,4 @@ namespace SixLabors.ImageSharp.Formats.Bmp
return bytesPerColorMapEntry;
}
}
-}
\ No newline at end of file
+}
diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
index 9fbd0b5adb..4e6ec45021 100644
--- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
@@ -51,10 +51,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
private const int ColorPaletteSize8Bit = 1024;
+ ///
+ /// Used for allocating memory during processing operations.
+ ///
private readonly MemoryAllocator memoryAllocator;
+ ///
+ /// The global configuration.
+ ///
private Configuration configuration;
+ ///
+ /// The color depth, in number of bits per pixel.
+ ///
private BmpBitsPerPixel? bitsPerPixel;
///
diff --git a/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs
index c0814b1dfc..4f862d9295 100644
--- a/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
+using System.Buffers.Binary;
namespace SixLabors.ImageSharp.Formats.Bmp
{
@@ -21,10 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
private bool IsSupportedFileFormat(ReadOnlySpan header)
{
- // TODO: This should be in constants
- return header.Length >= this.HeaderSize
- && header[0] == 0x42 // B
- && header[1] == 0x4D; // M
+ return header.Length >= this.HeaderSize && BinaryPrimitives.ReadInt16LittleEndian(header) == BmpConstants.TypeMarkers.Bitmap;
}
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs b/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs
index 219d37ca62..f456f2ba3a 100644
--- a/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs
+++ b/src/ImageSharp/Formats/Bmp/IBmpDecoderOptions.cs
@@ -8,6 +8,9 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
internal interface IBmpDecoderOptions
{
- // added this for consistency so we can add stuff as required, no options currently available
+ ///
+ /// Gets the value indicating how to deal with skipped pixels, which can occur during decoding run length encoded bitmaps.
+ ///
+ RleSkippedPixelHandling RleSkippedPixelHandling { get; }
}
}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Bmp/RleSkippedPixelHandling.cs b/src/ImageSharp/Formats/Bmp/RleSkippedPixelHandling.cs
new file mode 100644
index 0000000000..493fe366ad
--- /dev/null
+++ b/src/ImageSharp/Formats/Bmp/RleSkippedPixelHandling.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Bmp
+{
+ ///
+ /// Defines possible options, how skipped pixels during decoding of run length encoded bitmaps should be treated.
+ ///
+ public enum RleSkippedPixelHandling : int
+ {
+ ///
+ /// Undefined pixels should be black. This is the default behavior and equal to how System.Drawing handles undefined pixels.
+ ///
+ Black = 0,
+
+ ///
+ /// Undefined pixels should be transparent.
+ ///
+ Transparent = 1,
+
+ ///
+ /// Undefined pixels should have the first color of the palette.
+ ///
+ FirstColorOfPalette = 2
+ }
+}
diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs
index a056bc474e..4f8475738b 100644
--- a/tests/ImageSharp.Tests/FileTestBase.cs
+++ b/tests/ImageSharp.Tests/FileTestBase.cs
@@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Tests
///
/// A collection of all the bmp test images
///
- public static IEnumerable AllBmpFiles = TestImages.Bmp.All;
+ public static IEnumerable AllBmpFiles = TestImages.Bmp.Benchmark;
///
/// A collection of all the jpeg test images
diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
index e615dbe568..c4dfa724cc 100644
--- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
@@ -1,6 +1,7 @@
-// Copyright (c) Six Labors and contributors.
+// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
+using System;
using System.IO;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.PixelFormats;
@@ -20,27 +21,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
{
public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector;
- public static readonly string[] AllBmpFiles = All;
+ public static readonly string[] MiscBmpFiles = Miscellaneous;
public static readonly string[] BitfieldsBmpFiles = BitFields;
public static readonly TheoryData RatioFiles =
new TheoryData
{
- { TestImages.Bmp.Car, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter },
- { TestImages.Bmp.V5Header, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter },
- { TestImages.Bmp.RLE8, 2835, 2835, PixelResolutionUnit.PixelsPerMeter }
+ { Car, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter },
+ { V5Header, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter },
+ { RLE8, 2835, 2835, PixelResolutionUnit.PixelsPerMeter }
};
[Theory]
- [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32)]
- public void DecodeBmp(TestImageProvider provider)
+ [WithFileCollection(nameof(MiscBmpFiles), PixelTypes.Rgba32)]
+ public void BmpDecoder_CanDecode_MiscellaneousBitmaps(TestImageProvider provider)
where TPixel : struct, IPixel
{
using (Image image = provider.GetImage(new BmpDecoder()))
{
image.DebugSave(provider);
-
if (TestEnvironment.IsWindows)
{
image.CompareToOriginal(provider);
@@ -60,6 +60,174 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
}
}
+ [Theory]
+ [WithFile(Bit16Inverted, PixelTypes.Rgba32)]
+ [WithFile(Bit8Inverted, PixelTypes.Rgba32)]
+ public void BmpDecoder_CanDecode_Inverted(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new BmpDecoder()))
+ {
+ image.DebugSave(provider);
+ image.CompareToOriginal(provider);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit1, PixelTypes.Rgba32)]
+ [WithFile(Bit1Pal1, PixelTypes.Rgba32)]
+ public void BmpDecoder_CanDecode_1Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new BmpDecoder()))
+ {
+ image.DebugSave(provider);
+ image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder());
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit4, PixelTypes.Rgba32)]
+ public void BmpDecoder_CanDecode_4Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new BmpDecoder()))
+ {
+ image.DebugSave(provider);
+ // The Magick Reference Decoder can not decode 4-Bit bitmaps, so only execute this on windows.
+ if (TestEnvironment.IsWindows)
+ {
+ image.CompareToOriginal(provider);
+ }
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit8, PixelTypes.Rgba32)]
+ public void BmpDecoder_CanDecode_8Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new BmpDecoder()))
+ {
+ image.DebugSave(provider);
+ image.CompareToOriginal(provider);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit16, PixelTypes.Rgba32)]
+ public void BmpDecoder_CanDecode_16Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new BmpDecoder()))
+ {
+ image.DebugSave(provider);
+ image.CompareToOriginal(provider);
+ }
+ }
+
+ [Theory]
+ [WithFile(Bit32Rgb, PixelTypes.Rgba32)]
+ public void BmpDecoder_CanDecode_32Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new BmpDecoder()))
+ {
+ image.DebugSave(provider);
+ image.CompareToOriginal(provider);
+ }
+ }
+
+ [Theory]
+ [WithFile(Rgba32v4, PixelTypes.Rgba32)]
+ public void BmpDecoder_CanDecode_32BitV4Header_Fast(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new BmpDecoder()))
+ {
+ image.DebugSave(provider);
+ image.CompareToOriginal(provider);
+ }
+ }
+
+ [Theory]
+ [WithFile(RLE4Cut, PixelTypes.Rgba32)]
+ [WithFile(RLE4Delta, PixelTypes.Rgba32)]
+ [WithFile(Rle4Delta320240, PixelTypes.Rgba32)]
+ public void BmpDecoder_CanDecode_RunLengthEncoded_4Bit_WithDelta(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new BmpDecoder() { RleSkippedPixelHandling = RleSkippedPixelHandling.Black }))
+ {
+ image.DebugSave(provider);
+ // The Magick Reference Decoder can not decode 4-Bit bitmaps, so only execute this on windows.
+ if (TestEnvironment.IsWindows)
+ {
+ image.CompareToOriginal(provider);
+ }
+ }
+ }
+
+ [Theory]
+ [WithFile(RLE4, PixelTypes.Rgba32)]
+ public void BmpDecoder_CanDecode_RunLengthEncoded_4Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new BmpDecoder() { RleSkippedPixelHandling = RleSkippedPixelHandling.Black }))
+ {
+ image.DebugSave(provider);
+ // The Magick Reference Decoder can not decode 4-Bit bitmaps, so only execute this on windows.
+ if (TestEnvironment.IsWindows)
+ {
+ image.CompareToOriginal(provider);
+ }
+ }
+ }
+
+ [Theory]
+ [WithFile(RLE8Cut, PixelTypes.Rgba32)]
+ [WithFile(RLE8Delta, PixelTypes.Rgba32)]
+ [WithFile(Rle8Delta320240, PixelTypes.Rgba32)]
+ [WithFile(Rle8Blank160120, PixelTypes.Rgba32)]
+ public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_SystemDrawingRefDecoder(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new BmpDecoder() { RleSkippedPixelHandling = RleSkippedPixelHandling.Black }))
+ {
+ image.DebugSave(provider);
+ if (TestEnvironment.IsWindows)
+ {
+ image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder());
+ }
+ }
+ }
+
+ [Theory]
+ [WithFile(RLE8Cut, PixelTypes.Rgba32)]
+ [WithFile(RLE8Delta, PixelTypes.Rgba32)]
+ public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit_WithDelta_MagickRefDecoder(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new BmpDecoder() { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette }))
+ {
+ image.DebugSave(provider);
+ image.CompareToOriginal(provider, new MagickReferenceDecoder());
+ }
+ }
+
+ [Theory]
+ [WithFile(RLE8, PixelTypes.Rgba32)]
+ [WithFile(RLE8Inverted, PixelTypes.Rgba32)]
+ public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new BmpDecoder() { RleSkippedPixelHandling = RleSkippedPixelHandling.FirstColorOfPalette }))
+ {
+ image.DebugSave(provider);
+ image.CompareToOriginal(provider, new MagickReferenceDecoder());
+ }
+ }
+
[Theory]
[WithFile(RgbaAlphaBitfields, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecodeAlphaBitfields(TestImageProvider provider)
@@ -106,6 +274,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
[Theory]
[WithFile(WinBmpv2, PixelTypes.Rgba32)]
+ [WithFile(CoreHeader, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecodeBmpv2(TestImageProvider provider)
where TPixel : struct, IPixel
{
@@ -141,7 +310,41 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
}
[Theory]
- [WithFile(Rgba32bf56, PixelTypes.Rgba32)]
+ [WithFile(OversizedPalette, PixelTypes.Rgba32)]
+ [WithFile(Rgb24LargePalette, PixelTypes.Rgba32)]
+ public void BmpDecoder_CanDecodeOversizedPalette(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ using (Image image = provider.GetImage(new BmpDecoder()))
+ {
+ image.DebugSave(provider);
+ if (TestEnvironment.IsWindows)
+ {
+ image.CompareToOriginal(provider);
+ }
+ }
+ }
+
+ [Theory]
+ [WithFile(InvalidPaletteSize, PixelTypes.Rgba32)]
+ public void BmpDecoder_ThrowsImageFormatException_OnInvalidPaletteSize(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ Assert.Throws( () => { using (Image image = provider.GetImage(new BmpDecoder())) { } });
+ }
+
+ [Theory]
+ [WithFile(Rgb24jpeg, PixelTypes.Rgba32)]
+ [WithFile(Rgb24png, PixelTypes.Rgba32)]
+ public void BmpDecoder_ThrowsNotSupportedException_OnUnsupportedBitmaps(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ Assert.Throws(() => { using (Image image = provider.GetImage(new BmpDecoder())) { } });
+ }
+
+ [Theory]
+ [WithFile(Rgba32bf56AdobeV3, PixelTypes.Rgba32)]
+ [WithFile(Rgb32h52AdobeV3, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecodeAdobeBmpv3(TestImageProvider provider)
where TPixel : struct, IPixel
{
@@ -166,6 +369,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
[Theory]
[WithFile(WinBmpv5, PixelTypes.Rgba32)]
+ [WithFile(V5Header, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecodeBmpv5(TestImageProvider provider)
where TPixel : struct, IPixel
{
@@ -223,6 +427,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
[InlineData(Bit8, 8)]
[InlineData(Bit8Inverted, 8)]
[InlineData(Bit4, 4)]
+ [InlineData(Bit1, 1)]
+ [InlineData(Bit1Pal1, 1)]
public void Identify(string imagePath, int expectedPixelSize)
{
var testFile = TestFile.Create(imagePath);
@@ -281,4 +487,4 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
}
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs b/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs
index 65989556d2..95a47fd7cc 100644
--- a/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs
+++ b/tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs
@@ -82,7 +82,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks
return;
}
- string[] testFiles = TestImages.Bmp.All
+ string[] testFiles = TestImages.Bmp.Benchmark
.Concat(new[] { TestImages.Jpeg.Baseline.Calliphora, TestImages.Jpeg.Baseline.Cmyk }).ToArray();
Image[] testImages = testFiles.Select(
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index d8e8719ba1..d041f48544 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -231,8 +231,15 @@ namespace SixLabors.ImageSharp.Tests
public const string CoreHeader = "Bmp/BitmapCoreHeaderQR.bmp";
public const string V5Header = "Bmp/BITMAPV5HEADER.bmp";
public const string RLE8 = "Bmp/RunLengthEncoded.bmp";
+ public const string RLE8Cut = "Bmp/pal8rlecut.bmp";
+ public const string RLE8Delta = "Bmp/pal8rletrns.bmp";
+ public const string Rle8Delta320240 = "Bmp/rle8-delta-320x240.bmp";
+ public const string Rle8Blank160120 = "Bmp/rle8-blank-160x120.bmp";
+ public const string RLE8Inverted = "Bmp/RunLengthEncoded-inverted.bmp";
public const string RLE4 = "Bmp/pal4rle.bmp";
- public const string RLEInverted = "Bmp/RunLengthEncoded-inverted.bmp";
+ public const string RLE4Cut = "Bmp/pal4rlecut.bmp";
+ public const string RLE4Delta = "Bmp/pal4rletrns.bmp";
+ public const string Rle4Delta320240 = "Bmp/rle4-delta-320x240.bmp";
public const string Bit1 = "Bmp/pal1.bmp";
public const string Bit1Pal1 = "Bmp/pal1p1.bmp";
public const string Bit4 = "Bmp/pal4.bmp";
@@ -256,15 +263,22 @@ namespace SixLabors.ImageSharp.Tests
public const string Os2v2 = "Bmp/pal8os2v2.bmp";
public const string LessThanFullSizedPalette = "Bmp/pal8os2sp.bmp";
public const string Pal8Offset = "Bmp/pal8offs.bmp";
+ public const string OversizedPalette = "Bmp/pal8oversizepal.bmp";
+ public const string Rgb24LargePalette = "Bmp/rgb24largepal.bmp";
+ public const string InvalidPaletteSize = "Bmp/invalidPaletteSize.bmp";
+ public const string Rgb24jpeg = "Bmp/rgb24jpeg.bmp";
+ public const string Rgb24png = "Bmp/rgb24png.bmp";
+ public const string Rgba32v4 = "Bmp/rgba32v4.bmp";
- // Bitmap images with compression type BITFIELDS
+ // Bitmap images with compression type BITFIELDS.
public const string Rgb32bfdef = "Bmp/rgb32bfdef.bmp";
public const string Rgb32bf = "Bmp/rgb32bf.bmp";
public const string Rgb16bfdef = "Bmp/rgb16bfdef.bmp";
public const string Rgb16565 = "Bmp/rgb16-565.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 Rgba32bf56AdobeV3 = "Bmp/rgba32h56.bmp";
+ public const string Rgb32h52AdobeV3 = "Bmp/rgb32h52.bmp";
public const string Rgba321010102 = "Bmp/rgba32-1010102.bmp";
public const string RgbaAlphaBitfields = "Bmp/rgba32abf.bmp";
@@ -278,25 +292,32 @@ namespace SixLabors.ImageSharp.Tests
Issue735,
};
- public static readonly string[] All
+ public static readonly string[] Miscellaneous
= {
Car,
F,
- NegHeight,
- CoreHeader,
- V5Header,
- RLE4,
- RLE8,
- RLEInverted,
- Bit1,
- Bit1Pal1,
- Bit4,
- Bit8,
- Bit8Inverted,
- Bit16,
- Bit16Inverted,
- Bit32Rgb
+ NegHeight
};
+
+ public static readonly string[] Benchmark
+ = {
+ Car,
+ F,
+ NegHeight,
+ CoreHeader,
+ V5Header,
+ RLE4,
+ RLE8,
+ RLE8Inverted,
+ Bit1,
+ Bit1Pal1,
+ Bit4,
+ Bit8,
+ Bit8Inverted,
+ Bit16,
+ Bit16Inverted,
+ Bit32Rgb
+ };
}
public static class Gif
diff --git a/tests/Images/Input/Bmp/invalidPaletteSize.bmp b/tests/Images/Input/Bmp/invalidPaletteSize.bmp
new file mode 100644
index 0000000000..8a4b231166
--- /dev/null
+++ b/tests/Images/Input/Bmp/invalidPaletteSize.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:eaea78be2e8e5579e2469400b8d811b125be805459888c3bd390570d21ffeab8
+size 9270
diff --git a/tests/Images/Input/Bmp/pal4rlecut.bmp b/tests/Images/Input/Bmp/pal4rlecut.bmp
new file mode 100644
index 0000000000..f67004039b
--- /dev/null
+++ b/tests/Images/Input/Bmp/pal4rlecut.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e3dcbe1f39144845f339a88c9cbf34adc3e0b355440dbcb13e987aec77bb2137
+size 3610
diff --git a/tests/Images/Input/Bmp/pal4rletrns.bmp b/tests/Images/Input/Bmp/pal4rletrns.bmp
new file mode 100644
index 0000000000..674abdaff8
--- /dev/null
+++ b/tests/Images/Input/Bmp/pal4rletrns.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2fcbaa0f387c57ba678ced91dfb2db5b2e544e2ed28a7875459e551b514daf84
+size 4326
diff --git a/tests/Images/Input/Bmp/pal8oversizepal.bmp b/tests/Images/Input/Bmp/pal8oversizepal.bmp
new file mode 100644
index 0000000000..e80319df9a
--- /dev/null
+++ b/tests/Images/Input/Bmp/pal8oversizepal.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:51c89919fc6b85cc11850054ff16bb947c11dead620e35a92e46d7b0fde79faf
+size 9446
diff --git a/tests/Images/Input/Bmp/pal8rlecut.bmp b/tests/Images/Input/Bmp/pal8rlecut.bmp
new file mode 100644
index 0000000000..b3e46321cc
--- /dev/null
+++ b/tests/Images/Input/Bmp/pal8rlecut.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:726c092da72e5412d2e1a0e3d9e35ee3630869e368fd4534c631f53bb5608a11
+size 7980
diff --git a/tests/Images/Input/Bmp/pal8rletrns.bmp b/tests/Images/Input/Bmp/pal8rletrns.bmp
new file mode 100644
index 0000000000..22f5629186
--- /dev/null
+++ b/tests/Images/Input/Bmp/pal8rletrns.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:445b856331c5d03054887d6555f56a8882f0aabbe0d59e322d9ff0be7f0eee94
+size 9212
diff --git a/tests/Images/Input/Bmp/rgb24jpeg.bmp b/tests/Images/Input/Bmp/rgb24jpeg.bmp
new file mode 100644
index 0000000000..6f5ae3b56c
--- /dev/null
+++ b/tests/Images/Input/Bmp/rgb24jpeg.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c09aad5b4717408e42d7cdd3d51d884d20be6a69adf73c7afcc47201df051f30
+size 2457
diff --git a/tests/Images/Input/Bmp/rgb24largepal.bmp b/tests/Images/Input/Bmp/rgb24largepal.bmp
new file mode 100644
index 0000000000..ac46d03da8
--- /dev/null
+++ b/tests/Images/Input/Bmp/rgb24largepal.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:62976c8077dddd97b7de7396d70d6aaded717c387a5272f3f6e18bb4abcd5f45
+size 25830
diff --git a/tests/Images/Input/Bmp/rgb24png.bmp b/tests/Images/Input/Bmp/rgb24png.bmp
new file mode 100644
index 0000000000..40969196e7
--- /dev/null
+++ b/tests/Images/Input/Bmp/rgb24png.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:07dcf8ac7fceee50ba5c3d89ece956e14b9bb4ea05f0a2b75f6958150d3d7a03
+size 1210
diff --git a/tests/Images/Input/Bmp/rgb32h52.bmp b/tests/Images/Input/Bmp/rgb32h52.bmp
new file mode 100644
index 0000000000..114c03fbd7
--- /dev/null
+++ b/tests/Images/Input/Bmp/rgb32h52.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9baf23d23b75ad2a641decce0e5b59640b3359ff327eecfa1b888595a6b502d6
+size 32578
diff --git a/tests/Images/Input/Bmp/rgba32v4.bmp b/tests/Images/Input/Bmp/rgba32v4.bmp
new file mode 100644
index 0000000000..6f9bf07bf8
--- /dev/null
+++ b/tests/Images/Input/Bmp/rgba32v4.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c932d7241122b221ab8f35c427082643ea0241493276c5aef0e64a49b8b55b6c
+size 32634
diff --git a/tests/Images/Input/Bmp/rle4-delta-320x240.bmp b/tests/Images/Input/Bmp/rle4-delta-320x240.bmp
new file mode 100644
index 0000000000..a52aad3d89
--- /dev/null
+++ b/tests/Images/Input/Bmp/rle4-delta-320x240.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:514d8bafa663017276ce0a91eb90025bd5e0296984c50f0da2f477cd27ff6d68
+size 3686
diff --git a/tests/Images/Input/Bmp/rle8-blank-160x120.bmp b/tests/Images/Input/Bmp/rle8-blank-160x120.bmp
new file mode 100644
index 0000000000..d1d4496073
--- /dev/null
+++ b/tests/Images/Input/Bmp/rle8-blank-160x120.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ffb99a2ee179388c1e379492fee83e67d600fbf740fbcfb0a4cf8e4d42a662b3
+size 1080
diff --git a/tests/Images/Input/Bmp/rle8-delta-320x240.bmp b/tests/Images/Input/Bmp/rle8-delta-320x240.bmp
new file mode 100644
index 0000000000..ff8ee7a303
--- /dev/null
+++ b/tests/Images/Input/Bmp/rle8-delta-320x240.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:522a154731ea38e4ee29b4f27672b7d881bcfb1f0954fae126e79a27a63d5f06
+size 4646