From ab77171213d1d593b852a0bbdad68a2425bb52a3 Mon Sep 17 00:00:00 2001 From: JimBobSquarePants Date: Fri, 6 Oct 2017 15:49:43 +1100 Subject: [PATCH] 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