Skip to content

Commit 0b059b5

Browse files
authored
Merge branch 'master' into bp/tiff24bit
2 parents 9b1276d + 7da6c33 commit 0b059b5

23 files changed

+602
-269
lines changed

src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -830,5 +830,46 @@ public void TransposeInto(ref Block8x8F d)
830830
d.V7R.W = this.V7R.W;
831831
}
832832
}
833+
834+
/// <summary>
835+
/// Compares entire 8x8 block to a single scalar value.
836+
/// </summary>
837+
/// <param name="value">Value to compare to.</param>
838+
public bool EqualsToScalar(int value)
839+
{
840+
#if SUPPORTS_RUNTIME_INTRINSICS
841+
if (Avx2.IsSupported)
842+
{
843+
const int equalityMask = unchecked((int)0b1111_1111_1111_1111_1111_1111_1111_1111);
844+
845+
var targetVector = Vector256.Create(value);
846+
ref Vector256<float> blockStride = ref this.V0;
847+
848+
for (int i = 0; i < RowCount; i++)
849+
{
850+
Vector256<int> areEqual = Avx2.CompareEqual(Avx.ConvertToVector256Int32WithTruncation(Unsafe.Add(ref this.V0, i)), targetVector);
851+
if (Avx2.MoveMask(areEqual.AsByte()) != equalityMask)
852+
{
853+
return false;
854+
}
855+
}
856+
857+
return true;
858+
}
859+
#endif
860+
{
861+
ref float scalars = ref Unsafe.As<Block8x8F, float>(ref this);
862+
863+
for (int i = 0; i < Size; i++)
864+
{
865+
if ((int)Unsafe.Add(ref scalars, i) != value)
866+
{
867+
return false;
868+
}
869+
}
870+
871+
return true;
872+
}
873+
}
833874
}
834875
}

src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public JpegComponent(MemoryAllocator memoryAllocator, JpegFrame frame, byte id,
3232

3333
if (quantizationTableIndex > 3)
3434
{
35-
JpegThrowHelper.ThrowBadQuantizationTable();
35+
JpegThrowHelper.ThrowBadQuantizationTableIndex(quantizationTableIndex);
3636
}
3737

3838
this.QuantizationTableIndex = quantizationTableIndex;

src/ImageSharp/Formats/Jpeg/Components/Decoder/QualityEvaluator.cs

Lines changed: 0 additions & 144 deletions
This file was deleted.
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.Runtime.CompilerServices;
6+
7+
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
8+
{
9+
/// <summary>
10+
/// Provides methods and properties related to jpeg quantization.
11+
/// </summary>
12+
internal static class Quantization
13+
{
14+
/// <summary>
15+
/// Upper bound (inclusive) for jpeg quality setting.
16+
/// </summary>
17+
public const int MaxQualityFactor = 100;
18+
19+
/// <summary>
20+
/// Lower bound (inclusive) for jpeg quality setting.
21+
/// </summary>
22+
public const int MinQualityFactor = 1;
23+
24+
/// <summary>
25+
/// Default JPEG quality for both luminance and chominance tables.
26+
/// </summary>
27+
public const int DefaultQualityFactor = 75;
28+
29+
/// <summary>
30+
/// Represents lowest quality setting which can be estimated with enough confidence.
31+
/// Any quality below it results in a highly compressed jpeg image
32+
/// which shouldn't use standard itu quantization tables for re-encoding.
33+
/// </summary>
34+
public const int QualityEstimationConfidenceLowerThreshold = 25;
35+
36+
/// <summary>
37+
/// Represents highest quality setting which can be estimated with enough confidence.
38+
/// </summary>
39+
public const int QualityEstimationConfidenceUpperThreshold = 98;
40+
41+
/// <summary>
42+
/// Gets the unscaled luminance quantization table in zig-zag order. Each
43+
/// encoder copies and scales the tables according to its quality parameter.
44+
/// The values are derived from ITU section K.1 after converting from natural to
45+
/// zig-zag order.
46+
/// </summary>
47+
// The C# compiler emits this as a compile-time constant embedded in the PE file.
48+
// This is effectively compiled down to: return new ReadOnlySpan<byte>(&data, length)
49+
// More details can be found: https://github.com/dotnet/roslyn/pull/24621
50+
public static ReadOnlySpan<byte> UnscaledQuant_Luminance => new byte[]
51+
{
52+
16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24,
53+
40, 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60,
54+
57, 51, 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80,
55+
109, 81, 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112,
56+
100, 120, 92, 101, 103, 99,
57+
};
58+
59+
/// <summary>
60+
/// Gets the unscaled chrominance quantization table in zig-zag order. Each
61+
/// encoder copies and scales the tables according to its quality parameter.
62+
/// The values are derived from ITU section K.1 after converting from natural to
63+
/// zig-zag order.
64+
/// </summary>
65+
// The C# compiler emits this as a compile-time constant embedded in the PE file.
66+
// This is effectively compiled down to: return new ReadOnlySpan<byte>(&data, length)
67+
// More details can be found: https://github.com/dotnet/roslyn/pull/24621
68+
public static ReadOnlySpan<byte> UnscaledQuant_Chrominance => new byte[]
69+
{
70+
17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66,
71+
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
72+
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
73+
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
74+
99, 99, 99, 99, 99, 99, 99, 99,
75+
};
76+
77+
/// Ported from JPEGsnoop:
78+
/// https://github.com/ImpulseAdventure/JPEGsnoop/blob/9732ee0961f100eb69bbff4a0c47438d5997abee/source/JfifDecode.cpp#L4570-L4694
79+
/// <summary>
80+
/// Estimates jpeg quality based on quantization table in zig-zag order.
81+
/// </summary>
82+
/// <remarks>
83+
/// This technically can be used with any given table but internal decoder code uses ITU spec tables:
84+
/// <see cref="UnscaledQuant_Luminance"/> and <see cref="UnscaledQuant_Chrominance"/>.
85+
/// </remarks>
86+
/// <param name="table">Input quantization table.</param>
87+
/// <param name="target">Quantization to estimate against.</param>
88+
/// <returns>Estimated quality</returns>
89+
public static int EstimateQuality(ref Block8x8F table, ReadOnlySpan<byte> target)
90+
{
91+
// This method can be SIMD'ified if standard table is injected as Block8x8F.
92+
// Or when we go to full-int16 spectral code implementation and inject both tables as Block8x8.
93+
double comparePercent;
94+
double sumPercent = 0;
95+
96+
// Corner case - all 1's => 100 quality
97+
// It would fail to deduce using algorithm below without this check
98+
if (table.EqualsToScalar(1))
99+
{
100+
// While this is a 100% to be 100 quality, any given table can be scaled to all 1's.
101+
// According to jpeg creators, top of the line quality is 99, 100 is just a technical 'limit' which will affect result filesize drastically.
102+
// Quality=100 shouldn't be used in usual use case.
103+
return 100;
104+
}
105+
106+
int quality;
107+
for (int i = 0; i < Block8x8F.Size; i++)
108+
{
109+
float coeff = table[i];
110+
int coeffInteger = (int)coeff;
111+
112+
// Coefficients are actually int16 casted to float numbers so there's no truncating error.
113+
if (coeffInteger != 0)
114+
{
115+
comparePercent = 100.0 * (table[i] / target[i]);
116+
}
117+
else
118+
{
119+
// No 'valid' quantization table should contain zero at any position
120+
// while this is okay to decode with, it will throw DivideByZeroException at encoding proces stage.
121+
// Not sure what to do here, we can't throw as this technically correct
122+
// but this will screw up the encoder.
123+
comparePercent = 999.99;
124+
}
125+
126+
sumPercent += comparePercent;
127+
}
128+
129+
// Perform some statistical analysis of the quality factor
130+
// to determine the likelihood of the current quantization
131+
// table being a scaled version of the "standard" tables.
132+
// If the variance is high, it is unlikely to be the case.
133+
sumPercent /= 64.0;
134+
135+
// Generate the equivalent IJQ "quality" factor
136+
if (sumPercent <= 100.0)
137+
{
138+
quality = (int)Math.Round((200 - sumPercent) / 2);
139+
}
140+
else
141+
{
142+
quality = (int)Math.Round(5000.0 / sumPercent);
143+
}
144+
145+
return quality;
146+
}
147+
148+
/// <summary>
149+
/// Estimates jpeg quality based on quantization table in zig-zag order.
150+
/// </summary>
151+
/// <param name="luminanceTable">Luminance quantization table.</param>
152+
/// <returns>Estimated quality</returns>
153+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
154+
public static int EstimateLuminanceQuality(ref Block8x8F luminanceTable)
155+
=> EstimateQuality(ref luminanceTable, UnscaledQuant_Luminance);
156+
157+
/// <summary>
158+
/// Estimates jpeg quality based on quantization table in zig-zag order.
159+
/// </summary>
160+
/// <param name="chrominanceTable">Chrominance quantization table.</param>
161+
/// <returns>Estimated quality</returns>
162+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
163+
public static int EstimateChrominanceQuality(ref Block8x8F chrominanceTable)
164+
=> EstimateQuality(ref chrominanceTable, UnscaledQuant_Chrominance);
165+
166+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
167+
private static int QualityToScale(int quality)
168+
{
169+
DebugGuard.MustBeBetweenOrEqualTo(quality, MinQualityFactor, MaxQualityFactor, nameof(quality));
170+
171+
return quality < 50 ? (5000 / quality) : (200 - (quality * 2));
172+
}
173+
174+
private static Block8x8F ScaleQuantizationTable(int scale, ReadOnlySpan<byte> unscaledTable)
175+
{
176+
Block8x8F table = default;
177+
for (int j = 0; j < Block8x8F.Size; j++)
178+
{
179+
int x = ((unscaledTable[j] * scale) + 50) / 100;
180+
table[j] = Numerics.Clamp(x, 1, 255);
181+
}
182+
183+
return table;
184+
}
185+
186+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
187+
public static Block8x8F ScaleLuminanceTable(int quality)
188+
=> ScaleQuantizationTable(scale: QualityToScale(quality), UnscaledQuant_Luminance);
189+
190+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
191+
public static Block8x8F ScaleChrominanceTable(int quality)
192+
=> ScaleQuantizationTable(scale: QualityToScale(quality), UnscaledQuant_Chrominance);
193+
}
194+
}

0 commit comments

Comments
 (0)