From ad4aee89e30eb3103e4726d8b5280492c365c6fd Mon Sep 17 00:00:00 2001 From: ascensio Date: Mon, 17 Jul 2017 16:38:22 +0200 Subject: [PATCH 01/19] closes #269 --- .../Processors/Transforms/RotateProcessor.cs | 2 +- .../Image/ImageRotationTests.cs | 54 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 tests/ImageSharp.Tests/Image/ImageRotationTests.cs diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs index e6b1d180f..d563d072a 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs @@ -79,7 +79,7 @@ namespace ImageSharp.Processing.Processors return; } - this.processMatrix = Matrix3x2Extensions.CreateRotation(-this.Angle, new Point(0, 0)); + this.processMatrix = Matrix3x2Extensions.CreateRotationDegrees(-this.Angle, new Point(0, 0)); if (this.Expand) { this.CreateNewCanvas(sourceRectangle, this.processMatrix); diff --git a/tests/ImageSharp.Tests/Image/ImageRotationTests.cs b/tests/ImageSharp.Tests/Image/ImageRotationTests.cs new file mode 100644 index 000000000..44fa458fa --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageRotationTests.cs @@ -0,0 +1,54 @@ +using SixLabors.Primitives; +using Xunit; + +namespace ImageSharp.Tests +{ + public class ImageRotationTests + { + [Fact] + public void RotateImageByMinus90Degrees() + { + (Size original, Size rotated) = Rotate(-90); + Assert.Equal(new Size(original.Height, original.Width), rotated); + } + + [Fact] + public void RotateImageBy90Degrees() + { + (Size original, Size rotated) = Rotate(90); + Assert.Equal(new Size(original.Height, original.Width), rotated); + } + + [Fact] + public void RotateImageBy180Degrees() + { + (Size original, Size rotated) = Rotate(180); + Assert.Equal(original, rotated); + } + + [Fact] + public void RotateImageBy270Degrees() + { + (Size original, Size rotated) = Rotate(270); + Assert.Equal(new Size(original.Height, original.Width), rotated); + } + + [Fact] + public void RotateImageBy360Degrees() + { + (Size original, Size rotated) = Rotate(360); + Assert.Equal(original, rotated); + } + + private static (Size original, Size rotated) Rotate(int angle) + { + TestFile file = TestFile.Create(TestImages.Bmp.Car); + using (Image image = Image.Load(file.FilePath)) + { + Size expected = image.Bounds.Size; + image.Rotate(angle); + return (expected, image.Bounds.Size); + } + } + } +} From 82661d3958dfc80387601316a1762c7963736530 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Tue, 18 Jul 2017 20:42:00 +0100 Subject: [PATCH 02/19] fix concurrency issues in config --- src/ImageSharp/Configuration.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Configuration.cs b/src/ImageSharp/Configuration.cs index 226d45132..a9322467c 100644 --- a/src/ImageSharp/Configuration.cs +++ b/src/ImageSharp/Configuration.cs @@ -35,14 +35,14 @@ namespace ImageSharp private readonly ConcurrentDictionary mimeTypeDecoders = new ConcurrentDictionary(); /// - /// The list of supported s. + /// The list of supported s. /// - private readonly List imageFormatDetectors = new List(); + private readonly ConcurrentBag imageFormats = new ConcurrentBag(); /// - /// The list of supported s. + /// The list of supported s. /// - private readonly HashSet imageFormats = new HashSet(); + private ConcurrentBag imageFormatDetectors = new ConcurrentBag(); /// /// Initializes a new instance of the class. @@ -181,7 +181,7 @@ namespace ImageSharp /// public void ClearImageFormatDetectors() { - this.imageFormatDetectors.Clear(); + this.imageFormatDetectors = new ConcurrentBag(); } /// From ba6e00499c77fdbf530b4049c41dc3d763ea0e3e Mon Sep 17 00:00:00 2001 From: Nikita Balabaev Date: Fri, 28 Jul 2017 18:31:10 +0700 Subject: [PATCH 03/19] fix #277 --- src/ImageSharp/Formats/Bmp/BmpFormat.cs | 2 +- src/ImageSharp/Formats/Gif/GifFormat.cs | 2 +- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 20 +++++++++--------- src/ImageSharp/Formats/Png/PngFormat.cs | 2 +- .../Formats/Png/PngDecoderTests.cs | 2 +- tests/ImageSharp.Tests/TestImages.cs | 1 + .../Formats/Png/rgb-48bpp-interlaced.png | Bin 0 -> 69952 bytes 7 files changed, 15 insertions(+), 14 deletions(-) create mode 100644 tests/ImageSharp.Tests/TestImages/Formats/Png/rgb-48bpp-interlaced.png diff --git a/src/ImageSharp/Formats/Bmp/BmpFormat.cs b/src/ImageSharp/Formats/Bmp/BmpFormat.cs index fb65f34d7..bd25eb9b7 100644 --- a/src/ImageSharp/Formats/Bmp/BmpFormat.cs +++ b/src/ImageSharp/Formats/Bmp/BmpFormat.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Formats using System.Collections.Generic; /// - /// Registers the image encoders, decoders and mime type detectors for the jpeg format. + /// Registers the image encoders, decoders and mime type detectors for the bmp format. /// internal sealed class BmpFormat : IImageFormat { diff --git a/src/ImageSharp/Formats/Gif/GifFormat.cs b/src/ImageSharp/Formats/Gif/GifFormat.cs index ea7b72d32..744aadff9 100644 --- a/src/ImageSharp/Formats/Gif/GifFormat.cs +++ b/src/ImageSharp/Formats/Gif/GifFormat.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Formats using System.Collections.Generic; /// - /// Registers the image encoders, decoders and mime type detectors for the jpeg format. + /// Registers the image encoders, decoders and mime type detectors for the gif format. /// internal sealed class GifFormat : IImageFormat { diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index e6f19b915..467d41ff4 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -825,14 +825,14 @@ namespace ImageSharp.Formats using (var compressed = new Buffer(length)) { // TODO: Should we use pack from vector here instead? - this.From16BitTo8Bit(new Span(defilteredScanline), compressed, length); - for (int x = pixelOffset, o = 1; + this.From16BitTo8Bit(new Span(defilteredScanline, 1), compressed, length); + for (int x = pixelOffset, o = 0; x < this.header.Width; - x += increment, o += this.bytesPerPixel) + x += increment, o += 3) { rgba.R = compressed[o]; - rgba.G = compressed[o + this.bytesPerSample]; - rgba.B = compressed[o + (2 * this.bytesPerSample)]; + rgba.G = compressed[o + 1]; + rgba.B = compressed[o + 2]; color.PackFromRgba32(rgba); rowSpan[x] = color; @@ -862,13 +862,13 @@ namespace ImageSharp.Formats using (var compressed = new Buffer(length)) { // TODO: Should we use pack from vector here instead? - this.From16BitTo8Bit(new Span(defilteredScanline), compressed, length); - for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o += this.bytesPerPixel) + this.From16BitTo8Bit(new Span(defilteredScanline, 1), compressed, length); + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 4) { rgba.R = compressed[o]; - rgba.G = compressed[o + this.bytesPerSample]; - rgba.B = compressed[o + (2 * this.bytesPerSample)]; - rgba.A = compressed[o + (3 * this.bytesPerSample)]; + rgba.G = compressed[o + 1]; + rgba.B = compressed[o + 2]; + rgba.A = compressed[o + 3]; color.PackFromRgba32(rgba); rowSpan[x] = color; diff --git a/src/ImageSharp/Formats/Png/PngFormat.cs b/src/ImageSharp/Formats/Png/PngFormat.cs index 551b4a8c7..6df2aa7c5 100644 --- a/src/ImageSharp/Formats/Png/PngFormat.cs +++ b/src/ImageSharp/Formats/Png/PngFormat.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Formats using System.Collections.Generic; /// - /// Registers the image encoders, decoders and mime type detectors for the jpeg format. + /// Registers the image encoders, decoders and mime type detectors for the png format. /// internal sealed class PngFormat : IImageFormat { diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 6d82b5374..ee8a2ee55 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -18,7 +18,7 @@ namespace ImageSharp.Tests public static readonly string[] TestFiles = { TestImages.Png.Splash, TestImages.Png.Indexed, TestImages.Png.Interlaced, TestImages.Png.FilterVar, - TestImages.Png.Bad.ChunkLength1, TestImages.Png.Bad.ChunkLength2, TestImages.Png.Rgb48Bpp + TestImages.Png.Bad.ChunkLength1, TestImages.Png.Bad.ChunkLength2, TestImages.Png.Rgb48Bpp, TestImages.Png.Rgb48BppInterlaced }; [Theory] diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index f35720f19..3479457cf 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -26,6 +26,7 @@ namespace ImageSharp.Tests public const string SplashInterlaced = "Png/splash-interlaced.png"; public const string Interlaced = "Png/interlaced.png"; public const string Rgb48Bpp = "Png/rgb-48bpp.png"; + public const string Rgb48BppInterlaced = "Png/rgb-48bpp-interlaced.png"; // Filtered test images from http://www.schaik.com/pngsuite/pngsuite_fil_png.html public const string Filter0 = "Png/filter0.png"; diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Png/rgb-48bpp-interlaced.png b/tests/ImageSharp.Tests/TestImages/Formats/Png/rgb-48bpp-interlaced.png new file mode 100644 index 0000000000000000000000000000000000000000..9202eeae4cf9ab5f52e5d598137338559d30238d GIT binary patch literal 69952 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 Zg9>9`e_kqn`z6V=_w``Edf%9={{x|$O_2Zq literal 0 HcmV?d00001 From a209a8210d02457dc0e7c687018f9cc18aca1e90 Mon Sep 17 00:00:00 2001 From: Devedse Date: Sun, 6 Aug 2017 02:31:28 +0200 Subject: [PATCH 04/19] Added failing test for issue 288 --- .../ImageSharp.Tests/Image/ImageLoadTests.cs | 60 +++++++++++------- tests/ImageSharp.Tests/TestImages.cs | 5 +- .../TestImages/Formats/Png/vim16x16_1.png | Bin 0 -> 226 bytes .../TestImages/Formats/Png/vim16x16_2.png | Bin 0 -> 292 bytes 4 files changed, 41 insertions(+), 24 deletions(-) create mode 100644 tests/ImageSharp.Tests/TestImages/Formats/Png/vim16x16_1.png create mode 100644 tests/ImageSharp.Tests/TestImages/Formats/Png/vim16x16_2.png diff --git a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs index bb64ceda3..d821f4bc7 100644 --- a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs @@ -10,7 +10,6 @@ namespace ImageSharp.Tests using ImageSharp.Formats; using ImageSharp.IO; - using ImageSharp.PixelFormats; using Moq; using Xunit; @@ -44,7 +43,8 @@ namespace ImageSharp.Tests this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny())) - .Callback((c, s) => { + .Callback((c, s) => + { using (var ms = new MemoryStream()) { s.CopyTo(ms); @@ -76,7 +76,7 @@ namespace ImageSharp.Tests [Fact] public void LoadFromStream() { - Image img = Image.Load(this.DataStream); + Image img = Image.Load(this.DataStream); Assert.NotNull(img); @@ -87,12 +87,11 @@ namespace ImageSharp.Tests public void LoadFromNoneSeekableStream() { NoneSeekableStream stream = new NoneSeekableStream(this.DataStream); - Image img = Image.Load(stream); + Image img = Image.Load(stream); Assert.NotNull(img); TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default); - } [Fact] @@ -104,20 +103,18 @@ namespace ImageSharp.Tests Assert.Equal(TestFormat.GlobalTestFormat.Sample(), img); TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default); - } - + [Fact] public void LoadFromStreamWithConfig() { Stream stream = new MemoryStream(); - Image img = Image.Load(this.LocalConfiguration, stream); + Image img = Image.Load(this.LocalConfiguration, stream); Assert.NotNull(img); this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, stream)); - } [Fact] @@ -130,7 +127,6 @@ namespace ImageSharp.Tests Assert.Equal(this.returnImage, img); this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, stream)); - } @@ -138,7 +134,7 @@ namespace ImageSharp.Tests public void LoadFromStreamWithDecoder() { Stream stream = new MemoryStream(); - Image img = Image.Load(stream, this.localDecoder.Object); + Image img = Image.Load(stream, this.localDecoder.Object); Assert.NotNull(img); this.localDecoder.Verify(x => x.Decode(Configuration.Default, stream)); @@ -158,13 +154,11 @@ namespace ImageSharp.Tests [Fact] public void LoadFromBytes() { - Image img = Image.Load(this.DataStream.ToArray()); + Image img = Image.Load(this.DataStream.ToArray()); Assert.NotNull(img); - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default); - } [Fact] @@ -182,7 +176,7 @@ namespace ImageSharp.Tests [Fact] public void LoadFromBytesWithConfig() { - Image img = Image.Load(this.LocalConfiguration, this.DataStream.ToArray()); + Image img = Image.Load(this.LocalConfiguration, this.DataStream.ToArray()); Assert.NotNull(img); @@ -207,7 +201,7 @@ namespace ImageSharp.Tests [Fact] public void LoadFromBytesWithDecoder() { - Image img = Image.Load(this.DataStream.ToArray(), this.localDecoder.Object); + Image img = Image.Load(this.DataStream.ToArray(), this.localDecoder.Object); Assert.NotNull(img); this.localDecoder.Verify(x => x.Decode(Configuration.Default, It.IsAny())); @@ -228,13 +222,11 @@ namespace ImageSharp.Tests [Fact] public void LoadFromFile() { - Image img = Image.Load(this.DataStream); + Image img = Image.Load(this.DataStream); Assert.NotNull(img); - TestFormat.GlobalTestFormat.VerifyDecodeCall(this.Marker, Configuration.Default); - } [Fact] @@ -251,12 +243,11 @@ namespace ImageSharp.Tests [Fact] public void LoadFromFileWithConfig() { - Image img = Image.Load(this.LocalConfiguration, this.FilePath); + Image img = Image.Load(this.LocalConfiguration, this.FilePath); Assert.NotNull(img); this.localDecoder.Verify(x => x.Decode(this.LocalConfiguration, this.DataStream)); - } [Fact] @@ -273,7 +264,7 @@ namespace ImageSharp.Tests [Fact] public void LoadFromFileWithDecoder() { - Image img = Image.Load(this.FilePath, this.localDecoder.Object); + Image img = Image.Load(this.FilePath, this.localDecoder.Object); Assert.NotNull(img); this.localDecoder.Verify(x => x.Decode(Configuration.Default, this.DataStream)); @@ -305,7 +296,6 @@ namespace ImageSharp.Tests Assert.Equal(Rgba32.White, px[1, 0]); Assert.Equal(Rgba32.Black, px[1, 1]); - } } @@ -327,7 +317,31 @@ namespace ImageSharp.Tests Assert.Equal(Rgba32.White, px[1, 0]); Assert.Equal(Rgba32.Black, px[1, 1]); + } + } + [Fact] + public void TestThatTwoImagesAreEqual() + { + var image1Provider = TestImageProvider.File(TestImages.Png.VimImage1); + var image2Provider = TestImageProvider.File(TestImages.Png.VimImage2); + + using (Image img1 = image1Provider.GetImage()) + using (Image img2 = image2Provider.GetImage()) + { + Assert.Equal(img1.Width, img2.Width); + Assert.Equal(img1.Height, img2.Height); + + for (int y = 0; y < img1.Height; y++) + { + for (int x = 0; x < img1.Width; x++) + { + Rgba32 pixel1 = img1[x, y]; + Rgba32 pixel2 = img2[x, y]; + + Assert.Equal(pixel1, pixel2); + } + } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 3479457cf..7d552d109 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -38,6 +38,9 @@ namespace ImageSharp.Tests // Filter changing per scanline public const string FilterVar = "Png/filterVar.png"; + public const string VimImage1 = "Png/vim16x16_1.png"; + public const string VimImage2 = "Png/vim16x16_2.png"; + public static class Bad { // Odd chunk lengths @@ -50,7 +53,7 @@ namespace ImageSharp.Tests P1, Pd, Blur, Splash, Cross, Powerpoint, SplashInterlaced, Interlaced, Filter0, Filter1, Filter2, Filter3, Filter4, - FilterVar + FilterVar, VimImage1, VimImage2 }; } diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Png/vim16x16_1.png b/tests/ImageSharp.Tests/TestImages/Formats/Png/vim16x16_1.png new file mode 100644 index 0000000000000000000000000000000000000000..fb45d22a0560d23c20b65ea1922099a8ae56683e GIT binary patch literal 226 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!73?$#)eFPFNHZ`PcHfZ)Qj8@*e!&b5&u*jvIT@ZVjv*CuR8Mc@ zWj5qtc3Ad_O|n7D#PNZdv4p{<#;aOPpQ7wH&A-~H(0FEjI#ces{!lvI6;>1s;*b z3=DjSL74G){)!Z!pk#?_L`iUdT1k0gQ7S`0VrE{6US4X6f{C7io}syM-uz^sqJ^F= zjv*HQQztU=9Z}$5`CVd{_UQb(hFvnPSHI+#Z8^XfwcfDJ`}*>ea=mNh>_l#UO54d> z$|%%enLfGW$dO##Z9%gZDZX8BEs5Rzz`;LU2hQ*=(DV;uFugtT+QxSW)8aaLEM^J2 zeEofbA;VN~v(5U6j1!#iZf%>X&(L7NFr~P|oxwOXavsyC(uIZM(hoi!?@Z)oI5MTN jo^jP4$JXz1$G@_QMjE`zc;f8?bS#6XtDnm{r-UW|K_qIq literal 0 HcmV?d00001 From fec2d6fe9a6310a0af8a36671dd7e49d378ea424 Mon Sep 17 00:00:00 2001 From: JimBobSquarePants Date: Tue, 8 Aug 2017 15:37:56 +1000 Subject: [PATCH 05/19] Fix failing test --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 467d41ff4..cdbd3a689 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -688,19 +688,11 @@ namespace ImageSharp.Formats // channel and we should try to read it. for (int x = 0; x < this.header.Width; x++) { - int index = newScanline[x + 1]; + int index = newScanline[x]; int pixelOffset = index * 3; rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255; - - if (rgba.A > 0) - { - rgba.Rgb = pal.GetRgb24(pixelOffset); - } - else - { - rgba = default(Rgba32); - } + rgba.Rgb = pal.GetRgb24(pixelOffset); color.PackFromRgba32(rgba); row[x] = color; @@ -712,7 +704,7 @@ namespace ImageSharp.Formats for (int x = 0; x < this.header.Width; x++) { - int index = newScanline[x + 1]; + int index = newScanline[x]; int pixelOffset = index * 3; rgba.Rgb = pal.GetRgb24(pixelOffset); From 51eca28eb6177d96c437e3da05bc731d4e1eaa3d Mon Sep 17 00:00:00 2001 From: JimBobSquarePants Date: Tue, 8 Aug 2017 16:22:30 +1000 Subject: [PATCH 06/19] Disable smoke test --- .../Formats/Png/PngSmokeTests.cs | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs index bac340a71..c977f5fb5 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs @@ -40,25 +40,27 @@ namespace ImageSharp.Tests.Formats.Png } } - [Theory] - [WithTestPatternImages(100, 100, PixelTypes.Rgba32)] - public void CanSaveIndexedPng(TestImageProvider provider) - where TPixel : struct, IPixel - { - // does saving a file then repoening mean both files are identical??? - using (Image image = provider.GetImage()) - using (MemoryStream ms = new MemoryStream()) - { - // image.Save(provider.Utility.GetTestOutputFileName("bmp")); - image.Save(ms, new PngEncoder() { PaletteSize = 256 }); - ms.Position = 0; - using (Image img2 = Image.Load(ms, new PngDecoder())) - { - // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); - ImageComparer.CheckSimilarity(image, img2, 0.03f); - } - } - } + // JJS: Disabled for now as the decoder now correctly decodes the full pixel components if the + // paletted image has alpha of 0 + //[Theory] + //[WithTestPatternImages(100, 100, PixelTypes.Rgba32)] + //public void CanSaveIndexedPng(TestImageProvider provider) + // where TPixel : struct, IPixel + //{ + // // does saving a file then repoening mean both files are identical??? + // using (Image image = provider.GetImage()) + // using (MemoryStream ms = new MemoryStream()) + // { + // // image.Save(provider.Utility.GetTestOutputFileName("bmp")); + // image.Save(ms, new PngEncoder() { PaletteSize = 256 }); + // ms.Position = 0; + // using (Image img2 = Image.Load(ms, new PngDecoder())) + // { + // // img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder()); + // ImageComparer.CheckSimilarity(image, img2, 0.03f); + // } + // } + //} // JJS: Commented out for now since the test does not take into lossy nature of indexing. //[Theory] From 852ae7b777988e816827d629ec3d6c99f547b8d8 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Thu, 10 Aug 2017 09:10:22 +0200 Subject: [PATCH 07/19] Only throw exceptions when the CRC of a critical chunk is incorrect. --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 13 ++- .../Formats/Png/PngDecoderTests.cs | 87 ++++++++++++++++--- 2 files changed, 86 insertions(+), 14 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index cdbd3a689..95211c5f3 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -306,6 +306,15 @@ namespace ImageSharp.Formats return result; } + private static bool IsCriticalChunk(PngChunk chunk) + { + return + chunk.Type == PngChunkTypes.Header || + chunk.Type == PngChunkTypes.Palette || + chunk.Type == PngChunkTypes.Data || + chunk.Type == PngChunkTypes.End; + } + /// /// Reads the data chunk containing physical dimension data. /// @@ -1017,9 +1026,9 @@ namespace ImageSharp.Formats this.crc.Update(this.chunkTypeBuffer); this.crc.Update(chunk.Data, 0, chunk.Length); - if (this.crc.Value != chunk.Crc) + if (this.crc.Value != chunk.Crc && IsCriticalChunk(chunk)) { - throw new ImageFormatException("CRC Error. PNG Image chunk is corrupt!"); + throw new ImageFormatException($"CRC Error. PNG {chunk.Type} chunk is corrupt!"); } } diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index ee8a2ee55..3c335441a 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -3,14 +3,15 @@ // Licensed under the Apache License, Version 2.0. // +using System.IO; +using System.IO.Compression; +using System.Text; +using ImageSharp.Formats; +using ImageSharp.PixelFormats; +using Xunit; + namespace ImageSharp.Tests { - using System.Text; - using Xunit; - - using ImageSharp.Formats; - using ImageSharp.PixelFormats; - public class PngDecoderTests { private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; @@ -35,12 +36,12 @@ namespace ImageSharp.Tests [Fact] public void Decode_IgnoreMetadataIsFalse_TextChunckIsRead() { - PngDecoder options = new PngDecoder() + var options = new PngDecoder() { IgnoreMetadata = false }; - TestFile testFile = TestFile.Create(TestImages.Png.Blur); + var testFile = TestFile.Create(TestImages.Png.Blur); using (Image image = testFile.CreateImage(options)) { @@ -53,12 +54,12 @@ namespace ImageSharp.Tests [Fact] public void Decode_IgnoreMetadataIsTrue_TextChunksAreIgnored() { - PngDecoder options = new PngDecoder() + var options = new PngDecoder() { IgnoreMetadata = true }; - TestFile testFile = TestFile.Create(TestImages.Png.Blur); + var testFile = TestFile.Create(TestImages.Png.Blur); using (Image image = testFile.CreateImage(options)) { @@ -69,12 +70,12 @@ namespace ImageSharp.Tests [Fact] public void Decode_TextEncodingSetToUnicode_TextIsReadWithCorrectEncoding() { - PngDecoder options = new PngDecoder() + var options = new PngDecoder() { TextEncoding = Encoding.Unicode }; - TestFile testFile = TestFile.Create(TestImages.Png.Blur); + var testFile = TestFile.Create(TestImages.Png.Blur); using (Image image = testFile.CreateImage(options)) { @@ -82,5 +83,67 @@ namespace ImageSharp.Tests Assert.Equal("潓瑦慷敲", image.MetaData.Properties[0].Name); } } + + [Theory] + [InlineData(PngChunkTypes.Header)] + [InlineData(PngChunkTypes.Palette)] + // [InlineData(PngChunkTypes.Data)] //TODO: Figure out how to test this + [InlineData(PngChunkTypes.End)] + public void Decode_IncorrectCRCForCriticalChunk_ExceptionIsThrown(string chunkName) + { + using (var memStream = new MemoryStream()) + { + memStream.Skip(8); + + WriteChunk(memStream, chunkName); + + CompressStream(memStream); + + var decoder = new PngDecoder(); + + ImageFormatException exception = Assert.Throws(() => + { + decoder.Decode(null, memStream); + }); + + Assert.Equal($"CRC Error. PNG {chunkName} chunk is corrupt!", exception.Message); + } + } + + [Theory] + [InlineData(PngChunkTypes.Gamma)] + [InlineData(PngChunkTypes.PaletteAlpha)] + [InlineData(PngChunkTypes.Physical)] + //[InlineData(PngChunkTypes.Text)] //TODO: Figure out how to test this + public void Decode_IncorrectCRCForNonCriticalChunk_ExceptionIsThrown(string chunkName) + { + using (var memStream = new MemoryStream()) + { + memStream.Skip(8); + + WriteChunk(memStream, chunkName); + + CompressStream(memStream); + + var decoder = new PngDecoder(); + decoder.Decode(null, memStream); + } + } + + private static void WriteChunk(MemoryStream memStream, string chunkName) + { + memStream.Write(new byte[] { 0, 0, 0, 1 }, 0, 4); + memStream.Write(Encoding.GetEncoding("ASCII").GetBytes(chunkName), 0, 4); + memStream.Write(new byte[] { 0, 0, 0, 0, 0 }, 0, 5); + } + + private static void CompressStream(Stream stream) + { + stream.Position = 0; + using (var deflateStream = new DeflateStream(stream, CompressionLevel.NoCompression, true)) + { + } + stream.Position = 0; + } } } \ No newline at end of file From 4b2fbcd4538f4bbfaf977dc44397cbcd4cae5225 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 12 Aug 2017 19:01:05 +1000 Subject: [PATCH 08/19] Correctly read Adam7 palette colors --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 95211c5f3..372ecdc2b 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -768,10 +768,7 @@ namespace ImageSharp.Formats case PngColorType.Palette: - byte[] newScanline = ToArrayByBitsLength( - defilteredScanline, - this.bytesPerScanline, - this.header.BitDepth); + byte[] newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); var rgba = default(Rgba32); if (this.paletteAlpha != null && this.paletteAlpha.Length > 0) @@ -784,15 +781,7 @@ namespace ImageSharp.Formats int offset = index * 3; rgba.A = this.paletteAlpha.Length > index ? this.paletteAlpha[index] : (byte)255; - - if (rgba.A > 0) - { - rgba.Rgb = this.palette.GetRgb24(offset); - } - else - { - rgba = default(Rgba32); - } + rgba.Rgb = this.palette.GetRgb24(offset); color.PackFromRgba32(rgba); rowSpan[x] = color; From 07c0af934054c1e885073d628eae34b3373a3694 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 12 Aug 2017 19:08:58 +1000 Subject: [PATCH 09/19] Update AppVeyor key --- appveyor.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 079913311..6b7ba946e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,10 +7,6 @@ skip_branch_with_pr: true init: - ps: iex ((new-object net.webclient).DownloadString('https://gist.githubusercontent.com/PureKrome/0f79e25693d574807939/raw/8cf3160c9516ef1f4effc825c0a44acc918a0b5a/appveyor-build-info.ps')) -# temp work around - https://appveyor.statuspage.io/incidents/m2vdvw39kdk8 -hosts: - api.nuget.org: 93.184.221.200 - build_script: - cmd: build.cmd @@ -26,7 +22,7 @@ deploy: server: https://www.myget.org/F/imagesharp/api/v2/package symbol_server: https://www.myget.org/F/imagesharp/symbols/api/v2/package api_key: - secure: P2Fz82nty+itjL+kNRCsMQcqzngmVtkU0R4CZqgST7zgUaE6/1q9ekh5MKKlZLkD + secure: fz0rUrt3B1HczUC1ZehwVsrFSWX9WZGDQoueDztLte9/+yQG+BBU7UrO+coE8lUf artifact: /.*\.nupkg/ on: branch: master From 94fbdf554821f0bd745db47b8e2ac00309c9cd04 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 12 Aug 2017 19:40:28 +1000 Subject: [PATCH 10/19] Revert key change, we have a new CI! --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 6b7ba946e..fdbe46b01 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -22,7 +22,7 @@ deploy: server: https://www.myget.org/F/imagesharp/api/v2/package symbol_server: https://www.myget.org/F/imagesharp/symbols/api/v2/package api_key: - secure: fz0rUrt3B1HczUC1ZehwVsrFSWX9WZGDQoueDztLte9/+yQG+BBU7UrO+coE8lUf + secure: P2Fz82nty+itjL+kNRCsMQcqzngmVtkU0R4CZqgST7zgUaE6/1q9ekh5MKKlZLkD artifact: /.*\.nupkg/ on: branch: master From 50467456a72e1b6a03ed7c630fb359f8648b0779 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 12 Aug 2017 19:47:04 +1000 Subject: [PATCH 11/19] Fix #281 --- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index bb230beac..948103fed 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -157,6 +157,10 @@ namespace ImageSharp.Formats } nextFlag = stream.ReadByte(); + if (nextFlag == -1) + { + break; + } } } finally From 68df16b43865592b3165efe84a7da22e419d9522 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 12 Aug 2017 20:00:27 +1000 Subject: [PATCH 12/19] Update readme [skip ci] --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index e46b11379..70e48e52e 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# ImageSharp ImageSharp +# ImageSharp ImageSharp **ImageSharp** is a new, fully featured, fully managed, cross-platform, 2D graphics API designed to allow the processing of images without the use of `System.Drawing`. @@ -9,12 +9,12 @@ Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and emb > > Pre-release downloads are available from the [MyGet package repository](https://www.myget.org/gallery/imagesharp). -[![GitHub license](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/JimBobSquarePants/ImageSharp/master/APACHE-2.0-LICENSE.txt) -[![GitHub issues](https://img.shields.io/github/issues/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/issues) -[![GitHub stars](https://img.shields.io/github/stars/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/stargazers) -[![GitHub forks](https://img.shields.io/github/forks/JimBobSquarePants/ImageSharp.svg)](https://github.com/JimBobSquarePants/ImageSharp/network) +[![GitHub license](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/SixLabors/ImageSharp/master/APACHE-2.0-LICENSE.txt) +[![GitHub issues](https://img.shields.io/github/issues/SixLabors/ImageSharp.svg)](https://github.com/SixLabors/ImageSharp/issues) +[![GitHub stars](https://img.shields.io/github/stars/SixLabors/ImageSharp.svg)](https://github.com/SixLabors/ImageSharp/stargazers) +[![GitHub forks](https://img.shields.io/github/forks/SixLabors/ImageSharp.svg)](https://github.com/SixLabors/ImageSharp/network) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ImageSharp/General?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Twitter](https://img.shields.io/twitter/url/https/github.com/JimBobSquarePants/ImageSharp.svg?style=social)](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fJimBobSquarePants%2fImageSharp&via=james_m_south) +[![Twitter](https://img.shields.io/twitter/url/https/github.com/SixLabors/ImageSharp.svg?style=social)](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fSixLabors%2fImageSharp&via=sixlabors) [![OpenCollective](https://opencollective.com/imagesharp/backers/badge.svg)](#backers) [![OpenCollective](https://opencollective.com/imagesharp/sponsors/badge.svg)](#sponsors) @@ -22,8 +22,8 @@ Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and emb | |Build Status|Code Coverage| |-------------|:----------:|:-----------:| -|**Linux/Mac**|[![Build Status](https://travis-ci.org/JimBobSquarePants/ImageSharp.svg)](https://travis-ci.org/JimBobSquarePants/ImageSharp)|[![Code coverage](https://codecov.io/gh/JimBobSquarePants/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/JimBobSquarePants/ImageSharp)| -|**Windows** |[![Build Status](https://ci.appveyor.com/api/projects/status/hu6d1gdpxdw0q360/branch/master?svg=true)](https://ci.appveyor.com/project/JamesSouth/imagesharp/branch/master)|[![Code coverage](https://codecov.io/gh/JimBobSquarePants/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/JimBobSquarePants/ImageSharp)| +|**Linux/Mac**|[![Build Status](https://travis-ci.org/SixLabors/ImageSharp.svg)](https://travis-ci.org/SixLabors/ImageSharp)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)| +|**Windows** |[![Build Status](https://ci.appveyor.com/api/projects/status/m9pn907xdah3ca39/branch/master?svg=true)](https://ci.appveyor.com/project/SixLabors/imagesharp/branch/master)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)| ### Installation @@ -64,7 +64,7 @@ Alternatively on Linux you can use: To clone it locally click the "Clone in Windows" button above or run the following git commands. ```bash -git clone https://github.com/JimBobSquarePants/ImageSharp +git clone https://github.com/SixLabors/ImageSharp ``` ### Features @@ -121,7 +121,7 @@ For optimized access within a loop it is recommended that the following methods 1. `image.GetRowSpan(y)` 2. `image.GetRowSpan(x, y)` -For advanced pixel format usage there are multiple [PixelFormat implementations](https://github.com/JimBobSquarePants/ImageSharp/tree/master/src/ImageSharp/PixelFormats) available allowing developers to implement their own color models in the same manner as Microsoft XNA Game Studio and MonoGame. +For advanced pixel format usage there are multiple [PixelFormat implementations](https://github.com/SixLabors/ImageSharp/tree/master/src/ImageSharp/PixelFormats) available allowing developers to implement their own color models in the same manner as Microsoft XNA Game Studio and MonoGame. All in all this should allow image processing to be much more accessible to developers which has always been my goal from the start. From c69d2efae82bfdf8f399dd49dbf1f949b103fcaf Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 12 Aug 2017 21:03:46 +1000 Subject: [PATCH 13/19] Fix Appveyor link [skip ci] --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 70e48e52e..7113d6ba1 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Built against .Net Standard 1.1 ImageSharp can be used in device, cloud, and emb | |Build Status|Code Coverage| |-------------|:----------:|:-----------:| |**Linux/Mac**|[![Build Status](https://travis-ci.org/SixLabors/ImageSharp.svg)](https://travis-ci.org/SixLabors/ImageSharp)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)| -|**Windows** |[![Build Status](https://ci.appveyor.com/api/projects/status/m9pn907xdah3ca39/branch/master?svg=true)](https://ci.appveyor.com/project/SixLabors/imagesharp/branch/master)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)| +|**Windows** |[![Build Status](https://ci.appveyor.com/api/projects/status/m9pn907xdah3ca39/branch/master?svg=true)](https://ci.appveyor.com/project/six-labors/imagesharp/branch/master)|[![Code coverage](https://codecov.io/gh/SixLabors/ImageSharp/branch/master/graph/badge.svg)](https://codecov.io/gh/SixLabors/ImageSharp)| ### Installation From 03ccebb0df60d81688b9d9977b0e59f5e555c9e1 Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Sat, 12 Aug 2017 13:19:44 +0200 Subject: [PATCH 14/19] Renamed variable. --- tests/ImageSharp.Tests/Image/ImageRotationTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Image/ImageRotationTests.cs b/tests/ImageSharp.Tests/Image/ImageRotationTests.cs index 44fa458fa..56cec4219 100644 --- a/tests/ImageSharp.Tests/Image/ImageRotationTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageRotationTests.cs @@ -45,9 +45,9 @@ namespace ImageSharp.Tests TestFile file = TestFile.Create(TestImages.Bmp.Car); using (Image image = Image.Load(file.FilePath)) { - Size expected = image.Bounds.Size; + Size original = image.Bounds.Size; image.Rotate(angle); - return (expected, image.Bounds.Size); + return (original, image.Bounds.Size); } } } From aabf8a2cbe0163a7cb12a7993a56e88ac0bc8ba6 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sat, 12 Aug 2017 21:36:01 +1000 Subject: [PATCH 15/19] Update Travis build coverity details [skip ci] --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index af8d4ad9d..4ae163530 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,7 +26,7 @@ env: global: # The next declaration is the encrypted COVERITY_SCAN_TOKEN, created # via the "travis encrypt" command using the project repo's public key - - secure: "aim+fUyx7kDQQcAYV1mX16cvyFEYsxiW3y26xjmeuKzsOf6DIUK328pE8KnO50bMWhfVPjuW7jWc43jI+nbUeIW5018aFcjoOrEK2F8JvJ0UKtEo+ONchypJSXA2TSdL0iIlufMBepsmlBsSLkCwHCJYohYcueZV0u9NVPc3n282KLL8ItRZeSFG/cL/a2yrkFnTFhq9OtkUtP4CcVE7BOtzjfftNcn4Rup73e5JkLT7L9AZS7eCYkIYV0KRlT2pOa/FuOHlfP9NP+NVtd83GXUY2FKBsmN3EmrQgGDTfwfwcJjN5dqIqzkIXmLV8IKQ3aiW2//02pIe5VrdqHQG+EVMRcdpCWyKUkMj0g4rGYkqKCtVJojKtOR93ycOGUDc6+cMMoyn3J2qFydkp278dGWeLuwtGfD25fHXorqK1aL9/bGPcwdinrBmcwnuy1IECtuTkEfAPsb6O4nArnDsTEzeQxwa/MAicmpux//TNKgkQGqzCPeHKbl4vOfyyI6kCsf8edWv8fOSPvJUGvL14+/TZ6lY8S+30fosOmwMCe7xlbtcVlBVtOsKx/XUufrP2Vuptlc8INaq6++XtgpCoMLL0SJfBFQKZRmBGavv1Ztyf0aL6Qp303HKGTyXOEq2k18iJmukB6JcnEGVsaAyteGlruQIbPgHWbxhZSoJZPw=" + - secure: "rjMvEMN9rpvIXqXqCAAKzbHyABzr7E4wPU/dYJ/mHBqlCccFpQrEXVVM1MfRFXYuWZSaIioknhLATZjT5xvIYpTNM6D57z4OTmqeRHhYm80=" before_install: - echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca- @@ -34,7 +34,7 @@ before_install: addons: coverity_scan: project: - name: "JimBobSquarePants/ImageSharp" + name: "SixLabors/ImageSharp" description: "Build submitted via Travis CI" notification_email: james_south@hotmail.com build_command_prepend: "dotnet restore" From f8c2727c14b1168afc816f13bbdb62c11fb19a8f Mon Sep 17 00:00:00 2001 From: Devedse Date: Sat, 12 Aug 2017 15:31:55 +0200 Subject: [PATCH 16/19] Added tests for loading image with CRC Added tests for comparing Versioning image 1 and 2 Moved equality tests for images to seperate file --- .../Image/ImageDiscoverMimeType.cs | 2 +- .../ImageSharp.Tests/Image/ImageEqualTests.cs | 124 ++++++++++++++++++ .../ImageSharp.Tests/Image/ImageLoadTests.cs | 24 +--- .../ImageSharp.Tests/Image/ImageSaveTests.cs | 2 +- tests/ImageSharp.Tests/Image/ImageTests.cs | 2 +- tests/ImageSharp.Tests/TestImages.cs | 6 +- .../TestImages/Formats/Png/versioning-1_1.png | Bin 0 -> 22781 bytes .../TestImages/Formats/Png/versioning-1_2.png | Bin 0 -> 8165 bytes 8 files changed, 137 insertions(+), 23 deletions(-) create mode 100644 tests/ImageSharp.Tests/Image/ImageEqualTests.cs create mode 100644 tests/ImageSharp.Tests/TestImages/Formats/Png/versioning-1_1.png create mode 100644 tests/ImageSharp.Tests/TestImages/Formats/Png/versioning-1_2.png diff --git a/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs b/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs index 59a39c454..d34fa22e2 100644 --- a/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs +++ b/tests/ImageSharp.Tests/Image/ImageDiscoverMimeType.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // diff --git a/tests/ImageSharp.Tests/Image/ImageEqualTests.cs b/tests/ImageSharp.Tests/Image/ImageEqualTests.cs new file mode 100644 index 000000000..5e156f543 --- /dev/null +++ b/tests/ImageSharp.Tests/Image/ImageEqualTests.cs @@ -0,0 +1,124 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System; + using System.IO; + + using ImageSharp.Formats; + using ImageSharp.IO; + using ImageSharp.Tests; + using Moq; + using Xunit; + + public class ImageEqualTests + { + //private readonly Mock fileSystem; + //private Image returnImage; + //private Mock localDecoder; + //private readonly string FilePath; + //private readonly Mock localMimeTypeDetector; + //private readonly Mock localImageFormatMock; + + //public Configuration LocalConfiguration { get; private set; } + //public byte[] Marker { get; private set; } + //public MemoryStream DataStream { get; private set; } + //public byte[] DecodedData { get; private set; } + + public ImageEqualTests() + { + //this.returnImage = new Image(1, 1); + + //this.localImageFormatMock = new Mock(); + + //this.localDecoder = new Mock(); + //this.localMimeTypeDetector = new Mock(); + //this.localMimeTypeDetector.Setup(x => x.HeaderSize).Returns(1); + //this.localMimeTypeDetector.Setup(x => x.DetectFormat(It.IsAny>())).Returns(localImageFormatMock.Object); + + //this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny())) + + // .Callback((c, s) => + // { + // using (var ms = new MemoryStream()) + // { + // s.CopyTo(ms); + // this.DecodedData = ms.ToArray(); + // } + // }) + // .Returns(this.returnImage); + + //this.fileSystem = new Mock(); + + //this.LocalConfiguration = new Configuration() + //{ + // FileSystem = this.fileSystem.Object + //}; + //this.LocalConfiguration.AddImageFormatDetector(this.localMimeTypeDetector.Object); + //this.LocalConfiguration.SetDecoder(localImageFormatMock.Object, this.localDecoder.Object); + + //TestFormat.RegisterGloablTestFormat(); + //this.Marker = Guid.NewGuid().ToByteArray(); + //this.DataStream = TestFormat.GlobalTestFormat.CreateStream(this.Marker); + + //this.FilePath = Guid.NewGuid().ToString(); + //this.fileSystem.Setup(x => x.OpenRead(this.FilePath)).Returns(this.DataStream); + + //TestFileSystem.RegisterGloablTestFormat(); + //TestFileSystem.Global.AddFile(this.FilePath, this.DataStream); + } + + [Fact] + public void TestsThatVimImagesAreEqual() + { + var image1Provider = TestImageProvider.File(TestImages.Png.VimImage1); + var image2Provider = TestImageProvider.File(TestImages.Png.VimImage2); + + using (Image img1 = image1Provider.GetImage()) + using (Image img2 = image2Provider.GetImage()) + { + bool imagesEqual = AreImagesEqual(img1, img2); + Assert.True(imagesEqual); + } + } + + [Fact] + public void TestsThatVersioningImagesAreEqual() + { + var image1Provider = TestImageProvider.File(TestImages.Png.VersioningImage1); + var image2Provider = TestImageProvider.File(TestImages.Png.VersioningImage2); + + using (Image img1 = image1Provider.GetImage()) + using (Image img2 = image2Provider.GetImage()) + { + bool imagesEqual = AreImagesEqual(img1, img2); + //Assert.True(imagesEqual); + } + } + + private bool AreImagesEqual(Image img1, Image img2) + { + Assert.Equal(img1.Width, img2.Width); + Assert.Equal(img1.Height, img2.Height); + + for (int y = 0; y < img1.Height; y++) + { + for (int x = 0; x < img1.Width; x++) + { + Rgba32 pixel1 = img1[x, y]; + Rgba32 pixel2 = img2[x, y]; + + if (pixel1 != pixel2) + { + return false; + } + } + } + + return true; + } + } +} diff --git a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs index d821f4bc7..4573d0d9d 100644 --- a/tests/ImageSharp.Tests/Image/ImageLoadTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageLoadTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -321,27 +321,13 @@ namespace ImageSharp.Tests } [Fact] - public void TestThatTwoImagesAreEqual() + public void LoadsImageWithoutThrowingCrcException() { - var image1Provider = TestImageProvider.File(TestImages.Png.VimImage1); - var image2Provider = TestImageProvider.File(TestImages.Png.VimImage2); + var image1Provider = TestImageProvider.File(TestImages.Png.VersioningImage1); - using (Image img1 = image1Provider.GetImage()) - using (Image img2 = image2Provider.GetImage()) + using (Image img = image1Provider.GetImage()) { - Assert.Equal(img1.Width, img2.Width); - Assert.Equal(img1.Height, img2.Height); - - for (int y = 0; y < img1.Height; y++) - { - for (int x = 0; x < img1.Width; x++) - { - Rgba32 pixel1 = img1[x, y]; - Rgba32 pixel2 = img2[x, y]; - - Assert.Equal(pixel1, pixel2); - } - } + Assert.Equal(166036, img.Pixels.Length); } } diff --git a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs index eecb98380..ad8a5cc7d 100644 --- a/tests/ImageSharp.Tests/Image/ImageSaveTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageSaveTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // diff --git a/tests/ImageSharp.Tests/Image/ImageTests.cs b/tests/ImageSharp.Tests/Image/ImageTests.cs index 3157c27d8..217bf37fe 100644 --- a/tests/ImageSharp.Tests/Image/ImageTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageTests.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 7d552d109..46887d721 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -41,6 +41,9 @@ namespace ImageSharp.Tests public const string VimImage1 = "Png/vim16x16_1.png"; public const string VimImage2 = "Png/vim16x16_2.png"; + public const string VersioningImage1 = "Png/versioning-1_1.png"; + public const string VersioningImage2 = "Png/versioning-1_2.png"; + public static class Bad { // Odd chunk lengths @@ -53,7 +56,8 @@ namespace ImageSharp.Tests P1, Pd, Blur, Splash, Cross, Powerpoint, SplashInterlaced, Interlaced, Filter0, Filter1, Filter2, Filter3, Filter4, - FilterVar, VimImage1, VimImage2 + FilterVar, VimImage1, VimImage2, VersioningImage1, + VersioningImage2 }; } diff --git a/tests/ImageSharp.Tests/TestImages/Formats/Png/versioning-1_1.png b/tests/ImageSharp.Tests/TestImages/Formats/Png/versioning-1_1.png new file mode 100644 index 0000000000000000000000000000000000000000..c13f98fd16ed8318e88c2e550f31cca566aabdf9 GIT binary patch literal 22781 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

dm@8bx=l{7XaO`ql1=#NSsHpvzv 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 S+^K(l)=P`ae<~L<@ckcgU$2w^ literal 0 HcmV?d00001 From 43c92a1389c9d0adcf9275b87539813c3f3a9254 Mon Sep 17 00:00:00 2001 From: Devedse Date: Sat, 12 Aug 2017 15:33:12 +0200 Subject: [PATCH 17/19] Removed commented out code --- .../ImageSharp.Tests/Image/ImageEqualTests.cs | 62 ------------------- 1 file changed, 62 deletions(-) diff --git a/tests/ImageSharp.Tests/Image/ImageEqualTests.cs b/tests/ImageSharp.Tests/Image/ImageEqualTests.cs index 5e156f543..422e61d6d 100644 --- a/tests/ImageSharp.Tests/Image/ImageEqualTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageEqualTests.cs @@ -5,72 +5,10 @@ namespace ImageSharp.Tests { - using System; - using System.IO; - - using ImageSharp.Formats; - using ImageSharp.IO; - using ImageSharp.Tests; - using Moq; using Xunit; public class ImageEqualTests { - //private readonly Mock fileSystem; - //private Image returnImage; - //private Mock localDecoder; - //private readonly string FilePath; - //private readonly Mock localMimeTypeDetector; - //private readonly Mock localImageFormatMock; - - //public Configuration LocalConfiguration { get; private set; } - //public byte[] Marker { get; private set; } - //public MemoryStream DataStream { get; private set; } - //public byte[] DecodedData { get; private set; } - - public ImageEqualTests() - { - //this.returnImage = new Image(1, 1); - - //this.localImageFormatMock = new Mock(); - - //this.localDecoder = new Mock(); - //this.localMimeTypeDetector = new Mock(); - //this.localMimeTypeDetector.Setup(x => x.HeaderSize).Returns(1); - //this.localMimeTypeDetector.Setup(x => x.DetectFormat(It.IsAny>())).Returns(localImageFormatMock.Object); - - //this.localDecoder.Setup(x => x.Decode(It.IsAny(), It.IsAny())) - - // .Callback((c, s) => - // { - // using (var ms = new MemoryStream()) - // { - // s.CopyTo(ms); - // this.DecodedData = ms.ToArray(); - // } - // }) - // .Returns(this.returnImage); - - //this.fileSystem = new Mock(); - - //this.LocalConfiguration = new Configuration() - //{ - // FileSystem = this.fileSystem.Object - //}; - //this.LocalConfiguration.AddImageFormatDetector(this.localMimeTypeDetector.Object); - //this.LocalConfiguration.SetDecoder(localImageFormatMock.Object, this.localDecoder.Object); - - //TestFormat.RegisterGloablTestFormat(); - //this.Marker = Guid.NewGuid().ToByteArray(); - //this.DataStream = TestFormat.GlobalTestFormat.CreateStream(this.Marker); - - //this.FilePath = Guid.NewGuid().ToString(); - //this.fileSystem.Setup(x => x.OpenRead(this.FilePath)).Returns(this.DataStream); - - //TestFileSystem.RegisterGloablTestFormat(); - //TestFileSystem.Global.AddFile(this.FilePath, this.DataStream); - } - [Fact] public void TestsThatVimImagesAreEqual() { From 5cf62d2421a4675d3217049751839128a2a7ec8e Mon Sep 17 00:00:00 2001 From: Devedse Date: Sat, 12 Aug 2017 17:10:35 +0200 Subject: [PATCH 18/19] Commented in the Assert.True() to make the test fail. --- tests/ImageSharp.Tests/Image/ImageEqualTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Image/ImageEqualTests.cs b/tests/ImageSharp.Tests/Image/ImageEqualTests.cs index 422e61d6d..399006012 100644 --- a/tests/ImageSharp.Tests/Image/ImageEqualTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageEqualTests.cs @@ -33,7 +33,7 @@ namespace ImageSharp.Tests using (Image img2 = image2Provider.GetImage()) { bool imagesEqual = AreImagesEqual(img1, img2); - //Assert.True(imagesEqual); + Assert.True(imagesEqual); } } From a710ccca3a2eb993bdc591b701660a4d6c66c92f Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 13 Aug 2017 19:01:06 +1000 Subject: [PATCH 19/19] Fix marker skipping bug when decoding certain images --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 102 +++++++++---------- tests/ImageSharp.Tests/FileTestBase.cs | 4 +- 2 files changed, 50 insertions(+), 56 deletions(-) diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 372ecdc2b..d9df44a09 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -267,41 +267,35 @@ namespace ImageSharp.Formats /// The bytes to convert from. Cannot be null. /// The number of bytes per scanline /// The number of bits per value. - /// The resulting array. Is never null. + /// The resulting array. Is never null. /// is null. /// is less than or equals than zero. - private static byte[] ToArrayByBitsLength(byte[] source, int bytesPerScanline, int bits) + private static Span ToArrayByBitsLength(Span source, int bytesPerScanline, int bits) { Guard.NotNull(source, nameof(source)); Guard.MustBeGreaterThan(bits, 0, nameof(bits)); - byte[] result; - - if (bits < 8) + if (bits >= 8) { - result = new byte[bytesPerScanline * 8 / bits]; - int mask = 0xFF >> (8 - bits); - int resultOffset = 0; + return source; + } - // ReSharper disable once ForCanBeConvertedToForeach - // First byte is the marker so skip. - for (int i = 1; i < bytesPerScanline; i++) + byte[] result = new byte[bytesPerScanline * 8 / bits]; + int mask = 0xFF >> (8 - bits); + int resultOffset = 0; + + for (int i = 0; i < bytesPerScanline; i++) + { + byte b = source[i]; + for (int shift = 0; shift < 8; shift += bits) { - byte b = source[i]; - for (int shift = 0; shift < 8; shift += bits) - { - int colorIndex = (b >> (8 - bits - shift)) & mask; + int colorIndex = (b >> (8 - bits - shift)) & mask; - result[resultOffset] = (byte)colorIndex; + result[resultOffset] = (byte)colorIndex; - resultOffset++; - } + resultOffset++; } } - else - { - result = source; - } return result; } @@ -584,13 +578,15 @@ namespace ImageSharp.Formats { var color = default(TPixel); Span rowSpan = pixels.GetRowSpan(this.currentRow); + + // Trim the first marker byte from the buffer var scanlineBuffer = new Span(defilteredScanline, 1); switch (this.pngColorType) { case PngColorType.Grayscale: int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1); - byte[] newScanline1 = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); + Span newScanline1 = ToArrayByBitsLength(scanlineBuffer, this.bytesPerScanline, this.header.BitDepth); for (int x = 0; x < this.header.Width; x++) { byte intensity = (byte)(newScanline1[x] * factor); @@ -604,10 +600,10 @@ namespace ImageSharp.Formats for (int x = 0; x < this.header.Width; x++) { - int offset = 1 + (x * this.bytesPerPixel); + int offset = x * this.bytesPerPixel; - byte intensity = defilteredScanline[offset]; - byte alpha = defilteredScanline[offset + this.bytesPerSample]; + byte intensity = scanlineBuffer[offset]; + byte alpha = scanlineBuffer[offset + this.bytesPerSample]; color.PackFromRgba32(new Rgba32(intensity, intensity, intensity, alpha)); rowSpan[x] = color; @@ -617,7 +613,7 @@ namespace ImageSharp.Formats case PngColorType.Palette: - this.ProcessScanlineFromPalette(defilteredScanline, rowSpan); + this.ProcessScanlineFromPalette(scanlineBuffer, rowSpan); break; @@ -682,10 +678,10 @@ namespace ImageSharp.Formats /// The type of pixel we are expanding to /// The scanline /// Thecurrent output image row - private void ProcessScanlineFromPalette(byte[] defilteredScanline, Span row) + private void ProcessScanlineFromPalette(Span defilteredScanline, Span row) where TPixel : struct, IPixel { - byte[] newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); + Span newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); byte[] pal = this.palette; var color = default(TPixel); @@ -737,15 +733,15 @@ namespace ImageSharp.Formats { var color = default(TPixel); + // Trim the first marker byte from the buffer + var scanlineBuffer = new Span(defilteredScanline, 1); + switch (this.pngColorType) { case PngColorType.Grayscale: int factor = 255 / ((int)Math.Pow(2, this.header.BitDepth) - 1); - byte[] newScanline1 = ToArrayByBitsLength( - defilteredScanline, - this.bytesPerScanline, - this.header.BitDepth); - for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o++) + Span newScanline1 = ToArrayByBitsLength(scanlineBuffer, this.bytesPerScanline, this.header.BitDepth); + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++) { byte intensity = (byte)(newScanline1[o] * factor); color.PackFromRgba32(new Rgba32(intensity, intensity, intensity)); @@ -756,10 +752,10 @@ namespace ImageSharp.Formats case PngColorType.GrayscaleWithAlpha: - for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o += this.bytesPerPixel) + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel) { - byte intensity = defilteredScanline[o]; - byte alpha = defilteredScanline[o + this.bytesPerSample]; + byte intensity = scanlineBuffer[o]; + byte alpha = scanlineBuffer[o + this.bytesPerSample]; color.PackFromRgba32(new Rgba32(intensity, intensity, intensity, alpha)); rowSpan[x] = color; } @@ -768,14 +764,14 @@ namespace ImageSharp.Formats case PngColorType.Palette: - byte[] newScanline = ToArrayByBitsLength(defilteredScanline, this.bytesPerScanline, this.header.BitDepth); + Span newScanline = ToArrayByBitsLength(scanlineBuffer, this.bytesPerScanline, this.header.BitDepth); var rgba = default(Rgba32); if (this.paletteAlpha != null && this.paletteAlpha.Length > 0) { // If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha // channel and we should try to read it. - for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o++) + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++) { int index = newScanline[o]; int offset = index * 3; @@ -791,7 +787,7 @@ namespace ImageSharp.Formats { rgba.A = 255; - for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o++) + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o++) { int index = newScanline[o]; int offset = index * 3; @@ -815,10 +811,8 @@ namespace ImageSharp.Formats using (var compressed = new Buffer(length)) { // TODO: Should we use pack from vector here instead? - this.From16BitTo8Bit(new Span(defilteredScanline, 1), compressed, length); - for (int x = pixelOffset, o = 0; - x < this.header.Width; - x += increment, o += 3) + this.From16BitTo8Bit(scanlineBuffer, compressed, length); + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 3) { rgba.R = compressed[o]; rgba.G = compressed[o + 1]; @@ -831,11 +825,11 @@ namespace ImageSharp.Formats } else { - for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o += this.bytesPerPixel) + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel) { - rgba.R = defilteredScanline[o]; - rgba.G = defilteredScanline[o + this.bytesPerSample]; - rgba.B = defilteredScanline[o + (2 * this.bytesPerSample)]; + rgba.R = scanlineBuffer[o]; + rgba.G = scanlineBuffer[o + this.bytesPerSample]; + rgba.B = scanlineBuffer[o + (2 * this.bytesPerSample)]; color.PackFromRgba32(rgba); rowSpan[x] = color; @@ -852,7 +846,7 @@ namespace ImageSharp.Formats using (var compressed = new Buffer(length)) { // TODO: Should we use pack from vector here instead? - this.From16BitTo8Bit(new Span(defilteredScanline, 1), compressed, length); + this.From16BitTo8Bit(scanlineBuffer, compressed, length); for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += 4) { rgba.R = compressed[o]; @@ -867,12 +861,12 @@ namespace ImageSharp.Formats } else { - for (int x = pixelOffset, o = 1; x < this.header.Width; x += increment, o += this.bytesPerPixel) + for (int x = pixelOffset, o = 0; x < this.header.Width; x += increment, o += this.bytesPerPixel) { - rgba.R = defilteredScanline[o]; - rgba.G = defilteredScanline[o + this.bytesPerSample]; - rgba.B = defilteredScanline[o + (2 * this.bytesPerSample)]; - rgba.A = defilteredScanline[o + (3 * this.bytesPerSample)]; + rgba.R = scanlineBuffer[o]; + rgba.G = scanlineBuffer[o + this.bytesPerSample]; + rgba.B = scanlineBuffer[o + (2 * this.bytesPerSample)]; + rgba.A = scanlineBuffer[o + (3 * this.bytesPerSample)]; color.PackFromRgba32(rgba); rowSpan[x] = color; diff --git a/tests/ImageSharp.Tests/FileTestBase.cs b/tests/ImageSharp.Tests/FileTestBase.cs index 51a1562f5..08ed69f3e 100644 --- a/tests/ImageSharp.Tests/FileTestBase.cs +++ b/tests/ImageSharp.Tests/FileTestBase.cs @@ -79,8 +79,8 @@ namespace ImageSharp.Tests // TestFile.Create(TestImages.Bmp.NegHeight), // Perf: Enable for local testing only TestFile.Create(TestImages.Png.Splash), // TestFile.Create(TestImages.Png.Cross), // Perf: Enable for local testing only - // TestFile.Create(TestImages.Png.ChunkLength1), // Perf: Enable for local testing only - // TestFile.Create(TestImages.Png.ChunkLength2), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.Bad.ChunkLength1), // Perf: Enable for local testing only + // TestFile.Create(TestImages.Png.Bad.ChunkLength2), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Powerpoint), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Blur), // Perf: Enable for local testing only // TestFile.Create(TestImages.Png.Indexed), // Perf: Enable for local testing only