Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 20 additions & 9 deletions src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public Image<TPixel> Decode<TPixel>(Stream stream)
{
try
{
this.ReadImageHeaders(stream, out bool inverted, out byte[] palette);
int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette);

var image = new Image<TPixel>(this.configuration, this.infoHeader.Width, this.infoHeader.Height, this.metaData);

Expand Down Expand Up @@ -137,6 +137,7 @@ public Image<TPixel> Decode<TPixel>(Stream stream)
this.infoHeader.Width,
this.infoHeader.Height,
this.infoHeader.BitsPerPixel,
bytesPerColorMapEntry,
inverted);
}

Expand Down Expand Up @@ -329,18 +330,20 @@ private void UncompressRle8(int w, Span<byte> buffer)
/// <param name="colors">The <see cref="T:byte[]"/> containing the colors.</param>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
/// <param name="bits">The number of bits per pixel.</param>
/// <param name="bitsPerPixel">The number of bits per pixel.</param>
/// <param name="bytesPerColorMapEntry">Usually 4 bytes, but in case of Windows 2.x bitmaps or OS/2 1.x bitmaps
/// the bytes per color palette entry's can be 3 bytes instead of 4.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRgbPalette<TPixel>(Buffer2D<TPixel> pixels, byte[] colors, int width, int height, int bits, bool inverted)
private void ReadRgbPalette<TPixel>(Buffer2D<TPixel> pixels, byte[] colors, int width, int height, int bitsPerPixel, int bytesPerColorMapEntry, bool inverted)
where TPixel : struct, IPixel<TPixel>
{
// Pixels per byte (bits per pixel)
int ppb = 8 / bits;
int ppb = 8 / bitsPerPixel;

int arrayWidth = (width + ppb - 1) / ppb;

// Bit mask
int mask = 0xFF >> (8 - bits);
int mask = 0xFF >> (8 - bitsPerPixel);

// Rows are aligned on 4 byte boundaries
int padding = arrayWidth % 4;
Expand All @@ -366,7 +369,7 @@ private void ReadRgbPalette<TPixel>(Buffer2D<TPixel> pixels, byte[] colors, int
int colOffset = x * ppb;
for (int shift = 0, newX = colOffset; shift < ppb && newX < width; shift++, newX++)
{
int colorIndex = ((rowSpan[offset] >> (8 - bits - (shift * bits))) & mask) * 4;
int colorIndex = ((rowSpan[offset] >> (8 - bitsPerPixel - (shift * bitsPerPixel))) & mask) * bytesPerColorMapEntry;

color.FromBgr24(Unsafe.As<byte, Bgr24>(ref colors[colorIndex]));
pixelRow[newX] = color;
Expand Down Expand Up @@ -576,7 +579,9 @@ private void ReadFileHeader()
/// <summary>
/// Reads the <see cref="BmpFileHeader"/> and <see cref="BmpInfoHeader"/> from the stream and sets the corresponding fields.
/// </summary>
private void ReadImageHeaders(Stream stream, out bool inverted, out byte[] palette)
/// <returns>Bytes per color palette entry. Usually 4 bytes, but in case of Windows 2.x bitmaps or OS/2 1.x bitmaps
/// the bytes per color palette entry's can be 3 bytes instead of 4.</returns>
private int ReadImageHeaders(Stream stream, out bool inverted, out byte[] palette)
{
this.stream = stream;

Expand All @@ -596,19 +601,23 @@ private void ReadImageHeaders(Stream stream, out bool inverted, out byte[] palet
}

int colorMapSize = -1;
int bytesPerColorMapEntry = 4;

if (this.infoHeader.ClrUsed == 0)
{
if (this.infoHeader.BitsPerPixel == 1
|| this.infoHeader.BitsPerPixel == 4
|| this.infoHeader.BitsPerPixel == 8)
{
colorMapSize = ImageMaths.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel) * 4;
int colorMapSizeBytes = this.fileHeader.Offset - BmpFileHeader.Size - this.infoHeader.HeaderSize;
int colorCountForBitDepth = ImageMaths.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel);
bytesPerColorMapEntry = colorMapSizeBytes / colorCountForBitDepth;
colorMapSize = colorMapSizeBytes;
}
}
else
{
colorMapSize = this.infoHeader.ClrUsed * 4;
colorMapSize = this.infoHeader.ClrUsed * bytesPerColorMapEntry;
}

palette = null;
Expand All @@ -627,6 +636,8 @@ private void ReadImageHeaders(Stream stream, out bool inverted, out byte[] palet
}

this.infoHeader.VerifyDimensions();

return bytesPerColorMapEntry;
}
}
}
24 changes: 24 additions & 0 deletions tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,30 @@ public void BmpDecoder_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPi
}
}

[Theory]
[WithFile(WinBmpv2, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecodeBmpv2<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
{
image.DebugSave(provider, "png");
image.CompareToOriginal(provider);
}
}

[Theory]
[WithFile(Bit8Palette4, PixelTypes.Rgba32)]
public void BmpDecoder_CanDecode4BytePerEntryPalette<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
{
image.DebugSave(provider, "png");
image.CompareToOriginal(provider);
}
}

[Theory]
[InlineData(Car, 24)]
[InlineData(F, 24)]
Expand Down
4 changes: 4 additions & 0 deletions tests/ImageSharp.Tests/TestImages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,10 @@ public static class Bmp
public const string Bit16 = "Bmp/test16.bmp";
public const string Bit16Inverted = "Bmp/test16-inverted.bmp";
public const string Bit32Rgb = "Bmp/rgb32.bmp";

// Note: This format can be called OS/2 BMPv1, or Windows BMPv2
public const string WinBmpv2 = "Bmp/pal8os2v1_winv2.bmp";
public const string Bit8Palette4 = "Bmp/pal8-0.bmp";
public const string Os2v2Short = "Bmp/pal8os2v2-16.bmp";

public static readonly string[] All
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ internal static IImageFormat GetImageFormat(string filePath)
{
string extension = Path.GetExtension(filePath);

IImageFormat format = Configuration.ImageFormatsManager.FindFormatByFileExtension(extension);
return format;
return Configuration.ImageFormatsManager.FindFormatByFileExtension(extension);
}

private static void ConfigureCodecs(
Expand Down Expand Up @@ -69,7 +68,7 @@ private static Configuration CreateDefaultConfiguration()

cfg.ConfigureCodecs(
BmpFormat.Instance,
SystemDrawingReferenceDecoder.Instance,
IsWindows ? (IImageDecoder)SystemDrawingReferenceDecoder.Instance : MagickReferenceDecoder.Instance,
bmpEncoder,
new BmpImageFormatDetector());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public void GetReferenceEncoder_ReturnsCorrectEncoders_Linux(string fileName, Ty

[Theory]
[InlineData("lol/foo.png", typeof(MagickReferenceDecoder))]
[InlineData("lol/Rofl.bmp", typeof(SystemDrawingReferenceDecoder))]
[InlineData("lol/Rofl.bmp", typeof(MagickReferenceDecoder))]
[InlineData("lol/Baz.JPG", typeof(JpegDecoder))]
[InlineData("lol/Baz.gif", typeof(GifDecoder))]
public void GetReferenceDecoder_ReturnsCorrectDecoders_Linux(string fileName, Type expectedDecoderType)
Expand Down
Binary file added tests/Images/Input/Bmp/pal8-0.bmp
Binary file not shown.
Binary file added tests/Images/Input/Bmp/pal8os2v1_winv2.bmp
Binary file not shown.