Skip to content

Commit abaf558

Browse files
Merge pull request #792 from brianpopow/feature/BMPv2ColorPaletteFix
WIP: Fix for Windows 2.0 or OS/2 1.x bitmaps only use 3 bytes per color palette entry
2 parents 4f1e624 + 2f3c0fc commit abaf558

File tree

7 files changed

+51
-13
lines changed

7 files changed

+51
-13
lines changed

src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public Image<TPixel> Decode<TPixel>(Stream stream)
108108
{
109109
try
110110
{
111-
this.ReadImageHeaders(stream, out bool inverted, out byte[] palette);
111+
int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette);
112112

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

@@ -137,6 +137,7 @@ public Image<TPixel> Decode<TPixel>(Stream stream)
137137
this.infoHeader.Width,
138138
this.infoHeader.Height,
139139
this.infoHeader.BitsPerPixel,
140+
bytesPerColorMapEntry,
140141
inverted);
141142
}
142143

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

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

342345
// Bit mask
343-
int mask = 0xFF >> (8 - bits);
346+
int mask = 0xFF >> (8 - bitsPerPixel);
344347

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

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

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

598603
int colorMapSize = -1;
604+
int bytesPerColorMapEntry = 4;
599605

600606
if (this.infoHeader.ClrUsed == 0)
601607
{
602608
if (this.infoHeader.BitsPerPixel == 1
603609
|| this.infoHeader.BitsPerPixel == 4
604610
|| this.infoHeader.BitsPerPixel == 8)
605611
{
606-
colorMapSize = ImageMaths.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel) * 4;
612+
int colorMapSizeBytes = this.fileHeader.Offset - BmpFileHeader.Size - this.infoHeader.HeaderSize;
613+
int colorCountForBitDepth = ImageMaths.GetColorCountForBitDepth(this.infoHeader.BitsPerPixel);
614+
bytesPerColorMapEntry = colorMapSizeBytes / colorCountForBitDepth;
615+
colorMapSize = colorMapSizeBytes;
607616
}
608617
}
609618
else
610619
{
611-
colorMapSize = this.infoHeader.ClrUsed * 4;
620+
colorMapSize = this.infoHeader.ClrUsed * bytesPerColorMapEntry;
612621
}
613622

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

629638
this.infoHeader.VerifyDimensions();
639+
640+
return bytesPerColorMapEntry;
630641
}
631642
}
632643
}

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,30 @@ public void BmpDecoder_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPi
5555
}
5656
}
5757

58+
[Theory]
59+
[WithFile(WinBmpv2, PixelTypes.Rgba32)]
60+
public void BmpDecoder_CanDecodeBmpv2<TPixel>(TestImageProvider<TPixel> provider)
61+
where TPixel : struct, IPixel<TPixel>
62+
{
63+
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
64+
{
65+
image.DebugSave(provider, "png");
66+
image.CompareToOriginal(provider);
67+
}
68+
}
69+
70+
[Theory]
71+
[WithFile(Bit8Palette4, PixelTypes.Rgba32)]
72+
public void BmpDecoder_CanDecode4BytePerEntryPalette<TPixel>(TestImageProvider<TPixel> provider)
73+
where TPixel : struct, IPixel<TPixel>
74+
{
75+
using (Image<TPixel> image = provider.GetImage(new BmpDecoder()))
76+
{
77+
image.DebugSave(provider, "png");
78+
image.CompareToOriginal(provider);
79+
}
80+
}
81+
5882
[Theory]
5983
[InlineData(Car, 24)]
6084
[InlineData(F, 24)]

tests/ImageSharp.Tests/TestImages.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,10 @@ public static class Bmp
201201
public const string Bit16 = "Bmp/test16.bmp";
202202
public const string Bit16Inverted = "Bmp/test16-inverted.bmp";
203203
public const string Bit32Rgb = "Bmp/rgb32.bmp";
204+
205+
// Note: This format can be called OS/2 BMPv1, or Windows BMPv2
206+
public const string WinBmpv2 = "Bmp/pal8os2v1_winv2.bmp";
207+
public const string Bit8Palette4 = "Bmp/pal8-0.bmp";
204208
public const string Os2v2Short = "Bmp/pal8os2v2-16.bmp";
205209

206210
public static readonly string[] All

tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@ internal static IImageFormat GetImageFormat(string filePath)
3434
{
3535
string extension = Path.GetExtension(filePath);
3636

37-
IImageFormat format = Configuration.ImageFormatsManager.FindFormatByFileExtension(extension);
38-
return format;
37+
return Configuration.ImageFormatsManager.FindFormatByFileExtension(extension);
3938
}
4039

4140
private static void ConfigureCodecs(
@@ -69,7 +68,7 @@ private static Configuration CreateDefaultConfiguration()
6968

7069
cfg.ConfigureCodecs(
7170
BmpFormat.Instance,
72-
SystemDrawingReferenceDecoder.Instance,
71+
IsWindows ? (IImageDecoder)SystemDrawingReferenceDecoder.Instance : MagickReferenceDecoder.Instance,
7372
bmpEncoder,
7473
new BmpImageFormatDetector());
7574

tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ public void GetReferenceEncoder_ReturnsCorrectEncoders_Linux(string fileName, Ty
127127

128128
[Theory]
129129
[InlineData("lol/foo.png", typeof(MagickReferenceDecoder))]
130-
[InlineData("lol/Rofl.bmp", typeof(SystemDrawingReferenceDecoder))]
130+
[InlineData("lol/Rofl.bmp", typeof(MagickReferenceDecoder))]
131131
[InlineData("lol/Baz.JPG", typeof(JpegDecoder))]
132132
[InlineData("lol/Baz.gif", typeof(GifDecoder))]
133133
public void GetReferenceDecoder_ReturnsCorrectDecoders_Linux(string fileName, Type expectedDecoderType)

tests/Images/Input/Bmp/pal8-0.bmp

9.05 KB
Binary file not shown.
8.78 KB
Binary file not shown.

0 commit comments

Comments
 (0)