diff --git a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs index 8ca7b0c801..d55dfced72 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs @@ -830,5 +830,46 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components d.V7R.W = this.V7R.W; } } + + /// + /// Compares entire 8x8 block to a single scalar value. + /// + /// Value to compare to. + public bool EqualsToScalar(int value) + { +#if SUPPORTS_RUNTIME_INTRINSICS + if (Avx2.IsSupported) + { + const int equalityMask = unchecked((int)0b1111_1111_1111_1111_1111_1111_1111_1111); + + var targetVector = Vector256.Create(value); + ref Vector256 blockStride = ref this.V0; + + for (int i = 0; i < RowCount; i++) + { + Vector256 areEqual = Avx2.CompareEqual(Avx.ConvertToVector256Int32WithTruncation(Unsafe.Add(ref this.V0, i)), targetVector); + if (Avx2.MoveMask(areEqual.AsByte()) != equalityMask) + { + return false; + } + } + + return true; + } +#endif + { + ref float scalars = ref Unsafe.As(ref this); + + for (int i = 0; i < Size; i++) + { + if ((int)Unsafe.Add(ref scalars, i) != value) + { + return false; + } + } + + return true; + } + } } } diff --git a/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs b/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs index 3471558054..0c9a0ca416 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Quantization.cs @@ -69,37 +69,44 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components // https://github.com/ImpulseAdventure/JPEGsnoop/blob/9732ee0961f100eb69bbff4a0c47438d5997abee/source/JfifDecode.cpp#L4570-L4694 public static void EstimateQuality(ref Block8x8F table, ReadOnlySpan target, out double quality, out double variance) { - // 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 + // 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; - bool allOnes = true; + // 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'. + quality = 100; + variance = 0; + return; + } for (int i = 0; i < Block8x8F.Size; i++) { float coeff = table[i]; int coeffInteger = (int)coeff; - // coefficients are actually int16 casted to float numbers so there's no truncating error + // Coefficients are actually int16 casted to float numbers so there's no truncating error. if (coeffInteger != 0) { comparePercent = 100.0 * (table[i] / target[i]); } else { + // No 'valid' quantization table should contain zero at any position + // while this is okay to decode with, it will throw DivideByZeroException at encoding proces stage. + // Not sure what to do here, we can't throw as this technically correct + // but this will screw up the encoder. comparePercent = 999.99; } sumPercent += comparePercent; sumPercentSqr += comparePercent * comparePercent; - - // Check just in case entire table are ones (Quality 100) - if (coeffInteger != 1) - { - allOnes = false; - } } // Perform some statistical analysis of the quality factor @@ -111,11 +118,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components variance = sumPercentSqr - (sumPercent * sumPercent); // Generate the equivalent IJQ "quality" factor - if (allOnes) - { - quality = 100; - } - else if (sumPercent <= 100.0) + if (sumPercent <= 100.0) { quality = (200 - sumPercent) / 2; } diff --git a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs index 0a05aac170..1b43f26f07 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegMetadata.cs @@ -46,8 +46,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg { this.Quality = other.Quality; this.ColorType = other.ColorType; + this.lumaQuantizationTable = other.lumaQuantizationTable; this.chromaQuantizationTable = other.chromaQuantizationTable; + this.LumaQuality = other.LumaQuality; + this.ChromaQuality = other.ChromaQuality; } /// @@ -64,6 +67,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg } } + /// + /// Gets a value indicating whether jpeg was encoded using ITU section spec K.1 quantization tables + /// + public bool ItuSpecQuantization => !this.lumaQuantizationTable.HasValue && !this.chromaQuantizationTable.HasValue; + /// /// Gets or sets the encoded quality. ///