From 2e5416ba8d2414ca097afbab4775990f7bde7738 Mon Sep 17 00:00:00 2001 From: JimBobSquarePants Date: Thu, 5 Oct 2017 14:00:19 +1100 Subject: [PATCH 01/10] Update EXIF on rotate. Fix #268 Since we don't know the updated rotation value and EXIF tags only cover certain angles the best fix is to remove the rotate tag. We also update height width if the canvas has been expanded. --- .../Processors/Transforms/RotateProcessor.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs index a7fb400ac..f057c8254 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Helpers; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.MetaData.Profiles.Exif; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; @@ -24,6 +25,11 @@ namespace SixLabors.ImageSharp.Processing.Processors /// private Matrix3x2 processMatrix; + /// + /// The final rotated angle. + /// + private int optimizedRotatedAngle; + /// /// Gets or sets the angle of processMatrix in degrees. /// @@ -87,6 +93,30 @@ namespace SixLabors.ImageSharp.Processing.Processors } } + /// + protected override void AfterImageApply(Image source, Rectangle sourceRectangle) + { + ExifProfile profile = source.MetaData.ExifProfile; + if (profile == null) + { + return; + } + + if (MathF.Abs(this.Angle) < Constants.Epsilon) + { + // No need to do anything so return. + return; + } + + profile.RemoveValue(ExifTag.Orientation); + + if (this.Expand && profile.GetValue(ExifTag.PixelXDimension) != null) + { + profile.SetValue(ExifTag.PixelXDimension, source.Width); + profile.SetValue(ExifTag.PixelYDimension, source.Height); + } + } + /// /// Rotates the images with an optimized method when the angle is 90, 180 or 270 degrees. /// From ff774968f361670179a78e48be2eaa7834b8070b Mon Sep 17 00:00:00 2001 From: JimBobSquarePants Date: Thu, 5 Oct 2017 14:03:27 +1100 Subject: [PATCH 02/10] Remove unused field --- .../Processing/Processors/Transforms/RotateProcessor.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs index f057c8254..86a0c7360 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/RotateProcessor.cs @@ -25,11 +25,6 @@ namespace SixLabors.ImageSharp.Processing.Processors /// private Matrix3x2 processMatrix; - /// - /// The final rotated angle. - /// - private int optimizedRotatedAngle; - /// /// Gets or sets the angle of processMatrix in degrees. /// From 457c4f4b82fc70b3974b57ac75062826e491f6ae Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 5 Oct 2017 23:56:43 +1100 Subject: [PATCH 03/10] Handle corrupted data portions. Fix #358 --- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 13 ++++++++++++- tests/ImageSharp.Tests/TestImages.cs | 1 + tests/Images/Input/Png/big-corrupted-chunk.png | 3 +++ 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 tests/Images/Input/Png/big-corrupted-chunk.png diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 3bca4b261..7149b74d8 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -1124,12 +1124,23 @@ namespace SixLabors.ImageSharp.Formats.Png { var chunk = new PngChunk(); this.ReadChunkLength(chunk); - if (chunk.Length < 0) + + if (chunk.Length == -1) { + // IEND return null; } + if (chunk.Length < 0 || chunk.Length > this.currentStream.Length - this.currentStream.Position) + { + // Not a valid chunk so we skip back all but one of the four bytes we have just read. + // That lets us read one byte at a time until we reach a known chunk. + this.currentStream.Position -= 3; + return chunk; + } + this.ReadChunkType(chunk); + if (chunk.Type == PngChunkTypes.Data) { return chunk; diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index dbcacb4f3..8af9d170b 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -20,6 +20,7 @@ namespace SixLabors.ImageSharp.Tests public const string Blur = "Png/blur.png"; public const string Indexed = "Png/indexed.png"; public const string Splash = "Png/splash.png"; + public const string CorruptedChunk = "Png/big-corrupted-chunk.png"; public const string Cross = "Png/cross.png"; public const string Powerpoint = "Png/pp.png"; public const string SplashInterlaced = "Png/splash-interlaced.png"; diff --git a/tests/Images/Input/Png/big-corrupted-chunk.png b/tests/Images/Input/Png/big-corrupted-chunk.png new file mode 100644 index 000000000..2d46460fc --- /dev/null +++ b/tests/Images/Input/Png/big-corrupted-chunk.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6684985456687682d74b63ad8ef7983f2d6b593a6edc243b1a21c6a64cccf34a +size 9195 From a55cac8a99edceddc0d619250168c2b2a98e48e4 Mon Sep 17 00:00:00 2001 From: JimBobSquarePants Date: Fri, 6 Oct 2017 09:00:25 +1100 Subject: [PATCH 04/10] Add missing test for #359 --- tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs | 1 + tests/ImageSharp.Tests/TestImages.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index fc759fb56..d39d0651d 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -24,6 +24,7 @@ namespace SixLabors.ImageSharp.Tests TestImages.Png.Splash, TestImages.Png.Indexed, TestImages.Png.FilterVar, TestImages.Png.Bad.ChunkLength1, + TestImages.Png.Bad.CorruptedChunk, TestImages.Png.VimImage1, TestImages.Png.VersioningImage1, diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 8af9d170b..c2a6ed1ad 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -20,7 +20,6 @@ namespace SixLabors.ImageSharp.Tests public const string Blur = "Png/blur.png"; public const string Indexed = "Png/indexed.png"; public const string Splash = "Png/splash.png"; - public const string CorruptedChunk = "Png/big-corrupted-chunk.png"; public const string Cross = "Png/cross.png"; public const string Powerpoint = "Png/pp.png"; public const string SplashInterlaced = "Png/splash-interlaced.png"; @@ -57,6 +56,7 @@ namespace SixLabors.ImageSharp.Tests // Odd chunk lengths public const string ChunkLength1 = "Png/chunklength1.png"; public const string ChunkLength2 = "Png/chunklength2.png"; + public const string CorruptedChunk = "Png/big-corrupted-chunk.png"; } public static readonly string[] All = From 9e3729b1527d5e2bdfa1e2b03a0a9c99987dd0bc Mon Sep 17 00:00:00 2001 From: JimBobSquarePants Date: Fri, 6 Oct 2017 11:34:16 +1100 Subject: [PATCH 05/10] Ensure colormap cache is cleared --- src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs | 1 + src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs | 1 + src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs | 1 + 3 files changed, 3 insertions(+) diff --git a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs index 49adce23b..8766f1042 100644 --- a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs @@ -61,6 +61,7 @@ namespace SixLabors.ImageSharp.Quantizers this.colors = (byte)maxColors.Clamp(1, 255); this.octree = new Octree(this.GetBitsNeededForColorDepth(this.colors)); this.palette = null; + this.colorMap.Clear(); return base.Quantize(image, this.colors); } diff --git a/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs index ca11a042f..0b95c09a6 100644 --- a/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs @@ -58,6 +58,7 @@ namespace SixLabors.ImageSharp.Quantizers public override QuantizedImage Quantize(ImageFrame image, int maxColors) { Array.Resize(ref this.colors, maxColors.Clamp(1, 255)); + this.colorMap.Clear(); return base.Quantize(image, maxColors); } diff --git a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs index 77c421468..8ab390f4e 100644 --- a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs @@ -139,6 +139,7 @@ namespace SixLabors.ImageSharp.Quantizers this.colors = maxColors.Clamp(1, 255); this.palette = null; + this.colorMap.Clear(); try { From 3909313a87f774c0aa22ebb3e7742d302f0a3794 Mon Sep 17 00:00:00 2001 From: JimBobSquarePants Date: Fri, 6 Oct 2017 11:34:41 +1100 Subject: [PATCH 06/10] FS makes much smaller files --- src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs b/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs index 7f58ff1bf..20ba2e637 100644 --- a/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs +++ b/src/ImageSharp/Quantizers/QuantizerBase{TPixel}.cs @@ -43,7 +43,7 @@ namespace SixLabors.ImageSharp.Quantizers.Base public bool Dither { get; set; } = true; /// - public IErrorDiffuser DitherType { get; set; } = new SierraLiteDiffuser(); + public IErrorDiffuser DitherType { get; set; } = new FloydSteinbergDiffuser(); /// public virtual QuantizedImage Quantize(ImageFrame image, int maxColors) From ab77171213d1d593b852a0bbdad68a2425bb52a3 Mon Sep 17 00:00:00 2001 From: JimBobSquarePants Date: Fri, 6 Oct 2017 15:49:43 +1100 Subject: [PATCH 07/10] Quality 0 and 1 should be equal --- .../Jpeg/GolangPort/JpegEncoderCore.cs | 49 +++++++---------- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 2 - .../Formats/Jpg/JpegEncoderTests.cs | 54 +++++++++++++++++-- 3 files changed, 70 insertions(+), 35 deletions(-) diff --git a/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs index 2deb3f62d..2912a8719 100644 --- a/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/GolangPort/JpegEncoderCore.cs @@ -125,6 +125,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// private readonly byte[] huffmanBuffer = new byte[179]; + /// + /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. + /// + private readonly bool ignoreMetadata; + + /// + /// The quality, that will be used to encode the image. + /// + private readonly int quality; + + /// + /// Gets or sets the subsampling method to use. + /// + private readonly JpegSubsample? subsample; + /// /// The accumulated bits to write to the stream. /// @@ -150,37 +165,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort /// private Stream outputStream; - /// - /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded. - /// - private bool ignoreMetadata = false; - - /// - /// Gets or sets the quality, that will be used to encode the image. Quality - /// index must be between 0 and 100 (compression from max to min). - /// - /// The quality of the jpg image from 0 to 100. - private int quality = 0; - - /// - /// Gets or sets the subsampling method to use. - /// - private JpegSubsample? subsample; - /// /// Initializes a new instance of the class. /// /// The options public JpegEncoderCore(IJpegEncoderOptions options) { - int quality = options.Quality; - if (quality == 0) - { - quality = 75; - } - - this.quality = quality; - this.subsample = options.Subsample ?? (quality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420); + // System.Drawing produces identical output for jpegs with a quality parameter of 0 and 1. + this.quality = options.Quality.Clamp(1, 100); + this.subsample = options.Subsample ?? (this.quality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420); this.ignoreMetadata = options.IgnoreMetadata; } @@ -205,17 +198,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.GolangPort this.outputStream = stream; - int quality = this.quality.Clamp(1, 100); - // Convert from a quality rating to a scaling factor. int scale; if (this.quality < 50) { - scale = 5000 / quality; + scale = 5000 / this.quality; } else { - scale = 200 - (quality * 2); + scale = 200 - (this.quality * 2); } // Initialize the quantization tables. diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index 3d79faabc..a14fa16a5 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -21,13 +21,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// Gets or sets the quality, that will be used to encode the image. Quality /// index must be between 0 and 100 (compression from max to min). /// - /// The quality of the jpg image from 0 to 100. public int Quality { get; set; } /// /// Gets or sets the subsample ration, that will be used to encode the image. /// - /// The subsample ratio of the jpg image. public JpegSubsample? Subsample { get; set; } /// diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs index 3bd1ed265..c8d416bea 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs @@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg public void LoadResizeSave(TestImageProvider provider, int quality, JpegSubsample subsample) where TPixel : struct, IPixel { - using (Image image = provider.GetImage(x=>x.Resize(new ResizeOptions { Size = new Size(150, 100), Mode = ResizeMode.Max }))) + using (Image image = provider.GetImage(x => x.Resize(new ResizeOptions { Size = new Size(150, 100), Mode = ResizeMode.Max }))) { image.MetaData.ExifProfile = null; // Reduce the size of the file @@ -62,8 +62,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { image.Save(outputStream, new JpegEncoder() { - Subsample = subSample, - Quality = quality + Subsample = subSample, + Quality = quality }); } } @@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg { using (MemoryStream memStream = new MemoryStream()) { - input.Save(memStream, options); + input.Save(memStream, options); memStream.Position = 0; using (Image output = Image.Load(memStream)) @@ -118,5 +118,51 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg } } } + + [Fact] + public void Encode_Quality_0_And_1_Are_Identical() + { + var options = new JpegEncoder + { + Quality = 0 + }; + + var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora); + + using (Image input = testFile.CreateImage()) + using (var memStream0 = new MemoryStream()) + using (var memStream1 = new MemoryStream()) + { + input.SaveAsJpeg(memStream0, options); + + options.Quality = 1; + input.SaveAsJpeg(memStream1, options); + + Assert.Equal(memStream0.ToArray(), memStream1.ToArray()); + } + } + + [Fact] + public void Encode_Quality_0_And_100_Are_Not_Identical() + { + var options = new JpegEncoder + { + Quality = 0 + }; + + var testFile = TestFile.Create(TestImages.Jpeg.Baseline.Calliphora); + + using (Image input = testFile.CreateImage()) + using (var memStream0 = new MemoryStream()) + using (var memStream1 = new MemoryStream()) + { + input.SaveAsJpeg(memStream0, options); + + options.Quality = 100; + input.SaveAsJpeg(memStream1, options); + + Assert.NotEqual(memStream0.ToArray(), memStream1.ToArray()); + } + } } } \ No newline at end of file From aefa4b0ca529eb96a832d669bfa65d531bbe21fc Mon Sep 17 00:00:00 2001 From: JimBobSquarePants Date: Fri, 6 Oct 2017 16:53:01 +1100 Subject: [PATCH 08/10] Whoops! no default value --- src/ImageSharp/Formats/Jpeg/JpegEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs index a14fa16a5..8850f581c 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegEncoder.cs @@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// Gets or sets the quality, that will be used to encode the image. Quality /// index must be between 0 and 100 (compression from max to min). /// - public int Quality { get; set; } + public int Quality { get; set; } = 75; /// /// Gets or sets the subsample ration, that will be used to encode the image. From e39a3e0134c70f5b082a9c9b5b3d308174b198fa Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 8 Oct 2017 12:04:54 +1100 Subject: [PATCH 09/10] Update Xunit and Moq --- tests/ImageSharp.Tests/ImageSharp.Tests.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index e8a6e8c59..b3c241c22 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -17,10 +17,10 @@ - - + + - + From b48feb1207a600eb28321d4d0f02e117ff570077 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 8 Oct 2017 12:11:35 +1100 Subject: [PATCH 10/10] Remove SDK --- tests/ImageSharp.Tests/ImageSharp.Tests.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index b3c241c22..2f45e4c83 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -19,7 +19,6 @@ -