diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md index 21758d3cc..4ae8cab26 100644 --- a/HEIF_IMPLEMENTATION_PLAN.md +++ b/HEIF_IMPLEMENTATION_PLAN.md @@ -45,7 +45,8 @@ Checkboxes may be marked complete only when the implementation and the verificat - [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. - [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. - [x] Implement allocation-free SIMD-first HEVC inverse quantization for flat and custom scaling lists, 4/8/16/32 transforms, transform-skip scaling rules, extended precision, signed normalization shifts, and transform-range clipping. Store compact and transform-sized scaling matrices in one contiguous parameter-set allocation so the hot path can widen consecutive weights without coordinate division. - - [ ] 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. + - [x] 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. + - [x] Register the bounded `hvc1` still-image decoder, reconstruct the selected PPS/SPS picture, crop the conformance window directly in the pooled row converter, and pass the native planes through the shared SIMD-first H.273 pipeline. - [x] Implement the shared SIMD-first YUV/CICP paths in both directions for AV1 and HEVC bit depths, chroma formats, ranges, matrix coefficients, transfer functions, color primaries, and chroma positions, with scalar fallback only after the available vector widths are exhausted. - [ ] Complete the independently generated AV1 and HEVC conformance matrix for every supported color-signaling combination before closing the Phase 5 color exit gate. - [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. @@ -242,6 +243,7 @@ This snapshot pins or classifies the available references and failures; it does | `HevcSequenceParameterSet`, `HevcVideoUsabilityInformation`, `HevcScalingList`, `HevcShortTermReferencePictureSet`, and `HevcParameterSetSyntax` | HEVC sections 7.3.2.2 sequence parameter sets, 7.3.4 scaling-list data, 7.3.7 short-term reference-picture sets, E.2.1 VUI syntax, and Range Extensions SPS syntax | HM `source/Lib/TLibDecoder/TDecCAVLC.cpp` functions `parseSPS`, `parseScalingList`, `xDecodeScalingList`, `parseShortTermRefPicSet`, `parseVUI`, and `parseHrdParameters`, plus `source/Lib/TLibCommon/TComRom.cpp` default matrices and diagonal scans, at `9c1f298659ab0cee9dc13d23d0304221575410b9`; Android `libhevc` `decoder/ihevcd_parse_headers.c` SPS, VUI, scaling-list, and reference-set paths at `c83a76b084498d55f252f48b2e3786804cdf24b7` | Retain coded/display dimensions, conformance crop, monochrome/4:2:0/4:2:2/4:4:4 and separate-plane layout, 8-through-16-bit precision, coding/transform/PCM geometry, effective scaling matrices, compression-tool flags, bounded reference-set declarations, Range Extensions tools, and still-image VUI aspect/color/range/chroma-location/display-window fields. Consume timing, HRD, ordering, and bitstream-restriction syntax without scheduling state. Reject interlaced fields and multilayer extensions because they do not represent the one progressive independently coded image-item contract; add no DPB pictures, playback state, tracks, or sample tables. | | `HevcPictureParameterSet` and `HevcCodecConfiguration.PictureParameterSets` | HEVC sections 7.3.2.3 picture parameter sets, 7.3.4 scaling-list data, and Range Extensions PPS syntax | HM `source/Lib/TLibDecoder/TDecCAVLC.cpp` function `parsePPS`, `source/Lib/TLibDecoder/TDecConformance.cpp` PPS range checks, and `source/Lib/TLibCommon/TComPicSym.cpp` tile derivation at `9c1f298659ab0cee9dc13d23d0304221575410b9`; Android `libhevc` `decoder/ihevcd_parse_headers.c` PPS and tile paths at `c83a76b084498d55f252f48b2e3786804cdf24b7` | Link each PPS to a configuration-local SPS and retain the CABAC, quantization, transform-skip, scaling-list, tile/wavefront, deblocking, SAO, slice-header, and Range Extensions controls required to reconstruct the independently coded picture. Derive the complete CTB tile grid and validate every SPS-dependent depth, size, precision, and offset bound during parsing. Reject multilayer extensions and add no access-unit, reference-picture lifetime, playback, track, timing, or sample-table model. | | `HevcImageItemBitstream`, `HevcSliceSegmentHeader`, and `HevcSliceType` | HEVC sections 7.3.1.2 NAL-unit syntax, 7.3.6 slice-segment headers, and 7.4.7 slice-segment-header semantics; HEVC image-item length delimiting | HM `source/Lib/TLibDecoder/TDecCAVLC.cpp` function `parseSliceHeader`, `source/Lib/TLibDecoder/NALread.cpp`, and slice byte-alignment and entry-point handling at `9c1f298659ab0cee9dc13d23d0304221575410b9`; Android `libhevc` `decoder/ihevcd_nal.c`, `ihevcd_parse_slice_header.c`, and `ihevcd_parse_headers.c` at `c83a76b084498d55f252f48b2e3786804cdf24b7` | Split only the selected `hvc1` image item's one-through-four-byte length-delimited NAL units, reject in-band parameter-set replacement and end-of-sequence state, and require exactly one base-layer IDR picture. Retain the intra slice, separate-plane, SAO, quantization, deblocking, cross-slice filtering, tile/wavefront entry-point, extension, byte-alignment, and entropy-payload fields required by reconstruction. Keep emulation-prevention positions only to translate slice substream byte boundaries; add no Annex B scanner, access-unit collection, non-IDR reference lifetime, playback, timing, track, or sample model. | +| `HevcHeifItemDecoder`, `HeifCompressionFactory`, and `HevcYuvConverter` coded-window output | HEVC sections 6.1 through 6.3 decoded picture and conformance-window semantics; ISO/IEC 14496-12 section 12.1.5 color information and ISO/IEC 23008-12 HEVC image-item binding | HM `source/Lib/TLibDecoder/TDecTop.cpp` decoded-picture output and `source/Lib/TLibCommon/TComPicYuv.cpp` conformance-window handling at `9c1f298659ab0cee9dc13d23d0304221575410b9`; ImageSharp's shared HEIF H.273 SIMD operators, pooled row conversion, and item-decoder lifecycle | Register only `hvc1` still-image items, bind the selected slice PPS/SPS to one picture decoder, convert the conformance-window region directly from native component planes without an intermediate packed image, and preserve the effective container-or-VUI CICP description used by conversion. Reuse the shared descending-width SIMD color operators and pixel packers with one pooled row workspace. Add no Annex B entry point, decoded-picture queue, inter-picture state, track, timing, or general-purpose HEVC API. | | `HevcCabacContext` and `HevcCabacDecoder` | HEVC sections 9.3.3.2 through 9.3.3.4 context initialization, arithmetic decoding, bypass decoding, and termination | HM `source/Lib/TLibCommon/ContextModel.cpp`, `source/Lib/TLibCommon/TComCABACTables.cpp`, and `source/Lib/TLibDecoder/TDecBinCoderCABAC.cpp` at `9c1f298659ab0cee9dc13d23d0304221575410b9`; Android `libhevc` `common/ihevc_cabac_tables.c`, `common/ihevc_cabac_tables.h`, `decoder/ihevcd_cabac.c`, and `decoder/ihevcd_cabac.h` at `c83a76b084498d55f252f48b2e3786804cdf24b7` | Implement the bit-exact scalar probability-state transitions, range normalization, bounded byte refills, bypass modes, termination, and alignment validation for one slice tile or wavefront substream. CABAC decisions are serial and adaptive, so this primitive is the scalar oracle rather than a SIMD target; later prediction, transform, filtering, and color-conversion stages must reuse ImageSharp's compatible SIMD kernels. The decoder owns no container boxes, access units, reference pictures, tracks, timing, playback, or general-purpose bitstream surface. | | `HevcCabacContexts` | HEVC section 9.3.2.2 initialization of context variables for an intra-coded slice and Range Extensions context additions | HM `source/Lib/TLibCommon/ContextTables.h` and `source/Lib/TLibDecoder/TDecSbac.cpp` function `resetEntropy` at `9c1f298659ab0cee9dc13d23d0304221575410b9`; Android `libhevc` `common/ihevc_cabac_tables.c` and `decoder/ihevcd_cabac.c` at `c83a76b084498d55f252f48b2e3786804cdf24b7` | Initialize only the context groups reachable from the independently coded intra-picture syntax, including transform skip, chroma quantization adjustment, and cross-component prediction. Keep one contiguous mutable context set per entropy substream so tile and wavefront synchronization can copy the normative adaptive state without retaining reference-picture or playback state. Omit skip, merge, motion, reference-index, and other inter-picture-only groups. | | `HevcCabacSyntaxReader` | HEVC sections 9.3.3 through 9.3.4 CABAC binarization and context selection for intra coding units, transform trees, residual coefficients, sample-adaptive offset, and Range Extensions tools | HM `source/Lib/TLibDecoder/TDecSbac.cpp`, `source/Lib/TLibCommon/TComChromaFormat.h`, and `source/Lib/TLibCommon/TComTrQuant.cpp` at `9c1f298659ab0cee9dc13d23d0304221575410b9`; Android `libhevc` `decoder/ihevcd_cabac.c`, `decoder/ihevcd_parse_slice.c`, and `decoder/ihevcd_parse_residual.c` at `c83a76b084498d55f252f48b2e3786804cdf24b7` | Decode only syntax values reachable while reconstructing the selected independently coded still picture: intra partition and mode selectors, transform splitting and coded-block flags, quantization adjustments, transform skip, coefficient significance and levels, cross-component prediction, SAO, bypass alignment, and termination. Keep neighbor-dependent context selection in the owning coding-tree/reconstruction layer and expose no inter prediction, reference-picture, access-unit, track, timing, or playback syntax. | @@ -289,8 +291,8 @@ This assessment is based on the current source after the upstream ImageSharp mer ### HEVC decoder and encoder -- `Heif4CharCode` recognizes `hvc1` image items, `HevcCodecConfiguration` validates and associates the bounded `hvcC` property, and Identify reports its HEVC precision and monochrome shape, but `HeifCompressionFactory` has no HEVC item decoder. -- The HEVC path now validates NAL headers and RBSP escaping, parses and links `hvcC` VPS/SPS/PPS structures through Range Extensions, and parses the selected image item's length-delimited base-layer IDR slice headers and entropy payload boundaries. Its scalar CABAC primitive implements the exact adaptive state transitions, arithmetic range normalization, bypass modes, bounded refills, termination, and stop-bit validation from the pinned HM reference. The one-allocation context owner initializes every intra-picture and Range Extensions context that still-image reconstruction can select while omitting inter-picture-only groups. The codec-local syntax reader decodes the intra coding-unit, transform, coefficient, SAO, and Range Extensions binarizations. Coefficient decoding now covers grouped directional scans, last-significant and significance contexts, greater-than-one/two and escape levels, sign hiding, bypass alignment, and persistent Rice adaptation from one pooled entropy-substream scratch owner. Allocator-owned native `ushort` picture planes cover monochrome, 4:2:0, 4:2:2, 4:4:4, separate-color-plane, and 8-through-16-bit reconstruction without a decoded-picture queue. Coding-tree and transform-unit traversal are not yet wired. The path is deliberately not registered as an item decoder until coding-tree reconstruction, still-image intra prediction, inverse quantization/transform, deblocking, sample-adaptive offset, color conversion, and pixel output are implemented. +- `Heif4CharCode` recognizes `hvc1` image items, `HevcCodecConfiguration` validates and associates the bounded `hvcC` property, Identify reports its HEVC precision and monochrome shape, and `HeifCompressionFactory` registers the bounded HEVC still-image decoder. +- The HEVC path validates NAL headers and RBSP escaping, links `hvcC` VPS/SPS/PPS structures through Range Extensions, parses one length-delimited base-layer IDR picture, and reconstructs its complete intra-coded coding tree. It includes CABAC, transform traversal, coefficient decoding, intra prediction, inverse quantization and transform, PCM, deblocking, sample-adaptive offset, and allocator-owned native `ushort` planes for monochrome, 4:2:0, 4:2:2, 4:4:4, separate-color-plane, and 8-through-16-bit samples. The item adapter converts the SPS conformance window directly through the shared pooled SIMD-first H.273 pipeline and preserves the effective container-or-VUI CICP description. The internal HEVC suite passes, but independent pixel comparisons for the repository's HEIC fixtures and the complete color/precision/layout matrix remain open, so conformance is not yet established. - There is no HEVC encoder. The current HEIC-branded encoder writes a legacy JPEG payload and therefore cannot provide HEIC output. - Existing HEVC tests prove container identification only; they do not decode or compare HEIC pixels. diff --git a/src/ImageSharp/Formats/Heif/HeifCompressionFactory.cs b/src/ImageSharp/Formats/Heif/HeifCompressionFactory.cs index df0095703..3e73b0148 100644 --- a/src/ImageSharp/Formats/Heif/HeifCompressionFactory.cs +++ b/src/ImageSharp/Formats/Heif/HeifCompressionFactory.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Heif.Hevc; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Heif; @@ -21,6 +22,7 @@ internal static class HeifCompressionFactory { Heif4CharCode.Jpeg => new JpegHeifItemDecoder(), Heif4CharCode.Av01 => new Av1HeifItemDecoder(), + Heif4CharCode.Hvc1 => new HevcHeifItemDecoder(), _ => null }; } diff --git a/src/ImageSharp/Formats/Heif/Hevc/HevcYuvConverter.cs b/src/ImageSharp/Formats/Heif/Hevc/HevcYuvConverter.cs index c1d6a233e..012ff858e 100644 --- a/src/ImageSharp/Formats/Heif/Hevc/HevcYuvConverter.cs +++ b/src/ImageSharp/Formats/Heif/Hevc/HevcYuvConverter.cs @@ -47,17 +47,21 @@ internal static class HevcYuvConverter /// The destination image frame. /// The effective H.273 color description. /// The progressive-frame 4:2:0 chroma sample location. + /// The horizontal luma-sample offset of the first converted pixel. + /// The vertical luma-sample offset of the first converted pixel. public static void ConvertToRgb( Configuration configuration, HevcPictureBuffer picture, ImageFrame image, CicpProfile colorProfile, - HevcChromaSampleLocation chromaSampleLocation) + HevcChromaSampleLocation chromaSampleLocation, + int sourceX = 0, + int sourceY = 0) where TPixel : unmanaged, IPixel { HeifColorConversionParameters parameters = GetConversionParameters(picture, colorProfile, out HeifColorConversionMode mode); HeifColorConverterBase colorConverter = HeifColorConverterBase.Create(mode, in parameters, picture.ChromaFormat == 0); - YuvToRgbRowConverter converter = new(configuration, picture, image, colorConverter, chromaSampleLocation); + YuvToRgbRowConverter converter = new(configuration, picture, image, colorConverter, chromaSampleLocation, sourceX, sourceY); using IMemoryOwner scratchOwner = configuration.MemoryAllocator.Allocate(converter.BufferLength); Span scratch = scratchOwner.GetSpan(); @@ -243,6 +247,16 @@ internal static class HevcYuvConverter /// private readonly int subY; + /// + /// The horizontal luma-sample offset of the output window. + /// + private readonly int sourceX; + + /// + /// The vertical luma-sample offset of the output window. + /// + private readonly int sourceY; + /// /// Initializes a new instance of the struct. /// @@ -251,12 +265,16 @@ internal static class HevcYuvConverter /// The destination image frame. /// The selected H.273 color converter. /// The progressive-frame 4:2:0 chroma sample location. + /// The horizontal luma-sample offset of the output window. + /// The vertical luma-sample offset of the output window. public YuvToRgbRowConverter( Configuration configuration, HevcPictureBuffer picture, ImageFrame image, HeifColorConverterBase colorConverter, - HevcChromaSampleLocation chromaSampleLocation) + HevcChromaSampleLocation chromaSampleLocation, + int sourceX, + int sourceY) { this.configuration = configuration; this.picture = picture; @@ -264,6 +282,8 @@ internal static class HevcYuvConverter this.colorConverter = colorConverter; this.subX = picture.GetSubsamplingX(HevcPlane.Cb); this.subY = picture.GetSubsamplingY(HevcPlane.Cb); + this.sourceX = sourceX; + this.sourceY = sourceY; GetChromaPosition(picture, chromaSampleLocation, out this.horizontalPosition, out this.verticalPosition); } @@ -279,9 +299,16 @@ internal static class HevcYuvConverter { get { - int componentRowCount = this.picture.ChromaFormat == 0 ? 3 : 5; + int componentLength = this.image.Width * 3; + if (this.picture.ChromaFormat != 0 && this.subX != 0) + { + // Cropped output can begin between subsampled chroma positions. Reconstructing one complete + // coded-width row preserves the edge interpolation before selecting the visible window. + componentLength += this.picture.Width + (this.picture.GetWidth(HevcPlane.Cb) * 2); + } + int packedRowCount = this.UsesBytePacking ? 1 : 2; - return this.image.Width * (componentRowCount + packedRowCount); + return componentLength + (this.image.Width * packedRowCount); } } @@ -297,23 +324,56 @@ internal static class HevcYuvConverter Span red = scratch[..width]; Span green = scratch.Slice(width, width); Span blue = scratch.Slice(width * 2, width); - ConvertSamplesToFloat(this.picture.GetRowSpan(HevcPlane.Y, y), red); + int sourceY = y + this.sourceY; + ReadOnlySpan luma = this.picture.GetRowSpan(HevcPlane.Y, sourceY).Slice(this.sourceX, width); + ConvertSamplesToFloat(luma, red); int packedOffset = width * 3; if (this.picture.ChromaFormat != 0) { int chromaHeight = this.picture.GetHeight(HevcPlane.Cb); - GetChromaCoordinates(y, this.subY, this.verticalPosition, chromaHeight - 1, out int y0, out int y1, out int y1Weight); + GetChromaCoordinates(sourceY, this.subY, this.verticalPosition, chromaHeight - 1, out int y0, out int y1, out int y1Weight); ReadOnlySpan cb0 = this.picture.GetRowSpan(HevcPlane.Cb, y0); ReadOnlySpan cb1 = this.picture.GetRowSpan(HevcPlane.Cb, y1); ReadOnlySpan cr0 = this.picture.GetRowSpan(HevcPlane.Cr, y0); ReadOnlySpan cr1 = this.picture.GetRowSpan(HevcPlane.Cr, y1); - Span chroma0 = scratch.Slice(width * 3, width); - Span chroma1 = scratch.Slice(width * 4, width); - bool isCenteredX = this.subX != 0 && this.horizontalPosition == 1; - ReconstructChromaRow(cb0, cb1, y1Weight, this.subX, isCenteredX, green, chroma0, chroma1); - ReconstructChromaRow(cr0, cr1, y1Weight, this.subX, isCenteredX, blue, chroma0, chroma1); - packedOffset = width * 5; + if (this.subX == 0) + { + ConvertSamplesToFloat(cb0.Slice(this.sourceX, width), green); + ConvertSamplesToFloat(cr0.Slice(this.sourceX, width), blue); + } + else + { + int chromaWidth = this.picture.GetWidth(HevcPlane.Cb); + Span reconstructed = scratch.Slice(packedOffset, this.picture.Width); + Span chroma0 = scratch.Slice(packedOffset + this.picture.Width, chromaWidth); + Span chroma1 = scratch.Slice(packedOffset + this.picture.Width + chromaWidth, chromaWidth); + bool isCenteredX = this.horizontalPosition == 1; + + ReconstructChromaRow( + cb0, + cb1, + y1Weight, + this.subX, + isCenteredX, + reconstructed, + chroma0, + chroma1); + + reconstructed.Slice(this.sourceX, width).CopyTo(green); + ReconstructChromaRow( + cr0, + cr1, + y1Weight, + this.subX, + isCenteredX, + reconstructed, + chroma0, + chroma1); + + reconstructed.Slice(this.sourceX, width).CopyTo(blue); + packedOffset += this.picture.Width + (chromaWidth * 2); + } } this.colorConverter.ConvertToRgbInPlace(red, green, blue); diff --git a/src/ImageSharp/Formats/Heif/HevcHeifItemDecoder.cs b/src/ImageSharp/Formats/Heif/HevcHeifItemDecoder.cs new file mode 100644 index 000000000..4ba80c86c --- /dev/null +++ b/src/ImageSharp/Formats/Heif/HevcHeifItemDecoder.cs @@ -0,0 +1,111 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Hevc; +using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Cicp; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Heif; + +/// +/// Decodes a single HEVC-coded HEIF image item. +/// +/// The destination pixel type. +internal sealed class HevcHeifItemDecoder : IHeifItemDecoder + where TPixel : unmanaged, IPixel +{ + /// + /// Gets the HEVC-coded image item type. + /// + public Heif4CharCode Type => Heif4CharCode.Hvc1; + + /// + /// Gets the HEVC compression method. + /// + public HeifCompressionMethod CompressionMethod => HeifCompressionMethod.Hevc; + + /// + /// Decodes the encoded HEVC payload of an image item. + /// + /// The general options governing the containing HEIF decode. + /// The HEIF item whose encoded payload is being decoded. + /// The encoded HEVC payload. + /// The container color description that takes precedence over bitstream color information. + /// The token used to cancel the payload decode. + /// The decoded image. + public Image DecodeItemData( + DecoderOptions options, + HeifItem item, + Span data, + CicpProfile? colorProfile, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + HevcCodecConfiguration codecConfiguration = item.HevcCodecConfiguration + ?? throw new InvalidImageContentException($"HEVC image item {item.Id} has no codec configuration property."); + + if (item.ChannelBitDepths is not null) + { + codecConfiguration.ValidateChannelBitDepths(item.ChannelBitDepths); + } + + HevcImageItemBitstream bitstream = new(data, codecConfiguration); + HevcPictureParameterSet pictureParameterSet = bitstream.SliceSegments[0].PictureParameterSet; + HevcSequenceParameterSet sequenceParameterSet = pictureParameterSet.SequenceParameterSet; + HevcVideoUsabilityInformation? vui = sequenceParameterSet.VideoUsabilityInformation; + + // ISO BMFF color information takes precedence when both the container and HEVC VUI describe the image. + // Otherwise, retain the VUI values used by conversion so bitstream-only color information reaches metadata. + CicpProfile effectiveColorProfile = colorProfile is not null + ? new CicpProfile( + (byte)colorProfile.ColorPrimaries, + (byte)colorProfile.TransferCharacteristics, + (byte)colorProfile.MatrixCoefficients, + colorProfile.FullRange) + : new CicpProfile( + vui?.ColorDescriptionPresent == true ? vui.ColorPrimaries : (byte)CicpColorPrimaries.Unspecified, + vui?.ColorDescriptionPresent == true ? vui.TransferCharacteristics : (byte)CicpTransferCharacteristics.Unspecified, + vui?.ColorDescriptionPresent == true ? vui.MatrixCoefficients : (byte)CicpMatrixCoefficients.Unspecified, + vui?.VideoSignalTypePresent == true && vui.FullRange); + + HevcChromaSampleLocation chromaSampleLocation = vui?.ChromaLocationInfoPresent == true + ? vui.ChromaSampleLocationTopField + : HevcChromaSampleLocation.Left; + + using HevcPictureDecoder decoder = new(options.Configuration, pictureParameterSet); + decoder.Decode(bitstream); + cancellationToken.ThrowIfCancellationRequested(); + + ImageFrame? frame = null; + try + { + frame = new ImageFrame(options.Configuration, sequenceParameterSet.DisplayWidth, sequenceParameterSet.DisplayHeight); + HevcYuvConverter.ConvertToRgb( + options.Configuration, + decoder.Picture, + frame, + effectiveColorProfile, + chromaSampleLocation, + sequenceParameterSet.ConformanceWindowLeftOffset, + sequenceParameterSet.ConformanceWindowTopOffset); + + ImageMetadata metadata = new() + { + CicpProfile = effectiveColorProfile.DeepClone() + }; + + HeifMetadata heifMetadata = metadata.GetHeifMetadata(); + heifMetadata.CompressionMethod = this.CompressionMethod; + heifMetadata.BitDepth = codecConfiguration.BitDepth; + heifMetadata.IsMonochrome = codecConfiguration.IsMonochrome; + return new Image(options.Configuration, metadata, [frame]); + } + catch + { + // Ownership transfers only after the image constructor accepts the completely converted frame. + frame?.Dispose(); + throw; + } + } +}