diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantizers/Av1ForwardQuantizer.HighBitDepthRegularQuantizationOperator.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantizers/Av1ForwardQuantizer.HighBitDepthRegularQuantizationOperator.cs
new file mode 100644
index 0000000000..7aa7dfcf7a
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantizers/Av1ForwardQuantizer.HighBitDepthRegularQuantizationOperator.cs
@@ -0,0 +1,131 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.Intrinsics;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantizers;
+
+internal static partial class Av1ForwardQuantizer
+{
+ ///
+ /// Quantizes high-bit-depth magnitudes without the sixteen-bit saturation used for byte samples.
+ ///
+ public readonly struct HighBitDepthRegularQuantizationOperator : IRegularQuantizationOperator
+ {
+ ///
+ public static Vector128 Quantize(
+ Vector128 coefficients,
+ Vector128 zeroBin,
+ Vector128 rounding,
+ Vector128 quantizer,
+ Vector128 shift,
+ Vector128 dequantizer,
+ int logScale,
+ out Vector128 dequantizedCoefficients)
+ {
+ Vector128 sign = coefficients >> 31;
+ Vector128 magnitude = Vector128.Abs(coefficients);
+ Vector128 mask = ~Vector128.GreaterThan(zeroBin, magnitude);
+ Vector128 rounded = magnitude + rounding;
+
+ // Preserve signed products in two widened halves. The reciprocal correction can be negative;
+ // an arithmetic Q16 shift restores its implicit leading bit before the second multiplication.
+ Vector128 lower = Vector128.WidenLower(rounded);
+ Vector128 upper = Vector128.WidenUpper(rounded);
+ lower += (lower * Vector128.WidenLower(quantizer)) >> 16;
+ upper += (upper * Vector128.WidenUpper(quantizer)) >> 16;
+ lower = (lower * Vector128.WidenLower(shift)) >> (16 - logScale);
+ upper = (upper * Vector128.WidenUpper(shift)) >> (16 - logScale);
+ Vector128 quantizedMagnitude = Vector128.Narrow(lower, upper) & mask;
+ Vector128 dequantizedMagnitude = (quantizedMagnitude * dequantizer) >> logScale;
+ dequantizedCoefficients = (dequantizedMagnitude ^ sign) - sign;
+ return (quantizedMagnitude ^ sign) - sign;
+ }
+
+ ///
+ public static Vector256 Quantize(
+ Vector256 coefficients,
+ Vector256 zeroBin,
+ Vector256 rounding,
+ Vector256 quantizer,
+ Vector256 shift,
+ Vector256 dequantizer,
+ int logScale,
+ out Vector256 dequantizedCoefficients)
+ {
+ Vector256 sign = coefficients >> 31;
+ Vector256 magnitude = Vector256.Abs(coefficients);
+ Vector256 mask = ~Vector256.GreaterThan(zeroBin, magnitude);
+ Vector256 rounded = magnitude + rounding;
+
+ // Preserve signed products in two widened halves. The reciprocal correction can be negative;
+ // an arithmetic Q16 shift restores its implicit leading bit before the second multiplication.
+ Vector256 lower = Vector256.WidenLower(rounded);
+ Vector256 upper = Vector256.WidenUpper(rounded);
+ lower += (lower * Vector256.WidenLower(quantizer)) >> 16;
+ upper += (upper * Vector256.WidenUpper(quantizer)) >> 16;
+ lower = (lower * Vector256.WidenLower(shift)) >> (16 - logScale);
+ upper = (upper * Vector256.WidenUpper(shift)) >> (16 - logScale);
+ Vector256 quantizedMagnitude = Vector256.Narrow(lower, upper) & mask;
+ Vector256 dequantizedMagnitude = (quantizedMagnitude * dequantizer) >> logScale;
+ dequantizedCoefficients = (dequantizedMagnitude ^ sign) - sign;
+ return (quantizedMagnitude ^ sign) - sign;
+ }
+
+ ///
+ public static Vector512 Quantize(
+ Vector512 coefficients,
+ Vector512 zeroBin,
+ Vector512 rounding,
+ Vector512 quantizer,
+ Vector512 shift,
+ Vector512 dequantizer,
+ int logScale,
+ out Vector512 dequantizedCoefficients)
+ {
+ Vector512 sign = coefficients >> 31;
+ Vector512 magnitude = Vector512.Abs(coefficients);
+ Vector512 mask = ~Vector512.GreaterThan(zeroBin, magnitude);
+ Vector512 rounded = magnitude + rounding;
+
+ // Preserve signed products in two widened halves. The reciprocal correction can be negative;
+ // an arithmetic Q16 shift restores its implicit leading bit before the second multiplication.
+ Vector512 lower = Vector512.WidenLower(rounded);
+ Vector512 upper = Vector512.WidenUpper(rounded);
+ lower += (lower * Vector512.WidenLower(quantizer)) >> 16;
+ upper += (upper * Vector512.WidenUpper(quantizer)) >> 16;
+ lower = (lower * Vector512.WidenLower(shift)) >> (16 - logScale);
+ upper = (upper * Vector512.WidenUpper(shift)) >> (16 - logScale);
+ Vector512 quantizedMagnitude = Vector512.Narrow(lower, upper) & mask;
+ Vector512 dequantizedMagnitude = (quantizedMagnitude * dequantizer) >> logScale;
+ dequantizedCoefficients = (dequantizedMagnitude ^ sign) - sign;
+ return (quantizedMagnitude ^ sign) - sign;
+ }
+
+ ///
+ public static int Quantize(
+ int coefficients,
+ int zeroBin,
+ int rounding,
+ int quantizer,
+ int shift,
+ int dequantizer,
+ int logScale,
+ out int dequantizedCoefficients)
+ {
+ int sign = coefficients >> 31;
+ int magnitude = (coefficients ^ sign) - sign;
+ int quantizedMagnitude = 0;
+ if (magnitude >= zeroBin)
+ {
+ long rounded = (long)magnitude + rounding;
+ long corrected = rounded + ((rounded * quantizer) >> 16);
+ quantizedMagnitude = (int)((corrected * shift) >> (16 - logScale));
+ }
+
+ int dequantizedMagnitude = (quantizedMagnitude * dequantizer) >> logScale;
+ dequantizedCoefficients = (dequantizedMagnitude ^ sign) - sign;
+ return (quantizedMagnitude ^ sign) - sign;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantizers/Av1ForwardQuantizer.Regular.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantizers/Av1ForwardQuantizer.Regular.cs
new file mode 100644
index 0000000000..69dcc5f0d4
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantizers/Av1ForwardQuantizer.Regular.cs
@@ -0,0 +1,175 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantizers;
+
+internal static partial class Av1ForwardQuantizer
+{
+ ///
+ /// Quantizes a transform with the regular zero-bin and reciprocal-correction arithmetic.
+ ///
+ /// The raster-order transformed coefficients.
+ /// The raster-order coding coefficients.
+ /// The raster-order reconstruction coefficients.
+ /// The transform dimensions.
+ /// The transform type selecting coefficient scan order.
+ /// The base quantizer index.
+ /// The DC index adjustment.
+ /// The AC index adjustment.
+ /// The coded sample precision.
+ /// The encoder sharpness setting from zero through seven.
+ /// The one-based final nonzero scan position.
+ public static ushort QuantizeRegular(
+ ReadOnlySpan coefficients,
+ Span quantizedCoefficients,
+ Span dequantizedCoefficients,
+ Av1TransformSize transformSize,
+ Av1TransformType transformType,
+ int qIndex,
+ int dcDeltaQ,
+ int acDeltaQ,
+ Av1BitDepth bitDepth,
+ int sharpness)
+ => bitDepth == Av1BitDepth.EightBit
+ ? QuantizeRegular(
+ coefficients, quantizedCoefficients, dequantizedCoefficients, transformSize, transformType, qIndex, dcDeltaQ, acDeltaQ, bitDepth, sharpness)
+ : QuantizeRegular(
+ coefficients, quantizedCoefficients, dequantizedCoefficients, transformSize, transformType, qIndex, dcDeltaQ, acDeltaQ, bitDepth, sharpness);
+
+ ///
+ /// Traverses regular quantization with one DC coefficient followed by contiguous AC vectors and a scalar tail.
+ ///
+ private static ushort QuantizeRegular(
+ ReadOnlySpan coefficients,
+ Span quantizedCoefficients,
+ Span dequantizedCoefficients,
+ Av1TransformSize transformSize,
+ Av1TransformType transformType,
+ int qIndex,
+ int dcDeltaQ,
+ int acDeltaQ,
+ Av1BitDepth bitDepth,
+ int sharpness)
+ where TOperator : struct, IRegularQuantizationOperator
+ {
+ int count = transformSize.GetAdjusted().GetSize2d();
+ int logScale = transformSize.GetScale();
+ int zeroBinFactor = Av1InverseTransformMath.GetQzbinFactor(qIndex, bitDepth);
+ int roundingFactor = qIndex == 0 ? 64 : sharpness == 0 ? 48 : 64 - (16 * (7 - sharpness) / 7);
+ int dcDequantizer = Av1QuantizationLookup.GetDcQuant(qIndex, dcDeltaQ, bitDepth);
+ int acDequantizer = Av1QuantizationLookup.GetAcQuant(qIndex, acDeltaQ, bitDepth);
+ Av1InverseTransformMath.InvertQuantization(out int dcQuantizer, out int dcShift, dcDequantizer);
+ Av1InverseTransformMath.InvertQuantization(out int acQuantizer, out int acShift, acDequantizer);
+
+ // Zero-bin constants round twice: once from Q7 and once for the transform scale. Rounding
+ // constants first truncate from Q7, then round for that same scale. Combining either pair of
+ // shifts would change coefficients at the quantization boundary.
+ int dcZeroBin = RoundPowerOfTwo(RoundPowerOfTwo(zeroBinFactor * dcDequantizer, 7), logScale);
+ int acZeroBin = RoundPowerOfTwo(RoundPowerOfTwo(zeroBinFactor * acDequantizer, 7), logScale);
+ int dcRounding = RoundPowerOfTwo((roundingFactor * dcDequantizer) >> 7, logScale);
+ int acRounding = RoundPowerOfTwo((roundingFactor * acDequantizer) >> 7, logScale);
+ ref int sourceBase = ref MemoryMarshal.GetReference(coefficients);
+ ref int quantizedBase = ref MemoryMarshal.GetReference(quantizedCoefficients);
+ ref int dequantizedBase = ref MemoryMarshal.GetReference(dequantizedCoefficients);
+
+ // DC has different constants. Processing it once leaves a uniform AC traversal with no lane masks
+ // for DC and no scratch coefficient copy; every output position is overwritten on each candidate.
+ quantizedBase = TOperator.Quantize(
+ sourceBase, dcZeroBin, dcRounding, dcQuantizer, dcShift, dcDequantizer, logScale, out dequantizedBase);
+
+ int index = 1;
+
+ if (Vector512.IsHardwareAccelerated)
+ {
+ Vector512 zeroBin = Vector512.Create(acZeroBin);
+ Vector512 rounding = Vector512.Create(acRounding);
+ Vector512 quantizer = Vector512.Create(acQuantizer);
+ Vector512 shift = Vector512.Create(acShift);
+ Vector512 dequantizer = Vector512.Create(acDequantizer);
+
+ // Each lane owns one contiguous coefficient. Narrower tiers resume at the first unread
+ // coefficient so unaligned starts and vector tails need neither padding nor overlapping stores.
+ for (; index <= count - Vector512.Count; index += Vector512.Count)
+ {
+ Vector512 values = Vector512.LoadUnsafe(ref sourceBase, (nuint)index);
+ Vector512 quantized = TOperator.Quantize(
+ values, zeroBin, rounding, quantizer, shift, dequantizer, logScale, out Vector512 dequantized);
+
+ quantized.StoreUnsafe(ref quantizedBase, (nuint)index);
+ dequantized.StoreUnsafe(ref dequantizedBase, (nuint)index);
+ }
+ }
+
+ if (Vector256.IsHardwareAccelerated)
+ {
+ Vector256 zeroBin = Vector256.Create(acZeroBin);
+ Vector256 rounding = Vector256.Create(acRounding);
+ Vector256 quantizer = Vector256.Create(acQuantizer);
+ Vector256 shift = Vector256.Create(acShift);
+ Vector256 dequantizer = Vector256.Create(acDequantizer);
+
+ // Each lane owns one contiguous coefficient. Narrower tiers resume at the first unread
+ // coefficient so unaligned starts and vector tails need neither padding nor overlapping stores.
+ for (; index <= count - Vector256.Count; index += Vector256.Count)
+ {
+ Vector256 values = Vector256.LoadUnsafe(ref sourceBase, (nuint)index);
+ Vector256 quantized = TOperator.Quantize(
+ values, zeroBin, rounding, quantizer, shift, dequantizer, logScale, out Vector256 dequantized);
+
+ quantized.StoreUnsafe(ref quantizedBase, (nuint)index);
+ dequantized.StoreUnsafe(ref dequantizedBase, (nuint)index);
+ }
+ }
+
+ if (Vector128.IsHardwareAccelerated)
+ {
+ Vector128 zeroBin = Vector128.Create(acZeroBin);
+ Vector128 rounding = Vector128.Create(acRounding);
+ Vector128 quantizer = Vector128.Create(acQuantizer);
+ Vector128 shift = Vector128.Create(acShift);
+ Vector128 dequantizer = Vector128.Create(acDequantizer);
+
+ // Each lane owns one contiguous coefficient. Narrower tiers resume at the first unread
+ // coefficient so unaligned starts and vector tails need neither padding nor overlapping stores.
+ for (; index <= count - Vector128.Count; index += Vector128.Count)
+ {
+ Vector128 values = Vector128.LoadUnsafe(ref sourceBase, (nuint)index);
+ Vector128 quantized = TOperator.Quantize(
+ values, zeroBin, rounding, quantizer, shift, dequantizer, logScale, out Vector128 dequantized);
+
+ quantized.StoreUnsafe(ref quantizedBase, (nuint)index);
+ dequantized.StoreUnsafe(ref dequantizedBase, (nuint)index);
+ }
+ }
+
+ for (; index < count; index++)
+ {
+ Unsafe.Add(ref quantizedBase, index) = TOperator.Quantize(
+ Unsafe.Add(ref sourceBase, index),
+ acZeroBin,
+ acRounding,
+ acQuantizer,
+ acShift,
+ acDequantizer,
+ logScale,
+ out Unsafe.Add(ref dequantizedBase, index));
+ }
+
+ // Coding and reconstruction retain raster order. Only the end position is reduced in scan order.
+ ReadOnlySpan scan = Av1ScanOrderConstants.GetScanOrder(transformSize, transformType).Scan;
+ for (int scanIndex = count - 1; scanIndex >= 0; scanIndex--)
+ {
+ if (Unsafe.Add(ref quantizedBase, scan[scanIndex]) != 0)
+ {
+ return (ushort)(scanIndex + 1);
+ }
+ }
+
+ return 0;
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantizers/Av1ForwardQuantizer.RegularOperator.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantizers/Av1ForwardQuantizer.RegularOperator.cs
new file mode 100644
index 0000000000..d400ab6b90
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantizers/Av1ForwardQuantizer.RegularOperator.cs
@@ -0,0 +1,103 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.Intrinsics;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantizers;
+
+internal static partial class Av1ForwardQuantizer
+{
+ ///
+ /// Applies regular quantization while preserving the precision-specific rounded-magnitude contract.
+ ///
+ public interface IRegularQuantizationOperator
+ {
+ ///
+ /// Applies the zero bin, corrected reciprocal, dequantization, and coefficient sign.
+ ///
+ /// The signed transform coefficients.
+ /// The inclusive magnitude threshold after transform scaling.
+ /// The rounded magnitude adjustment.
+ /// The signed reciprocal correction below its implicit leading bit.
+ /// The power-of-two reciprocal scale.
+ /// The reconstruction quantizer.
+ /// The transform coefficient scale.
+ /// The signed reconstruction coefficients.
+ /// The signed coding coefficients.
+ static abstract Vector128 Quantize(
+ Vector128 coefficients,
+ Vector128 zeroBin,
+ Vector128 rounding,
+ Vector128 quantizer,
+ Vector128 shift,
+ Vector128 dequantizer,
+ int logScale,
+ out Vector128 dequantizedCoefficients);
+
+ ///
+ /// Applies the zero bin, corrected reciprocal, dequantization, and coefficient sign.
+ ///
+ /// The signed transform coefficients.
+ /// The inclusive magnitude threshold after transform scaling.
+ /// The rounded magnitude adjustment.
+ /// The signed reciprocal correction below its implicit leading bit.
+ /// The power-of-two reciprocal scale.
+ /// The reconstruction quantizer.
+ /// The transform coefficient scale.
+ /// The signed reconstruction coefficients.
+ /// The signed coding coefficients.
+ static abstract Vector256 Quantize(
+ Vector256 coefficients,
+ Vector256 zeroBin,
+ Vector256 rounding,
+ Vector256 quantizer,
+ Vector256 shift,
+ Vector256 dequantizer,
+ int logScale,
+ out Vector256 dequantizedCoefficients);
+
+ ///
+ /// Applies the zero bin, corrected reciprocal, dequantization, and coefficient sign.
+ ///
+ /// The signed transform coefficients.
+ /// The inclusive magnitude threshold after transform scaling.
+ /// The rounded magnitude adjustment.
+ /// The signed reciprocal correction below its implicit leading bit.
+ /// The power-of-two reciprocal scale.
+ /// The reconstruction quantizer.
+ /// The transform coefficient scale.
+ /// The signed reconstruction coefficients.
+ /// The signed coding coefficients.
+ static abstract Vector512 Quantize(
+ Vector512 coefficients,
+ Vector512 zeroBin,
+ Vector512 rounding,
+ Vector512 quantizer,
+ Vector512 shift,
+ Vector512 dequantizer,
+ int logScale,
+ out Vector512 dequantizedCoefficients);
+
+ ///
+ /// Applies the zero bin, corrected reciprocal, dequantization, and coefficient sign.
+ ///
+ /// The signed transform coefficients.
+ /// The inclusive magnitude threshold after transform scaling.
+ /// The rounded magnitude adjustment.
+ /// The signed reciprocal correction below its implicit leading bit.
+ /// The power-of-two reciprocal scale.
+ /// The reconstruction quantizer.
+ /// The transform coefficient scale.
+ /// The signed reconstruction coefficients.
+ /// The signed coding coefficients.
+ static abstract int Quantize(
+ int coefficients,
+ int zeroBin,
+ int rounding,
+ int quantizer,
+ int shift,
+ int dequantizer,
+ int logScale,
+ out int dequantizedCoefficients);
+ }
+}
diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantizers/Av1ForwardQuantizer.RegularQuantizationOperator.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantizers/Av1ForwardQuantizer.RegularQuantizationOperator.cs
new file mode 100644
index 0000000000..ec86601788
--- /dev/null
+++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Quantizers/Av1ForwardQuantizer.RegularQuantizationOperator.cs
@@ -0,0 +1,116 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.Intrinsics;
+
+namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantizers;
+
+internal static partial class Av1ForwardQuantizer
+{
+ ///
+ /// Quantizes byte-source coefficients with saturated sixteen-bit rounded magnitudes.
+ ///
+ public readonly struct RegularQuantizationOperator : IRegularQuantizationOperator
+ {
+ ///
+ public static Vector128 Quantize(
+ Vector128 coefficients,
+ Vector128 zeroBin,
+ Vector128 rounding,
+ Vector128 quantizer,
+ Vector128 shift,
+ Vector128 dequantizer,
+ int logScale,
+ out Vector128 dequantizedCoefficients)
+ {
+ Vector128 sign = coefficients >> 31;
+ Vector128 magnitude = Vector128.Abs(coefficients);
+ Vector128 mask = ~Vector128.GreaterThan(zeroBin, magnitude);
+ Vector128 rounded = Vector128.Min(magnitude + rounding, Vector128.Create((int)short.MaxValue));
+
+ // Saturation bounds both signed products to 32-bit lanes. The first Q16 multiplication
+ // restores the reciprocal's implicit leading bit; the second removes its power-of-two scale.
+ Vector128 corrected = rounded + ((rounded * quantizer) >> 16);
+ Vector128 quantizedMagnitude = ((corrected * shift) >> (16 - logScale)) & mask;
+ Vector128 dequantizedMagnitude = (quantizedMagnitude * dequantizer) >> logScale;
+ dequantizedCoefficients = (dequantizedMagnitude ^ sign) - sign;
+ return (quantizedMagnitude ^ sign) - sign;
+ }
+
+ ///
+ public static Vector256 Quantize(
+ Vector256 coefficients,
+ Vector256 zeroBin,
+ Vector256 rounding,
+ Vector256 quantizer,
+ Vector256 shift,
+ Vector256 dequantizer,
+ int logScale,
+ out Vector256 dequantizedCoefficients)
+ {
+ Vector256 sign = coefficients >> 31;
+ Vector256 magnitude = Vector256.Abs(coefficients);
+ Vector256 mask = ~Vector256.GreaterThan(zeroBin, magnitude);
+ Vector256 rounded = Vector256.Min(magnitude + rounding, Vector256.Create((int)short.MaxValue));
+
+ // Saturation bounds both signed products to 32-bit lanes. The first Q16 multiplication
+ // restores the reciprocal's implicit leading bit; the second removes its power-of-two scale.
+ Vector256 corrected = rounded + ((rounded * quantizer) >> 16);
+ Vector256 quantizedMagnitude = ((corrected * shift) >> (16 - logScale)) & mask;
+ Vector256 dequantizedMagnitude = (quantizedMagnitude * dequantizer) >> logScale;
+ dequantizedCoefficients = (dequantizedMagnitude ^ sign) - sign;
+ return (quantizedMagnitude ^ sign) - sign;
+ }
+
+ ///
+ public static Vector512 Quantize(
+ Vector512 coefficients,
+ Vector512 zeroBin,
+ Vector512 rounding,
+ Vector512 quantizer,
+ Vector512 shift,
+ Vector512 dequantizer,
+ int logScale,
+ out Vector512 dequantizedCoefficients)
+ {
+ Vector512 sign = coefficients >> 31;
+ Vector512 magnitude = Vector512.Abs(coefficients);
+ Vector512 mask = ~Vector512.GreaterThan(zeroBin, magnitude);
+ Vector512 rounded = Vector512.Min(magnitude + rounding, Vector512.Create((int)short.MaxValue));
+
+ // Saturation bounds both signed products to 32-bit lanes. The first Q16 multiplication
+ // restores the reciprocal's implicit leading bit; the second removes its power-of-two scale.
+ Vector512 corrected = rounded + ((rounded * quantizer) >> 16);
+ Vector512 quantizedMagnitude = ((corrected * shift) >> (16 - logScale)) & mask;
+ Vector512 dequantizedMagnitude = (quantizedMagnitude * dequantizer) >> logScale;
+ dequantizedCoefficients = (dequantizedMagnitude ^ sign) - sign;
+ return (quantizedMagnitude ^ sign) - sign;
+ }
+
+ ///
+ public static int Quantize(
+ int coefficients,
+ int zeroBin,
+ int rounding,
+ int quantizer,
+ int shift,
+ int dequantizer,
+ int logScale,
+ out int dequantizedCoefficients)
+ {
+ int sign = coefficients >> 31;
+ int magnitude = (coefficients ^ sign) - sign;
+ int quantizedMagnitude = 0;
+ if (magnitude >= zeroBin)
+ {
+ int rounded = Math.Min(magnitude + rounding, short.MaxValue);
+ int corrected = rounded + ((rounded * quantizer) >> 16);
+ quantizedMagnitude = (corrected * shift) >> (16 - logScale);
+ }
+
+ int dequantizedMagnitude = (quantizedMagnitude * dequantizer) >> logScale;
+ dequantizedCoefficients = (dequantizedMagnitude ^ sign) - sign;
+ return (quantizedMagnitude ^ sign) - sign;
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1RegularQuantizerTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1RegularQuantizerTests.cs
new file mode 100644
index 0000000000..22ffb8ce99
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1RegularQuantizerTests.cs
@@ -0,0 +1,148 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Runtime.InteropServices;
+using System.Runtime.Intrinsics;
+using SixLabors.ImageSharp.Formats.Heif.Av1;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantizers;
+using SixLabors.ImageSharp.Formats.Heif.Av1.Transform;
+using SixLabors.ImageSharp.Tests.TestUtilities;
+
+namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1;
+
+///
+/// Verifies regular quantization's precision, scan position, and buffer boundaries across hardware paths.
+///
+[Trait("Format", "Avif")]
+public class Av1RegularQuantizerTests
+{
+ ///
+ /// Exercises regular quantization under each available vector width and scalar fallback.
+ ///
+ [Fact]
+ public void RegularQuantizationPreservesCoefficientContracts()
+ => FeatureTestRunner.RunWithHwIntrinsicsFeature(
+ ValidateQuantization,
+ HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
+
+ ///
+ /// Exports inputs and results for independent native comparison while checking observable storage contracts.
+ ///
+ private static void ValidateQuantization()
+ {
+ string vectorWidth = Vector512.IsHardwareAccelerated ? "512"
+ : Vector256.IsHardwareAccelerated ? "256"
+ : Vector128.IsHardwareAccelerated ? "128" : "0";
+
+ string directory = Path.Combine(TestEnvironment.ActualOutputDirectoryFullPath, "Heif", "Av1", "RegularQuantization", vectorWidth);
+
+ Directory.CreateDirectory(directory);
+ foreach (Av1BitDepth bitDepth in new[] { Av1BitDepth.EightBit, Av1BitDepth.TenBit, Av1BitDepth.TwelveBit })
+ {
+ foreach (Av1TransformSize size in new[]
+ {
+ Av1TransformSize.Size4x4, Av1TransformSize.Size8x8, Av1TransformSize.Size16x16,
+ Av1TransformSize.Size32x32, Av1TransformSize.Size64x64, Av1TransformSize.Size8x16
+ })
+ {
+ int count = size.GetAdjusted().GetSize2d();
+ int[] input = new int[count + 2];
+ int[] quantized = new int[count + 2];
+ int[] dequantized = new int[count + 2];
+ ReadOnlySpan scan = Av1ScanOrderConstants.GetScanOrder(size, Av1TransformType.DctDct).Scan;
+ foreach (int qIndex in new[] { 0, 1, 10, 90, 200, 255 })
+ {
+ foreach (int sharpness in new[] { 0, 3, 7 })
+ {
+ for (int pattern = 0; pattern < 4; pattern++)
+ {
+ int dcDelta = pattern == 1 ? -20 : pattern == 2 ? 17 : 0;
+ int acDelta = pattern == 1 ? 13 : pattern == 2 ? -11 : 0;
+ int bits = bitDepth.GetBitCount();
+ int maximum = (1 << (bits + 7)) - 1;
+ uint state = (uint)(qIndex + 123);
+ Array.Fill(input, int.MinValue);
+ Array.Fill(quantized, int.MinValue);
+ Array.Fill(dequantized, int.MinValue);
+ for (int index = 0; index < count; index++)
+ {
+ state = unchecked((state * 1664525) + 1013904223);
+ int magnitude = (int)(state % (uint)maximum);
+ if (pattern == 1)
+ {
+ int divisor = index == 0
+ ? Av1QuantizationLookup.GetDcQuant(qIndex, dcDelta, bitDepth)
+ : Av1QuantizationLookup.GetAcQuant(qIndex, acDelta, bitDepth);
+
+ int threshold = ((Av1InverseTransformMath.GetQzbinFactor(qIndex, bitDepth) * divisor) + 64) >> 7;
+ int scale = size.GetScale();
+ threshold = scale == 0 ? threshold : (threshold + (1 << (scale - 1))) >> scale;
+ magnitude = threshold + (index % 3) - 1;
+ }
+ else if (pattern == 2)
+ {
+ magnitude = maximum;
+ }
+ else if (pattern == 3)
+ {
+ // A sparse final scan position exposes stale output coefficients across candidate reuse.
+ magnitude = index == scan[count / 3] ? 97 : 0;
+ }
+
+ input[index + 1] = (index & 1) == 0 ? magnitude : -magnitude;
+ }
+
+ int[] original = (int[])input.Clone();
+ ushort end = Av1ForwardQuantizer.QuantizeRegular(
+ input.AsSpan(1, count),
+ quantized.AsSpan(1, count),
+ dequantized.AsSpan(1, count),
+ size,
+ Av1TransformType.DctDct,
+ qIndex,
+ dcDelta,
+ acDelta,
+ bitDepth,
+ sharpness);
+
+ Assert.Equal(original, input);
+ Assert.Equal(int.MinValue, quantized[0]);
+ Assert.Equal(int.MinValue, quantized[^1]);
+ Assert.Equal(int.MinValue, dequantized[0]);
+ Assert.Equal(int.MinValue, dequantized[^1]);
+ int expectedEnd = 0;
+ for (int index = 0; index < count; index++)
+ {
+ int level = quantized[index + 1];
+ int divisor = index == 0
+ ? Av1QuantizationLookup.GetDcQuant(qIndex, dcDelta, bitDepth)
+ : Av1QuantizationLookup.GetAcQuant(qIndex, acDelta, bitDepth);
+
+ // Signed dequantization truncates the magnitude before restoring sign.
+ int restored = (Math.Abs(level) * divisor) >> size.GetScale();
+ Assert.Equal(level < 0 ? -restored : restored, dequantized[index + 1]);
+ if (quantized[scan[index] + 1] != 0)
+ {
+ expectedEnd = index + 1;
+ }
+ }
+
+ Assert.Equal(expectedEnd, end);
+ using BinaryWriter output = new(File.Create(Path.Combine(
+ directory, $"{bits}-{(int)size}-{qIndex}-{sharpness}-{pattern}.bin")));
+
+ foreach (int value in new[] { bits, (int)size, qIndex, dcDelta, acDelta, sharpness, count, end })
+ {
+ output.Write(value);
+ }
+
+ output.Write(MemoryMarshal.AsBytes(input.AsSpan(1, count)));
+ output.Write(MemoryMarshal.AsBytes(quantized.AsSpan(1, count)));
+ output.Write(MemoryMarshal.AsBytes(dequantized.AsSpan(1, count)));
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs
index d0b77882d2..c1ff4e007b 100644
--- a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs
@@ -415,6 +415,12 @@ public static class FeatureTestRunner
features.Add(key, nameof(HwIntrinsics.AllowAll));
break;
#if NET11_0_OR_GREATER
+ case nameof(HwIntrinsics.DisableAVX512F):
+
+ // AVX-512's baseline features share one switch in .NET 11. The old F-only switch is
+ // ignored, which would run 512-bit code again instead of exercising narrower vectors.
+ features.Add(key, "EnableAVX512");
+ break;
case nameof(HwIntrinsics.DisableSSE42):
// SSE3 through SSE4.2 and POPCNT are x86-64-v2 baseline in .NET 11+ and the