From 1b9979c9ef1c37c37b7da8aad5693fc4cfa8c97d 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 | Bin 0 -> 3836 bytes 5 files changed, 135 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 362fe64430..8ca698b878 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 60e45ae35f..0ebfbf311a 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 b9f855cf12..f818be8a91 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 5ecc266572..6e6c7ce47a 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 0000000000000000000000000000000000000000..a5672aebd6247351172b0f602197611554de8e1f GIT binary patch literal 3836 zcmb`J(NE*p6~@2V_&Sbn3u>v1^8d9M89K`5N3ASRxOtsJJ-JFoZtD*y*Jx`{#Oy{ zUyq-4_V3xZ*mGEb#_I{6m+h=SCV^N!PG9RA=}4?nNrDk%iD$7r4`q7HBV{MVR({Ia>8~~-$H5udLK)s(8_qz zec;W{5of%#T^M*7u>%z7b;c~uTHaICB~pj<90$f4HskU6xho^m`qEC14$?Q8Zrn6# z_$J=%c0+fr1)-a9K-HP0D@Z2zgotKMTpp~h6%UIYEc8z^&F{5UdzL280u@S!9)$m$y2{hE zhn{0iq5MeOylAUjE)j~y(q(3N~tI>JZEc4hr{$!oL^0WatUX-YvR9rNO#}h1rwWEijNjbvP(*p#&(?gf}T&wvt zzq{vI((2Pj6SA{R*X9bPaG3(Wua1w7u?Vh2V8PopyFvj2rBp2|s}{AUVJXomM;IdE zB#Y`PuFS;*ZquId-5PAJi!ch5rPZ^Jh59}3bbV5g1WU{{jabJ=;GV$&&$TCd<#(>p zeIc|9o<#6!N*oI@U3>w2n@qSU6J*G8!~~vB!tlusXjwu zRmYiMP5jnsP42Ve(Is<}8;ztmKC>rud%odqem^Atq=w;>E8 zdUq1m4hL#3cj8Rz64PVoD6_gEqBGgd^{<%Sh?62Jejs~r#$Cc!joGSvCVMUtISQ#_ zBxXlpA$?agUBWZ!snYWx<$GBwv5K3a8`IB3Lg5J(WnKsw(4Fk0OH3G-1XRR*Anmze zrYBEJfzUv^MNSwYAsIswt~3yq>6ad7m-0>MQJD9!kjA(`vkfXrCpH-~rhxaPHCS2r z@}7*RXI{8C{tdZ2&Y1lR7t=*s^uI*@tWiAfHKk^Wbj>JKKK{rlKYCTq7yTQY#mfo1 zJ+bn`&bSKh zQ$D9?F%r?_;J}_F5L6U=e^RE3b8Wvnrdl z{VpGAb=ofe6m=}r%rvv}o0K?#KRQu_&4@X!I0Uny#Z)ymB1Z6v0c0Um>OTF*DlaAz zEzP+a@fV52kXGzerIM(_tj{Z~Ndej*?*z)%P7DP*ut_ZAQV&A=a^^~CrOJxj_FPyI zj1iQgD>^gkZd!p~@!375Tsi-dzAG?%u}tQr52WP&KwY`7{<`7MHBPg0def+|m|HQP zOV#jri(rmy#cU0pqm-?YPqZxh+K*Nhdc3tX<>BcYg&g1enlxq(8)&v#Uet<)@$Kz+ z+sj()#iMw1!&6q_C4#}giw03UzPj2CsoY9LnETGy9+={gm7#w;%WuPsx8tp%kClvz zzdP)P)Qz{lKSw5%_jIPv#n&)ym#apsKG;eR%({$Sz_|LtgPkVQHR=zV3EyJXiAAe? zAnR1fjQiS<`cP4hYZ9)Lb8=<9U1?V~8Xx@R<8l=$zKLPn;7>i(Q-}FseOP}j&-UxA za?8!4EKzL7FmGV=dVJo~17pbN!|UXV&+SZA zec)Ot^>mH?yslW-^QUV=u_WUtWcbR#A9Cd~;oHRDOg?t1S=*`Z_=%r%dz}}5L1(yo^J|3n z+`ehw68j_84DCZ*CYQXk=))2pBi2`f)kkPvL*Tx5O6#S!&9}{W(M5a_C%2vL&i19W zSXy$^JNV(?BsdA)N7wOnoLuqrnQu(9P484!y=z~WDbICg)q^j!u@S5NF%cMa{ zJz^zmk>W3vi2H)!z|qSu%GSo_CePFh=>Ej$z^Zn3I{bdA`D!N}>%qw%es^fSe)ERo z*E|9ezhU~c6icUF*1P0^krdvwvrIT06d21a7mTLxe)yBwsHfQ|JI=cEcKz-8MOfJ6 kubxT1!yr1BTg4r$B>(Ik_P%2fe|wF>8+b|b*^18n8}acBDF6Tf literal 0 HcmV?d00001