diff --git a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs
index 1b73d8b189..f66883c203 100644
--- a/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs
@@ -13,6 +13,11 @@ public enum BmpBitsPerPixel : short
///
Pixel1 = 1,
+ ///
+ /// 2 bits per pixel.
+ ///
+ Pixel2 = 2,
+
///
/// 4 bits per pixel.
///
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
index 1adc34849e..350823decb 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
@@ -836,7 +836,11 @@ private void ReadRgbPalette(Buffer2D pixels, byte[] colors, int
for (int y = 0; y < height; y++)
{
int newY = Invert(y, height, inverted);
- this.stream.Read(rowSpan);
+ if (this.stream.Read(rowSpan) == 0)
+ {
+ BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
+ }
+
int offset = 0;
Span pixelRow = pixels.DangerousGetRowSpan(newY);
@@ -888,7 +892,11 @@ private void ReadRgb16(Buffer2D pixels, int width, int height, b
for (int y = 0; y < height; y++)
{
- this.stream.Read(bufferSpan);
+ if (this.stream.Read(bufferSpan) == 0)
+ {
+ BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
+ }
+
int newY = Invert(y, height, inverted);
Span pixelRow = pixels.DangerousGetRowSpan(newY);
@@ -943,7 +951,11 @@ private void ReadRgb24(Buffer2D pixels, int width, int height, b
for (int y = 0; y < height; y++)
{
- this.stream.Read(rowSpan);
+ if (this.stream.Read(rowSpan) == 0)
+ {
+ BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
+ }
+
int newY = Invert(y, height, inverted);
Span pixelSpan = pixels.DangerousGetRowSpan(newY);
PixelOperations.Instance.FromBgr24Bytes(
@@ -971,7 +983,11 @@ private void ReadRgb32Fast(Buffer2D pixels, int width, int heigh
for (int y = 0; y < height; y++)
{
- this.stream.Read(rowSpan);
+ if (this.stream.Read(rowSpan) == 0)
+ {
+ BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
+ }
+
int newY = Invert(y, height, inverted);
Span pixelSpan = pixels.DangerousGetRowSpan(newY);
PixelOperations.Instance.FromBgra32Bytes(
@@ -1007,7 +1023,10 @@ private void ReadRgb32Slow(Buffer2D pixels, int width, int heigh
// actually a BGRA image, and change tactics accordingly.
for (int y = 0; y < height; y++)
{
- this.stream.Read(rowSpan);
+ if (this.stream.Read(rowSpan) == 0)
+ {
+ BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
+ }
PixelOperations.Instance.FromBgra32Bytes(
this.configuration,
@@ -1040,7 +1059,10 @@ private void ReadRgb32Slow(Buffer2D pixels, int width, int heigh
{
for (int y = 0; y < height; y++)
{
- this.stream.Read(rowSpan);
+ if (this.stream.Read(rowSpan) == 0)
+ {
+ BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
+ }
int newY = Invert(y, height, inverted);
Span pixelSpan = pixels.DangerousGetRowSpan(newY);
@@ -1058,7 +1080,11 @@ private void ReadRgb32Slow(Buffer2D pixels, int width, int heigh
// Slow path. We need to set each alpha component value to fully opaque.
for (int y = 0; y < height; y++)
{
- this.stream.Read(rowSpan);
+ if (this.stream.Read(rowSpan) == 0)
+ {
+ BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
+ }
+
PixelOperations.Instance.FromBgra32Bytes(
this.configuration,
rowSpan,
@@ -1119,7 +1145,11 @@ private void ReadRgb32BitFields(Buffer2D pixels, int width, int
for (int y = 0; y < height; y++)
{
- this.stream.Read(bufferSpan);
+ if (this.stream.Read(bufferSpan) == 0)
+ {
+ BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for a pixel row!");
+ }
+
int newY = Invert(y, height, inverted);
Span pixelRow = pixels.DangerousGetRowSpan(newY);
@@ -1391,7 +1421,7 @@ private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out b
int colorMapSizeBytes = -1;
if (this.infoHeader.ClrUsed == 0)
{
- if (this.infoHeader.BitsPerPixel is 1 or 4 or 8)
+ if (this.infoHeader.BitsPerPixel is 1 or 2 or 4 or 8)
{
switch (this.fileMarkerType)
{
@@ -1435,7 +1465,10 @@ private int ReadImageHeaders(BufferedReadStream stream, out bool inverted, out b
palette = new byte[colorMapSizeBytes];
- this.stream.Read(palette, 0, colorMapSizeBytes);
+ if (this.stream.Read(palette, 0, colorMapSizeBytes) == 0)
+ {
+ BmpThrowHelper.ThrowInvalidImageContentException("Could not read enough data for the palette!");
+ }
}
this.infoHeader.VerifyDimensions();
diff --git a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
index f71275b7cc..257159bd23 100644
--- a/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
@@ -57,6 +57,11 @@ internal sealed class BmpEncoderCore : IImageEncoderInternals
///
private const int ColorPaletteSize4Bit = 64;
+ ///
+ /// The color palette for an 2 bit image will have 4 entry's with 4 bytes for each entry.
+ ///
+ private const int ColorPaletteSize2Bit = 16;
+
///
/// The color palette for an 1 bit image will have 2 entry's with 4 bytes for each entry.
///
@@ -125,19 +130,14 @@ public void Encode(Image image, Stream stream, CancellationToken
int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32);
this.padding = bytesPerLine - (int)(image.Width * (bpp / 8F));
- int colorPaletteSize = 0;
- if (this.bitsPerPixel == BmpBitsPerPixel.Pixel8)
- {
- colorPaletteSize = ColorPaletteSize8Bit;
- }
- else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel4)
- {
- colorPaletteSize = ColorPaletteSize4Bit;
- }
- else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel1)
+ int colorPaletteSize = this.bitsPerPixel switch
{
- colorPaletteSize = ColorPaletteSize1Bit;
- }
+ BmpBitsPerPixel.Pixel8 => ColorPaletteSize8Bit,
+ BmpBitsPerPixel.Pixel4 => ColorPaletteSize4Bit,
+ BmpBitsPerPixel.Pixel2 => ColorPaletteSize2Bit,
+ BmpBitsPerPixel.Pixel1 => ColorPaletteSize1Bit,
+ _ => 0
+ };
byte[] iccProfileData = null;
int iccProfileSize = 0;
@@ -322,27 +322,31 @@ private void WriteImage(Stream stream, ImageFrame image)
switch (this.bitsPerPixel)
{
case BmpBitsPerPixel.Pixel32:
- this.Write32Bit(stream, pixels);
+ this.Write32BitPixelData(stream, pixels);
break;
case BmpBitsPerPixel.Pixel24:
- this.Write24Bit(stream, pixels);
+ this.Write24BitPixelData(stream, pixels);
break;
case BmpBitsPerPixel.Pixel16:
- this.Write16Bit(stream, pixels);
+ this.Write16BitPixelData(stream, pixels);
break;
case BmpBitsPerPixel.Pixel8:
- this.Write8Bit(stream, image);
+ this.Write8BitPixelData(stream, image);
break;
case BmpBitsPerPixel.Pixel4:
- this.Write4BitColor(stream, image);
+ this.Write4BitPixelData(stream, image);
+ break;
+
+ case BmpBitsPerPixel.Pixel2:
+ this.Write2BitPixelData(stream, image);
break;
case BmpBitsPerPixel.Pixel1:
- this.Write1BitColor(stream, image);
+ this.Write1BitPixelData(stream, image);
break;
}
}
@@ -351,12 +355,12 @@ private IMemoryOwner AllocateRow(int width, int bytesPerPixel)
=> this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding);
///
- /// Writes the 32bit color palette to the stream.
+ /// Writes 32-bit data with a color palette to the stream.
///
/// The pixel format.
/// The to write to.
/// The containing pixel data.
- private void Write32Bit(Stream stream, Buffer2D pixels)
+ private void Write32BitPixelData(Stream stream, Buffer2D pixels)
where TPixel : unmanaged, IPixel
{
using IMemoryOwner row = this.AllocateRow(pixels.Width, 4);
@@ -375,12 +379,12 @@ private void Write32Bit(Stream stream, Buffer2D pixels)
}
///
- /// Writes the 24bit color palette to the stream.
+ /// Writes 24-bit pixel data with a color palette to the stream.
///
/// The pixel format.
/// The to write to.
/// The containing pixel data.
- private void Write24Bit(Stream stream, Buffer2D pixels)
+ private void Write24BitPixelData(Stream stream, Buffer2D pixels)
where TPixel : unmanaged, IPixel
{
int width = pixels.Width;
@@ -401,12 +405,12 @@ private void Write24Bit(Stream stream, Buffer2D pixels)
}
///
- /// Writes the 16bit color palette to the stream.
+ /// Writes 16-bit pixel data with a color palette to the stream.
///
/// The type of the pixel.
/// The to write to.
/// The containing pixel data.
- private void Write16Bit(Stream stream, Buffer2D pixels)
+ private void Write16BitPixelData(Stream stream, Buffer2D pixels)
where TPixel : unmanaged, IPixel
{
int width = pixels.Width;
@@ -429,12 +433,12 @@ private void Write16Bit(Stream stream, Buffer2D pixels)
}
///
- /// Writes an 8 bit image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
+ /// Writes 8 bit pixel data with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
///
/// The type of the pixel.
/// The to write to.
/// The containing pixel data.
- private void Write8Bit(Stream stream, ImageFrame image)
+ private void Write8BitPixelData(Stream stream, ImageFrame image)
where TPixel : unmanaged, IPixel
{
bool isL8 = typeof(TPixel) == typeof(L8);
@@ -443,7 +447,7 @@ private void Write8Bit(Stream stream, ImageFrame image)
if (isL8)
{
- this.Write8BitGray(stream, image, colorPalette);
+ this.Write8BitPixelData(stream, image, colorPalette);
}
else
{
@@ -480,13 +484,13 @@ private void Write8BitColor(Stream stream, ImageFrame image, Spa
}
///
- /// Writes an 8 bit gray image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
+ /// Writes 8 bit gray pixel data with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
///
/// The type of the pixel.
/// The to write to.
/// The containing pixel data.
/// A byte span of size 1024 for the color palette.
- private void Write8BitGray(Stream stream, ImageFrame image, Span colorPalette)
+ private void Write8BitPixelData(Stream stream, ImageFrame image, Span colorPalette)
where TPixel : unmanaged, IPixel
{
// Create a color palette with 256 different gray values.
@@ -518,12 +522,12 @@ private void Write8BitGray(Stream stream, ImageFrame image, Span
}
///
- /// Writes an 4 bit color image with a color palette. The color palette has 16 entry's with 4 bytes for each entry.
+ /// Writes 4 bit pixel data with a color palette. The color palette has 16 entry's with 4 bytes for each entry.
///
/// The type of the pixel.
/// The to write to.
/// The containing pixel data.
- private void Write4BitColor(Stream stream, ImageFrame image)
+ private void Write4BitPixelData(Stream stream, ImageFrame image)
where TPixel : unmanaged, IPixel
{
using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration, new QuantizerOptions()
@@ -562,12 +566,65 @@ private void Write4BitColor(Stream stream, ImageFrame image)
}
///
- /// Writes a 1 bit image with a color palette. The color palette has 2 entry's with 4 bytes for each entry.
+ /// Writes 2 bit pixel data with a color palette. The color palette has 4 entry's with 4 bytes for each entry.
+ ///
+ /// The type of the pixel.
+ /// The to write to.
+ /// The containing pixel data.
+ private void Write2BitPixelData(Stream stream, ImageFrame image)
+ where TPixel : unmanaged, IPixel
+ {
+ using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration, new QuantizerOptions()
+ {
+ MaxColors = 4
+ });
+ using IndexedImageFrame quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
+ using IMemoryOwner colorPaletteBuffer = this.memoryAllocator.Allocate(ColorPaletteSize2Bit, AllocationOptions.Clean);
+
+ Span colorPalette = colorPaletteBuffer.GetSpan();
+ ReadOnlySpan quantizedColorPalette = quantized.Palette.Span;
+ this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
+
+ ReadOnlySpan pixelRowSpan = quantized.DangerousGetRowSpan(0);
+ int rowPadding = pixelRowSpan.Length % 4 != 0 ? this.padding - 1 : this.padding;
+ for (int y = image.Height - 1; y >= 0; y--)
+ {
+ pixelRowSpan = quantized.DangerousGetRowSpan(y);
+
+ int endIdx = pixelRowSpan.Length % 4 == 0 ? pixelRowSpan.Length : pixelRowSpan.Length - 4;
+ int i = 0;
+ for (i = 0; i < endIdx; i += 4)
+ {
+ stream.WriteByte((byte)((pixelRowSpan[i] << 6) | (pixelRowSpan[i + 1] << 4) | (pixelRowSpan[i + 2] << 2) | pixelRowSpan[i + 3]));
+ }
+
+ if (pixelRowSpan.Length % 4 != 0)
+ {
+ int shift = 6;
+ byte pixelData = 0;
+ for (; i < pixelRowSpan.Length; i++)
+ {
+ pixelData = (byte)(pixelData | (pixelRowSpan[i] << shift));
+ shift -= 2;
+ }
+
+ stream.WriteByte(pixelData);
+ }
+
+ for (i = 0; i < rowPadding; i++)
+ {
+ stream.WriteByte(0);
+ }
+ }
+ }
+
+ ///
+ /// Writes 1 bit pixel data with a color palette. The color palette has 2 entry's with 4 bytes for each entry.
///
/// The type of the pixel.
/// The to write to.
/// The containing pixel data.
- private void Write1BitColor(Stream stream, ImageFrame image)
+ private void Write1BitPixelData(Stream stream, ImageFrame image)
where TPixel : unmanaged, IPixel
{
using IQuantizer frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer(this.configuration, new QuantizerOptions()
@@ -622,7 +679,7 @@ private void WriteColorPalette(Stream stream, ReadOnlySpan quant
Span colorPaletteAsUInt = MemoryMarshal.Cast(colorPalette);
for (int i = 0; i < colorPaletteAsUInt.Length; i++)
{
- colorPaletteAsUInt[i] = colorPaletteAsUInt[i] & 0x00FFFFFF; // Padding byte, always 0.
+ colorPaletteAsUInt[i] &= 0x00FFFFFF; // Padding byte, always 0.
}
stream.Write(colorPalette);
diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
index e303cb5164..248c7c41cf 100644
--- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
@@ -121,6 +121,19 @@ public void BmpDecoder_CanDecode_1Bit(TestImageProvider provider
image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder());
}
+ [Theory]
+ [WithFile(Bit2, PixelTypes.Rgba32)]
+ [WithFile(Bit2Color, PixelTypes.Rgba32)]
+ public void BmpDecoder_CanDecode_2Bit(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ using Image image = provider.GetImage(BmpDecoder);
+ image.DebugSave(provider);
+
+ // Reference decoder cant decode 2-bit, compare to reference output instead.
+ image.CompareToReferenceOutput(provider, extension: "png");
+ }
+
[Theory]
[WithFile(Bit4, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecode_4Bit(TestImageProvider provider)
@@ -266,8 +279,9 @@ public void BmpDecoder_CanDecode_RunLengthEncoded_24Bit(TestImageProvide
using Image image = provider.GetImage(BmpDecoder, options);
image.DebugSave(provider);
- // TODO: Neither System.Drawing nor MagickReferenceDecoder decode this file.
- // image.CompareToOriginal(provider);
+ // Neither System.Drawing nor MagickReferenceDecoder decode this file.
+ // Compare to reference output instead.
+ image.CompareToReferenceOutput(provider, extension: "png");
}
[Theory]
@@ -278,8 +292,9 @@ public void BmpDecoder_CanDecodeAlphaBitfields(TestImageProvider
using Image image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
- // TODO: Neither System.Drawing nor MagickReferenceDecoder decode this file.
- // image.CompareToOriginal(provider);
+ // Neither System.Drawing nor MagickReferenceDecoder decode this file.
+ // Compare to reference output instead.
+ image.CompareToReferenceOutput(provider, extension: "png");
}
[Theory]
@@ -512,8 +527,9 @@ public void BmpDecoder_CanDecode_Os2v2XShortHeader(TestImageProvider image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
- // TODO: Neither System.Drawing or MagickReferenceDecoder can correctly decode this file.
- // image.CompareToOriginal(provider);
+ // Neither System.Drawing or MagickReferenceDecoder can correctly decode this file.
+ // Compare to reference output instead.
+ image.CompareToReferenceOutput(provider, extension: "png");
}
[Theory]
@@ -524,10 +540,9 @@ public void BmpDecoder_CanDecode_Os2v2Header(TestImageProvider p
using Image image = provider.GetImage(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());
+ // System.Drawing can not decode this image. MagickReferenceDecoder can decode it,
+ // Compare to reference output instead.
+ image.CompareToReferenceOutput(provider, extension: "png");
}
[Theory]
@@ -546,8 +561,9 @@ public void BmpDecoder_CanDecode_Os2BitmapArray(TestImageProvider image = provider.GetImage(BmpDecoder);
image.DebugSave(provider);
- // TODO: Neither System.Drawing or MagickReferenceDecoder can correctly decode this file.
- // image.CompareToOriginal(provider);
+ // Neither System.Drawing or MagickReferenceDecoder can correctly decode this file.
+ // Compare to reference output instead.
+ image.CompareToReferenceOutput(provider, extension: "png");
}
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
index 5dd712c1ff..bb88cc4629 100644
--- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
@@ -20,6 +20,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
[Trait("Format", "Bmp")]
public class BmpEncoderTests
{
+ private static BmpDecoder BmpDecoder => new();
+
+ private static BmpEncoder BmpEncoder => new();
+
public static readonly TheoryData BitsPerPixel =
new()
{
@@ -39,6 +43,7 @@ public class BmpEncoderTests
new()
{
{ Bit1, BmpBitsPerPixel.Pixel1 },
+ { Bit2, BmpBitsPerPixel.Pixel2 },
{ Bit4, BmpBitsPerPixel.Pixel4 },
{ Bit8, BmpBitsPerPixel.Pixel8 },
{ Rgb16, BmpBitsPerPixel.Pixel16 },
@@ -50,12 +55,10 @@ public class BmpEncoderTests
[MemberData(nameof(RatioFiles))]
public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
- var options = new BmpEncoder();
-
var testFile = TestFile.Create(imagePath);
using Image input = testFile.CreateRgba32Image();
using var memStream = new MemoryStream();
- input.Save(memStream, options);
+ input.Save(memStream, BmpEncoder);
memStream.Position = 0;
using var output = Image.Load(memStream);
@@ -69,12 +72,10 @@ public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolut
[MemberData(nameof(BmpBitsPerPixelFiles))]
public void Encode_PreserveBitsPerPixel(string imagePath, BmpBitsPerPixel bmpBitsPerPixel)
{
- var options = new BmpEncoder();
-
var testFile = TestFile.Create(imagePath);
using Image input = testFile.CreateRgba32Image();
using var memStream = new MemoryStream();
- input.Save(memStream, options);
+ input.Save(memStream, BmpEncoder);
memStream.Position = 0;
using var output = Image.Load(memStream);
@@ -192,6 +193,50 @@ public void Encode_4Bit_WithV4Header_Works(
TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true, customComparer: comparer);
}
+ [Theory]
+ [WithFile(Bit2, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel2)]
+ public void Encode_2Bit_WithV3Header_Works(
+ TestImageProvider provider,
+ BmpBitsPerPixel bitsPerPixel)
+ where TPixel : unmanaged, IPixel
+ {
+ // arrange
+ var encoder = new BmpEncoder() { BitsPerPixel = bitsPerPixel };
+ using var memoryStream = new MemoryStream();
+ using Image input = provider.GetImage(BmpDecoder);
+
+ // act
+ encoder.Encode(input, memoryStream);
+ memoryStream.Position = 0;
+
+ // assert
+ using var actual = Image.Load(memoryStream);
+ ImageSimilarityReport similarityReport = ImageComparer.Exact.CompareImagesOrFrames(input, actual);
+ Assert.True(similarityReport.IsEmpty, "encoded image does not match reference image");
+ }
+
+ [Theory]
+ [WithFile(Bit2, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel2)]
+ public void Encode_2Bit_WithV4Header_Works(
+ TestImageProvider provider,
+ BmpBitsPerPixel bitsPerPixel)
+ where TPixel : unmanaged, IPixel
+ {
+ // arrange
+ var encoder = new BmpEncoder() { BitsPerPixel = bitsPerPixel };
+ using var memoryStream = new MemoryStream();
+ using Image input = provider.GetImage(BmpDecoder);
+
+ // act
+ encoder.Encode(input, memoryStream);
+ memoryStream.Position = 0;
+
+ // assert
+ using var actual = Image.Load(memoryStream);
+ ImageSimilarityReport similarityReport = ImageComparer.Exact.CompareImagesOrFrames(input, actual);
+ Assert.True(similarityReport.IsEmpty, "encoded image does not match reference image");
+ }
+
[Theory]
[WithFile(Bit1, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel1)]
public void Encode_1Bit_WithV3Header_Works(
@@ -320,7 +365,8 @@ private static void TestBmpEncoderCore(
BmpBitsPerPixel bitsPerPixel,
bool supportTransparency = true, // if set to true, will write a V4 header, otherwise a V3 header.
IQuantizer quantizer = null,
- ImageComparer customComparer = null)
+ ImageComparer customComparer = null,
+ IImageDecoder referenceDecoder = null)
where TPixel : unmanaged, IPixel
{
using Image image = provider.GetImage();
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index ddaf672b43..0f9479a764 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -347,6 +347,8 @@ public static class 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 Bit2 = "Bmp/pal2.bmp";
+ public const string Bit2Color = "Bmp/pal2color.bmp";
public const string Bit1Pal1 = "Bmp/pal1p1.bmp";
public const string Bit4 = "Bmp/pal4.bmp";
public const string Bit8 = "Bmp/test8.bmp";
diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs
index cea2784b67..d2750c31c5 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs
@@ -19,10 +19,8 @@ public abstract class ImageComparer
/// A ImageComparer instance.
public static ImageComparer Tolerant(
float imageThreshold = TolerantImageComparer.DefaultImageThreshold,
- int perPixelManhattanThreshold = 0)
- {
- return new TolerantImageComparer(imageThreshold, perPixelManhattanThreshold);
- }
+ int perPixelManhattanThreshold = 0) =>
+ new TolerantImageComparer(imageThreshold, perPixelManhattanThreshold);
///
/// Returns Tolerant(imageThresholdInPercents/100)
@@ -45,10 +43,7 @@ public static ImageSimilarityReport CompareImagesOrFrames expected,
Image actual)
where TPixelA : unmanaged, IPixel
- where TPixelB : unmanaged, IPixel
- {
- return comparer.CompareImagesOrFrames(expected.Frames.RootFrame, actual.Frames.RootFrame);
- }
+ where TPixelB : unmanaged, IPixel => comparer.CompareImagesOrFrames(expected.Frames.RootFrame, actual.Frames.RootFrame);
public static IEnumerable> CompareImages(
this ImageComparer comparer,
diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeAlphaBitfields_Rgba32_rgba32abf.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeAlphaBitfields_Rgba32_rgba32abf.png
new file mode 100644
index 0000000000..40613ca7e5
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeAlphaBitfields_Rgba32_rgba32abf.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1c7c5d24cf8ba473a22d1c12dcd196f626d2ef056a35bb3ff54b5c84516544bf
+size 14547
diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2.png
new file mode 100644
index 0000000000..4a1ac40887
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:2c3e8b87af737c40d7be02e55a2aec93bb0e7bd123cd1f3e3b74482a0c7d18bd
+size 2376
diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2color.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2color.png
new file mode 100644
index 0000000000..6f7bd68696
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2color.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c3da68e15f4edf6ce5da76360f3704d52baff5292ee12efe5415540b5788dda5
+size 2578
diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_9S.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_9S.png
new file mode 100644
index 0000000000..0319b97182
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_9S.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8597af653507fb625a8f387ce01ab900603086892f046b7b92e6fcf60a636295
+size 884
diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_DIAMOND.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_DIAMOND.png
new file mode 100644
index 0000000000..630b44ecd2
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_DIAMOND.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0a6989978a0fe36399a774000ee04336d090a4e6a2b63bcbfcd45312ccac4dab
+size 648
diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_GMARBLE.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_GMARBLE.png
new file mode 100644
index 0000000000..ff484218d3
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_GMARBLE.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:951d9d48a5b5df5b70a8c217e2a3d94f4b2c8e8cc63d70cb807627b8e98b8b1d
+size 20567
diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_PINES.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_PINES.png
new file mode 100644
index 0000000000..78ffbe76c5
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_PINES.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:35dc46a1f19f3f0a91948bee9b173f6ce264ade69754c01b688e2a878f1374a9
+size 21406
diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SKATER.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SKATER.png
new file mode 100644
index 0000000000..7b6ec01dcf
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SKATER.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3191c0ac33c1749f770f96814c0585715aa1c0b085f02256317cedeabc531c12
+size 636
diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SPADE.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SPADE.png
new file mode 100644
index 0000000000..89bf24a228
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SPADE.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:50c5f1adb8b9f0f9a111fdd4b04df023d4239d409f93e2ab5823352c02761118
+size 802
diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SUNFLOW.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SUNFLOW.png
new file mode 100644
index 0000000000..5e7a2c071a
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SUNFLOW.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:8f0f9b6a5f1a36596fbe8ac1416e69af82e24c5892a8012a6b68206b6e467bec
+size 14190
diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_WARPD.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_WARPD.png
new file mode 100644
index 0000000000..6a62cc9c71
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_WARPD.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:452e8aeca41c0899f4e7a4f0458f7cf2dd8002e42a752708d7dd308e040641a0
+size 103892
diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_ba-bm.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_ba-bm.png
new file mode 100644
index 0000000000..2c9fab29fe
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_ba-bm.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9e1fc90acab9db3469b673036c8cdcf5920ca3d12915a412280f09a86a14ad2e
+size 5081
diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2v2Header_Rgba32_pal8os2v2.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2v2Header_Rgba32_pal8os2v2.png
new file mode 100644
index 0000000000..2c9fab29fe
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2v2Header_Rgba32_pal8os2v2.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9e1fc90acab9db3469b673036c8cdcf5920ca3d12915a412280f09a86a14ad2e
+size 5081
diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2v2XShortHeader_Rgba32_pal8os2v2-16.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2v2XShortHeader_Rgba32_pal8os2v2-16.png
new file mode 100644
index 0000000000..2c9fab29fe
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2v2XShortHeader_Rgba32_pal8os2v2-16.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9e1fc90acab9db3469b673036c8cdcf5920ca3d12915a412280f09a86a14ad2e
+size 5081
diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_24Bit_Rgba32_rgb24rle24.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_24Bit_Rgba32_rgb24rle24.png
new file mode 100644
index 0000000000..2c9fab29fe
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_24Bit_Rgba32_rgb24rle24.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9e1fc90acab9db3469b673036c8cdcf5920ca3d12915a412280f09a86a14ad2e
+size 5081
diff --git a/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_24Bit_Rgba32_rle24rlecut.png b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_24Bit_Rgba32_rle24rlecut.png
new file mode 100644
index 0000000000..8311bc95be
--- /dev/null
+++ b/tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_24Bit_Rgba32_rle24rlecut.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:13d9b630227069f3fd744ef486d64d3f997ee0a9844824e9986c55d754bf413c
+size 4379
diff --git a/tests/Images/Input/Bmp/pal2.bmp b/tests/Images/Input/Bmp/pal2.bmp
new file mode 100644
index 0000000000..ac351d5fb6
--- /dev/null
+++ b/tests/Images/Input/Bmp/pal2.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:bac6eec4100831e635fcd34a9e0e34a8a9082abdec132ac327aa1bfc7137d40f
+size 2118
diff --git a/tests/Images/Input/Bmp/pal2color.bmp b/tests/Images/Input/Bmp/pal2color.bmp
new file mode 100644
index 0000000000..dd7c31bf67
--- /dev/null
+++ b/tests/Images/Input/Bmp/pal2color.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6ac541592afb207524091aa19d59614851c293193600eacb1170b4854d351dae
+size 2118