From f154ed2195ef2eb709d15a701b0d0045831cd410 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 | Bin 0 -> 7278 bytes 3 files changed, 120 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 0000000000000000000000000000000000000000..4c2397988d91cfbdffe88c0c2bcba261ac1a4f61 GIT binary patch literal 7278 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 literal 0 HcmV?d00001