Skip to content

Commit f9f7a56

Browse files
brianpopowJimBobSquarePants
authored andcommitted
Add support for decoding RLE24 Bitmaps (#939)
* Add support for decoding RLE24 * Simplified determining colorMapSize, OS/2 always has 3 bytes for each palette entry * Enum value for RLE24 is remapped to a different value, to be clearly separate from valid windows values.
1 parent 692e244 commit f9f7a56

File tree

10 files changed

+284
-34
lines changed

10 files changed

+284
-34
lines changed

src/ImageSharp/Formats/Bmp/BmpCompression.cs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
// Copyright (c) Six Labors and contributors.
1+
// Copyright (c) Six Labors and contributors.
22
// Licensed under the Apache License, Version 2.0.
33

44
namespace SixLabors.ImageSharp.Formats.Bmp
55
{
66
/// <summary>
7-
/// Defines how the compression type of the image data
7+
/// Defines the compression type of the image data
88
/// in the bitmap file.
99
/// </summary>
1010
internal enum BmpCompression : int
@@ -21,7 +21,7 @@ internal enum BmpCompression : int
2121

2222
/// <summary>
2323
/// Two bytes are one data record. If the first byte is not zero, the
24-
/// next two half bytes will be repeated as much as the value of the first byte.
24+
/// next byte will be repeated as much as the value of the first byte.
2525
/// If the first byte is zero, the record has different meanings, depending
2626
/// on the second byte. If the second byte is zero, it is the end of the row,
2727
/// if it is one, it is the end of the image.
@@ -30,7 +30,7 @@ internal enum BmpCompression : int
3030

3131
/// <summary>
3232
/// Two bytes are one data record. If the first byte is not zero, the
33-
/// next byte will be repeated as much as the value of the first byte.
33+
/// next two half bytes will be repeated as much as the value of the first byte.
3434
/// If the first byte is zero, the record has different meanings, depending
3535
/// on the second byte. If the second byte is zero, it is the end of the row,
3636
/// if it is one, it is the end of the image.
@@ -60,6 +60,17 @@ internal enum BmpCompression : int
6060
/// Specifies that the bitmap is not compressed and that the color table consists of four DWORD color
6161
/// masks that specify the red, green, blue, and alpha components of each pixel.
6262
/// </summary>
63-
BI_ALPHABITFIELDS = 6
63+
BI_ALPHABITFIELDS = 6,
64+
65+
/// <summary>
66+
/// OS/2 specific compression type.
67+
/// Similar to run length encoding of 4 and 8 bit.
68+
/// The only difference is that run values encoded are three bytes in size (one byte per RGB color component),
69+
/// rather than four or eight bits in size.
70+
///
71+
/// Note: Because compression value of 4 is ambiguous for BI_RGB for windows and RLE24 for OS/2, the enum value is remapped
72+
/// to a different value.
73+
/// </summary>
74+
RLE24 = 100,
6475
}
65-
}
76+
}

src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

Lines changed: 200 additions & 27 deletions
Large diffs are not rendered by default.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright (c) Six Labors and contributors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
namespace SixLabors.ImageSharp.Formats.Bmp
5+
{
6+
/// <summary>
7+
/// Indicates which bitmap file marker was read.
8+
/// </summary>
9+
public enum BmpFileMarkerType
10+
{
11+
/// <summary>
12+
/// Single-image BMP file that may have been created under Windows or OS/2.
13+
/// </summary>
14+
Bitmap,
15+
16+
/// <summary>
17+
/// OS/2 Bitmap Array.
18+
/// </summary>
19+
BitmapArray,
20+
21+
/// <summary>
22+
/// OS/2 Color Icon.
23+
/// </summary>
24+
ColorIcon,
25+
26+
/// <summary>
27+
/// OS/2 Color Pointer.
28+
/// </summary>
29+
ColorPointer,
30+
31+
/// <summary>
32+
/// OS/2 Icon.
33+
/// </summary>
34+
Icon,
35+
36+
/// <summary>
37+
/// OS/2 Pointer.
38+
/// </summary>
39+
Pointer
40+
}
41+
}

src/ImageSharp/Formats/Bmp/BmpInfoHeader.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,8 +388,12 @@ public static BmpInfoHeader ParseOs2Version2(ReadOnlySpan<byte> data)
388388
case 2:
389389
infoHeader.Compression = BmpCompression.RLE4;
390390
break;
391+
case 4:
392+
infoHeader.Compression = BmpCompression.RLE24;
393+
break;
391394
default:
392-
BmpThrowHelper.ThrowImageFormatException($"Compression type is not supported. ImageSharp only supports uncompressed, RLE4 and RLE8.");
395+
// Compression type 3 (1DHuffman) is not supported.
396+
BmpThrowHelper.ThrowImageFormatException("Compression type is not supported. ImageSharp only supports uncompressed, RLE4, RLE8 and RLE24.");
393397
break;
394398
}
395399

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,22 @@ public void BmpDecoder_CanDecode_RunLengthEncoded_8Bit<TPixel>(TestImageProvider
228228
}
229229
}
230230

231+
[Theory]
232+
[WithFile(RLE24, PixelTypes.Rgba32)]
233+
[WithFile(RLE24Cut, PixelTypes.Rgba32)]
234+
[WithFile(RLE24Delta, PixelTypes.Rgba32)]
235+
public void BmpDecoder_CanDecode_RunLengthEncoded_24Bit<TPixel>(TestImageProvider<TPixel> provider)
236+
where TPixel : struct, IPixel<TPixel>
237+
{
238+
using (Image<TPixel> image = provider.GetImage(new BmpDecoder() { RleSkippedPixelHandling = RleSkippedPixelHandling.Black }))
239+
{
240+
image.DebugSave(provider);
241+
242+
// TODO: Neither System.Drawing nor MagickReferenceDecoder decode this file.
243+
// image.CompareToOriginal(provider);
244+
}
245+
}
246+
231247
[Theory]
232248
[WithFile(RgbaAlphaBitfields, PixelTypes.Rgba32)]
233249
public void BmpDecoder_CanDecodeAlphaBitfields<TPixel>(TestImageProvider<TPixel> provider)
@@ -521,6 +537,7 @@ public void BmpDecoder_CanDecode_Os2v2Header<TPixel>(TestImageProvider<TPixel> p
521537
}
522538

523539
[Theory]
540+
[WithFile(Os2BitmapArray, PixelTypes.Rgba32)]
524541
[WithFile(Os2BitmapArray9s, PixelTypes.Rgba32)]
525542
[WithFile(Os2BitmapArrayDiamond, PixelTypes.Rgba32)]
526543
[WithFile(Os2BitmapArraySkater, PixelTypes.Rgba32)]

tests/ImageSharp.Tests/TestImages.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,9 @@ public static class Bmp
231231
public const string NegHeight = "Bmp/neg_height.bmp";
232232
public const string CoreHeader = "Bmp/BitmapCoreHeaderQR.bmp";
233233
public const string V5Header = "Bmp/BITMAPV5HEADER.bmp";
234+
public const string RLE24 = "Bmp/rgb24rle24.bmp";
235+
public const string RLE24Cut = "Bmp/rle24rlecut.bmp";
236+
public const string RLE24Delta = "Bmp/rle24rlecut.bmp";
234237
public const string RLE8 = "Bmp/RunLengthEncoded.bmp";
235238
public const string RLE8Cut = "Bmp/pal8rlecut.bmp";
236239
public const string RLE8Delta = "Bmp/pal8rletrns.bmp";
@@ -262,6 +265,7 @@ public static class Bmp
262265
public const string Bit8Palette4 = "Bmp/pal8-0.bmp";
263266
public const string Os2v2Short = "Bmp/pal8os2v2-16.bmp";
264267
public const string Os2v2 = "Bmp/pal8os2v2.bmp";
268+
public const string Os2BitmapArray = "Bmp/ba-bm.bmp";
265269
public const string Os2BitmapArray9s = "Bmp/9S.BMP";
266270
public const string Os2BitmapArrayDiamond = "Bmp/DIAMOND.BMP";
267271
public const string Os2BitmapArrayMarble = "Bmp/GMARBLE.BMP";

tests/Images/Input/Bmp/ba-bm.bmp

8.79 KB
Binary file not shown.
20.9 KB
Binary file not shown.
16.4 KB
Binary file not shown.
19.6 KB
Binary file not shown.

0 commit comments

Comments
 (0)