diff --git a/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs b/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs
index fc602b7f8..7e528d056 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs
@@ -40,30 +40,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
///
public const int QualityEstimationConfidenceUpperThreshold = 98;
- ///
- /// Threshold at which given luminance quantization table should be considered 'standard'.
- /// Bigger the variance - more likely it to be a non-ITU complient table.
- ///
- ///
- /// Jpeg does not define either 'quality' nor 'standard quantization table' properties
- /// so this is purely a practical value derived from tests.
- /// For actual variances output against standard table see tests at Formats.Jpg.QuantizationTests.PrintVariancesFromStandardTables_*.
- /// Actual value is 2.3629059983706604, truncated unsignificant part.
- ///
- public const double StandardLuminanceTableVarianceThreshold = 2.36291;
-
- ///
- /// Threshold at which given chrominance quantization table should be considered 'standard'.
- /// Bigger the variance - more likely it to be a non-ITU complient table.
- ///
- ///
- /// Jpeg does not define either 'quality' nor 'standard quantization table' properties
- /// so this is purely a practical value derived from tests.
- /// For actual variances output against standard table see tests at Formats.Jpg.QuantizationTests.PrintVariancesFromStandardTables_*.
- /// Actual value is 0.8949631033036098, truncated unsignificant part.
- ///
- public const double StandardChrominanceTableVarianceThreshold = 0.894963;
-
///
/// Gets the unscaled luminance quantization table in zig-zag order. Each
/// encoder copies and scales the tables according to its quality parameter.
@@ -113,25 +89,24 @@ 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 int quality)
+ public static int EstimateQuality(ref Block8x8F table, ReadOnlySpan target)
{
// 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.
double comparePercent;
double sumPercent = 0;
- double sumPercentSqr = 0;
// Corner case - all 1's => 100 quality
// It would fail to deduce using algorithm below without this check
if (table.EqualsToScalar(1))
{
// While this is a 100% to be 100 quality, any given table can be scaled to all 1's.
- // According to jpeg creators, top of the line quality is 99, 100 is just a technical 'limit' will affect result filesize drastically.
+ // According to jpeg creators, top of the line quality is 99, 100 is just a technical 'limit' which will affect result filesize drastically.
// Quality=100 shouldn't be used in usual use case.
- quality = 100;
- return 0;
+ return 100;
}
+ int quality;
for (int i = 0; i < Block8x8F.Size; i++)
{
float coeff = table[i];
@@ -152,7 +127,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
}
sumPercent += comparePercent;
- sumPercentSqr += comparePercent * comparePercent;
}
// Perform some statistical analysis of the quality factor
@@ -160,7 +134,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
// table being a scaled version of the "standard" tables.
// If the variance is high, it is unlikely to be the case.
sumPercent /= 64.0;
- sumPercentSqr /= 64.0;
// Generate the equivalent IJQ "quality" factor
if (sumPercent <= 100.0)
@@ -172,7 +145,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
quality = (int)Math.Round(5000.0 / sumPercent);
}
- return sumPercentSqr - (sumPercent * sumPercent);
+ return quality;
}
///
@@ -182,11 +155,8 @@ 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 int quality)
- {
- double variance = EstimateQuality(ref luminanceTable, UnscaledQuant_Luminance, out quality);
- return variance <= StandardLuminanceTableVarianceThreshold;
- }
+ public static int EstimateLuminanceQuality(ref Block8x8F luminanceTable)
+ => EstimateQuality(ref luminanceTable, UnscaledQuant_Luminance);
///
/// Estimates jpeg quality based on quantization table in zig-zag order.
@@ -195,11 +165,8 @@ 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 int quality)
- {
- double variance = EstimateQuality(ref chrominanceTable, UnscaledQuant_Chrominance, out quality);
- return variance <= StandardChrominanceTableVarianceThreshold;
- }
+ public static int EstimateChrominanceQuality(ref Block8x8F chrominanceTable)
+ => EstimateQuality(ref chrominanceTable, UnscaledQuant_Luminance);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int QualityToScale(int quality)
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
index cf21dd226..b6d5aafd1 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
@@ -830,30 +830,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// luminance table
case 0:
{
- // 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 int quality))
- {
- jpegMetadata.LuminanceQuantizationTable = table;
- }
-
- jpegMetadata.LuminanceQuality = quality;
+ jpegMetadata.LuminanceQuality = Quantization.EstimateLuminanceQuality(ref table);
break;
}
// chrominance table
case 1:
{
- // 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 int quality))
- {
- jpegMetadata.ChromaQuantizationTable = table;
- }
-
- jpegMetadata.ChrominanceQuality = quality;
+ jpegMetadata.ChrominanceQuality = Quantization.EstimateChrominanceQuality(ref table);
break;
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
index 828e03de7..88d96f554 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
@@ -41,12 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
///
/// The quality, that will be used to encode the image.
///
- private readonly int? luminanceQuality;
-
- ///
- /// The quality, that will be used to encode the image.
- ///
- private readonly int? chrominanceQuality;
+ private readonly int? quality;
///
/// Gets or sets the subsampling method to use.
@@ -64,8 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// The options
public JpegEncoderCore(IJpegEncoderOptions options)
{
- this.luminanceQuality = options.Quality;
- this.chrominanceQuality = options.Quality;
+ this.quality = options.Quality;
this.subsample = options.Subsample;
this.colorType = options.ColorType;
}
@@ -654,30 +648,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// Output chrominance quantization table.
private void InitQuantizationTables(int componentCount, JpegMetadata metadata, out Block8x8F luminanceQuantTable, out Block8x8F chrominanceQuantTable)
{
- if (this.luminanceQuality.HasValue)
+ int lumaQuality;
+ int chromaQuality;
+ if (this.quality.HasValue)
{
- int lumaQuality = Numerics.Clamp(this.luminanceQuality.Value, 1, 100);
- luminanceQuantTable = Quantization.ScaleLuminanceTable(lumaQuality);
+ lumaQuality = this.quality.Value;
+ chromaQuality = this.quality.Value;
}
else
{
- luminanceQuantTable = metadata.LuminanceQuantizationTable;
+ lumaQuality = metadata.LuminanceQuality;
+ chromaQuality = metadata.ChrominanceQuality;
}
+ // Luminance
+ lumaQuality = Numerics.Clamp(lumaQuality, 1, 100);
+ luminanceQuantTable = Quantization.ScaleLuminanceTable(lumaQuality);
+
+ // Chrominance
chrominanceQuantTable = default;
if (componentCount > 1)
{
- int chromaQuality;
- if (this.chrominanceQuality.HasValue)
- {
- chromaQuality = Numerics.Clamp(this.chrominanceQuality.Value, 1, 100);
- chrominanceQuantTable = Quantization.ScaleLuminanceTable(chromaQuality);
- }
- else
- {
- chromaQuality = Numerics.Clamp(metadata.ChrominanceQuality ?? Quantization.DefaultQualityFactor, 1, 100);
- chrominanceQuantTable = metadata.ChromaQuantizationTable;
- }
+ chromaQuality = Numerics.Clamp(chromaQuality, 1, 100);
+ chrominanceQuantTable = Quantization.ScaleChrominanceTable(chromaQuality);
if (!this.subsample.HasValue)
{
diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs
index 77d27ee93..1b17bdce7 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs
@@ -11,8 +11,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
///
public class JpegMetadata : IDeepCloneable
{
- private Block8x8F? lumaQuantTable;
- private Block8x8F? chromaQuantTable;
+ private int? luminanceQuality;
+ private int? chrominanceQuality;
///
/// Initializes a new instance of the class.
@@ -29,46 +29,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
this.ColorType = other.ColorType;
- this.LuminanceQuantizationTable = other.LuminanceQuantizationTable;
- this.ChromaQuantizationTable = other.ChromaQuantizationTable;
- this.LuminanceQuality = other.LuminanceQuality;
- this.ChrominanceQuality = other.ChrominanceQuality;
- }
-
- ///
- /// Gets or sets luminance qunatization table for jpeg image.
- ///
- internal Block8x8F LuminanceQuantizationTable
- {
- get
- {
- if (this.lumaQuantTable.HasValue)
- {
- return this.lumaQuantTable.Value;
- }
-
- return Quantization.ScaleLuminanceTable(this.LuminanceQuality ?? Quantization.DefaultQualityFactor);
- }
-
- set => this.lumaQuantTable = value;
- }
-
- ///
- /// Gets or sets chrominance qunatization table for jpeg image.
- ///
- internal Block8x8F ChromaQuantizationTable
- {
- get
- {
- if (this.chromaQuantTable.HasValue)
- {
- return this.chromaQuantTable.Value;
- }
-
- return Quantization.ScaleChrominanceTable(this.ChrominanceQuality ?? Quantization.DefaultQualityFactor);
- }
-
- set => this.chromaQuantTable = value;
+ this.luminanceQuality = other.luminanceQuality;
+ this.chrominanceQuality = other.chrominanceQuality;
}
///
@@ -78,7 +40,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// This value might not be accurate if it was calculated during jpeg decoding
/// with non-complient ITU quantization tables.
///
- internal int? LuminanceQuality { get; set; }
+ internal int LuminanceQuality
+ {
+ get => this.luminanceQuality ?? Quantization.DefaultQualityFactor;
+ set => this.luminanceQuality = value;
+ }
///
/// Gets or sets the jpeg chrominance quality.
@@ -87,7 +53,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// This value might not be accurate if it was calculated during jpeg decoding
/// with non-complient ITU quantization tables.
///
- internal int? ChrominanceQuality { get; set; }
+ internal int ChrominanceQuality
+ {
+ get => this.chrominanceQuality ?? Quantization.DefaultQualityFactor;
+ set => this.chrominanceQuality = value;
+ }
///
/// Gets or sets the encoded quality.
@@ -96,25 +66,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// Note that jpeg image can have different quality for luminance and chrominance components.
/// This property returns maximum value of luma/chroma qualities.
///
- public int? Quality
+ public int Quality
{
get
{
// Jpeg always has a luminance table thus it must have a luminance quality derived from it
- if (!this.LuminanceQuality.HasValue)
+ if (!this.luminanceQuality.HasValue)
{
- return null;
+ return Quantization.DefaultQualityFactor;
}
- // Jpeg might not have a chrominance table
- if (!this.ChrominanceQuality.HasValue)
+ int lumaQuality = this.luminanceQuality.Value;
+
+ // Jpeg might not have a chrominance table - return luminance quality (grayscale images)
+ if (!this.chrominanceQuality.HasValue)
{
- return this.LuminanceQuality.Value;
+ return lumaQuality;
}
+ int chromaQuality = this.chrominanceQuality.Value;
+
// Theoretically, luma quality would always be greater or equal to chroma quality
// But we've already encountered images which can have higher quality of chroma components
- return Math.Max(this.LuminanceQuality.Value, this.ChrominanceQuality.Value);
+ return Math.Max(lumaQuality, chromaQuality);
}
set
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/QuantizationTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/QuantizationTests.cs
index 8ed14bd81..2f673ef2f 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/QuantizationTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/QuantizationTests.cs
@@ -13,13 +13,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Trait("Format", "Jpg")]
public class QuantizationTests
{
- public QuantizationTests(ITestOutputHelper output)
- {
- this.Output = output;
- }
-
- private ITestOutputHelper Output { get; }
-
[Fact]
public void QualityEstimationFromStandardEncoderTables_Luminance()
{
@@ -28,10 +21,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
for (int quality = firstIndex; quality <= lastIndex; quality++)
{
Block8x8F table = JpegQuantization.ScaleLuminanceTable(quality);
- bool isStrandard = JpegQuantization.EstimateLuminanceQuality(ref table, out int actualQuality);
+ int estimatedQuality = JpegQuantization.EstimateLuminanceQuality(ref table);
- Assert.True(isStrandard, $"Standard table is estimated to be non-spec complient at quality level {quality}");
- Assert.Equal(quality, actualQuality);
+ Assert.True(quality.Equals(estimatedQuality), $"Failed to estimate luminance quality for standard table at quality level {quality}");
}
}
@@ -43,54 +35,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
for (int quality = firstIndex; quality <= lastIndex; quality++)
{
Block8x8F table = JpegQuantization.ScaleChrominanceTable(quality);
- bool isStrandard = JpegQuantization.EstimateChrominanceQuality(ref table, out int actualQuality);
+ int estimatedQuality = JpegQuantization.EstimateChrominanceQuality(ref table);
- Assert.True(isStrandard, $"Standard table is estimated to be non-spec complient at quality level {quality}");
- Assert.Equal(quality, actualQuality);
+ Assert.True(quality.Equals(estimatedQuality), $"Failed to estimate chrominance quality for standard table at quality level {quality}");
}
}
-
- [Fact(Skip = "Debug only, enable manually!")]
- public void PrintVariancesFromStandardTables_Luminance()
- {
- this.Output.WriteLine("Variances for Luminance table.\nQuality levels 25-100:");
-
- double minVariance = double.MaxValue;
- double maxVariance = double.MinValue;
-
- for (int q = JpegQuantization.QualityEstimationConfidenceLowerThreshold; q <= JpegQuantization.MaxQualityFactor; q++)
- {
- Block8x8F table = JpegQuantization.ScaleLuminanceTable(q);
- double variance = JpegQuantization.EstimateQuality(ref table, JpegQuantization.UnscaledQuant_Luminance, out int quality);
-
- minVariance = Math.Min(minVariance, variance);
- maxVariance = Math.Max(maxVariance, variance);
-
- this.Output.WriteLine($"q={q}\t{variance}\test. q: {quality}");
- }
-
- this.Output.WriteLine($"Min variance: {minVariance}\nMax variance: {maxVariance}");
- }
-
- [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.QualityEstimationConfidenceLowerThreshold; q <= JpegQuantization.MaxQualityFactor; q++)
- {
- Block8x8F table = JpegQuantization.ScaleChrominanceTable(q);
- double variance = JpegQuantization.EstimateQuality(ref table, JpegQuantization.UnscaledQuant_Chrominance, out int quality);
-
- minVariance = Math.Min(minVariance, variance);
- maxVariance = Math.Max(maxVariance, variance);
-
- this.Output.WriteLine($"q={q}\t{variance}\test. q: {quality}");
- }
-
- this.Output.WriteLine($"Min variance: {minVariance}\nMax variance: {maxVariance}");
- }
}
}