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);