From ca01fae47c0884d86a5c9188743201c3a0f593d2 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 11 Oct 2017 23:38:02 +1100 Subject: [PATCH 1/5] Ensure can read palette bitmaps --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 4fda80d72..21d7d8756 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -148,7 +148,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp + $"bigger then the max allowed size '{int.MaxValue}x{int.MaxValue}'"); } - Image image = new Image(this.configuration, this.infoHeader.Width, this.infoHeader.Height); + var image = new Image(this.configuration, this.infoHeader.Width, this.infoHeader.Height); using (PixelAccessor pixels = image.Lock()) { switch (this.infoHeader.Compression) @@ -196,6 +196,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// The height of the bitmap. /// Whether the bitmap is inverted. /// The representing the inverted value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int Invert(int y, int height, bool inverted) { int row; @@ -369,16 +370,16 @@ namespace SixLabors.ImageSharp.Formats.Bmp } byte[] row = new byte[arrayWidth + padding]; - TPixel color = default(TPixel); + var color = default(TPixel); - Rgba32 rgba = default(Rgba32); + var rgba = default(Rgba32); for (int y = 0; y < height; y++) { int newY = Invert(y, height, inverted); this.currentStream.Read(row, 0, row.Length); int offset = 0; - Span pixelRow = pixels.GetRowSpan(y); + Span pixelRow = pixels.GetRowSpan(newY); // TODO: Could use PixelOperations here! for (int x = 0; x < arrayWidth; x++) @@ -417,10 +418,10 @@ namespace SixLabors.ImageSharp.Formats.Bmp const int ScaleG = 4; // 256/64 const int ComponentCount = 2; - TPixel color = default(TPixel); - Rgba32 rgba = new Rgba32(0, 0, 0, 255); + var color = default(TPixel); + var rgba = new Rgba32(0, 0, 0, 255); - using (PixelArea row = new PixelArea(width, ComponentOrder.Xyz)) + using (var row = new PixelArea(width, ComponentOrder.Xyz)) { for (int y = 0; y < height; y++) { @@ -459,7 +460,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp where TPixel : struct, IPixel { int padding = CalculatePadding(width, 3); - using (PixelArea row = new PixelArea(width, ComponentOrder.Zyx, padding)) + using (var row = new PixelArea(width, ComponentOrder.Zyx, padding)) { for (int y = 0; y < height; y++) { @@ -483,7 +484,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp where TPixel : struct, IPixel { int padding = CalculatePadding(width, 4); - using (PixelArea row = new PixelArea(width, ComponentOrder.Zyxw, padding)) + using (var row = new PixelArea(width, ComponentOrder.Zyxw, padding)) { for (int y = 0; y < height; y++) { From 9d0e16f92f0bfda0c9e832a82ca70d83d1b2d3a0 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 12 Oct 2017 07:40:32 +1100 Subject: [PATCH 2/5] Add optimisation from #367 --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 21d7d8756..db9e90adf 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -235,7 +235,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// Looks up color values and builds the image from de-compressed RLE8 data. - /// Compresssed RLE8 stream is uncompressed by + /// Compresssed RLE8 stream is uncompressed by /// /// The pixel format. /// The to assign the palette to. @@ -249,17 +249,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp var color = default(TPixel); var rgba = default(Rgba32); - using (var buffer = Buffer.CreateClean(width * height)) + using (var buffer = Buffer2D.CreateClean(width, height)) { this.UncompressRle8(width, buffer); for (int y = 0; y < height; y++) { int newY = Invert(y, height, inverted); + Span bufferRow = buffer.GetRowSpan(y); Span pixelRow = pixels.GetRowSpan(newY); + for (int x = 0; x < width; x++) { - rgba.Bgr = Unsafe.As(ref colors[buffer[(y * width) + x] * 4]); + rgba.Bgr = Unsafe.As(ref colors[bufferRow[x] * 4]); color.PackFromRgba32(rgba); pixelRow[x] = color; } @@ -277,7 +279,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp /// /// The width of the bitmap. /// Buffer for uncompressed data. - private void UncompressRle8(int w, Buffer buffer) + private void UncompressRle8(int w, Span buffer) { byte[] cmd = new byte[2]; int count = 0; From 36658e13f9fb9eee5979055312c87b30eb23d3b8 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 12 Oct 2017 08:08:27 +1100 Subject: [PATCH 3/5] Fix #369 and update test --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 2 +- tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index db9e90adf..e962d6df6 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -247,7 +247,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp where TPixel : struct, IPixel { var color = default(TPixel); - var rgba = default(Rgba32); + var rgba = new Rgba32(0, 0, 0, 255); using (var buffer = Buffer2D.CreateClean(width, height)) { diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index 8a50b760e..8f4b05108 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -13,7 +13,7 @@ namespace SixLabors.ImageSharp.Tests public class BmpDecoderTests : FileTestBase { [Theory] - [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgb24)] + [WithFileCollection(nameof(AllBmpFiles), PixelTypes.Rgba32)] public void DecodeBmp(TestImageProvider provider) where TPixel : struct, IPixel { From cce1bf93d4dec21e2a54e4a83791d1a8e3cbf1e9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 12 Oct 2017 09:48:45 +1100 Subject: [PATCH 4/5] Fix alpha channel and add inverted test --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 45 ++++++++++--------- tests/ImageSharp.Tests/TestImages.cs | 4 +- tests/Images/Input/Bmp/test8-inverted.bmp | Bin 0 -> 9270 bytes tests/Images/Input/Bmp/test8.bmp | Bin 0 -> 9270 bytes 4 files changed, 26 insertions(+), 23 deletions(-) create mode 100644 tests/Images/Input/Bmp/test8-inverted.bmp create mode 100644 tests/Images/Input/Bmp/test8.bmp diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index e962d6df6..39bab442f 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -371,35 +371,36 @@ namespace SixLabors.ImageSharp.Formats.Bmp padding = 4 - padding; } - byte[] row = new byte[arrayWidth + padding]; - var color = default(TPixel); - - var rgba = default(Rgba32); - - for (int y = 0; y < height; y++) + using (var row = Buffer.CreateClean(arrayWidth + padding)) { - int newY = Invert(y, height, inverted); - this.currentStream.Read(row, 0, row.Length); - int offset = 0; - Span pixelRow = pixels.GetRowSpan(newY); + var color = default(TPixel); + var rgba = new Rgba32(0, 0, 0, 255); - // TODO: Could use PixelOperations here! - for (int x = 0; x < arrayWidth; x++) + for (int y = 0; y < height; y++) { - int colOffset = x * ppb; + int newY = Invert(y, height, inverted); + this.currentStream.Read(row.Array, 0, row.Length); + int offset = 0; + Span pixelRow = pixels.GetRowSpan(newY); - for (int shift = 0; shift < ppb && (x + shift) < width; shift++) + // TODO: Could use PixelOperations here! + for (int x = 0; x < arrayWidth; x++) { - int colorIndex = ((row[offset] >> (8 - bits - (shift * bits))) & mask) * 4; - int newX = colOffset + shift; + int colOffset = x * ppb; - // Stored in b-> g-> r order. - rgba.Bgr = Unsafe.As(ref colors[colorIndex]); - color.PackFromRgba32(rgba); - pixelRow[newX] = color; - } + for (int shift = 0; shift < ppb && (x + shift) < width; shift++) + { + int colorIndex = ((row[offset] >> (8 - bits - (shift * bits))) & mask) * 4; + int newX = colOffset + shift; - offset++; + // Stored in b-> g-> r order. + rgba.Bgr = Unsafe.As(ref colors[colorIndex]); + color.PackFromRgba32(rgba); + pixelRow[newX] = color; + } + + offset++; + } } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 948f519d4..4a0d248e4 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -135,8 +135,10 @@ namespace SixLabors.ImageSharp.Tests public const string CoreHeader = "Bmp/BitmapCoreHeaderQR.bmp"; public const string V5Header = "Bmp/BITMAPV5HEADER.bmp"; public const string RLE = "Bmp/RunLengthEncoded.bmp"; + public const string Bit8 = "Bmp/test8.bmp"; + public const string Bit8Inverted = "Bmp/test8-inverted.bmp"; - public static readonly string[] All = { Car, F, NegHeight, CoreHeader, V5Header, RLE }; + public static readonly string[] All = { Car, F, NegHeight, CoreHeader, V5Header, RLE, Bit8, Bit8Inverted }; } public static class Gif diff --git a/tests/Images/Input/Bmp/test8-inverted.bmp b/tests/Images/Input/Bmp/test8-inverted.bmp new file mode 100644 index 0000000000000000000000000000000000000000..b0909ae6e5a7dcfc7482797ff25dde5fe1105219 GIT binary patch literal 9270 zcmeI&jaOx37zOY>gb+dq;Ua_(k`QHt(2$Q1LI@#*5HdmtA%qY@K0*i~WP}hx2qAr6nRPEec5Dv)Rm?$;rtXh@N^O zH1)9X_o%2SL`O#>CME{4v9XAYi$i>TJQ5NT5DJBmn3#y9q$DIKCnF^#1*xg2NKa2k zMn(p*va*n!osFEF9OUNaA}=ow`T6-MC@4T-VIhi&icnl!jFOTPl$MsFtgH;><>jcT zs6b_9C90~bP+eV(nwlEa*4Cn~t`7C}^=N2lKx1Pgnwpx>+}w)oV=NQL#4`y@hzTY(n9*b=g-K=7nG7b2$!2nxTqcjnX9}1? zridwKN|;inj45X-m`bLKsb*@JTBeSvXBwDBrip20T9{U*jcI2(m`1KMEUZ#)f zX9k!-W{4SPMwn4%j2UMpnCbEVKm7mfyU)+B^sF(*$kec-!Oyt#oUwz3nZ`2)&p*=k z8g??CHl83oeiGnr5BBDHSA(MY4F-3ZKGjV;|b#>gV$*#xNb|2 z8!sDMYuL@;wO)F~NYk*p@tDDNhqRT3J&Z?y0L|Z zJ&lJAt~sU6HSA?PWV~T;y{-i7b?HIlO=B|+dmCKyOK%yQYS_nkz~KFcw26j&jr)za z4c@aU*w47nc*odS3+@#q-uFrG8XIZY-?-P{y{5FGh69XyjQ0%Qw<|c%xZ8N&*gy;F z1kzo`2gdpu4l-DKkXUDs*3)pXai{U2!PSYMRZ&~T)2v+=pX8m@w) zjGK%vjMcTEHZ0v}d}*wv;b?<(Xo*rnk|ZxY+pJSVChDdLi5xu%L^KKa9mS z9z2s@nVI1P&(Dg)y$KTc5lD+^)5j0bLGXtFXGglw_|xFt4rSi>r{*9y{yC8@F#a+Y z)#i_%hYpXQn?e?JzVWxQh&KPdpZ7m-ZY1tck^V7eXu+T(&5m@Q!96Te1Sl&`7a=XAy zOiT=7V`C8)7l-)xcqAkwATco!Nl8fvg+fS9PDV;f3Q|*3k(QQ*^z?LOW@aKQD+}Rp z7&$pP$j!|~US1yZ^Yc+qP=Lb1LKGDhp}4phB_$;&EiFY^SsBX9%TZBLfy&BCR8>`> zy1E)QH8rTMtwmj39qQ}r(a_L<#>Pf8H8r8Rxfv}jEog0RMO#}N+S}XF(b0j<&Q5f7 zb)mbv8$CTe=QnvRIhquw0hM@>u~ZWJRo)m9SD) z#>!a*t7KKIn$@scR>$gD18ZbWteLg2R@TPaSqJN6U96k+uwK^3`q=;*WJ7G2jj&NR z#>UwMn`G15|NrLyXP0yMq0VJB{~@^|WA}K)Su5O8xZU``;M$FXgN)mZ4~@08V2wz+)%eI*OT)ni*PkS=F-dD`IK;Tc z_}Jk3oq|J+n~hJ5HMC%TP`b(Z)L31^VFuSAC9W?@t7$mgxY78`;2N%iBa9o2&y7{J zU~O2s-uS{;MZ=K>*P$h@ElVqFILf%r_|o7yzJjBTYmKjrm9$_kAYEg8ZLFx_7=yD2 ziE{;M1r5g)>vK(W!MVPI(~S#_-;BjH&Y=H=7Y5Al0^@gMQH>9t&9Ch2 zNWuLxqVd`UiPsTGi)ho2AK3@N9|D{i?R?`8gV%N_bGQGWeGokUS<%ij{xlZW=50S8 xIG(;N!lc*y_& literal 0 HcmV?d00001 From 9b70d90088f183c5dfa867844dcb1dd012831f72 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 12 Oct 2017 10:15:32 +1100 Subject: [PATCH 5/5] Add RLE inverted test image --- tests/ImageSharp.Tests/TestImages.cs | 5 ++++- .../Input/Bmp/RunLengthEncoded-inverted.bmp | Bin 0 -> 7278 bytes 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 tests/Images/Input/Bmp/RunLengthEncoded-inverted.bmp diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 4a0d248e4..8be892233 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -129,16 +129,19 @@ namespace SixLabors.ImageSharp.Tests public static class Bmp { + // Note: The inverted images have been generated by altering the BitmapInfoHeader using a hex editor. + // As such, the expected pixel output will be the reverse of the unaltered equivalent images. public const string Car = "Bmp/Car.bmp"; public const string F = "Bmp/F.bmp"; 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 RLEInverted = "Bmp/RunLengthEncoded-inverted.bmp"; public const string Bit8 = "Bmp/test8.bmp"; public const string Bit8Inverted = "Bmp/test8-inverted.bmp"; - public static readonly string[] All = { Car, F, NegHeight, CoreHeader, V5Header, RLE, Bit8, Bit8Inverted }; + public static readonly string[] All = { Car, F, NegHeight, CoreHeader, V5Header, RLE, RLEInverted, Bit8, Bit8Inverted }; } public static class Gif diff --git a/tests/Images/Input/Bmp/RunLengthEncoded-inverted.bmp b/tests/Images/Input/Bmp/RunLengthEncoded-inverted.bmp new file mode 100644 index 0000000000000000000000000000000000000000..bce0be02f85fdbd5e683410c59c277b435dfc1c5 GIT binary patch literal 7278 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