From b3f772b8f60c7f99552e76a01f531ef83b5339c2 Mon Sep 17 00:00:00 2001 From: George Collins Date: Mon, 9 Oct 2017 22:05:47 +0100 Subject: [PATCH] Add RLE8 bmp decoding. Fix #323 --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 117 +++++++++++++++++++ tests/ImageSharp.Tests/TestImages.cs | 5 +- tests/Images/Input/Bmp/RunLengthEncoded.bmp | 3 + 3 files changed, 123 insertions(+), 2 deletions(-) create mode 100644 tests/Images/Input/Bmp/RunLengthEncoded.bmp 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..7e8d3acd48 --- /dev/null +++ b/tests/Images/Input/Bmp/RunLengthEncoded.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37d9fe070ed05309a4baa81acc1e63c690b5a706d238ca200034d17dd53c8bb5 +size 7278