diff --git a/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs b/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs index dc1801f6d..fb477dda8 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs @@ -28,7 +28,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// Any quality below it results in a highly compressed jpeg image /// which shouldn't use standard itu quantization tables for re-encoding. /// - public const int QualityEstimationConfidenceThreshold = 25; + public const int QualityEstimationConfidenceLowerThreshold = 25; + + /// + /// Represents highest quality setting which can be estimated with enough confidence. + /// + public const int QualityEstimationConfidenceUpperThreshold = 98; /// /// Threshold at which given luminance quantization table should be considered 'standard'. @@ -103,7 +108,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// Quantization to estimate against. /// Estimated quality /// indicating if given table is target-complient - public static double EstimateQuality(ref Block8x8F table, ReadOnlySpan target, out double quality) + public static double EstimateQuality(ref Block8x8F table, ReadOnlySpan target, out int quality) { // This method can be SIMD'ified if standard table is injected as Block8x8F. // Or when we go to full-int16 spectral code implementation and inject both tables as Block8x8. @@ -155,11 +160,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components // Generate the equivalent IJQ "quality" factor if (sumPercent <= 100.0) { - quality = (200 - sumPercent) / 2; + quality = (int)Math.Round((200 - sumPercent) / 2); } else { - quality = 5000.0 / sumPercent; + quality = (int)Math.Round(5000.0 / sumPercent); } return sumPercentSqr - (sumPercent * sumPercent); @@ -172,7 +177,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// Output jpeg quality. /// indicating if given table is ITU-complient. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool EstimateLuminanceQuality(ref Block8x8F luminanceTable, out double quality) + public static bool EstimateLuminanceQuality(ref Block8x8F luminanceTable, out int quality) { double variance = EstimateQuality(ref luminanceTable, UnscaledQuant_Luminance, out quality); return variance <= StandardLuminanceTableVarianceThreshold; @@ -185,7 +190,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components /// Output jpeg quality. /// indicating if given table is ITU-complient. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool EstimateChrominanceQuality(ref Block8x8F chrominanceTable, out double quality) + public static bool EstimateChrominanceQuality(ref Block8x8F chrominanceTable, out int quality) { double variance = EstimateQuality(ref chrominanceTable, UnscaledQuant_Chrominance, out quality); return variance <= StandardChrominanceTableVarianceThreshold; diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index 09b40e09d..b58e99a10 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -833,7 +833,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // if quantization table is non-complient to stardard itu table // we can't reacreate it later with calculated quality as this is an approximation // so we save it in the metadata - if (!Quantization.EstimateLuminanceQuality(ref table, out double quality)) + if (!Quantization.EstimateLuminanceQuality(ref table, out int quality)) { jpegMetadata.LumaQuantizationTable = table.RoundAsInt16Block(); } @@ -848,7 +848,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // if quantization table is non-complient to stardard itu table // we can't reacreate it later with calculated quality as this is an approximation // so we save it in the metadata - if (!Quantization.EstimateChrominanceQuality(ref table, out double quality)) + if (!Quantization.EstimateChrominanceQuality(ref table, out int quality)) { jpegMetadata.ChromaQuantizationTable = table.RoundAsInt16Block(); } diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs index a768f6651..e6183705d 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs @@ -56,7 +56,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// This value might not be accurate if it was calculated during jpeg decoding /// with non-complient ITU quantization tables. /// - public double LuminanceQuality { get; set; } + public int LuminanceQuality { get; set; } /// /// Gets or sets the jpeg chrominance quality. @@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg /// This value might not be accurate if it was calculated during jpeg decoding /// with non-complient ITU quantization tables. /// - public double ChrominanceQuality { get; set; } + public int ChrominanceQuality { get; set; } /// /// Gets a value indicating whether jpeg luminance data was encoded using ITU complient quantization table. @@ -83,7 +83,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg // [Obsolete("Use LumanQuality and ChromaQuality instead. Quality is now separated for luminance and chrominance data.")] public int Quality { - get => (int)Math.Round((this.LuminanceQuality + this.ChrominanceQuality) / 2); + get => (int)Math.Round((this.LuminanceQuality + this.ChrominanceQuality) / 2f); set { this.LuminanceQuality = value; diff --git a/tests/ImageSharp.Tests/Formats/Jpg/QuantizationTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/QuantizationTests.cs index 1870c39fa..8ed14bd81 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/QuantizationTests.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/QuantizationTests.cs @@ -20,8 +20,37 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg private ITestOutputHelper Output { get; } - //[Fact(Skip = "Debug only, enable manually!")] [Fact] + public void QualityEstimationFromStandardEncoderTables_Luminance() + { + int firstIndex = JpegQuantization.QualityEstimationConfidenceLowerThreshold; + int lastIndex = JpegQuantization.QualityEstimationConfidenceUpperThreshold; + for (int quality = firstIndex; quality <= lastIndex; quality++) + { + Block8x8F table = JpegQuantization.ScaleLuminanceTable(quality); + bool isStrandard = JpegQuantization.EstimateLuminanceQuality(ref table, out int actualQuality); + + Assert.True(isStrandard, $"Standard table is estimated to be non-spec complient at quality level {quality}"); + Assert.Equal(quality, actualQuality); + } + } + + [Fact] + public void QualityEstimationFromStandardEncoderTables_Chrominance() + { + int firstIndex = JpegQuantization.QualityEstimationConfidenceLowerThreshold; + int lastIndex = JpegQuantization.QualityEstimationConfidenceUpperThreshold; + for (int quality = firstIndex; quality <= lastIndex; quality++) + { + Block8x8F table = JpegQuantization.ScaleChrominanceTable(quality); + bool isStrandard = JpegQuantization.EstimateChrominanceQuality(ref table, out int actualQuality); + + Assert.True(isStrandard, $"Standard table is estimated to be non-spec complient at quality level {quality}"); + Assert.Equal(quality, actualQuality); + } + } + + [Fact(Skip = "Debug only, enable manually!")] public void PrintVariancesFromStandardTables_Luminance() { this.Output.WriteLine("Variances for Luminance table.\nQuality levels 25-100:"); @@ -29,10 +58,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg double minVariance = double.MaxValue; double maxVariance = double.MinValue; - for (int q = JpegQuantization.QualityEstimationConfidenceThreshold; q <= JpegQuantization.MaxQualityFactor; q++) + for (int q = JpegQuantization.QualityEstimationConfidenceLowerThreshold; q <= JpegQuantization.MaxQualityFactor; q++) { Block8x8F table = JpegQuantization.ScaleLuminanceTable(q); - double variance = JpegQuantization.EstimateQuality(ref table, JpegQuantization.UnscaledQuant_Luminance, out double quality); + double variance = JpegQuantization.EstimateQuality(ref table, JpegQuantization.UnscaledQuant_Luminance, out int quality); minVariance = Math.Min(minVariance, variance); maxVariance = Math.Max(maxVariance, variance); @@ -43,18 +72,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg this.Output.WriteLine($"Min variance: {minVariance}\nMax variance: {maxVariance}"); } - //[Fact(Skip = "Debug only, enable manually!")] - [Fact] + [Fact(Skip = "Debug only, enable manually!")] public void PrintVariancesFromStandardTables_Chrominance() { this.Output.WriteLine("Variances for Chrominance table.\nQuality levels 25-100:"); double minVariance = double.MaxValue; double maxVariance = double.MinValue; - for (int q = JpegQuantization.QualityEstimationConfidenceThreshold; q <= JpegQuantization.MaxQualityFactor; q++) + for (int q = JpegQuantization.QualityEstimationConfidenceLowerThreshold; q <= JpegQuantization.MaxQualityFactor; q++) { Block8x8F table = JpegQuantization.ScaleChrominanceTable(q); - double variance = JpegQuantization.EstimateQuality(ref table, JpegQuantization.UnscaledQuant_Chrominance, out double quality); + double variance = JpegQuantization.EstimateQuality(ref table, JpegQuantization.UnscaledQuant_Chrominance, out int quality); minVariance = Math.Min(minVariance, variance); maxVariance = Math.Max(maxVariance, variance);