From b19f65560210bee73db3007019ad971e10941db6 Mon Sep 17 00:00:00 2001 From: JimBobSquarePants Date: Thu, 5 Oct 2017 14:00:19 +1100 Subject: [PATCH 1/8] 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 14756d7a603857ec8ccaa78037023810667fded9 Mon Sep 17 00:00:00 2001 From: JimBobSquarePants Date: Thu, 5 Oct 2017 14:03:27 +1100 Subject: [PATCH 2/8] 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 f741b3a2a0803cb34b560169b2b719e624ca82da Mon Sep 17 00:00:00 2001 From: JimBobSquarePants Date: Fri, 6 Oct 2017 11:34:16 +1100 Subject: [PATCH 3/8] 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 5237688ce4ab4e74a2f4f18cfb05469e0316cdb0 Mon Sep 17 00:00:00 2001 From: JimBobSquarePants Date: Fri, 6 Oct 2017 11:34:41 +1100 Subject: [PATCH 4/8] 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 eeab5854d0c2857c485916ba934061a5ab0ab910 Mon Sep 17 00:00:00 2001 From: JimBobSquarePants Date: Fri, 6 Oct 2017 15:49:43 +1100 Subject: [PATCH 5/8] 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 3a8f4fd39411a4bdf70dfad22588262f5815326f Mon Sep 17 00:00:00 2001 From: JimBobSquarePants Date: Fri, 6 Oct 2017 16:53:01 +1100 Subject: [PATCH 6/8] 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 b0f140af87e4a3e67985aeefd196030aae976c15 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 8 Oct 2017 12:04:54 +1100 Subject: [PATCH 7/8] 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 051088160a7704e05259371c5e4f17cfb947b7f2 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 8 Oct 2017 12:11:35 +1100 Subject: [PATCH 8/8] 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 @@ -