diff --git a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs index d8ea6bb0e8..373475f6bb 100644 --- a/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs +++ b/src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; #if SUPPORTS_RUNTIME_INTRINSICS @@ -441,7 +442,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder // Lzcnt would return 32 for input value of 0 - no need to check that with branching // Fallback code if Lzcnt is not supported still use if-check // But most modern CPUs support this instruction so this should not be a problem - return 32 - System.Numerics.BitOperations.LeadingZeroCount(value); + return 32 - BitOperations.LeadingZeroCount(value); #else // Ideally: // if 0 - return 0 in this case @@ -458,13 +459,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder } /// - /// Returns index of the last non-zero element in given mcu block. - /// If all values of the mcu block are zero, this method might return different results depending on the runtime and hardware support. - /// This is jpeg mcu specific code, mcu[0] stores a dc value which will be encoded outside of the loop. - /// This method is guaranteed to return either -1 or 0 if all elements are zero. + /// Returns index of the last non-zero element in given matrix. /// /// - /// This is an internal operation supposed to be used only in class for jpeg encoding. + /// Returns 0 for all-zero matrix by convention. /// /// Mcu block. /// Index of the last non-zero element. @@ -484,24 +482,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder { int areEqual = Avx2.MoveMask(Avx2.CompareEqual(Avx.ConvertToVector256Int32WithTruncation(Unsafe.Add(ref mcuStride, i)), zero8).AsByte()); - // we do not know for sure if this stride contain all non-zero elements or if it has some trailing zeros if (areEqual != equalityMask) { - // last index in the stride, we go from the end to the start of the stride - int startIndex = i * 8; - int index = startIndex + 7; - ref float elemRef = ref Unsafe.As(ref mcu); - while (index >= startIndex && (int)Unsafe.Add(ref elemRef, index) == 0) - { - index--; - } - - // this implementation will return -1 if all ac components are zero and dc are zero - return index; + // Each 4 bits represents comparison operation for each 4-byte element in input vectors + // LSB represents first element in the stride + // MSB represents last element in the stride + // lzcnt operation would calculate number of zero numbers at the end + + // Given mask is not actually suitable for lzcnt as 1's represent zero elements and 0's represent non-zero elements + // So we need to invert it + int lzcnt = BitOperations.LeadingZeroCount(~(uint)areEqual); + + // As input number is represented by 4 bits in the mask, we need to divide lzcnt result by 4 + // to get the exact number of zero elements in the stride + int strideRelativeIndex = 7 - (lzcnt / 4); + return (i * 8) + strideRelativeIndex; } } - return -1; + return 0; } else #endif @@ -514,7 +513,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder index--; } - // this implementation will return 0 if all ac components and dc are zero return index; } }