Browse Source

Made quality metadata int, added tests for standard tables

pull/1706/head
Dmitry Pentin 5 years ago
parent
commit
e40313cc5e
  1. 17
      src/ImageSharp/Formats/Jpeg/Components/Quantization.cs
  2. 4
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  3. 6
      src/ImageSharp/Formats/Jpeg/JpegMetadata.cs
  4. 42
      tests/ImageSharp.Tests/Formats/Jpg/QuantizationTests.cs

17
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.
/// </summary>
public const int QualityEstimationConfidenceThreshold = 25;
public const int QualityEstimationConfidenceLowerThreshold = 25;
/// <summary>
/// Represents highest quality setting which can be estimated with enough confidence.
/// </summary>
public const int QualityEstimationConfidenceUpperThreshold = 98;
/// <summary>
/// Threshold at which given luminance quantization table should be considered 'standard'.
@ -103,7 +108,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <param name="target">Quantization to estimate against.</param>
/// <param name="quality">Estimated quality</param>
/// <returns><see cref="bool"/> indicating if given table is target-complient</returns>
public static double EstimateQuality(ref Block8x8F table, ReadOnlySpan<byte> target, out double quality)
public static double EstimateQuality(ref Block8x8F table, ReadOnlySpan<byte> 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
/// <param name="quality">Output jpeg quality.</param>
/// <returns><see cref="bool"/> indicating if given table is ITU-complient.</returns>
[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
/// <param name="quality">Output jpeg quality.</param>
/// <returns><see cref="bool"/> indicating if given table is ITU-complient.</returns>
[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;

4
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();
}

6
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.
/// </remarks>
public double LuminanceQuality { get; set; }
public int LuminanceQuality { get; set; }
/// <summary>
/// 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.
/// </remarks>
public double ChrominanceQuality { get; set; }
public int ChrominanceQuality { get; set; }
/// <summary>
/// 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;

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

Loading…
Cancel
Save