Browse Source

Added support for RLE4 encoded bitmaps (#812)

af/merge-core
Brian Popow 7 years ago
committed by James Jackson-South
parent
commit
0d68e6d09d
  1. 132
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  2. 2
      tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
  3. 2
      tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
  4. 10
      tests/ImageSharp.Tests/TestImages.cs
  5. 3
      tests/Images/Input/Bmp/pal4rle.bmp

132
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
}
/// <summary>
/// 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 <see cref="UncompressRle8(int, Span{byte})"/>
/// Compressed RLE4 stream is uncompressed by <see cref="UncompressRle4(int, Span{byte})"/>
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="compression">The compression type. Either RLE4 or RLE8.</param>
/// <param name="pixels">The <see cref="Buffer2D{TPixel}"/> to assign the palette to.</param>
/// <param name="colors">The <see cref="T:byte[]"/> containing the colors.</param>
/// <param name="width">The width of the bitmap.</param>
/// <param name="height">The height of the bitmap.</param>
/// <param name="inverted">Whether the bitmap is inverted.</param>
private void ReadRle8<TPixel>(Buffer2D<TPixel> pixels, byte[] colors, int width, int height, bool inverted)
private void ReadRle<TPixel>(BmpCompression compression, Buffer2D<TPixel> pixels, byte[] colors, int width, int height, bool inverted)
where TPixel : struct, IPixel<TPixel>
{
TPixel color = default;
using (Buffer2D<byte> buffer = this.memoryAllocator.Allocate2D<byte>(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
}
/// <summary>
/// Produce uncompressed bitmap data from RLE8 stream.
/// Produce uncompressed bitmap data from a RLE4 stream.
/// </summary>
/// <remarks>
/// RLE4 is a 2-byte run-length encoding.
/// <br/>If first byte is 0, the second byte may have special meaning.
/// <br/>Otherwise, the first byte is the length of the run and second byte contains two color indexes.
/// </remarks>
/// <param name="w">The width of the bitmap.</param>
/// <param name="buffer">Buffer for uncompressed data.</param>
private void UncompressRle4(int w, Span<byte> buffer)
{
#if NETCOREAPP2_1
Span<byte> 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++;
}
}
}
}
/// <summary>
/// Produce uncompressed bitmap data from a RLE8 stream.
/// </summary>
/// <remarks>
/// RLE8 is a 2-byte run-length encoding.
/// <br/>If first byte is 0, the second byte may have special meaning.
/// <br/>Otherwise, first byte is the length of the run and second byte is the color for the run.
/// <br/>Otherwise, the first byte is the length of the run and second byte is the color for the run.
/// </remarks>
/// <param name="w">The width of the bitmap.</param>
/// <param name="buffer">Buffer for uncompressed data.</param>

2
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]

2
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<string, BmpBitsPerPixel> BmpBitsPerPixelFiles =

10
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,

3
tests/Images/Input/Bmp/pal4rle.bmp

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3c26b6bdd22311e03b1ac0713b0a3b6af5e579bef16694cbea74ae0c4a2599e4
size 3836
Loading…
Cancel
Save