diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md
index 9f4ed3d1f2..fa5224d13c 100644
--- a/HEIF_IMPLEMENTATION_PLAN.md
+++ b/HEIF_IMPLEMENTATION_PLAN.md
@@ -45,6 +45,9 @@ Reconciled with the worktree on 2026-09-05.
Current interpolation-search checkpoint, implemented on 2026-09-05 with focused verification in progress:
- [x] The requested in-progress tree was committed as `433afd1a9` before further encoder work. That commit is a checkpoint, not a claim of completed interpolation or codec delivery.
+- [x] The subsequent partition/interpolation/lossless correction was committed as `7cf7fc4` after the 225-case affected encoder run and exact current-main native comparison.
+- [x] Odd-sized color sequence verification exposed and corrected two further production defects. Empty inter luma transforms now retain the inferred DCT type before chroma inherits it; normalizing only during writing was too late. Region-major coefficient writing now rounds chroma end coordinates in 4x4 units, preserving the shared minimum chroma transform on sub-8x8 partitions instead of truncating it away. Both rules match current libaom's transform-type inference and `av1_write_intra_coeffs_mb` region bounds. The corrections add no allocation or sample copy.
+- [x] All 12 moving-color sequence cases pass with SIMD enabled and disabled: 8/10/12-bit 4:2:0 at effort eight, and all three bit depths across 4:2:0/4:2:2/4:4:4 at effort nine. The 23x19 sources require actual inter motion and subsampled chroma phases. Current-main libaom decodes all 24 emitted frames with exact native Y/U/V equality. Evidence: `artifacts/TestResults/av1-interpolation-color-sequence-20260905/color-sequence-r3.trx`, `color-sequence-scalar-r3.trx`, and the adjacent raw plane outputs under `tests/Images/ActualOutput/Heif/Av1/SequenceEncoderPreservesNativeColorPlanesWithSubpixelMotion`. The affected public/frame/superblock set passes 237 cases; the expanded encoder/entropy set passes 2,524, with no failures or skips. Evidence: `artifacts/TestResults/av1-interpolation-partition-broad-20260905/color-broad-r3.trx` and `color-encoder-entropy-r3.trx`. The final Release build and Roslyn compiler/analyzer passes have no errors. This closes the identified subsampled inter syntax/reconstruction gaps, not the remaining end-to-end performance and complete codec release matrix.
- [x] Production interpolation verification now forces Smooth and Sharp at effort eight, both dual-filter axis orders at effort nine, and native 10/12-bit two-axis half-sample motion. The tests assert actual retained filter symbols, vectors, exact reconstruction, and no allocator rent during tile coding. The sequence fixture now uses the retained-reference decode contract; the still-image buffer-transfer API intentionally releases the reference map and cannot decode dependent samples in succession.
- [x] This verification exposed a live partition traversal defect: after search changed an earlier node's child count, a later node could consume an unrelated entry from the initial flat 8x8 skeleton. Unsearched intra partitions now derive their default from the current block size, preserving the geometry-driven traversal used by current libaom. Wide and tall lossless regressions cover clipped parents and superblock boundaries at efforts nine and ten.
- [x] The affected frame encoder, intra-superblock encoder, and public HEIF encoder set passes all 225 cases on the corrected tree with zero failures or skips. Evidence: `artifacts/TestResults/av1-interpolation-partition-broad-20260905/partition-broad-r3.trx`. This is the affected encoder surface, not the full codec release matrix.
diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ReferenceModeDecision.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ReferenceModeDecision.cs
index 2c810a0741..dc29dfdbc5 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ReferenceModeDecision.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1IntraSuperblockEncoder.ReferenceModeDecision.cs
@@ -1100,6 +1100,13 @@ internal static partial class Av1IntraSuperblockEncoder
out Av1EncoderTransformBlockState emptyLumaState,
out long emptyLumaDistortion);
+ // Empty luma transforms signal no transform type. Chroma inherits the decoder's inferred DCT
+ // type, not the last searched luma type, so normalize before evaluating either chroma plane.
+ if (lumaState.EndOfBlock == 0)
+ {
+ lumaState.TransformType = Av1TransformType.DctDct;
+ }
+
ObuColorConfig colorConfig = this.picture.Sequence.SequenceHeader.ColorConfig;
int subsamplingX = colorConfig.SubSamplingX ? 1 : 0;
int subsamplingY = colorConfig.SubSamplingY ? 1 : 0;
diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs
index 480f6d80d6..a757f23c94 100644
--- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs
+++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileWriter.cs
@@ -2223,8 +2223,11 @@ internal partial class Av1TileWriter
{
int chromaRegionRow = regionRow >> subsamplingY;
int chromaRegionColumn = regionColumn >> subsamplingX;
- int chromaUnitBottom = unitBottom >> subsamplingY;
- int chromaUnitRight = unitRight >> subsamplingX;
+
+ // Region limits count 4x4 units. Round the subsampled end upward so a chroma-owning
+ // 4x4, 4x8, or 8x4 luma block still emits its shared 4x4 chroma transform.
+ int chromaUnitBottom = Av1Math.RoundPowerOf2(unitBottom, subsamplingY);
+ int chromaUnitRight = Av1Math.RoundPowerOf2(unitRight, subsamplingX);
EncodeTransformCoefficientRegion(
pcs,
entropyCodingContext,
diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EncoderFrameTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EncoderFrameTests.cs
index 2ba63c7a05..3fce5999c3 100644
--- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EncoderFrameTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EncoderFrameTests.cs
@@ -237,6 +237,134 @@ public class Av1EncoderFrameTests
Assert.Equal(new Size(Width, Height), decoded.Size);
}
+ ///
+ /// Verifies dependent color samples with odd visible dimensions and motion across subsampled chroma phases.
+ ///
+ [Theory]
+ [InlineData(EightBit, Yuv420, 8)]
+ [InlineData(TenBit, Yuv420, 8)]
+ [InlineData(TwelveBit, Yuv420, 8)]
+ [InlineData(EightBit, Yuv420, 9)]
+ [InlineData(TenBit, Yuv420, 9)]
+ [InlineData(TwelveBit, Yuv420, 9)]
+ [InlineData(EightBit, Yuv422, 9)]
+ [InlineData(TenBit, Yuv422, 9)]
+ [InlineData(TwelveBit, Yuv422, 9)]
+ [InlineData(EightBit, Yuv444, 9)]
+ [InlineData(TenBit, Yuv444, 9)]
+ [InlineData(TwelveBit, Yuv444, 9)]
+ public void SequenceEncoderPreservesNativeColorPlanesWithSubpixelMotion(int bitDepthValue, int colorFormatValue, int effort)
+ {
+ const int Width = 23;
+ const int Height = 19;
+ const int QIndex = 17;
+ const int ByteToUInt16Scale = ushort.MaxValue / byte.MaxValue;
+ Av1BitDepth bitDepth = (Av1BitDepth)bitDepthValue;
+ Av1ColorFormat colorFormat = (Av1ColorFormat)colorFormatValue;
+ ObuColorConfig colorConfig = CreateColorConfig(bitDepth, colorFormat);
+ ReadOnlySpan period = [0, 28, 40, 28, 0, -28, -40, -12];
+ using Image source = new(Width, Height);
+ using Av1FrameEncoder.SequenceEncoder encoder = Av1FrameEncoder.CreateColorSequenceEncoder(
+ Configuration.Default, Width, Height, colorConfig, QIndex, effort);
+
+ string outputDirectory = TestEnvironment.CreateOutputDirectory("Heif", "Av1", nameof(this.SequenceEncoderPreservesNativeColorPlanesWithSubpixelMotion));
+ string outputName = $"{bitDepth.GetBitCount()}-{colorFormat}-effort{effort}";
+ using FileStream output = File.Create(Path.Combine(outputDirectory, outputName + ".obu"));
+ using BinaryWriter rawOutput = new(File.Create(Path.Combine(outputDirectory, outputName + ".managed.yuv")));
+ using Av1Decoder decoder = new(Configuration.Default);
+ using MemoryStream sample = new();
+ for (int frameIndex = 0; frameIndex < 2; frameIndex++)
+ {
+ // The second source translates all three channels by one luma sample on each axis. Chroma is
+ // converted independently by the production converter, so 4:2:0 and 4:2:2 cannot hide behind
+ // constant neutral planes. Odd dimensions also exercise each plane's visible-edge clipping.
+ for (int y = 0; y < Height; y++)
+ {
+ Span row = source.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(y);
+ int referenceY = Math.Min(y + frameIndex, Height - 1);
+ for (int x = 0; x < Width; x++)
+ {
+ int referenceX = Math.Min(x + frameIndex, Width - 1);
+ row[x] = new Rgb48(
+ (ushort)((128 + period[referenceX % period.Length]) * ByteToUInt16Scale),
+ (ushort)((128 + period[referenceY % period.Length]) * ByteToUInt16Scale),
+ (ushort)((128 + period[(referenceX + referenceY) % period.Length]) * ByteToUInt16Scale));
+ }
+ }
+
+ sample.SetLength(0);
+ if (frameIndex == 0)
+ {
+ encoder.EncodeKeyFrame(source.Frames.RootFrame, sample);
+ }
+ else
+ {
+ encoder.EncodeInterFrame(source.Frames.RootFrame, sample);
+ }
+
+ sample.Position = 0;
+ sample.CopyTo(output);
+ decoder.DecodeSequenceReference(sample.ToArray(), null, null);
+ Av1FrameBuffer decoded = Assert.IsType>(decoder.FrameBuffer);
+ Assert.Equal(Width, decoded.Width);
+ Assert.Equal(Height, decoded.Height);
+ Assert.Equal(bitDepth, decoded.BitDepth);
+ for (int planeIndex = 0; planeIndex < 3; planeIndex++)
+ {
+ Av1Plane plane = (Av1Plane)planeIndex;
+ int subsamplingX = plane == Av1Plane.Y || !colorConfig.SubSamplingX ? 0 : 1;
+ int subsamplingY = plane == Av1Plane.Y || !colorConfig.SubSamplingY ? 0 : 1;
+ int planeHeight = (Height + subsamplingY) >> subsamplingY;
+ if (bitDepth == Av1BitDepth.EightBit)
+ {
+ Buffer2DRegion planeSamples = decoded.DeriveBlockPointer(plane, subsamplingX, subsamplingY);
+ for (int y = 0; y < planeHeight; y++)
+ {
+ rawOutput.Write(planeSamples.DangerousGetRowSpan(y));
+ }
+ }
+ else
+ {
+ for (int y = 0; y < planeHeight; y++)
+ {
+ foreach (ushort value in decoded.GetHighBitDepthRowSpan(plane, y, subsamplingX, subsamplingY))
+ {
+ // Raw high-bit-depth output uses explicit little-endian samples on every host.
+ rawOutput.Write(value);
+ }
+ }
+ }
+ }
+ }
+
+ ObuFrameHeader frameHeader = Assert.IsType(decoder.FrameHeader);
+ Assert.Equal(ObuFrameType.InterFrame, frameHeader.FrameType);
+ Assert.Equal(Av1InterpolationFilter.Switchable, frameHeader.InterpolationFilter);
+ Av1FrameInfo frameInfo = Assert.IsType(decoder.FrameInfo);
+ bool hasMotion = false;
+ bool hasFractionalChromaMotion = false;
+ foreach (Av1BlockModeInfo mode in frameInfo.GetModeInfos(Point.Empty, frameInfo.GetModeInfoCount(Point.Empty)))
+ {
+ if (mode.ReferenceFrames[0] == Av1ReferenceFrameType.Last)
+ {
+ Av1MotionVector vector = mode.MotionVectors[0];
+ hasMotion |= vector.Column != 0 || vector.Row != 0;
+
+ // A subsampled chroma phase repeats every two luma pixels, or sixteen Q3 motion units.
+ int chromaPhaseMask = (Av1MotionVector.SubpixelScale << 1) - 1;
+ hasFractionalChromaMotion |=
+ (colorConfig.SubSamplingX && (vector.Column & chromaPhaseMask) != 0) ||
+ (colorConfig.SubSamplingY && (vector.Row & chromaPhaseMask) != 0);
+ }
+ }
+
+ Assert.True(hasMotion);
+ if (colorConfig.SubSamplingX || colorConfig.SubSamplingY)
+ {
+ Assert.True(hasFractionalChromaMotion);
+ }
+ }
+
///
/// Verifies retained reference reconstruction and effort-dependent filter signaling through production sequence decoding.
///