From 9ef4059ee65b782e42d17c79df838a9c78388f84 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 24 Aug 2026 18:00:39 +1000 Subject: [PATCH] Correct AV1 inverse transform row traversal --- HEIF_IMPLEMENTATION_PLAN.md | 1 + .../Av1/Transform/Av1Inverse2dTransformer.cs | 4 +- .../Heif/Av1/Av1InverseTransformTests.cs | 46 +++++++++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md index 3c6151bd91..2bcc74685f 100644 --- a/HEIF_IMPLEMENTATION_PLAN.md +++ b/HEIF_IMPLEMENTATION_PLAN.md @@ -59,6 +59,7 @@ This snapshot pins or classifies the available references and failures; it does | `Av1YuvConverter.ConvertToRgb`, `ConvertFromRgb`, scalar row conversion, and chroma reconstruction | H.273 formulas 20-31 and the identity, YCgCo, and non-constant-luminance matrix formulas; AV1 section 6.4.2 chroma sample positions | libavif `src/reformat.c` and `src/colr.c` at `092276ce89098ead06db80975173191e5fee1826`; libaom `aom/aom_image.h` at `03087864cf4bea6abb0d28f95cf7843511413d8f` | Scalar behavioral oracle for 8-bit full/limited-range conversion. Decode covers monochrome, YUV 4:2:0, 4:2:2, and 4:4:4 with AV1 chroma sample positioning; encode remains YUV 4:4:4 at this snapshot. Later high-bit-depth and SIMD paths must match it. | | `Av1FrameBuffer` high-bit-depth sample layout and `Av1YuvConverter` 10/12-bit output conversion | AV1 section 6.4.1 bit depth and H.273 sample-range scaling | libaom `aom_scale/yv12config.h`, `av1/common/idct.c`, and `av1/common/reconintra.c` at `03087864cf4bea6abb0d28f95cf7843511413d8f`; libavif `src/avif.c` and `src/reformat.c` at `092276ce89098ead06db80975173191e5fee1826` | Establish two-byte native sample storage with sample-unit strides for 10/12-bit reconstruction and use the same scalar color model at every supported bit depth. | | `Av1PredictionDecoder` and the scalar DC, directional, Paeth, smooth, and filter-intra predictors | AV1 sections 7.11.2 and 7.11.2.3 intra prediction | libaom `aom_dsp/intrapred.c` and `av1/common/reconintra.c` at `03087864cf4bea6abb0d28f95cf7843511413d8f` | Behavioral oracle for neighbor addressing, directional upsampling, Paeth selection, smooth normalization, filter-intra taps, and chroma-from-luma row strides. Existing managed scalar predictors remain the implementation base. The WIP rectangular smooth digest expectations encode width/height-swapped weights and must be replaced only from an independently generated oracle, not regenerated from this implementation. | +| `Av1Inverse2dTransformer` and `Av1InverseTransformerFactory` | AV1 section 7.11.2 inverse transform and reconstruction | libaom `av1/common/av1_inv_txfm1d.c`, `av1/common/av1_inv_txfm2d.c`, and `av1/common/idct.c` at `03087864cf4bea6abb0d28f95cf7843511413d8f` | Scalar transform oracle for coefficient-row traversal, intermediate layout, stage ranges, clipping, and high-bit-depth sample addition. The managed 16-bit overload is also used as a parity oracle for the byte overload. | This table is intentionally incomplete. Add a row before each additional AV1 or HEVC algorithm is ported or materially reshaped. diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Inverse2dTransformer.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Inverse2dTransformer.cs index db0a42610c..c885ca8d36 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Inverse2dTransformer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1Inverse2dTransformer.cs @@ -208,8 +208,8 @@ internal class Av1Inverse2dTransformer } Av1InverseTransformMath.RoundShiftArray(bufPtr, transformWidth, -shift[0]); - input.Slice(transformWidth); - bufPtr.Slice(transformWidth); + input = input[transformWidth..]; + bufPtr = bufPtr[transformWidth..]; } // Columns diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs index 8f71751396..31554a94b8 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1InverseTransformTests.cs @@ -221,6 +221,52 @@ public class Av1InverseTransformTests Assert.True(CompareWithError(expected, actual, 1)); } + [Theory] + [InlineData((int)Av1TransformType.DctDct)] + [InlineData((int)Av1TransformType.AdstAdst)] + [InlineData((int)Av1TransformType.Identity)] + public void EightBitTransformMatchesSixteenBitPipeline(int transformTypeIndex) + { + const int width = 4; + Av1TransformType transformType = (Av1TransformType)transformTypeIndex; + int[] coefficients = + [ + 48, -15, 7, 3, + 11, 5, -9, 2, + -6, 8, 4, -3, + 9, -2, 6, 1, + ]; + byte[] actual = new byte[width * width]; + short[] expected = new short[width * width]; + Array.Fill(actual, (byte)96); + Array.Fill(expected, (short)96); + int[] actualBuffer = new int[(width * width) + (2 * width)]; + int[] expectedBuffer = new int[(width * width) + (2 * width)]; + Av1Transform2dFlipConfiguration actualConfig = new(transformType, Av1TransformSize.Size4x4); + Av1Transform2dFlipConfiguration expectedConfig = new(transformType, Av1TransformSize.Size4x4); + + Av1Inverse2dTransformer.Transform2dAdd( + coefficients, + actual, + width, + actual, + width, + actualConfig, + actualBuffer); + + Av1Inverse2dTransformer.Transform2dAdd( + coefficients, + expected, + width, + expected, + width, + expectedConfig, + expectedBuffer, + 8); + + Assert.Equal(expected.Select(value => (byte)value), actual); + } + private static void AssertAccuracy1d( Av1TransformType transformType, Av1TransformSize transformSize,