diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md index 78f07240d2..a05065e906 100644 --- a/HEIF_IMPLEMENTATION_PLAN.md +++ b/HEIF_IMPLEMENTATION_PLAN.md @@ -43,7 +43,8 @@ Checkboxes may be marked complete only when the implementation and the verificat - [x] Move the H.273 parameter resolver, closed color operators, transfer functions, sample loading/storage, chroma filtering, and RGB packing into one shared HEIF color pipeline used by both AV1 and HEVC rather than maintaining codec-specific arithmetic copies. - [x] Implement sequential pooled HEVC conversion in both directions for monochrome, 4:2:0, 4:2:2, and 4:4:4; independent luma/chroma precision; 8/10/12-bit full/limited ranges; and all six progressive 4:2:0 chroma sample locations. Eight-bit paths use JPEG's optimized RGB plane contracts, while high-bit-depth paths retain 16-bit packed pixels. - [x] Implement allocation-free SIMD-first HEVC intra prediction for all 35 modes, 4/8/16/32 blocks, and 8/10/12-bit samples, including three-tap and strong-bilinear reference filtering, negative-angle reference extension, luma boundary filters, and SIMD horizontal transposition. Verify the normal and forced-scalar paths against one scalar oracle. - - [ ] Connect prediction to coding-tree and transform-unit traversal, reconstructed-plane reference availability, and the bounded item decoder lifecycle. + - [x] Implement allocation-free reconstructed-plane reference collection and normative substitution for complete, empty, and partially available borders, including asymmetric 4:2:2 availability units and the HEVC mode/size/component filter-selection rules. + - [ ] Derive each reference-unit availability flag from coding-tree, slice, tile, constrained-intra, and picture-boundary state, then connect prediction to transform-unit traversal and the bounded item decoder lifecycle. - [ ] Complete the SIMD YUV/CICP paths for every supported AV1 bit depth, chroma format, range, matrix, transfer function, color primary, and chroma position, with scalar fallback only when hardware vectorization is unavailable or the operation is inherently non-vectorizable. - [x] Apply ICC conversion only after the SIMD YUV/CICP stage, alpha composition, grid assembly, and presentation transforms have produced the presented RGB image; retain ImageSharp's shared ICC converter and optimize reusable bulk kernels rather than creating a HEIF-specific color-management implementation. - [ ] Verify ICC preservation, conversion, compaction, and metadata skipping for grids, alpha-composited images, every presented sequence frame, and the completed HEVC path. @@ -462,7 +463,8 @@ Implement and verify in dependency order: - [ ] Coding-tree, coding-unit, prediction-unit, and transform-unit traversal across all permitted sizes and partition modes. - [ ] Intra prediction for every luma and chroma mode, including strong intra smoothing and constrained prediction rules. - [x] Implement and verify the allocation-free SIMD-first predictor primitive for all 35 modes, 4/8/16/32 blocks, and 8/10/12-bit samples. - - [ ] Build reference availability from reconstructed-plane and coding state, and connect the predictor to transform-unit traversal. + - [x] Implement reconstructed-plane reference collection, substitution, asymmetric chroma-unit handling, and reference-filter selection from caller-owned availability and scratch spans. + - [ ] Build each availability flag from reconstructed-plane and coding state, and connect the predictor to transform-unit traversal. - [ ] Scaling lists, inverse quantization, transform skip, every required inverse transform, range-extension precision, and lossless reconstruction. - [ ] Deblocking and sample-adaptive offset for every signaled luma/chroma and bit-depth path. - [ ] Tiles, wavefront entry points, dependent slices, and all other parallelization syntax permitted by the exposed still-image profiles. @@ -558,6 +560,7 @@ Tasks: - [x] Complete the active AV1 forward and inverse transform-family checklist above. - [ ] Benchmark codec-specific costs for CABAC/range decode, inverse transforms, still-image prediction, deblocking, SAO, CDEF, restoration, chroma upsampling, color conversion, alpha packing, and grid copies. - [x] Add a permanent frame-wide HEVC intra-prediction benchmark. On .NET 10, SIMD planar, vertical-angular, and horizontal-angular prediction measured 155.2, 150.7, and 255.0 microseconds per padded 1920x1088 frame, compared with forced-scalar timings of 1.890, 1.081, and 0.979 milliseconds: 12.2, 7.2, and 3.8 times faster with zero managed allocations. + - [x] Add a permanent frame-wide HEVC reference-preparation benchmark. On .NET 10, complete and partially substituted borders measured 465.3 and 566.7 microseconds per 2,040-block padded 1920x1088 frame, compared with forced-scalar timings of 477.5 and 608.0 microseconds, with zero managed allocations. - [ ] Implement vector paths only for confirmed hot loops, using existing `Vector128`, `Vector256`, and `Vector512` helper and dispatch patterns where supported. - [ ] Prioritize shared color conversion and pixel packing, chroma upsampling, inverse-transform add-and-clip, intra predictors, HEVC deblock/SAO, AV1 loop filter/CDEF/restoration, and contiguous grid copies. - [ ] Benchmark the complete decode color pipeline on representative 8/10/12-bit AVIF and HEIC images with and without embedded ICC profiles. Report absolute end-to-end timings and allocations in addition to the isolated YUV/CICP and ICC stage costs. diff --git a/src/ImageSharp/Formats/Heif/Hevc/HevcIntraPredictor.References.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcIntraPredictor.References.cs new file mode 100644 index 0000000000..2c33acba18 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Hevc/HevcIntraPredictor.References.cs @@ -0,0 +1,174 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif.Hevc; + +/// +/// Provides reference-sample preparation and filter selection. +/// +internal static partial class HevcIntraPredictor +{ + /// + /// Gets the angular-distance threshold for reference filtering at each supported block size. + /// + private static ReadOnlySpan ReferenceFilterThresholds => [10, 7, 1, 0]; + + /// + /// Gets the temporary sample count required while preparing prediction references. + /// + /// The base-two logarithm of the square prediction-block side. + /// The horizontal availability-unit width in plane samples. + /// The required number of elements. + public static int GetReferenceScratchLength(int log2Size, int unitWidth) + { + DebugGuard.MustBeBetweenOrEqualTo(log2Size, 2, 5, nameof(log2Size)); + return (4 << log2Size) + unitWidth; + } + + /// + /// Determines whether the selected mode uses filtered prediction references. + /// + /// The reconstructed plane. + /// The effective prediction mode in the inclusive range zero through thirty-four. + /// The base-two logarithm of the square prediction-block side. + /// The HEVC chroma-format identifier. + /// Whether the sequence disables intra-reference smoothing. + /// when the prepared references require filtering; otherwise, . + public static bool ShouldFilterReferenceSamples(HevcPlane plane, int mode, int log2Size, byte chromaFormat, bool intraSmoothingDisabled) + { + DebugGuard.MustBeBetweenOrEqualTo(mode, PlanarMode, 34, nameof(mode)); + DebugGuard.MustBeBetweenOrEqualTo(log2Size, 2, 5, nameof(log2Size)); + if (intraSmoothingDisabled || (plane != HevcPlane.Y && chromaFormat != 3) || mode == DcMode) + { + return false; + } + + int angularDistance = Math.Min(Math.Abs(mode - HorizontalMode), Math.Abs(mode - VerticalMode)); + return angularDistance > ReferenceFilterThresholds[log2Size - 2]; + } + + /// + /// Prepares substituted top and left references from one reconstructed picture plane. + /// + /// The reconstructed still-picture planes. + /// The plane containing the prediction block. + /// The prediction-block left coordinate in plane samples. + /// The prediction-block top coordinate in plane samples. + /// The base-two logarithm of the square prediction-block side. + /// The horizontal availability-unit width in plane samples. + /// The vertical availability-unit height in plane samples. + /// + /// The availability flags ordered from the bottom-most below-left unit upward through top-left, then from the + /// left-most above unit through the right-most above-right unit. + /// + /// The destination top-left, top, and top-right reference samples. + /// The destination top-left, left, and below-left reference samples. + /// The caller-owned temporary storage sized by . + public static void PrepareReferenceSamples( + HevcPictureBuffer picture, + HevcPlane plane, + int x, + int y, + int log2Size, + int unitWidth, + int unitHeight, + ReadOnlySpan availableUnits, + Span top, + Span left, + Span scratch) + { + DebugGuard.MustBeBetweenOrEqualTo(log2Size, 2, 5, nameof(log2Size)); + int size = 1 << log2Size; + int referenceLength = (size * 2) + 1; + int leftUnitCount = (size * 2) / unitHeight; + int aboveUnitCount = (size * 2) / unitWidth; + int totalUnitCount = leftUnitCount + aboveUnitCount + 1; + ReadOnlySpan availability = availableUnits[..totalUnitCount]; + + if (availability.IndexOf(true) < 0) + { + ushort midpoint = (ushort)(1 << (picture.GetBitDepth(plane) - 1)); + top[..referenceLength].Fill(midpoint); + left[..referenceLength].Fill(midpoint); + return; + } + + if (availability.IndexOf(false) < 0) + { + picture.GetRowSpan(plane, y - 1).Slice(x - 1, referenceLength).CopyTo(top); + left[0] = top[0]; + for (int i = 1; i < referenceLength; i++) + { + left[i] = picture.GetRowSpan(plane, y + i - 1)[x - 1]; + } + + return; + } + + int leftSampleCount = size * 2; + int lineLength = leftSampleCount + unitWidth + (size * 2); + Span line = scratch[..lineLength]; + line.Fill((ushort)(1 << (picture.GetBitDepth(plane) - 1))); + + // The logical line runs from the bottom-most below-left sample towards the corner and then to the farthest + // above-right sample. This makes substitution a forward fill across availability units. + for (int unit = 0; unit < leftUnitCount; unit++) + { + if (!availability[unit]) + { + continue; + } + + int sourceY = y + ((leftUnitCount - unit - 1) * unitHeight); + int destinationEnd = ((unit + 1) * unitHeight) - 1; + for (int offset = 0; offset < unitHeight; offset++) + { + line[destinationEnd - offset] = picture.GetRowSpan(plane, sourceY + offset)[x - 1]; + } + } + + int cornerUnit = leftUnitCount; + if (availability[cornerUnit]) + { + line.Slice(leftSampleCount, unitWidth).Fill(picture.GetRowSpan(plane, y - 1)[x - 1]); + } + + ReadOnlySpan aboveRow = picture.GetRowSpan(plane, y - 1); + int topStart = leftSampleCount + unitWidth; + for (int unit = 0; unit < aboveUnitCount; unit++) + { + if (availability[cornerUnit + unit + 1]) + { + aboveRow.Slice(x + (unit * unitWidth), unitWidth).CopyTo(line.Slice(topStart + (unit * unitWidth), unitWidth)); + } + } + + int firstAvailableUnit = availability.IndexOf(true); + int firstAvailableOffset = firstAvailableUnit < leftUnitCount + ? firstAvailableUnit * unitHeight + : leftSampleCount + ((firstAvailableUnit - leftUnitCount) * unitWidth); + + int lineOffset = 0; + ushort precedingSample = line[firstAvailableOffset]; + for (int unit = 0; unit < totalUnitCount; unit++) + { + int sampleCount = unit < leftUnitCount ? unitHeight : unitWidth; + Span unitSamples = line.Slice(lineOffset, sampleCount); + if (!availability[unit]) + { + unitSamples.Fill(precedingSample); + } + + precedingSample = unitSamples[^1]; + lineOffset += sampleCount; + } + + int cornerOffset = leftSampleCount + unitWidth - 1; + top[0] = left[0] = line[cornerOffset]; + line.Slice(topStart, size * 2).CopyTo(top[1..]); + for (int i = 1; i < referenceLength; i++) + { + left[i] = line[leftSampleCount - i]; + } + } +} diff --git a/src/ImageSharp/Formats/Heif/Hevc/HevcPictureBuffer.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcPictureBuffer.cs index bd2ebd72c1..152757fb87 100644 --- a/src/ImageSharp/Formats/Heif/Hevc/HevcPictureBuffer.cs +++ b/src/ImageSharp/Formats/Heif/Hevc/HevcPictureBuffer.cs @@ -136,6 +136,13 @@ internal sealed class HevcPictureBuffer : IDisposable /// Zero for luma and full-resolution planes; otherwise, the chroma shift. public int GetSubsamplingY(HevcPlane plane) => plane == HevcPlane.Y ? 0 : this.chromaSubsamplingY; + /// + /// Gets the sample precision for the selected reconstruction plane. + /// + /// The reconstruction plane. + /// The plane sample precision in bits. + public int GetBitDepth(HevcPlane plane) => plane == HevcPlane.Y ? this.BitDepthLuma : this.BitDepthChroma; + /// /// Gets the selected plane width in samples. /// diff --git a/tests/ImageSharp.Benchmarks/Codecs/Heif/HevcIntraReferencePreparationBenchmarks.cs b/tests/ImageSharp.Benchmarks/Codecs/Heif/HevcIntraReferencePreparationBenchmarks.cs new file mode 100644 index 0000000000..89f9937151 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Codecs/Heif/HevcIntraReferencePreparationBenchmarks.cs @@ -0,0 +1,154 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats.Heif.Hevc; + +namespace SixLabors.ImageSharp.Benchmarks.Codecs.Heif; + +/// +/// Measures frame-wide preparation of complete and partially substituted HEVC intra references. +/// +[MemoryDiagnoser(displayGenColumns: false)] +public class HevcIntraReferencePreparationBenchmarks +{ + /// + /// The coded frame width. + /// + private const int Width = 1920; + + /// + /// The coded frame height including the final padded coding-tree row for a 1080-line presentation. + /// + private const int Height = 1088; + + /// + /// The base-two logarithm of the benchmark prediction-block side. + /// + private const int BlockLog2 = 5; + + /// + /// The prediction-block side in samples. + /// + private const int BlockSize = 1 << BlockLog2; + + /// + /// The availability-unit side in samples. + /// + private const int UnitSize = 4; + + /// + /// The number of availability units surrounding one benchmark block. + /// + private const int AvailabilityUnitCount = ((BlockSize * 2) / UnitSize * 2) + 1; + + /// + /// The complete reference availability pattern. + /// + private readonly bool[] completeAvailability = new bool[AvailabilityUnitCount]; + + /// + /// The partial availability pattern exercising forward substitution. + /// + private readonly bool[] partialAvailability = new bool[AvailabilityUnitCount]; + + /// + /// The reusable top reference destination. + /// + private readonly ushort[] top = new ushort[(BlockSize * 2) + 1]; + + /// + /// The reusable left reference destination. + /// + private readonly ushort[] left = new ushort[(BlockSize * 2) + 1]; + + /// + /// The reusable reference-line scratch storage. + /// + private readonly ushort[] scratch = new ushort[HevcIntraPredictor.GetReferenceScratchLength(BlockLog2, UnitSize)]; + + /// + /// The reconstructed picture providing benchmark reference samples. + /// + private HevcPictureBuffer picture; + + /// + /// Allocates and initializes the reconstructed benchmark picture outside measured operations. + /// + [GlobalSetup] + public void Setup() + { + this.picture = new HevcPictureBuffer( + Configuration.Default, + Width + (BlockSize * 2), + Height + (BlockSize * 2), + 12, + 12, + 1, + false); + + for (int y = 0; y < this.picture.Height; y++) + { + Span row = this.picture.GetRowSpan(HevcPlane.Y, y); + for (int x = 0; x < row.Length; x++) + { + row[x] = (ushort)(((37 * x) + (53 * y)) & 4095); + } + } + + this.completeAvailability.AsSpan().Fill(true); + for (int i = 0; i < this.partialAvailability.Length; i++) + { + this.partialAvailability[i] = (i & 3) != 0; + } + } + + /// + /// Releases the reconstructed benchmark picture. + /// + [GlobalCleanup] + public void Cleanup() => this.picture.Dispose(); + + /// + /// Measures the complete-border fast path across a padded 1080p coded frame. + /// + /// A prepared reference sample, keeping the result observable. + [Benchmark(Baseline = true)] + public ushort PrepareCompleteFrame() => this.PrepareFrame(this.completeAvailability); + + /// + /// Measures partial-unit collection and substitution across a padded 1080p coded frame. + /// + /// A prepared reference sample, keeping the result observable. + [Benchmark] + public ushort PreparePartialFrame() => this.PrepareFrame(this.partialAvailability); + + /// + /// Prepares references for every maximum-size block in the benchmark frame. + /// + /// The repeated availability pattern. + /// The final prepared reference sample. + private ushort PrepareFrame(ReadOnlySpan availability) + { + for (int y = 0; y < Height; y += BlockSize) + { + for (int x = 0; x < Width; x += BlockSize) + { + HevcIntraPredictor.PrepareReferenceSamples( + this.picture, + HevcPlane.Y, + x + BlockSize, + y + BlockSize, + BlockLog2, + UnitSize, + UnitSize, + availability, + this.top, + this.left, + this.scratch); + } + } + + return this.left[^1]; + } +} diff --git a/tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcIntraReferenceSamplesTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcIntraReferenceSamplesTests.cs new file mode 100644 index 0000000000..9e4c206133 --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/Hevc/HevcIntraReferenceSamplesTests.cs @@ -0,0 +1,228 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Hevc; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif.Hevc; + +/// +/// Verifies HEVC intra-reference collection, substitution, and filtering decisions. +/// +[Trait("Format", "Heic")] +public class HevcIntraReferenceSamplesTests +{ + /// + /// Verifies that a fully available reference border is copied directly from the reconstructed plane. + /// + [Fact] + public void PrepareReferenceSamplesCopiesCompleteBorder() + { + const int blockX = 8; + const int blockY = 8; + const int log2Size = 2; + const int unitSize = 2; + const int referenceLength = 9; + using HevcPictureBuffer picture = CreatePicture(32, 32, 10, 1); + bool[] availableUnits = new bool[9]; + availableUnits.AsSpan().Fill(true); + ushort[] top = new ushort[referenceLength]; + ushort[] left = new ushort[referenceLength]; + ushort[] scratch = new ushort[HevcIntraPredictor.GetReferenceScratchLength(log2Size, unitSize)]; + + HevcIntraPredictor.PrepareReferenceSamples( + picture, + HevcPlane.Y, + blockX, + blockY, + log2Size, + unitSize, + unitSize, + availableUnits, + top, + left, + scratch); + + for (int i = 0; i < referenceLength; i++) + { + Assert.Equal(GetSample(blockX + i - 1, blockY - 1), top[i]); + Assert.Equal(GetSample(blockX - 1, blockY + i - 1), left[i]); + } + } + + /// + /// Verifies that a block without available neighbors receives the component midpoint. + /// + [Fact] + public void PrepareReferenceSamplesUsesMidpointWithoutNeighbors() + { + const int log2Size = 2; + const int unitSize = 2; + const ushort midpoint = 512; + using HevcPictureBuffer picture = CreatePicture(16, 16, 10, 1); + bool[] availableUnits = new bool[9]; + ushort[] top = new ushort[9]; + ushort[] left = new ushort[9]; + ushort[] scratch = new ushort[HevcIntraPredictor.GetReferenceScratchLength(log2Size, unitSize)]; + + HevcIntraPredictor.PrepareReferenceSamples( + picture, + HevcPlane.Y, + 0, + 0, + log2Size, + unitSize, + unitSize, + availableUnits, + top, + left, + scratch); + + Assert.All(top, sample => Assert.Equal(midpoint, sample)); + Assert.All(left, sample => Assert.Equal(midpoint, sample)); + } + + /// + /// Verifies forward substitution across unavailable below-left, left, corner, top, and above-right units. + /// + [Fact] + public void PrepareReferenceSamplesSubstitutesPartialBorderInNormativeOrder() + { + const int log2Size = 2; + const int unitSize = 2; + using HevcPictureBuffer picture = CreatePicture(32, 32, 10, 1); + bool[] availableUnits = [false, true, false, true, false, false, true, false, false]; + ushort[] top = new ushort[9]; + ushort[] left = new ushort[9]; + ushort[] scratch = new ushort[HevcIntraPredictor.GetReferenceScratchLength(log2Size, unitSize)]; + + HevcIntraPredictor.PrepareReferenceSamples( + picture, + HevcPlane.Y, + 8, + 8, + log2Size, + unitSize, + unitSize, + availableUnits, + top, + left, + scratch); + + ushort[] expectedTop = [807, 807, 807, 710, 711, 711, 711, 711, 711]; + ushort[] expectedLeft = [807, 807, 907, 1207, 1207, 1207, 1307, 1307, 1307]; + Assert.True(expectedTop.AsSpan().SequenceEqual(top)); + Assert.True(expectedLeft.AsSpan().SequenceEqual(left)); + } + + /// + /// Verifies asymmetric availability units used by horizontally subsampled 4:2:2 chroma planes. + /// + [Fact] + public void PrepareReferenceSamplesSupportsAsymmetricChromaUnits() + { + const int blockX = 16; + const int blockY = 16; + const int log2Size = 3; + const int unitWidth = 2; + const int unitHeight = 4; + const int referenceLength = 17; + using HevcPictureBuffer picture = CreatePicture(48, 48, 10, 2); + bool[] availableUnits = [true, false, false, false, false, true, false, false, false, false, false, false, false]; + ushort[] top = new ushort[referenceLength]; + ushort[] left = new ushort[referenceLength]; + ushort[] scratch = new ushort[HevcIntraPredictor.GetReferenceScratchLength(log2Size, unitWidth)]; + + HevcIntraPredictor.PrepareReferenceSamples( + picture, + HevcPlane.Cb, + blockX, + blockY, + log2Size, + unitWidth, + unitHeight, + availableUnits, + top, + left, + scratch); + + ushort substitutedLeft = GetSample(blockX - 1, blockY + 12); + Assert.Equal(substitutedLeft, top[0]); + Assert.Equal(GetSample(blockX, blockY - 1), top[1]); + Assert.Equal(GetSample(blockX + 1, blockY - 1), top[2]); + for (int i = 3; i < referenceLength; i++) + { + Assert.Equal(top[2], top[i]); + } + + for (int i = 0; i <= 13; i++) + { + Assert.Equal(substitutedLeft, left[i]); + } + + Assert.Equal(GetSample(blockX - 1, blockY + 13), left[14]); + Assert.Equal(GetSample(blockX - 1, blockY + 14), left[15]); + Assert.Equal(GetSample(blockX - 1, blockY + 15), left[16]); + } + + /// + /// Verifies the mode, size, component, chroma-format, and sequence controls for reference filtering. + /// + [Fact] + public void ShouldFilterReferenceSamplesUsesHevcThresholds() + { + Assert.False(HevcIntraPredictor.ShouldFilterReferenceSamples(HevcPlane.Y, 0, 2, 1, false)); + Assert.True(HevcIntraPredictor.ShouldFilterReferenceSamples(HevcPlane.Y, 0, 3, 1, false)); + Assert.False(HevcIntraPredictor.ShouldFilterReferenceSamples(HevcPlane.Y, 1, 5, 1, false)); + Assert.False(HevcIntraPredictor.ShouldFilterReferenceSamples(HevcPlane.Y, 26, 5, 1, false)); + Assert.True(HevcIntraPredictor.ShouldFilterReferenceSamples(HevcPlane.Y, 2, 3, 1, false)); + Assert.False(HevcIntraPredictor.ShouldFilterReferenceSamples(HevcPlane.Cb, 2, 3, 1, false)); + Assert.True(HevcIntraPredictor.ShouldFilterReferenceSamples(HevcPlane.Cb, 2, 3, 3, false)); + Assert.False(HevcIntraPredictor.ShouldFilterReferenceSamples(HevcPlane.Y, 2, 3, 1, true)); + } + + /// + /// Creates a picture whose luma and chroma samples encode their source coordinates. + /// + /// The coded luma width. + /// The coded luma height. + /// The component precision. + /// The HEVC chroma-format identifier. + /// The initialized picture. + private static HevcPictureBuffer CreatePicture(int width, int height, int bitDepth, byte chromaFormat) + { + HevcPictureBuffer picture = new(Configuration.Default, width, height, bitDepth, bitDepth, chromaFormat, false); + FillPlane(picture, HevcPlane.Y); + if (chromaFormat != 0) + { + FillPlane(picture, HevcPlane.Cb); + FillPlane(picture, HevcPlane.Cr); + } + + return picture; + } + + /// + /// Fills one picture plane with coordinate-derived samples. + /// + /// The destination picture. + /// The destination plane. + private static void FillPlane(HevcPictureBuffer picture, HevcPlane plane) + { + for (int y = 0; y < picture.GetHeight(plane); y++) + { + Span row = picture.GetRowSpan(plane, y); + for (int x = 0; x < row.Length; x++) + { + row[x] = GetSample(x, y); + } + } + } + + /// + /// Gets the deterministic sample value for one plane coordinate. + /// + /// The sample X coordinate. + /// The sample Y coordinate. + /// The coordinate-derived sample. + private static ushort GetSample(int x, int y) => (ushort)((y * 100) + x); +}