diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
index 625b2a3a83..55d21cc47e 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
@@ -29,6 +29,11 @@ namespace SixLabors.ImageSharp.Formats.Bmp
///
private const int Rgb16BMask = 0x0000001F;
+ private const int RleCommand = 0x00;
+ private const int RleEndOfLine = 0x00;
+ private const int RleEndOfBitmap = 0x01;
+ private const int RleDelta = 0x02;
+
///
/// The stream to decode from.
///
@@ -151,6 +156,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.ReadRgbPalette(pixels, palette, this.infoHeader.Width, this.infoHeader.Height, this.infoHeader.BitsPerPixel, inverted);
}
+ break;
+ case BmpCompression.RLE8:
+ this.ReadRle8(pixels, palette, this.infoHeader.Width, this.infoHeader.Height, inverted);
+
break;
default:
throw new NotSupportedException("Does not support this kind of bitmap files.");
@@ -208,6 +217,114 @@ namespace SixLabors.ImageSharp.Formats.Bmp
return padding;
}
+ ///
+ /// Looks up color values and builds the image from de-compressed RLE8 data.
+ /// Compresssed RLE8 stream is uncompressed by
+ ///
+ /// The pixel format.
+ /// The to assign the palette to.
+ /// The containing the colors.
+ /// The width of the bitmap.
+ /// The height of the bitmap.
+ /// Whether the bitmap is inverted.
+ private void ReadRle8(PixelAccessor pixels, byte[] colors, int width, int height, bool inverted)
+ where TPixel : struct, IPixel
+ {
+ var color = default(TPixel);
+ var rgba = default(Rgba32);
+ byte[] data = this.UncompressRle8(width, height);
+
+ for (int y = 0; y < height; y++)
+ {
+ int newY = Invert(y, height, inverted);
+ Span pixelRow = pixels.GetRowSpan(newY);
+ for (int x = 0; x < width; x++)
+ {
+ rgba.Bgr = Unsafe.As(ref colors[data[(y * width) + x] * 4]);
+ color.PackFromRgba32(rgba);
+ pixelRow[x] = color;
+ }
+ }
+ }
+
+ ///
+ /// Produce uncompressed bitmap data from RLE8 stream
+ ///
+ ///
+ /// RLE8 is a 2-byte run-length encoding
+ ///
If first byte is 0, the second byte may have special meaning
+ ///
Otherwise, first byte is the length of the run and second byte is the color for the run
+ ///
+ /// The width of the bitmap.
+ /// The height of the bitmap.
+ /// The uncompressed data.
+ private byte[] UncompressRle8(int w, int h)
+ {
+ byte[] cmd = new byte[2];
+ byte[] data = new byte[w * h];
+ int count = 0;
+
+ while (count < w * h)
+ {
+ if (this.currentStream.Read(cmd, 0, cmd.Length) != 2)
+ {
+ throw new Exception("Failed to read 2 bytes from stream");
+ }
+
+ if (cmd[0] == RleCommand)
+ {
+ switch (cmd[1])
+ {
+ case RleEndOfBitmap:
+ return data;
+
+ case RleEndOfLine:
+ int extra = count % w;
+ if (extra > 0)
+ {
+ count += w - extra;
+ }
+
+ break;
+
+ case RleDelta:
+ int dx = this.currentStream.ReadByte();
+ int dy = this.currentStream.ReadByte();
+ count += (w * dy) + dx;
+
+ break;
+
+ default:
+ // If the second byte > 2, signals 'absolute mode'
+ // Take this number of bytes from the stream as uncompressed data
+ int length = cmd[1];
+ int copyLength = length;
+
+ // Absolute mode data is aligned to two-byte word-boundary
+ length += length & 1;
+
+ byte[] run = new byte[length];
+ this.currentStream.Read(run, 0, run.Length);
+ for (int i = 0; i < copyLength; i++)
+ {
+ data[count++] = run[i];
+ }
+
+ break;
+ }
+ }
+ else
+ {
+ for (int i = 0; i < cmd[0]; i++)
+ {
+ data[count++] = cmd[1];
+ }
+ }
+ }
+
+ return data;
+ }
+
///
/// Reads the color palette from the stream.
///
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index dbcacb4f37..3fa07819f7 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -133,8 +133,9 @@ namespace SixLabors.ImageSharp.Tests
public const string NegHeight = "Bmp/neg_height.bmp";
public const string CoreHeader = "Bmp/BitmapCoreHeaderQR.bmp";
public const string V5Header = "Bmp/BITMAPV5HEADER.bmp";
-
- public static readonly string[] All = { Car, F, NegHeight, CoreHeader, V5Header };
+ public const string RLE = "Bmp/RunLengthEncoded.bmp";
+
+ public static readonly string[] All = { Car, F, NegHeight, CoreHeader, V5Header, RLE };
}
public static class Gif
diff --git a/tests/Images/Input/Bmp/RunLengthEncoded.bmp b/tests/Images/Input/Bmp/RunLengthEncoded.bmp
new file mode 100644
index 0000000000..4c2397988d
Binary files /dev/null and b/tests/Images/Input/Bmp/RunLengthEncoded.bmp differ