Browse Source

Merge branch 'main' into bp/Issue2217

pull/2221/head
James Jackson-South 3 years ago
parent
commit
2f918575e0
  1. 13
      .github/workflows/build-and-test.yml
  2. 5
      src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs
  3. 53
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  4. 125
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  5. 40
      tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
  6. 60
      tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
  7. 2
      tests/ImageSharp.Tests/TestImages.cs
  8. 11
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs
  9. 3
      tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecodeAlphaBitfields_Rgba32_rgba32abf.png
  10. 3
      tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2.png
  11. 3
      tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_2Bit_Rgba32_pal2color.png
  12. 3
      tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_9S.png
  13. 3
      tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_DIAMOND.png
  14. 3
      tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_GMARBLE.png
  15. 3
      tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_PINES.png
  16. 3
      tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SKATER.png
  17. 3
      tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SPADE.png
  18. 3
      tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_SUNFLOW.png
  19. 3
      tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_WARPD.png
  20. 3
      tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2BitmapArray_Rgba32_ba-bm.png
  21. 3
      tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2v2Header_Rgba32_pal8os2v2.png
  22. 3
      tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_Os2v2XShortHeader_Rgba32_pal8os2v2-16.png
  23. 3
      tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_24Bit_Rgba32_rgb24rle24.png
  24. 3
      tests/Images/External/ReferenceOutput/BmpDecoderTests/BmpDecoder_CanDecode_RunLengthEncoded_24Bit_Rgba32_rle24rlecut.png
  25. 3
      tests/Images/Input/Bmp/pal2.bmp
  26. 3
      tests/Images/Input/Bmp/pal2color.bmp

13
.github/workflows/build-and-test.yml

@ -21,18 +21,19 @@ jobs:
runtime: -x64
codecov: false
# Temp disabled due to runtime preview issues.
# https://github.com/SixLabors/ImageSharp/issues/2117
#- os: macos-latest
# framework: net7.0
# sdk: 7.0.x
# sdk-preview: true
# runtime: -x64
# codecov: false
- os: windows-latest
framework: net7.0
sdk: 7.0.x
sdk-preview: true
runtime: -x64
codecov: false
#- os: windows-latest
# framework: net7.0
# sdk: 7.0.x
# sdk-preview: true
# runtime: -x64
# codecov: false
- os: ubuntu-latest
framework: net6.0
sdk: 6.0.x

5
src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs

@ -13,6 +13,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
Pixel1 = 1,
/// <summary>
/// 2 bits per pixel.
/// </summary>
Pixel2 = 2,
/// <summary>
/// 4 bits per pixel.
/// </summary>

53
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -836,7 +836,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
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<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
@ -888,7 +892,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
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<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
@ -943,7 +951,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
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<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgr24Bytes(
@ -971,7 +983,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
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<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(
@ -1007,7 +1023,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
// 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<Bgra32>.Instance.FromBgra32Bytes(
this.configuration,
@ -1040,7 +1059,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
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<TPixel> pixelSpan = pixels.DangerousGetRowSpan(newY);
@ -1058,7 +1080,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
// 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<Bgra32>.Instance.FromBgra32Bytes(
this.configuration,
rowSpan,
@ -1119,7 +1145,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
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<TPixel> pixelRow = pixels.DangerousGetRowSpan(newY);
@ -1391,7 +1421,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
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 @@ namespace SixLabors.ImageSharp.Formats.Bmp
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();

125
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

@ -57,6 +57,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
private const int ColorPaletteSize4Bit = 64;
/// <summary>
/// The color palette for an 2 bit image will have 4 entry's with 4 bytes for each entry.
/// </summary>
private const int ColorPaletteSize2Bit = 16;
/// <summary>
/// The color palette for an 1 bit image will have 2 entry's with 4 bytes for each entry.
/// </summary>
@ -125,19 +130,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp
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 @@ namespace SixLabors.ImageSharp.Formats.Bmp
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 @@ namespace SixLabors.ImageSharp.Formats.Bmp
=> this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding);
/// <summary>
/// Writes the 32bit color palette to the stream.
/// Writes 32-bit data with a color palette to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write32Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
private void Write32BitPixelData<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 4);
@ -375,12 +379,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Writes the 24bit color palette to the stream.
/// Writes 24-bit pixel data with a color palette to the stream.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write24Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
private void Write24BitPixelData<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = pixels.Width;
@ -401,12 +405,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Writes the 16bit color palette to the stream.
/// Writes 16-bit pixel data with a color palette to the stream.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> containing pixel data.</param>
private void Write16Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
private void Write16BitPixelData<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
int width = pixels.Width;
@ -429,12 +433,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write8Bit<TPixel>(Stream stream, ImageFrame<TPixel> image)
private void Write8BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
bool isL8 = typeof(TPixel) == typeof(L8);
@ -443,7 +447,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
if (isL8)
{
this.Write8BitGray(stream, image, colorPalette);
this.Write8BitPixelData(stream, image, colorPalette);
}
else
{
@ -480,13 +484,13 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
/// <param name="colorPalette">A byte span of size 1024 for the color palette.</param>
private void Write8BitGray<TPixel>(Stream stream, ImageFrame<TPixel> image, Span<byte> colorPalette)
private void Write8BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image, Span<byte> colorPalette)
where TPixel : unmanaged, IPixel<TPixel>
{
// Create a color palette with 256 different gray values.
@ -518,12 +522,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write4BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image)
private void Write4BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
@ -562,12 +566,65 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// 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.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write2BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
{
MaxColors = 4
});
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize2Bit, AllocationOptions.Clean);
Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span;
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
ReadOnlySpan<byte> 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);
}
}
}
/// <summary>
/// Writes 1 bit pixel data with a color palette. The color palette has 2 entry's with 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write1BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image)
private void Write1BitPixelData<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
@ -622,7 +679,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
Span<uint> colorPaletteAsUInt = MemoryMarshal.Cast<byte, uint>(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);

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

@ -121,6 +121,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
image.CompareToOriginal(provider, new SystemDrawingReferenceDecoder());
}
[Theory]
[WithFile(Bit2, PixelTypes.Rgba32)]
[WithFile(Bit2Color, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecode_2Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> 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<TPixel>(TestImageProvider<TPixel> provider)
@ -266,8 +279,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
using Image<TPixel> 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
using Image<TPixel> 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
using Image<TPixel> 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
using Image<TPixel> 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 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
using Image<TPixel> 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");
}
}
}

60
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<BmpBitsPerPixel> BitsPerPixel =
new()
{
@ -39,6 +43,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
new()
{
{ Bit1, BmpBitsPerPixel.Pixel1 },
{ Bit2, BmpBitsPerPixel.Pixel2 },
{ Bit4, BmpBitsPerPixel.Pixel4 },
{ Bit8, BmpBitsPerPixel.Pixel8 },
{ Rgb16, BmpBitsPerPixel.Pixel16 },
@ -50,12 +55,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
[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<Rgba32> input = testFile.CreateRgba32Image();
using var memStream = new MemoryStream();
input.Save(memStream, options);
input.Save(memStream, BmpEncoder);
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
@ -69,12 +72,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
[MemberData(nameof(BmpBitsPerPixelFiles))]
public void Encode_PreserveBitsPerPixel(string imagePath, BmpBitsPerPixel bmpBitsPerPixel)
{
var options = new BmpEncoder();
var testFile = TestFile.Create(imagePath);
using Image<Rgba32> input = testFile.CreateRgba32Image();
using var memStream = new MemoryStream();
input.Save(memStream, options);
input.Save(memStream, BmpEncoder);
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
@ -192,6 +193,50 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true, customComparer: comparer);
}
[Theory]
[WithFile(Bit2, PixelTypes.Rgba32, BmpBitsPerPixel.Pixel2)]
public void Encode_2Bit_WithV3Header_Works<TPixel>(
TestImageProvider<TPixel> provider,
BmpBitsPerPixel bitsPerPixel)
where TPixel : unmanaged, IPixel<TPixel>
{
// arrange
var encoder = new BmpEncoder() { BitsPerPixel = bitsPerPixel };
using var memoryStream = new MemoryStream();
using Image<TPixel> input = provider.GetImage(BmpDecoder);
// act
encoder.Encode(input, memoryStream);
memoryStream.Position = 0;
// assert
using var actual = Image.Load<TPixel>(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<TPixel>(
TestImageProvider<TPixel> provider,
BmpBitsPerPixel bitsPerPixel)
where TPixel : unmanaged, IPixel<TPixel>
{
// arrange
var encoder = new BmpEncoder() { BitsPerPixel = bitsPerPixel };
using var memoryStream = new MemoryStream();
using Image<TPixel> input = provider.GetImage(BmpDecoder);
// act
encoder.Encode(input, memoryStream);
memoryStream.Position = 0;
// assert
using var actual = Image.Load<TPixel>(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<TPixel>(
@ -320,7 +365,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
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<TPixel>
{
using Image<TPixel> image = provider.GetImage();

2
tests/ImageSharp.Tests/TestImages.cs

@ -350,6 +350,8 @@ namespace SixLabors.ImageSharp.Tests
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";

11
tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs

@ -19,10 +19,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
/// <returns>A ImageComparer instance.</returns>
public static ImageComparer Tolerant(
float imageThreshold = TolerantImageComparer.DefaultImageThreshold,
int perPixelManhattanThreshold = 0)
{
return new TolerantImageComparer(imageThreshold, perPixelManhattanThreshold);
}
int perPixelManhattanThreshold = 0) =>
new TolerantImageComparer(imageThreshold, perPixelManhattanThreshold);
/// <summary>
/// Returns Tolerant(imageThresholdInPercents/100)
@ -45,10 +43,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
Image<TPixelA> expected,
Image<TPixelB> actual)
where TPixelA : unmanaged, IPixel<TPixelA>
where TPixelB : unmanaged, IPixel<TPixelB>
{
return comparer.CompareImagesOrFrames(expected.Frames.RootFrame, actual.Frames.RootFrame);
}
where TPixelB : unmanaged, IPixel<TPixelB> => comparer.CompareImagesOrFrames(expected.Frames.RootFrame, actual.Frames.RootFrame);
public static IEnumerable<ImageSimilarityReport<TPixelA, TPixelB>> CompareImages<TPixelA, TPixelB>(
this ImageComparer comparer,

3
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

3
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

3
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

3
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

3
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

3
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

3
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

3
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

3
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

3
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

3
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

3
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

3
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

3
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

3
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

3
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

3
tests/Images/Input/Bmp/pal2.bmp

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bac6eec4100831e635fcd34a9e0e34a8a9082abdec132ac327aa1bfc7137d40f
size 2118

3
tests/Images/Input/Bmp/pal2color.bmp

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6ac541592afb207524091aa19d59614851c293193600eacb1170b4854d351dae
size 2118
Loading…
Cancel
Save