From 0d68e6d09d23ce3c6fa6fbf9fe48af1ece53490e Mon Sep 17 00:00:00 2001
From: Brian Popow <38701097+brianpopow@users.noreply.github.com>
Date: Sun, 20 Jan 2019 07:54:47 +0100
Subject: [PATCH] Added support for RLE4 encoded bitmaps (#812)
---
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 132 +++++++++++++++++-
.../Formats/Bmp/BmpDecoderTests.cs | 2 +-
.../Formats/Bmp/BmpEncoderTests.cs | 2 +-
tests/ImageSharp.Tests/TestImages.cs | 10 +-
tests/Images/Input/Bmp/pal4rle.bmp | 3 +
5 files changed, 138 insertions(+), 11 deletions(-)
create mode 100644 tests/Images/Input/Bmp/pal4rle.bmp
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
index 362fe6443..8ca698b87 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
@@ -157,7 +157,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp
break;
case BmpCompression.RLE8:
- this.ReadRle8(pixels, palette, this.infoHeader.Width, this.infoHeader.Height, inverted);
+ case BmpCompression.RLE4:
+ this.ReadRle(this.infoHeader.Compression, pixels, palette, this.infoHeader.Width, this.infoHeader.Height, inverted);
break;
@@ -254,22 +255,31 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
///
- /// Looks up color values and builds the image from de-compressed RLE8 data.
+ /// Looks up color values and builds the image from de-compressed RLE8 or RLE4 data.
/// Compressed RLE8 stream is uncompressed by
+ /// Compressed RLE4 stream is uncompressed by
///
/// The pixel format.
+ /// The compression type. Either RLE4 or RLE8.
/// 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(Buffer2D pixels, byte[] colors, int width, int height, bool inverted)
+ private void ReadRle(BmpCompression compression, Buffer2D pixels, byte[] colors, int width, int height, bool inverted)
where TPixel : struct, IPixel
{
TPixel color = default;
using (Buffer2D buffer = this.memoryAllocator.Allocate2D(width, height, AllocationOptions.Clean))
{
- this.UncompressRle8(width, buffer.GetSpan());
+ if (compression == BmpCompression.RLE8)
+ {
+ this.UncompressRle8(width, buffer.GetSpan());
+ }
+ else
+ {
+ this.UncompressRle4(width, buffer.GetSpan());
+ }
for (int y = 0; y < height; y++)
{
@@ -287,12 +297,122 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
///
- /// Produce uncompressed bitmap data from RLE8 stream.
+ /// Produce uncompressed bitmap data from a RLE4 stream.
+ ///
+ ///
+ /// RLE4 is a 2-byte run-length encoding.
+ ///
If first byte is 0, the second byte may have special meaning.
+ ///
Otherwise, the first byte is the length of the run and second byte contains two color indexes.
+ ///
+ /// The width of the bitmap.
+ /// Buffer for uncompressed data.
+ private void UncompressRle4(int w, Span buffer)
+ {
+#if NETCOREAPP2_1
+ Span cmd = stackalloc byte[2];
+#else
+ byte[] cmd = new byte[2];
+#endif
+ int count = 0;
+
+ while (count < buffer.Length)
+ {
+ if (this.stream.Read(cmd, 0, cmd.Length) != 2)
+ {
+ throw new Exception("Failed to read 2 bytes from the stream");
+ }
+
+ if (cmd[0] == RleCommand)
+ {
+ switch (cmd[1])
+ {
+ case RleEndOfBitmap:
+ return;
+
+ case RleEndOfLine:
+ int extra = count % w;
+ if (extra > 0)
+ {
+ count += w - extra;
+ }
+
+ break;
+
+ case RleDelta:
+ int dx = this.stream.ReadByte();
+ int dy = this.stream.ReadByte();
+ count += (w * dy) + dx;
+
+ break;
+
+ default:
+ // If the second byte > 2, we are in 'absolute mode'.
+ // The second byte contains the number of color indexes that follow.
+ int max = cmd[1];
+ int bytesToRead = (max + 1) / 2;
+
+ byte[] run = new byte[bytesToRead];
+
+ this.stream.Read(run, 0, run.Length);
+
+ int idx = 0;
+ for (int i = 0; i < max; i++)
+ {
+ byte twoPixels = run[idx];
+ if (i % 2 == 0)
+ {
+ byte leftPixel = (byte)((twoPixels >> 4) & 0xF);
+ buffer[count++] = leftPixel;
+ }
+ else
+ {
+ byte rightPixel = (byte)(twoPixels & 0xF);
+ buffer[count++] = rightPixel;
+ idx++;
+ }
+ }
+
+ // Absolute mode data is aligned to two-byte word-boundary
+ int padding = bytesToRead & 1;
+
+ this.stream.Skip(padding);
+
+ break;
+ }
+ }
+ else
+ {
+ int max = cmd[0];
+
+ // The second byte contains two color indexes, one in its high-order 4 bits and one in its low-order 4 bits.
+ byte twoPixels = cmd[1];
+ byte rightPixel = (byte)(twoPixels & 0xF);
+ byte leftPixel = (byte)((twoPixels >> 4) & 0xF);
+
+ for (int idx = 0; idx < max; idx++)
+ {
+ if (idx % 2 == 0)
+ {
+ buffer[count] = leftPixel;
+ }
+ else
+ {
+ buffer[count] = rightPixel;
+ }
+
+ count++;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Produce uncompressed bitmap data from a 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.
+ ///
Otherwise, the first byte is the length of the run and second byte is the color for the run.
///
/// The width of the bitmap.
/// Buffer for uncompressed data.
diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
index 60e45ae35..0ebfbf311 100644
--- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
@@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests
{
{ TestImages.Bmp.Car, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter },
{ TestImages.Bmp.V5Header, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter },
- { TestImages.Bmp.RLE, 2835, 2835, PixelResolutionUnit.PixelsPerMeter }
+ { TestImages.Bmp.RLE8, 2835, 2835, PixelResolutionUnit.PixelsPerMeter }
};
[Theory]
diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
index b9f855cf1..f818be8a9 100644
--- a/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
@@ -25,7 +25,7 @@ namespace SixLabors.ImageSharp.Tests
{
{ TestImages.Bmp.Car, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter },
{ TestImages.Bmp.V5Header, 3780, 3780 , PixelResolutionUnit.PixelsPerMeter },
- { TestImages.Bmp.RLE, 2835, 2835, PixelResolutionUnit.PixelsPerMeter }
+ { TestImages.Bmp.RLE8, 2835, 2835, PixelResolutionUnit.PixelsPerMeter }
};
public static readonly TheoryData BmpBitsPerPixelFiles =
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 5ecc26657..6e6c7ce47 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -195,7 +195,8 @@ 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 const string RLE = "Bmp/RunLengthEncoded.bmp";
+ public const string RLE8 = "Bmp/RunLengthEncoded.bmp";
+ public const string RLE4 = "Bmp/pal4rle.bmp";
public const string RLEInverted = "Bmp/RunLengthEncoded-inverted.bmp";
public const string Bit1 = "Bmp/pal1.bmp";
public const string Bit1Pal1 = "Bmp/pal1p1.bmp";
@@ -209,6 +210,7 @@ namespace SixLabors.ImageSharp.Tests
// Note: This format can be called OS/2 BMPv1, or Windows BMPv2
public const string WinBmpv2 = "Bmp/pal8os2v1_winv2.bmp";
+
public const string WinBmpv3 = "Bmp/rgb24.bmp";
public const string WinBmpv4 = "Bmp/pal8v4.bmp";
public const string WinBmpv5 = "Bmp/pal8v5.bmp";
@@ -218,8 +220,8 @@ namespace SixLabors.ImageSharp.Tests
// Bitmap images with compression type BITFIELDS
public const string Rgb32bfdef = "Bmp/rgb32bfdef.bmp";
public const string Rgb32bf = "Bmp/rgb32bf.bmp";
- public const string Rgb16565 = "Bmp/rgb16-565.bmp";
public const string Rgb16bfdef = "Bmp/rgb16bfdef.bmp";
+ public const string Rgb16565 = "Bmp/rgb16-565.bmp";
public const string Rgb16565pal = "Bmp/rgb16-565pal.bmp";
public const string Issue735 = "Bmp/issue735.bmp";
public const string Rgba32bf56 = "Bmp/rgba32h56.bmp";
@@ -241,7 +243,9 @@ namespace SixLabors.ImageSharp.Tests
F,
NegHeight,
CoreHeader,
- V5Header, RLE,
+ V5Header,
+ RLE4,
+ RLE8,
RLEInverted,
Bit1,
Bit1Pal1,
diff --git a/tests/Images/Input/Bmp/pal4rle.bmp b/tests/Images/Input/Bmp/pal4rle.bmp
new file mode 100644
index 000000000..b83a2a98d
--- /dev/null
+++ b/tests/Images/Input/Bmp/pal4rle.bmp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3c26b6bdd22311e03b1ac0713b0a3b6af5e579bef16694cbea74ae0c4a2599e4
+size 3836