Browse Source

Fixed invalid quality estimation

pull/1706/head
Dmitry Pentin 5 years ago
parent
commit
2494131cfa
  1. 48
      src/ImageSharp/Formats/Jpeg/Components/Quantization.cs
  2. 16
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  3. 51
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

48
src/ImageSharp/Formats/Jpeg/Components/Quantization.cs

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text; using System.Text;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components
@ -20,7 +21,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// Jpeg does not define either 'quality' nor 'standard quantization table' properties /// Jpeg does not define either 'quality' nor 'standard quantization table' properties
/// so this is purely a practical value derived from tests. /// so this is purely a practical value derived from tests.
/// </remarks> /// </remarks>
public const double StandardLuminanceTableVarianceThreshold = 10.0; private const double StandardLuminanceTableVarianceThreshold = 10.0;
/// <summary> /// <summary>
/// Threshold at which given chrominance quantization table should be considered 'standard'. /// Threshold at which given chrominance quantization table should be considered 'standard'.
@ -30,7 +31,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// Jpeg does not define either 'quality' nor 'standard quantization table' properties /// Jpeg does not define either 'quality' nor 'standard quantization table' properties
/// so this is purely a practical value derived from tests. /// so this is purely a practical value derived from tests.
/// </remarks> /// </remarks>
public const double StandardChrominanceTableVarianceThreshold = 10.0; private const double StandardChrominanceTableVarianceThreshold = 10.0;
/// <summary> /// <summary>
/// Gets the unscaled luminance quantization table in zig-zag order. Each /// Gets the unscaled luminance quantization table in zig-zag order. Each
@ -41,7 +42,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
// The C# compiler emits this as a compile-time constant embedded in the PE file. // The C# compiler emits this as a compile-time constant embedded in the PE file.
// This is effectively compiled down to: return new ReadOnlySpan<byte>(&data, length) // This is effectively compiled down to: return new ReadOnlySpan<byte>(&data, length)
// More details can be found: https://github.com/dotnet/roslyn/pull/24621 // More details can be found: https://github.com/dotnet/roslyn/pull/24621
public static ReadOnlySpan<byte> UnscaledQuant_Luminance => new byte[] private static ReadOnlySpan<byte> UnscaledQuant_Luminance => new byte[]
{ {
16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24, 16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24,
40, 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60, 40, 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60,
@ -59,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
// The C# compiler emits this as a compile-time constant embedded in the PE file. // The C# compiler emits this as a compile-time constant embedded in the PE file.
// This is effectively compiled down to: return new ReadOnlySpan<byte>(&data, length) // This is effectively compiled down to: return new ReadOnlySpan<byte>(&data, length)
// More details can be found: https://github.com/dotnet/roslyn/pull/24621 // More details can be found: https://github.com/dotnet/roslyn/pull/24621
public static ReadOnlySpan<byte> UnscaledQuant_Chrominance => new byte[] private static ReadOnlySpan<byte> UnscaledQuant_Chrominance => new byte[]
{ {
17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66, 17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66,
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
@ -82,7 +83,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <param name="varianceThreshold">Variance threshold after which given table is considered non-complient.</param> /// <param name="varianceThreshold">Variance threshold after which given table is considered non-complient.</param>
/// <param name="quality">Estimated quality</param> /// <param name="quality">Estimated quality</param>
/// <returns><see cref="bool"/> indicating if given table is target-complient</returns> /// <returns><see cref="bool"/> indicating if given table is target-complient</returns>
private static bool EstimateQuality(ref Block8x8F table, ReadOnlySpan<byte> target, double varianceThreshold, out double quality) public static bool EstimateQuality(ref Block8x8F table, ReadOnlySpan<byte> target, double varianceThreshold, out double quality)
{ {
// This method can be SIMD'ified if standard table is injected as Block8x8F. // 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. // Or when we go to full-int16 spectral code implementation and inject both tables as Block8x8.
@ -151,6 +152,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <param name="luminanceTable">Luminance quantization table.</param> /// <param name="luminanceTable">Luminance quantization table.</param>
/// <param name="quality">Output jpeg quality.</param> /// <param name="quality">Output jpeg quality.</param>
/// <returns><see cref="bool"/> indicating if given table is ITU-complient.</returns> /// <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 double quality)
=> EstimateQuality(ref luminanceTable, UnscaledQuant_Luminance, StandardLuminanceTableVarianceThreshold, out quality); => EstimateQuality(ref luminanceTable, UnscaledQuant_Luminance, StandardLuminanceTableVarianceThreshold, out quality);
@ -160,7 +162,43 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <param name="chrominanceTable">Chrominance quantization table.</param> /// <param name="chrominanceTable">Chrominance quantization table.</param>
/// <param name="quality">Output jpeg quality.</param> /// <param name="quality">Output jpeg quality.</param>
/// <returns><see cref="bool"/> indicating if given table is ITU-complient.</returns> /// <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 double quality)
=> EstimateQuality(ref chrominanceTable, UnscaledQuant_Chrominance, StandardChrominanceTableVarianceThreshold, out quality); => EstimateQuality(ref chrominanceTable, UnscaledQuant_Chrominance, StandardChrominanceTableVarianceThreshold, out quality);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int QualityToScale(int quality)
=> quality < 50 ? 5000 / quality : 200 - (quality * 2);
private static Block8x8F ScaleQuantizationTable(int scale, ReadOnlySpan<byte> unscaledTable)
{
Block8x8F table = default;
for (int j = 0; j < Block8x8F.Size; j++)
{
int x = unscaledTable[j];
x = ((x * scale) + 50) / 100;
if (x < 1)
{
x = 1;
}
if (x > 255)
{
x = 255;
}
table[j] = x;
}
return table;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Block8x8F ScaleLuminanceTable(int quality)
=> ScaleQuantizationTable(scale: QualityToScale(quality), UnscaledQuant_Luminance);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Block8x8F ScaleChrominanceTable(int quality)
=> ScaleQuantizationTable(scale: QualityToScale(quality), UnscaledQuant_Chrominance);
} }
} }

16
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -830,26 +830,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// luminance table // luminance table
case 0: case 0:
{ {
Quantization.EstimateQuality(ref table, Quantization.UnscaledQuant_Luminance, out double quality, out double variance); // if quantization table is non-complient to stardard itu table
jpegMetadata.LuminanceQuality = quality; // we can't reacreate it later with calculated quality as this is an approximation
if (variance <= Quantization.StandardLuminanceTableVarianceThreshold) // so we save it in the metadata
if (!Quantization.EstimateLuminanceQuality(ref table, out double quality))
{ {
jpegMetadata.LumaQuantizationTable = table.RoundAsInt16Block(); jpegMetadata.LumaQuantizationTable = table.RoundAsInt16Block();
} }
jpegMetadata.LuminanceQuality = quality;
break; break;
} }
// chrominance table // chrominance table
case 1: case 1:
{ {
Quantization.EstimateQuality(ref table, Quantization.UnscaledQuant_Chrominance, out double quality, out double variance); // if quantization table is non-complient to stardard itu table
jpegMetadata.ChrominanceQuality = quality; // we can't reacreate it later with calculated quality as this is an approximation
if (variance <= Quantization.StandardChrominanceTableVarianceThreshold) // so we save it in the metadata
if (!Quantization.EstimateChrominanceQuality(ref table, out double quality))
{ {
jpegMetadata.ChromaQuantizationTable = table.RoundAsInt16Block(); jpegMetadata.ChromaQuantizationTable = table.RoundAsInt16Block();
} }
jpegMetadata.ChrominanceQuality = quality;
break; break;
} }
} }

51
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -94,27 +94,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
int qlty = Numerics.Clamp(this.quality ?? metadata.GetJpegMetadata().Quality, 1, 100); int qlty = Numerics.Clamp(this.quality ?? metadata.GetJpegMetadata().Quality, 1, 100);
this.subsample ??= qlty >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420; this.subsample ??= qlty >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420;
// Convert from a quality rating to a scaling factor.
int scale;
if (qlty < 50)
{
scale = 5000 / qlty;
}
else
{
scale = 200 - (qlty * 2);
}
// Initialize the quantization tables. // Initialize the quantization tables.
// TODO: This looks ugly, should we write chrominance table for luminance-only images? // TODO: This looks ugly, should we write chrominance table for luminance-only images?
// If not - this can code can be simplified // If not - this can code can be simplified
Block8x8F luminanceQuantTable = default; Block8x8F luminanceQuantTable = Quantization.ScaleLuminanceTable(qlty);
Block8x8F chrominanceQuantTable = default; Block8x8F chrominanceQuantTable = Quantization.ScaleChrominanceTable(qlty);
InitQuantizationTable(0, scale, ref luminanceQuantTable);
if (componentCount > 1)
{
InitQuantizationTable(1, scale, ref chrominanceQuantTable);
}
// Write the Start Of Image marker. // Write the Start Of Image marker.
this.WriteApplicationHeader(metadata); this.WriteApplicationHeader(metadata);
@ -138,10 +122,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
var scanEncoder = new HuffmanScanEncoder(stream); var scanEncoder = new HuffmanScanEncoder(stream);
if (this.colorType == JpegColorType.Luminance) if (this.colorType == JpegColorType.Luminance)
{ {
// luminance quantization table only
scanEncoder.EncodeGrayscale(image, ref luminanceQuantTable, cancellationToken); scanEncoder.EncodeGrayscale(image, ref luminanceQuantTable, cancellationToken);
} }
else else
{ {
// luminance and chrominance quantization tables
switch (this.subsample) switch (this.subsample)
{ {
case JpegSubsample.Ratio444: case JpegSubsample.Ratio444:
@ -650,34 +636,5 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.buffer[3] = (byte)(length & 0xff); this.buffer[3] = (byte)(length & 0xff);
this.outputStream.Write(this.buffer, 0, 4); this.outputStream.Write(this.buffer, 0, 4);
} }
/// <summary>
/// Initializes quantization table.
/// </summary>
/// <param name="i">The quantization index.</param>
/// <param name="scale">The scaling factor.</param>
/// <param name="quant">The quantization table.</param>
private static void InitQuantizationTable(int i, int scale, ref Block8x8F quant)
{
DebugGuard.MustBeBetweenOrEqualTo(i, 0, 1, nameof(i));
ReadOnlySpan<byte> unscaledQuant = (i == 0) ? Quantization.UnscaledQuant_Luminance : Quantization.UnscaledQuant_Chrominance;
for (int j = 0; j < Block8x8F.Size; j++)
{
int x = unscaledQuant[j];
x = ((x * scale) + 50) / 100;
if (x < 1)
{
x = 1;
}
if (x > 255)
{
x = 255;
}
quant[j] = x;
}
}
} }
} }

Loading…
Cancel
Save