From 4c972965c92498b3c180db537e9125b656d13722 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 9 Sep 2026 10:09:24 +1000 Subject: [PATCH] Decode AVIF directly into destination regions Write still images, grid tiles, and sequence frames into their final regions, combining presentation transforms, alpha, and ICC conversion. Add explicit chroma upsampling selection and correct high-bit-depth sample normalization. Remove JPEG item handling and restore shared Flip/Rotate implementations. Update encoded versus decoded metadata assertions and reference-image output. Preserve the existing allocator identity contract. Document native reference provenance, accepted ICC baselines, and remaining encoder acceptance gaps. Validation: Roslynk reports zero errors; Release/net11 builds successfully. Visual Studio VSTest passed all 10,487 selected HEIF/AV1, ICC, Flip/Rotate, and allocator cases. Temporary native tooling and unused Hadamard changes are excluded from this commit. --- HEIF_IMPLEMENTATION_PLAN.md | 356 +++++++++- .../Formats/Heif/Av1/Av1CodecConfiguration.cs | 6 +- src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs | 256 ++----- .../Formats/Heif/Av1/Av1FrameBuffer.cs | 20 + .../Formats/Heif/Av1/Color/Av1YuvConverter.cs | 190 ++--- .../Heif/Av1/Pipeline/Av1FrameDecoder.cs | 42 +- .../Heif/Av1/Pipeline/Av1FrameEncoder.cs | 3 +- .../LoopFilter/Av1LoopFilterContext.cs | 167 ----- .../LoopFilter/Av1LoopFilterDecoder.cs | 26 +- .../Heif/Av1/Tiling/Av1BlockModeInfo.cs | 34 + .../Formats/Heif/Av1/Tiling/Av1TileReader.cs | 21 +- .../Heif/Av1/Transform/Av1BlockDecoder.cs | 30 - .../Formats/Heif/Av1HeifItemDecoder.cs | 85 ++- .../Components/Alpha/HeifAlphaRowSource.cs | 74 ++ .../Alpha/HeifPlanarAlphaCompositor.cs | 91 ++- .../Alpha/HeifPlanarAlphaResizeWorker.cs | 126 ++-- .../Components/Alpha/IHeifAlphaItemDecoder.cs | 27 +- .../HeifColorConverter.Alpha.cs | 100 +++ .../ColorConverters/HeifColorConverter.Icc.cs | 235 +++++++ .../HeifColorConverter.Operator.cs | 56 +- .../HeifPlanarColorConverter.cs | 443 ++++++++---- .../ColorConverters/HeifSampleConversion.cs | 396 ++++++----- .../ColorConverters/HeifYuvToRgb8Converter.cs | 44 +- .../Formats/Heif/GridHeifItemDecoder.cs | 394 +++++------ src/ImageSharp/Formats/Heif/Heif4CharCode.cs | 10 - src/ImageSharp/Formats/Heif/Heif4CharCode.tt | 2 - .../Formats/Heif/HeifChromaUpsampling.cs | 25 + .../Formats/Heif/HeifCompressionFactory.cs | 1 - .../Formats/Heif/HeifCompressionMethod.cs | 20 - src/ImageSharp/Formats/Heif/HeifConstants.cs | 17 +- src/ImageSharp/Formats/Heif/HeifDecoder.cs | 16 +- .../Formats/Heif/HeifDecoderCore.cs | 607 ++++++---------- .../Formats/Heif/HeifDecoderOptions.cs | 18 + src/ImageSharp/Formats/Heif/HeifEncoder.cs | 14 +- .../Formats/Heif/HeifEncoderCore.cs | 112 +-- .../Formats/Heif/HeifItemDecoderUtilities.cs | 33 - src/ImageSharp/Formats/Heif/HeifMetadata.cs | 6 - .../Formats/Heif/HeifPixelTransform.cs | 113 +++ .../Formats/Heif/IHeifItemDecoder.cs | 32 +- .../Formats/Heif/JpegHeifItemDecoder.cs | 52 -- .../Linear/FlipProcessor{TPixel}.cs | 16 +- .../Linear/RotateProcessor{TPixel}.cs | 33 +- .../Heif/Av1ColorConversionBenchmarks.cs | 17 +- .../Heif/Av1SequenceEncoderBenchmarks.cs | 208 ------ .../Codecs/Heif/README.md | 118 +++- .../Heif/Av1/Av1CoefficientsEntropyTests.cs | 6 +- .../Heif/Av1/Av1CompoundBlockDecoderTests.cs | 39 -- .../Heif/Av1/Av1DeblockingFilterTests.cs | 12 +- .../Formats/Heif/Av1/Av1EncoderFrameTests.cs | 445 +++++++----- .../Heif/Av1/Av1EncoderModeInfoBufferTests.cs | 6 +- .../Formats/Heif/Av1/Av1EntropyTests.cs | 14 +- .../Formats/Heif/Av1/Av1FrameBufferTests.cs | 71 +- .../Av1/Av1IntraSuperblockEncoderTests.cs | 145 ++-- .../Formats/Heif/Av1/Av1MotionSearchTests.cs | 79 +-- .../Av1/Av1ReconstructionConformanceTests.cs | 99 ++- .../Heif/Av1/Av1ReferenceFrameStoreTests.cs | 8 +- .../Heif/Av1/Av1RegularQuantizerTests.cs | 18 - .../Formats/Heif/Av1/Av1TilingTests.cs | 43 +- .../Heif/Av1/Av1TransformBlockEncoderTests.cs | 2 +- .../Heif/Av1/Av1TransformEstimateTests.cs | 28 - .../Formats/Heif/Av1/Av1YuvConverterTests.cs | 285 +++++++- .../Formats/Heif/Av1/ObuFrameHeaderTests.cs | 14 +- .../Heif/Av1/ObuFrameLifecycleTests.cs | 27 +- .../Formats/Heif/HeifDecoderTests.cs | 365 +++++----- .../Formats/Heif/HeifEncoderTests.cs | 653 +++++++----------- .../Formats/Heif/HeifMetadataTests.cs | 4 - .../Formats/Heif/HeifSequenceParserTests.cs | 5 - .../ReferenceCodecs/MagickReferenceDecoder.cs | 9 + ...ference_Rgba32_libavif-cdef-kodim23-8b.png | 4 +- ..._Rgba32_libavif-restoration-kodim23-8b.png | 4 +- ...nce_Rgba32_libavif-superres-kodim23-8b.png | 4 +- .../Decode_Rgba32_Irvine_CA.png | 3 + .../Decode_Rgba32_Orange4x4.png | 3 + .../00.png | 3 + .../01.png | 3 + .../02.png | 3 + .../03.png | 3 + .../04.png | 3 + .../00.png | 3 + .../01.png | 3 + .../02.png | 3 + .../03.png | 3 + .../04.png | 3 + .../00.png | 3 + .../01.png | 3 + .../02.png | 3 + .../03.png | 3 + .../04.png | 3 + .../Decode_Rgba32_ducky_romm_icc_alpha.png | 3 + .../Decode_Rgba32_jpeg444_xnconvert.png | 3 + .../Decode_Rgba32_libavif-cdef-kodim23-8b.png | 3 + ...code_Rgba32_libavif-intrabc-abc-8b-444.png | 3 + .../Decode_Rgba32_libavif-kodim23-8b.png | 3 + ..._Rgba32_libavif-lossless-circle-8b-444.png | 3 + ..._Rgba32_libavif-palette-draw-points-8b.png | 3 + .../Decode_Rgba32_libavif-profile-8b-400.png | 3 + .../Decode_Rgba32_libavif-profile-8b-420.png | 3 + .../Decode_Rgba32_libavif-profile-8b-422.png | 3 + .../Decode_Rgba32_libavif-profile-8b-444.png | 3 + ...a32_libavif-progressive-draw-points-8b.png | 3 + ..._Rgba32_libavif-restoration-kodim23-8b.png | 3 + .../00.png | 3 + .../01.png | 3 + .../00.png | 3 + .../01.png | 3 + ...ode_Rgba32_libavif-superres-kodim23-8b.png | 3 + .../00.png | 3 + .../01.png | 3 + .../02.png | 3 + .../03.png | 3 + .../04.png | 3 + .../05.png | 3 + .../06.png | 3 + .../07.png | 3 + .../08.png | 3 + .../09.png | 3 + .../10.png | 3 + .../11.png | 3 + .../12.png | 3 + .../13.png | 3 + .../14.png | 3 + .../15.png | 3 + .../16.png | 3 + .../17.png | 3 + .../18.png | 3 + .../00.png | 3 + .../01.png | 3 + .../02.png | 3 + .../03.png | 3 + .../04.png | 3 + .../05.png | 3 + .../06.png | 3 + .../07.png | 3 + .../08.png | 3 + .../09.png | 3 + .../10.png | 3 + .../11.png | 3 + .../12.png | 3 + .../13.png | 3 + .../14.png | 3 + .../15.png | 3 + .../16.png | 3 + .../17.png | 3 + .../18.png | 3 + .../00.png | 3 + .../01.png | 3 + .../02.png | 3 + .../03.png | 3 + .../04.png | 3 + .../05.png | 3 + .../06.png | 3 + .../07.png | 3 + .../08.png | 3 + .../09.png | 3 + .../10.png | 3 + .../11.png | 3 + .../12.png | 3 + .../13.png | 3 + .../14.png | 3 + .../15.png | 3 + .../16.png | 3 + .../17.png | 3 + .../18.png | 3 + .../00.png | 3 + .../01.png | 3 + .../02.png | 3 + .../03.png | 3 + .../04.png | 3 + .../05.png | 3 + .../06.png | 3 + .../07.png | 3 + .../08.png | 3 + .../09.png | 3 + .../10.png | 3 + .../11.png | 3 + .../12.png | 3 + .../13.png | 3 + .../14.png | 3 + .../15.png | 3 + .../16.png | 3 + .../17.png | 3 + .../18.png | 3 + .../00.png | 3 + .../01.png | 3 + .../02.png | 3 + .../03.png | 3 + .../04.png | 3 + .../05.png | 3 + .../06.png | 3 + .../07.png | 3 + .../08.png | 3 + .../09.png | 3 + .../10.png | 3 + .../11.png | 3 + .../12.png | 3 + .../13.png | 3 + .../14.png | 3 + .../15.png | 3 + .../16.png | 3 + .../17.png | 3 + .../18.png | 3 + ...bavif-webp-logo-scaled-reference-lsel0.png | 3 + ...a32_libavif-webp-logo-scaled-reference.png | 3 + .../00.png | 3 + .../01.png | 3 + .../02.png | 3 + .../03.png | 3 + .../04.png | 3 + .../05.png | 3 + .../06.png | 3 + .../07.png | 3 + .../08.png | 3 + .../09.png | 3 + .../10.png | 3 + .../11.png | 3 + .../12.png | 3 + .../13.png | 3 + .../14.png | 3 + .../15.png | 3 + .../16.png | 3 + .../17.png | 3 + .../18.png | 3 + .../Decode_Rgba32_paris_icc_exif_xmp.png | 3 + .../Decode_Rgba32_perceptual_icc.png | 3 + .../Decode_Rgba32_perceptual_icc_grid.png | 3 + .../00.png | 3 + .../01.png | 3 + .../00.png | 3 + .../01.png | 3 + .../02.png | 3 + .../03.png | 3 + .../04.png | 3 + .../Decode_Rgba64_libavif-cdef-cosmos-10b.png | 3 + .../Decode_Rgba64_libavif-cdef-cosmos-12b.png | 3 + .../00.png | 3 + .../01.png | 3 + .../02.png | 3 + .../03.png | 3 + .../04.png | 3 + .../Decode_Rgba64_libavif-cosmos1650-10b.png | 3 + ...ode_Rgba64_libavif-intrabc-abc-10b-444.png | 3 + ...ode_Rgba64_libavif-intrabc-abc-12b-444.png | 3 + ...Rgba64_libavif-lossless-circle-10b-444.png | 3 + ...Rgba64_libavif-lossless-circle-12b-444.png | 3 + .../Decode_Rgba64_libavif-profile-10b-400.png | 3 + .../Decode_Rgba64_libavif-profile-10b-420.png | 3 + .../Decode_Rgba64_libavif-profile-10b-422.png | 3 + .../Decode_Rgba64_libavif-profile-10b-444.png | 3 + .../Decode_Rgba64_libavif-profile-12b-400.png | 3 + .../Decode_Rgba64_libavif-profile-12b-420.png | 3 + .../Decode_Rgba64_libavif-profile-12b-422.png | 3 + .../Decode_Rgba64_libavif-profile-12b-444.png | 3 + ..._Rgba64_libavif-restoration-cosmos-10b.png | 3 + ..._Rgba64_libavif-restoration-cosmos-12b.png | 3 + ...ode_Rgba64_libavif-superres-cosmos-10b.png | 3 + ...ode_Rgba64_libavif-superres-cosmos-12b.png | 3 + ...actNonSrgb_Rgba32_ducky_romm_icc_alpha.png | 3 + ...ccCompactNonSrgb_Rgba32_perceptual_icc.png | 3 + ...pactNonSrgb_Rgba32_perceptual_icc_grid.png | 3 + .../00.png | 3 + .../01.png | 3 + ...cCompactSrgb_Rgba32_paris_icc_exif_xmp.png | 3 + ...nvertAlpha_Rgba32_ducky_romm_icc_alpha.png | 3 + ...ConvertGrid_Rgba32_perceptual_icc_grid.png | 3 + .../00.png | 3 + .../01.png | 3 + .../IccConvert_Rgba32_perceptual_icc.png | 3 + .../IccPreserve_Rgba32_paris_icc_exif_xmp.png | 3 + ...ipMetadata_Rgba32_ducky_romm_icc_alpha.png | 3 + ...SkipMetadata_Rgba32_paris_icc_exif_xmp.png | 3 + ...kipMetadata_Rgba32_perceptual_icc_grid.png | 3 + .../00.png | 3 + .../01.png | 3 + 273 files changed, 4358 insertions(+), 3329 deletions(-) delete mode 100644 src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterContext.cs create mode 100644 src/ImageSharp/Formats/Heif/Components/Alpha/HeifAlphaRowSource.cs create mode 100644 src/ImageSharp/Formats/Heif/Components/ColorConverters/HeifColorConverter.Alpha.cs create mode 100644 src/ImageSharp/Formats/Heif/Components/ColorConverters/HeifColorConverter.Icc.cs create mode 100644 src/ImageSharp/Formats/Heif/HeifChromaUpsampling.cs delete mode 100644 src/ImageSharp/Formats/Heif/HeifCompressionMethod.cs create mode 100644 src/ImageSharp/Formats/Heif/HeifDecoderOptions.cs delete mode 100644 src/ImageSharp/Formats/Heif/HeifItemDecoderUtilities.cs create mode 100644 src/ImageSharp/Formats/Heif/HeifPixelTransform.cs delete mode 100644 src/ImageSharp/Formats/Heif/JpegHeifItemDecoder.cs delete mode 100644 tests/ImageSharp.Benchmarks/Codecs/Heif/Av1SequenceEncoderBenchmarks.cs create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_Irvine_CA.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_Orange4x4.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-alpha-exif-xmp.avif/00.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-alpha-exif-xmp.avif/01.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-alpha-exif-xmp.avif/02.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-alpha-exif-xmp.avif/03.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-alpha-exif-xmp.avif/04.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-audio.avif/00.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-audio.avif/01.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-audio.avif/02.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-audio.avif/03.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-audio.avif/04.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc.avif/00.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc.avif/01.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc.avif/02.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc.avif/03.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc.avif/04.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_ducky_romm_icc_alpha.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_jpeg444_xnconvert.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-cdef-kodim23-8b.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-intrabc-abc-8b-444.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-kodim23-8b.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-lossless-circle-8b-444.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-palette-draw-points-8b.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-profile-8b-400.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-profile-8b-420.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-profile-8b-422.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-profile-8b-444.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-progressive-draw-points-8b.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-restoration-kodim23-8b.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-rotating-grid-global-warp.avif/00.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-rotating-grid-global-warp.avif/01.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-rotating-grid-local-warp.avif/00.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-rotating-grid-local-warp.avif/01.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-superres-kodim23-8b.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/00.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/01.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/02.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/03.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/04.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/05.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/06.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/07.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/08.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/09.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/10.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/11.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/12.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/13.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/14.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/15.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/16.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/17.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/18.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/00.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/01.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/02.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/03.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/04.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/05.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/06.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/07.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/08.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/09.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/10.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/11.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/12.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/13.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/14.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/15.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/16.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/17.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/18.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/00.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/01.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/02.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/03.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/04.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/05.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/06.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/07.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/08.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/09.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/10.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/11.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/12.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/13.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/14.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/15.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/16.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/17.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/18.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/00.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/01.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/02.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/03.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/04.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/05.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/06.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/07.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/08.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/09.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/10.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/11.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/12.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/13.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/14.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/15.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/16.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/17.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/18.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/00.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/01.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/02.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/03.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/04.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/05.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/06.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/07.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/08.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/09.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/10.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/11.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/12.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/13.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/14.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/15.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/16.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/17.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/18.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-scaled-reference-lsel0.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-scaled-reference.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/00.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/01.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/02.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/03.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/04.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/05.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/06.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/07.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/08.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/09.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/10.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/11.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/12.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/13.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/14.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/15.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/16.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/17.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/18.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_paris_icc_exif_xmp.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_perceptual_icc.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_perceptual_icc_grid.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_perceptual_icc_sequence.avif/00.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_perceptual_icc_sequence.avif/01.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_colors-animated-12bpc-keyframes-0-2-3.avif/00.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_colors-animated-12bpc-keyframes-0-2-3.avif/01.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_colors-animated-12bpc-keyframes-0-2-3.avif/02.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_colors-animated-12bpc-keyframes-0-2-3.avif/03.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_colors-animated-12bpc-keyframes-0-2-3.avif/04.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-cdef-cosmos-10b.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-cdef-cosmos-12b.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-colors-12b.avif/00.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-colors-12b.avif/01.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-colors-12b.avif/02.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-colors-12b.avif/03.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-colors-12b.avif/04.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-cosmos1650-10b.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-intrabc-abc-10b-444.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-intrabc-abc-12b-444.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-lossless-circle-10b-444.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-lossless-circle-12b-444.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-10b-400.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-10b-420.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-10b-422.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-10b-444.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-12b-400.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-12b-420.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-12b-422.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-12b-444.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-restoration-cosmos-10b.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-restoration-cosmos-12b.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-superres-cosmos-10b.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-superres-cosmos-12b.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/IccCompactNonSrgb_Rgba32_ducky_romm_icc_alpha.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/IccCompactNonSrgb_Rgba32_perceptual_icc.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/IccCompactNonSrgb_Rgba32_perceptual_icc_grid.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/IccCompactNonSrgb_Rgba32_perceptual_icc_sequence.avif/00.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/IccCompactNonSrgb_Rgba32_perceptual_icc_sequence.avif/01.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/IccCompactSrgb_Rgba32_paris_icc_exif_xmp.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/IccConvertAlpha_Rgba32_ducky_romm_icc_alpha.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/IccConvertGrid_Rgba32_perceptual_icc_grid.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/IccConvertSequence_Rgba32_perceptual_icc_sequence.avif/00.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/IccConvertSequence_Rgba32_perceptual_icc_sequence.avif/01.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/IccConvert_Rgba32_perceptual_icc.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/IccPreserve_Rgba32_paris_icc_exif_xmp.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/IccSkipMetadata_Rgba32_ducky_romm_icc_alpha.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/IccSkipMetadata_Rgba32_paris_icc_exif_xmp.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/IccSkipMetadata_Rgba32_perceptual_icc_grid.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/IccSkipMetadata_Rgba32_perceptual_icc_sequence.avif/00.png create mode 100644 tests/Images/External/ReferenceOutput/HeifDecoderTests/IccSkipMetadata_Rgba32_perceptual_icc_sequence.avif/01.png diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md index 58cb57e1bc..e18b03c21d 100644 --- a/HEIF_IMPLEMENTATION_PLAN.md +++ b/HEIF_IMPLEMENTATION_PLAN.md @@ -1,5 +1,161 @@ # AVIF and AV1 implementation plan +## Managed checkpoint verification: 2026-09-09 + +ICC interpolation selection is committed as `9aeed10da`. The region conversion, container metadata, +chroma selection, and sample-rounding changes passed final combined verification. +Earlier unresolved-checkpoint notes below describe their state at the recorded investigation date. + +The first combined Release/net11 VSTest run (`managed-checkpoint-r1`) passed 10,448 of 10,487 cases. +Its 39 failures exposed test expectations and reference files that had not followed the completed refactor: + +- Encoded YUV range/matrix assertions now inspect encoded metadata; decoded RGB metadata is asserted as full-range identity. +- The chroma-position test explicitly selects Bilinear and retains its expected pixels. +- Allocation-return checks again use the allocator's existing unique allocation IDs. Array hash codes collided; + they did not establish duplicate disposal. TestMemoryAllocator.cs is unchanged from HEAD. +- Three old CDEF, restoration, and super-resolution PNGs now use the corresponding existing public decoder + references. Each was independently checked against the stored avifdec 16-bit output after 8-bit PNG conversion: + all three comparisons reported zero differing pixels. These are not promoted managed outputs. +- Presentation comparisons again call DebugSave before CompareToReferenceOutput. + +The final source edit passed Roslynk diagnostics with zero errors and a Release/net11 build with zero errors. +The combined rerun (`managed-checkpoint-r2`) passed all 10,487 cases in 2.1611 minutes through Visual Studio +VSTest on Release/net11. This covers HEIF/AV1, ICC, Flip/Rotate, and allocator tests. Test tolerances were not widened. +FlipProcessor and RotateProcessor now match upstream/main and are included with their migrated HEIF callers. +JPEG, PNG, ImageDecoderCore, DecoderOptions, and IccProfile also match upstream/main. +The five explicitly accepted ICC PNGs remain +managed regression baselines as documented below; they do not independently prove native parity. + +The unused Hadamard primitive and its two tests remain outside this checkpoint. Temporary native benchmark +changes and integration files are also excluded. Previously committed native tooling is reserved for the +user-requested final deletion commit. Complete encoder control-flow parity and end-to-end performance remain open. + +## ICC interpolation selection: 2026-09-09 + +ColorConversionOptions.IccInterpolationMethod now exposes Auto (default), Trilinear, and Tetrahedral. +The choice is passed through converter construction and applies to both source and target profiles. +Trilinear selects the previous three-channel calculation and four-channel multilinear interpolation; +the four-channel implementation uses local values without per-pixel allocations or stackalloc. +The Unicolour comparisons explicitly select Trilinear because both local versions 6.0.0 and +8.0.0-1-g3c888f0 use multilinear interpolation. Expected values and tolerances remain unchanged. +Release/net11 Visual Studio VSTest icc-interpolation-options-r1 passed all 63 selected cases, +including all 27 full-profile comparisons and the existing CLUT/LUT calculator tests. +This resolves the 21 Unicolour failures described below. Following explicit user approval, +the four HEIF ICC comparisons now use managed-output regression references: the singular, +grid, alpha PNGs and both sequence frame PNGs were promoted from a fresh DebugSave run. +These preserve the accepted floating-point ICC results instead of the LittleCMS-quantized results. +Release/net11 Visual Studio VSTest icc-promoted-r1 passed all four exact image comparisons. +These five PNGs are managed regression baselines, not independent native-decoder evidence. +Roslynk reported zero errors before the successful Release build. No decoder option was added; +the new setting is available through the shared ColorConversionOptions API. + +Earlier investigation and verification: + +ClutCalculator now uses tetrahedral interpolation for three-channel device-input tables, and linear +blending of two tetrahedral slices for four-channel tables. Lab-indexed output/linking tables retain +trilinear interpolation, selected when constructing the LUT pipeline. The Vector4 contract remains; +the unused N-channel implementation and its scratch allocations were removed. Constructor channel +checks now enforce the existing one-to-four-channel calculator capacity. No span API was added. +The selection follows local LittleCMS revision ab329ad5ce09dbb1f3547b6c126031aca606eb42: +src/cmsintrp.c:623-714,1039-1078 and src/cmsio1.c:581-624,776. Comments explain arithmetic without source citations. + +The large perceptual ICC interpolation difference was reproduced independently from the embedded table bytes: +sequence frame 01, pixel (488,212), changed from RGB (150,192,163) to (0,222,51), matching the unchanged reference. +After the interpolation change, raw RGB comparisons reported maximum errors of one for the singular, +grid, and both sequence outputs; no RGB component exceeded one. Differing component counts were +3511, 3508, 2072, and 2072 respectively. The alpha fixture had 11 differing green components, +each one unit. These measurements do not establish byte-exact acceptance or alpha-plane parity. + +Release/net11 VSTest icc-tetrahedral-r3: 115 cases, 90 passed, 25 failed. All 22 new mathematical +interpolation cases and eight existing CLUT cases passed. Four HEIF exact image comparisons and +21 full-profile comparisons remain failing. Full-profile tests currently use Unicolour; their expected +values and tolerances have not been changed. Further source comparison and independently justified +test updates remain required. LittleCMS's integer CLUT evaluation can quantize inside an otherwise +floating-point transform (src/cmslut.c:444-455); production precision must not be reduced to mimic this. +No reference PNGs were changed for this fix. The native source, build, and transicc utility remain outside +ImageSharp. This work is not a verified commit checkpoint or complete ICC/codec acceptance. + +The ICC source at 79ecb74135ad47bac7d42692905a079839b7e105 supplies both trilinear and tetrahedral +interpolation. Matching a different interpolation choice does not establish that its original algorithm +was defective. The four-channel algorithm change also remains a separate policy choice. + +Rounding investigation: sequence frame 01 pixel (420,39) is RGB (1,0,2) in ActualOutput and (2,0,2) +in ReferenceOutput. The native 16-bit pre-ICC PNG contains RGB (360,0,455). Evaluating the embedded +A2B0 table and target profile in double precision from that same input gives red 1.3962556266 in +eight-bit code units. Rounding the interpolated PCS values to 16 bits instead gives red 1.5442997191; +the unoptimized LittleCMS transicc transform independently reports 1.5443. Those values round to +the observed managed and reference values respectively. This demonstrates intermediate-quantization +error for this sample, not an incorrect final rounding rule. It does not attribute every remaining +mismatch or establish precision relative to the original unquantized YUV-to-RGB result. +The profile's mft2 reader allocates a 16-bit CLUT (LittleCMS src/cmstypes.c:2395), whose float evaluator +rounds inputs and returns integer-interpolated output normalized back to float (src/cmslut.c:83-99,445-455,565-566). +Production precision, test tolerances, and reference images were not changed for this investigation. + +## Non-ICC 12-bit rounding investigation: 2026-09-09 + +The two 12-bit fixture files have identical SHA256 +3bf9f91da471749e7df639ba7945d4d94c1c3e3968c26f3619fbbcfc92790576. They contain the same five-frame +64x64 limited-range YUV422 sequence with alpha and no ICC profile. Existing outputs for +colors-animated-12bpc-keyframes-0-2-3 were compared as raw RGB and alpha independently: +frames 0-2 are exact; frame 3 has five differing RGB components; frame 4 has four differing RGB +components and 33 differing alpha samples. Maximum error is one in each component; none exceeds one. + +The RGB differences are reproduced at all nine locations by evaluating the two source arithmetic orders +on the same native-decoded YUV samples. HeifSampleConversion.ReconstructChromaRowBilinear blends sample +values before HeifColorConverter normalizes them. Libavif src/reformat.c:836-841 normalizes each sample +before weighted blending. Weights and sample positions agree. Single-precision intermediate rounding +moves values across the final half-unit boundary. Relative to a double-precision calculation with the +BT.601 coefficients, managed rounding agrees at five of these nine components and native rounding at four. +Neither path is uniformly more accurate. The inspected libavif checkout is v1.4.2-76-g66663952; +comparison with v1.4.2, used by avifdec, shows no changes to this interpolation arithmetic. + +Alpha has a separate demonstrated arithmetic defect: HeifPlanarAlphaCompositor.cs:120-122 multiplies +by a single-precision reciprocal. A 12-bit alpha of 2047 should scale to 32759.49816849817. Reciprocal +multiplication produces 32759.5 and packs as 32760, while direct division produces 32759.498046875 +and packs as 32759. Existing frame 4 pixel (60,3) contains those respective actual/reference alpha values. +Libavif src/alpha.c:93-97 uses direct division. The initial investigation made no production edits. + +Implemented normalization during the existing SIMD sample loads, before chroma interpolation. Bilinear +reconstruction retains the four separate products in closest/horizontal/vertical/diagonal addition order, +including duplicated boundary samples. The color-model traversal consumes normalized components without +normalizing them again. Alpha uses direct division in the same loader. Existing scratch rows are reused; +no buffers, overloads, expected images, or comparison tolerances were added or changed. + +Release/net11 VSTest heif-12bit-order-r2 passed both exact five-frame decoder comparisons (plus four +integration cases selected by the filename filter). The broader corpus exposed a scalar-tail luma +double-normalization introduced during this change; that was corrected before final verification. +After the final source edit, Roslynk reported zero errors, the Release/net11 build succeeded, and +heif-normalization-corpus-r2 passed all 54 public Decode corpus cases. Both 12-bit sequences now have +zero RGBA component error against their unchanged references, including alpha. DebugSave refreshed the +ActualOutput images. Temporary theory enumeration was restored to its previous setting after testing. +This verifies the decode corpus, not the separate ICC-conversion tests or complete codec acceptance. + +## Container range correction: 2026-09-08 + +AVIF item and sequence RGB conversion now uses the container CICP range when present, falling back to the +decoded AV1 range otherwise. The conversion receives the range explicitly; reconstruction and reference-frame +color state are unchanged. Alpha conversion and encoder conversion continue to use their coded ranges. +This matches libavif src/read.c:6909-6926, which retains the container range despite a conflicting sequence header. +Irvine_CA.avif now passes the unchanged exact reference-image comparison in Release/net11 VSTest +heif-irvine-range-r1 (one test, one pass). Its ActualOutput PNG was refreshed. The other six previously failing +cases were not rerun for this change. Broader sequence/alpha regression verification remains outstanding. + +## Reference regeneration: 2026-09-08 + +Regenerated the 138 PNG frames for the 19 previously failing decoder tests using one AVIF decoding pipeline: +libavif avifdec 1.4.2, dav1d 1.5.3, one worker, explicit 16-bit RGB PNG output, and all frame indices. +Chroma upsampling is explicitly nearest for 8-bit sources and bilinear for the two 12-bit animations. +Requesting 16-bit RGB bypasses libavif's libyuv conversion path. ImageMagick 7.1.2-31 writes the final PNGs; +Rgba32 references use 8-bit output and Rgba64 references retain 16-bit output. ImageMagick does not decode AVIF here. +For the four ICC cases only, ImageMagick/LittleCMS converts the embedded source ICC profile to the exact +CompactSrgbV4Profile bytes using perceptual intent, before final 8-bit output. These cases still have a 16-bit +integer RGB intermediate before ICC conversion; no claim of a floating-point-only reference pipeline is made. +Native tooling, intermediates, and the previous-reference backup remain outside this repository in +D:/GitHub/ynse01/av1-takeover-20260905/unified-references-20260908. +The reference images were regenerated independently; no managed decoded pixels or weakened assertions were used. +Release/net11 VSTest heif-unified-references-r1 ran only the 19 previous failures: 12 passed, 7 failed, 14.5724 seconds. +Remaining pixel mismatches still require investigation. This is not decoder byte-exact acceptance or libaom parity. + ## Goal Complete a production-quality, fully managed AV1 codec and its bounded AVIF/HEIF image container integration for ImageSharp. The finished work must decode and encode still images and bounded image sequences, preserve source precision, use ImageSharp memory ownership, and provide SIMD-first hot paths with one behaviorally identical scalar fallback. @@ -8,6 +164,127 @@ This plan is the authoritative delivery checklist. A source file, unit test, bui Checkpoint handling: work stays in the existing checkout. Do not create or use worktrees; the user reported a crash. Commit completed, verified features regularly, with native reference material and temporary integration excluded. +Cleanup commit `e38989d63` restored three JPEG production files and the PNG encoder exactly to upstream/main. +Cleanup commit `93f48a480` restored DecoderOptions, ImageDecoderCore, IccProfile, Point, and PointTests, +included the PixelOperations formatting correction, and migrated dependent HEIF integrity, ICC serialization, +and coordinate-shift callers. Its 16-file scope excludes component-domain ICC and region conversion changes. +The restored Point implementation passed 55 focused Release/net11 tests (`point-upstream-cleanup-r1.trx`). +FlipProcessor and RotateProcessor restorations remain coupled to the decoder-region caller changes: committing +only their helper removals would break the committed HEIF decoder. The region/ICC checkpoint remains unresolved. + +## Required final cleanup and deletion commit + +2026-09-08 chroma-mode correction: HeifDecoder now follows the specialized-options API used by JPEG. +HeifDecoderOptions.ChromaUpsampling has three values: Auto (default), NearestNeighbor, and Bilinear. +Auto chooses nearest-neighbour for 8-bit source chroma and bilinear for higher source bit depths. +The selection reaches still items, grid children, and sequence presentation. Nearest-neighbour writes +directly into component rows without interpolation scratch; bilinear retains its existing SIMD sampling path. +Existing helper callers now pass the mode explicitly. The existing bilinear test explicitly selects Bilinear; +its expected pixels and every golden remain unchanged. Roslynk reported zero compiler errors. +Release/net11 build passed; heif-chroma-modes-r1 ran 126 tests: 107 passed, 19 exact image comparisons failed. +The explicit bilinear test passed and all 125 public decoder tests ran and regenerated their PNGs. +Remaining comparisons are unresolved; passing the bilinear component check is not end-to-end parity evidence. +Explicit-mode end-to-end references, all SIMD-tier checks, and performance verification remain outstanding. + +2026-09-08 nearest-neighbour presentation change: the user selected chroma sample replication, +matching JPEG's ScaledCopyTo behavior. HeifPlanarColorConverter now selects the chroma row by +the absolute source coordinate and subsampling shift. HeifSampleConversion widens and duplicates +samples directly into the exact output row with 512/256/128-bit SIMD and a scalar tail. +Odd crop origins retain the second half of the first pair; odd right edges write only the requested pixel. +Removed vertical/horizontal weighted interpolation, its scratch rows, and full-width crop reconstruction. +No intermediate rounding, golden changes, or comparison-tolerance changes were introduced. +Release/net11 build succeeded with zero errors and 1,011 existing test-project warnings. +HeifDecoderTests run heif-nearest-decoder-r1 completed all 125 cases: 106 passed, 19 exact comparisons +failed. ActualOutput PNGs were regenerated through DebugSave. The existing ImageMagick/libavif PNG +references have unreconciled presentation conversions and do not establish libaom parity. +The old component test explicitly requiring bilinear chroma is obsolete under the user's new contract; +it has not been rewritten or used as acceptance evidence. No speed improvement has been measured. + +2026-09-08 container mismatch correction: local libaom decodeframe.c:4167-4242 reads range from the +sequence header and reads chroma sample position only for non-monochrome 4:2:0. Removed the container +range-equality rejection in Av1Decoder; the coded range is retained. Av1CodecConfiguration.Validate now +compares chroma sample position only when that field exists in the coded color configuration. Other +configuration comparisons and structural validation remain. XnConvert's av1C bytes 81 20 02 00 declare +4:4:4 with a sample-position value of 2; that unused value caused its former rejection. Both original +fixtures' extracted AV1 payloads decoded successfully with local aomdec. Final Release/net11 build passed +with zero warnings/errors; heif-decoder-container-position-r4 ran all 125 HeifDecoderTests: 108 passed, +17 exact pixel comparisons failed, no decoder rejection failures. XnConvert passes its exact comparison; +Irvine now decodes and saves PNG output but still differs from its reference. Reference pixels and test +assertions were not changed. These two corrections do not establish that all container checks match the +reference, nor do they resolve the remaining color-conversion comparisons. + +2026-09-08 reference-output checkpoint: all 54 corpus cases in HeifDecoderTests.Decode now have named PNG +references, including every frame of both 12-bit animations. The missing 19 high-depth still references +were produced with official libavif 1.4.2 avifdec/dav1d 1.5.3 as 16-bit PNGs. Four ICC conversion cases +use native 16-bit RGB PNG intermediates with embedded source ICC profiles, then ImageMagick 7.1.2-31 +LittleCMS conversion to the same CompactSrgbV4 target profile. Preservation/compaction/metadata-skip +references reuse the independent unconverted reference pixels. Native binaries/intermediates stay outside +the repository. Existing mismatching references were not replaced and exact assertions remain unchanged. +Release/net11 full HeifDecoderTests run heif-decoder-all-references-r2 completed: 125 tests, 107 passed, +18 failed, no missing-reference failures. Remaining failures require source-led investigation; these PNGs +do not establish codec completeness. ActualOutput contains DebugSave/DebugSaveMultiFrame decoder images. +The temporary runner no longer overrides test parallelism and does not stop on failure. Only net11 runs; +the user's serialization constraint concerns simultaneous target-framework runs, not individual tests. + +Test-convention correction is active: decoder verification must use independently produced reference images, +and encoder verification must use TestImageProvider/VerifyEncoder with the registered independent decoder. +Calculated expected-pixel code is not a replacement for reviewable decoder reference images. +Remove ad hoc raw-output generation and its generated files, preserving meaningful assertions. + +Commit 7b5e7d4dc registers ImageMagick for AVIF reference decoding through the existing test infrastructure. +Exact VerifyEncoder checks passed for 8-bit RGBA and 10/12-bit RGBA64. The reference adapter retains native +precision and normalizes ImageMagick's left-aligned 10/12-bit samples to the Rgba64 range. No tolerance changed. +The migrated test methods remain uncommitted with their decoder-refactor dependencies. The 8-bit end-to-end +encoder test now uses the original Ducky, Bike, and Splash PNG fixtures, with exact ImageMagick verification +and DebugSave PNG output. Raw-output writers have been removed from the HEIF/AV1 test sources. The legacy +ActualOutput/Heif directory was deleted; HeifEncoderTests and HeifDecoderTests were emptied before the requested +Release/net11 serialized codec rerun. The calculated ICC pixel oracle has been removed. ICC conversion tests +now compare against independently decoded pixels, but those exact comparisons remain failing and unresolved. +The first full codec run passed 8,837 cases and stopped on a progressive-layer reference-image mismatch. +The failing helper called provider.GetImage(), which selects the registered reference decoder; four conformance +paths now explicitly select HeifDecoder.Instance so that decoder assertions exercise the managed codec. +The encoder cases now include Png.Transparency and use existing 10/12-bit RGBA TIFF fixtures for the high-depth +test, preserving exact Rgba64 comparison. All six known-image encoder cases passed in codec-clean-output-r2; +that broad run stopped on an allocation-log hash-uniqueness assertion after 8,857 passes. Further runs are +restricted to the decoder and encoder classes. No golden image or comparison tolerance has changed. +The Windows AVIF colour/alpha disagreement remains unresolved despite the ImageMagick encoder checks passing. +ICC reference conversion now selects the same CompactSrgbV4 target profile as production. Exact decoded-pixel +comparison still fails; ImageMagick's integer RGB intermediate is not a reason to quantize our floating-point +RGB before ICC. The attempted pre-ICC rounding was reverted and its absence verified in both scalar and SIMD paths. +PNG debug output now explicitly uses PngEncoder to retain ICC metadata, and single-frame cases use DebugSave +rather than DebugSaveMultiFrame. Old output directories must be cleared before final class reruns. +Returned still and sequence CICP metadata now describes full-range RGB, retaining source primaries and transfer +when ICC is not converted. The preserve-profile PNG export passed in heif-icc-preserve-metadata-r1. +After correcting DebugSave overload selection and clearing stale output directories, heif-decoder-convention-r5 +passed ten cases and stopped on the exact Ducky ICC pixel comparison. Single images now produce .png files; +the sequence alone produces a frame directory. The decoder class has not completed. +The ICC output path is being corrected to retain floating-point RGBA through final TPixel conversion, using +the shared pixel operations and existing row scratch ownership. The earlier byte/Rgba64 intermediate could +discard precision for floating-point destination formats. This change is not yet runtime-verified and does +not establish the cause or resolution of the independent ICC comparison mismatch. + +Perform this cleanup at the end of implementation and verification, before final delivery. It is part of the +task, not optional follow-up work. Update this checklist and the active milestone as changes and verification land. + +- [ ] Review all task-added files against the final production implementation and acceptance requirements. +- [ ] Delete temporary native reference source, integration code, libraries, executables, build output, and + generated comparison artifacts from the repository. Preserve needed local reference tooling outside the repository. + `tests/ImageSharp.Benchmarks/Codecs/Heif/Native/aom_benchmark.c` is already tracked from `a7f0fca6b`; + include its deletion and other tracked temporary reference material in the final cleanup commit. +- [ ] Delete stray task READMEs, redundant reports, temporary notes, and documentation for discarded approaches. + Retain only documentation needed for the delivered codec and its supported verification workflow. +- [ ] Delete obsolete or unnecessary tests, including tests for rejected implementations, unrequested features, + and implementation details that do not establish a required contract. Preserve independent acceptance coverage. + Do not delete or weaken a failing test merely to conceal an unresolved production or verification defect. +- [ ] Delete obsolete component benchmarks, including `Av1SequenceEncoderBenchmarks`, and their stale references. + Retained performance verification must measure full encode/decode with equivalent end-to-end boundaries. +- [ ] Inspect the final diff against upstream/main for unrelated codec changes, accidental files, and temporary + tooling. Inspect the staged changes before committing the reviewed deletions. +- [ ] Commit the deletions normally at the end. The user explicitly chose this approach: no history rewriting + and no force-push to remove earlier native-tooling commits. + +## Earlier checkpoint evidence + The probability-storage checkpoint is `a658a2cb7`; adaptive syntax and retained palette tokens are `d8f6a1de3`. The final supporting entropy, mode-grid, frame-buffer, and intra-copy run passed 2,085 tests with zero failures. The encoder deblocking comparison covered 102,390 samples in 12 streams at 8/10/12 bits and 400/420/422/444: @@ -45,9 +322,78 @@ Acceptance criteria are separate for encoding and decoding: - Historical decoder reports of zero samples exceeding one are insufficient by themselves. A recorded maximum error of zero establishes sample equality only for the stated comparison scope, not complete decoder correctness. -## Active production milestone: encoder motion search - -The persistent goal remains active. Complete the integrated encoder motion-search path, including configuration, +## JPEG scope correction: 2026-09-07 + +- Removed legacy JPEG item decoding and encoding, compression selection, container brands, and dedicated HEIF JPEG tests. +- Removed the single-value compression-method enum and its encoder, metadata, and item-decoder properties. Encoding selects AV1 directly; still output always writes AVIF brands. +- Restored all JPEG production files to upstream/main, including earlier branch changes to metadata writing and spectral pixel packing. +- Restored the PNG cICP writer to upstream/main. No codec-specific production changes remain outside HEIF/AV1 against upstream/main. +- Removed the added spectral pixel converter and HEIF JPEG adapter. The HEIF implementation has no JPEG codec dependency. +- General container tests retain their assertions and now generate an opaque AV1 item instead of a JPEG item. +- The decoder-region callers now compile: Roslynk reports zero compiler errors across the loaded solution. +- Release .NET 11 test assembly preparation passed. Runtime verification is in progress and the refactor is not yet a verified checkpoint. +- The Compact ICC regression is corrected: HEIF invokes ICC conversion only for Convert mode. The subsequent decoder run passed 71 cases. +- The earlier ICC implementation processed packed destination pixels using two scratch rows. It was rejected + and deleted. Component-domain ICC conversion is now implemented before pixel packing, as recorded below; + its exact-output verification remains unresolved. Earlier ICC test results do not verify the replacement. +- Deblocking transform sizes now reside in block metadata: one selected size and sixteen inline variable-transform entries. + The separate frame-sized luma/chroma maps and their ownership class are removed. Runtime verification after this change is pending. +- Historical JPEG-backed verification below does not establish acceptance for the remaining AV1-only implementation. + +## Active production milestone: complete decoder-region refactor + +Complete the still-image, grid, sequence, alpha, crop, rotation, and mirroring paths using exact destination regions. +Keep JPEG and other codec implementations unchanged against upstream/main. Retain the existing shared L16 SIMD implementation. +The generic RGB packer must accept exact-length regions, consistent with its existing scalar and SIMD implementations. +Complete existing caller migration and focused runtime verification before committing this checkpoint. +No new test infrastructure, extra decoder overloads, or component-test results substitute for native decoder parity. + +### Transform and ICC integration: 2026-09-08, verification in progress + +- The float/ICC output path now packs directly into the final region, removing its temporary TPixel row + and subsequent WriteRow copy. Destination origins and integer row/column increments are resolved once. + Contiguous output retains bulk pixel packing; reversed rows and columns currently use scalar final packing. + This is not completion of the optimized traversal: tiled/SIMD placement and end-to-end performance remain + unverified, and the outstanding ICC oracle mismatch below still prevents a verified region/ICC checkpoint. + Roslynk reported zero errors after this edit. Release/net11 preparation succeeded with 1,008 warnings and + zero errors; serialized VSTest passed 38 sequence cases (direct-region-sequences-r1.trx) and 70 public + encoder/grid cases (direct-region-grid-r1.trx). These runs do not verify the outstanding ICC mismatch. +- Rotation/mirroring matrices are prepared outside pixel loops. Row traversal uses integer destination increments; + crop and grid callers supply exact source and destination rectangles. +- ICC conversion now operates on reconstructed float components before final pixel packing, using the existing + ColorProfileConverter span APIs and configured allocator. Still images, grid tiles, and sequence frames carry + the selected profile through the same conversion boundary. Compact and Preserve retain source colors. +- Native auxiliary alpha rows are normalized/resampled before RGB unassociation and ICC conversion. Alpha is + packed only with the final pixels; the ICC path does not read back or process a packed image. Unassociation uses + planar Vector512/256/128 arithmetic without scalar alpha extraction or broadcasts. +- Scalar unassociation clips zero-alpha RGB just as the vector paths do, avoiding a row-width-dependent result + when reconstructed RGB is outside the nominal device range. +- After that final alpha edit, Release/net11 built with zero warnings/errors and `alpha-final-public-r1.trx` + passed all 70 public encoder/grid cases. Subsequent matrix-parameter and formatting corrections have Roslynk + verification only: all three Matrix3x2 parameters and six calls now pass by value, with zero compiler errors + and no production warnings. No runtime run is claimed for those later edits. +- The cropped alpha resizer's odd-column tail now selects its kernel using the destination X offset. +- Release/net11 preparation succeeded. The first runtime launch selected no tests because its filter contained + literal quotes. The corrected serialized VSTest run stopped after six passes and one ICC lookup failure. + Reconstructed RGB exceeded the device lookup interval; clipping was added during ICC interleaving without + quantizing through packed pixels. The corrected Release/net11 build succeeded with zero warnings/errors. +- `icc-transform-regions-r6.trx` passed ten cases and stopped on the existing alpha/ICC exact comparison. + Its expected image applies ICC to an already packed eight-bit preserved decode; the new component path + applies ICC before that quantization. Alpha-equality assertions passed, but RGB comparison failed. The test + and expected values remain unchanged; the numerical contract must be resolved before this checkpoint closes. +- JPEG's source path retains float normalization and profile conversion (`JpegColorConverterBase.Icc.cs:72-80,134`, + `JpegColorConverter.Packing.cs:48-50,80-82`, `ColorProfiles/YCbCr.cs:153-160`). It does not justify adding an + eight-bit rounding stage solely to reproduce the old packed-image test oracle. +- Independent focused runs passed 70 public encoder/grid cases (`transform-grid-regions-r1.trx`), 38 sequence + cases (`transform-sequence-regions-r1.trx`), and 12 retained native-reference presentation cases at 8/10/12 bits + and monochrome/420/422/444 (`native-presented-regions-r1.trx`). The last group uses exact image comparison + across configured intrinsic paths. These passing groups do not clear the outstanding ICC failure. +- No benchmark or codec-wide parity claim follows from these edits. Earlier ICC results used the rejected + post-packing implementation and do not verify this implementation. + +## Remaining encoder milestone: motion search + +The overall codec goal remains unresolved. After the decoder refactor, complete the integrated encoder motion-search path, including configuration, allocation geometry, rate costs, candidate and winner state, full-pixel search, fractional refinement, and production verification before advancing. The following dependency result does not close that milestone. @@ -2759,7 +3105,7 @@ Writer primitives are not an encoder. The public encoder remains incomplete unti - [x] Use the existing PNG, TIFF, and JPEG encoders as the ImageSharp architecture reference: generic `Image` input, encoder options taking precedence over converted format metadata and codec defaults, allocator-owned temporary storage, and deterministic disposal. - [x] Treat source pixel type, source alpha representation, and decoded source bit depth as conversion inputs, never as output-eligibility checks. Do not pre-scan pixels before encoding. - [x] Resolve output configuration once from explicit encoder options, converted `HeifMetadata`, and AV1 defaults in that order. Sanitize only combinations that cannot describe a legal requested output, and never write resolved values back to source metadata. -- [~] Finalize observable options for quality, effort, lossless mode, bit depth, chroma subsampling, alpha quality, metadata, and bounded sequences. Sequence repeat-count options override converted HEIF metadata like the existing animated encoders. Legacy JPEG treats the AV1-specific lossless and bit-depth options as inapplicable and continues with its native eight-bit encoding contract. AV1 sequences now follow the existing animated-image contract: the primary item reuses the first sync sample when the root is animated, while an excluded root is encoded once as the independent primary image and the sequence begins at frame index one. Final verification remains open. +- [~] Finalize observable options for quality, effort, lossless mode, bit depth, chroma subsampling, alpha quality, metadata, and bounded sequences. Sequence repeat-count options override converted HEIF metadata like the existing animated encoders. AV1 sequences now follow the existing animated-image contract: the primary item reuses the first sync sample when the root is animated, while an excluded root is encoded once as the independent primary image and the sequence begins at frame index one. Final verification remains open. - [x] Preserve high-bit-depth source precision through 16-bit RGB and native 10/12-bit component planes. - [~] HEIF is registered through the default configuration module. Keep public AV1 capability claims limited to the paths covered by the encoder verification matrix until the remaining encoder work is complete. @@ -2850,7 +3196,7 @@ Encoder verification contract: - [~] AV1 image properties now write `ispe`, `pixi`, `av1C`, `colr`, and `auxC` in current AVIF item order. Only `av1C` is essential; color and alpha items retain independent property sets and the registered alpha auxiliary type. The property container reacquires its span after nested expansion before patching `ipco`, removing the prior stale-buffer write, and selects compact or 15-bit `ipma` indices from the property count rather than the unrelated item count. A forced-growth color-plus-alpha case validates every property payload and association byte; a separate 43-item, 129-property case proves indices 127 through 129 and the extended essential bit. Both pass direct foreground net11 Release VSTest. Complete AVIF assembly remains open. - [~] Explicit public AV1 encoding now writes a still-image AVIF with `avif` major brand, compatible `avif`, `mif1`, and `miaf` brands, one primary color item, an optional alpha auxiliary item, `auxl` from alpha to color, independent item properties, absolute version-one `iloc` extents, and a shared `mdat`. Quality uses current libaom's quantizer-to-qindex mapping with public quality 100 deliberately clamped from lossless qindex 0 to qindex 4. Effort controls the implemented search stages, and the resolved value is required explicitly by every internal frame, tile, and mode-decision operation rather than repeated as optional defaults. Encoder options take precedence over source metadata for 8-, 10-, and 12-bit monochrome, 4:2:0, 4:2:2, and 4:4:4 output. Alpha derives from the source pixel type without scanning pixels, and incompatible identity-matrix metadata is normalized without mutating the source image. - [~] The production path writes color and alpha payloads sequentially through allocator-backed chunked storage, supports non-seekable and prefixed destinations, and does not materialize a complete file or payload copy. Uniform encoder-side `pixi` depth is written directly without allocating per-item channel-depth arrays; decoder-side non-uniform channel depths remain supported. The Release test project builds with zero errors, all 39 encoder cases pass, the complete non-HEVC HEIF namespace passes 9,277 of 9,277, and current official libaom accepts all 47 generated payloads. -- [x] Still-image AVIF metadata preservation now writes an unrestricted ICC `colr/prof` property before the independent `colr/nclx` property, Exif and XMP as separate `mdat` items, and one `cdsc` relationship from each metadata item to the primary color item. Exif stores the exact big-endian TIFF-header offset required by the HEIF item syntax; XMP uses the `mime` item type and `application/rdf+xml` content type. Existing ICC and XMP storage is read synchronously and copied once into final encoder storage rather than cloned into an intermediate array. `SkipMetadata` suppresses all three profile types while retaining the CICP values required to describe the encoded planes. The same option now reaches legacy JPEG payloads, whose encoder no longer writes application profiles or comments when metadata is disabled. +- [x] Still-image AVIF metadata preservation now writes an unrestricted ICC `colr/prof` property before the independent `colr/nclx` property, Exif and XMP as separate `mdat` items, and one `cdsc` relationship from each metadata item to the primary color item. Exif stores the exact big-endian TIFF-header offset required by the HEIF item syntax; XMP uses the `mime` item type and `application/rdf+xml` content type. Existing ICC and XMP storage is read synchronously and copied once into final encoder storage rather than cloned into an intermediate array. `SkipMetadata` suppresses all three profile types while retaining the CICP values required to describe the encoded planes. - [x] Exact container tests verify every emitted item declaration, name, MIME content type, `cdsc` relationship, Exif offset and payload, XMP payload, ICC/CICP property order, compact association byte, propertyless metadata exclusion, decoded profile value, and both `SkipMetadata` branches. The final HEIF encoder set passes 44 of 44 and the complete JPEG encoder set passes 257 of 257 through direct foreground net11 Release VSTest. The complete non-HEVC HEIF namespace passes 9,282 of 9,282 with no failure, crash, or detached test host, and current official libaom accepts all 47 current generated AV1 payloads. - [x] A code-wide production HEIF/AV1 stack-storage audit, excluding HEVC, removed every block-sized, variable-length, or repeatedly nested scratch buffer. Spatial luma and chroma, filter-intra, chroma-from-luma, luma and chroma palette selection, and K-means iteration now use typed views over 642 signed-integer elements, about 2.51 KiB, at the start of the shared inter-prediction region. Those searches are sequential for one block, so the block-workspace owner does not grow and no rent, copy, or additional lifetime is introduced. CDEF directions, variances, and its 64-entry block list now append 1 KiB to the existing bounded operation owner instead of occupying hidden inline or explicit stack arrays. No remaining `stackalloc` depends on block dimensions, sample count, or runtime length; the largest remaining individual span is 128 bytes, and the remaining sites are fixed syntax, SIMD-lane, filter-tap, plane-metadata, or small candidate storage. The exact-owner test now proves the mode, palette, and reference-prediction views share one allocation. Roslynk reports zero compiler errors and no diagnostics in the changed files, the Release test-project build completes with the established 1,992 warnings and zero errors, 81 of 81 focused cases pass, and the complete non-HEVC HEIF/AV1 namespace passes 9,282 of 9,282 through one foreground net11 VSTest run. - [~] Bounded public image-sequence output now emits an `avis` movie with version-one movie, track, and media headers; AV1 visual sample entries; exact run-length-compressed timing; per-sample sizes; 64-bit chunk offsets; and an explicit sync-sample table. The file type includes the required `miaf` compatibility brand, and every sequence now has the MIAF primary image item emitted by current libavif: normal sequences share the first sync-sample extent without another encode or copy, while separate-root sequences retain the still root as the primary image and begin timed samples at frame index one. The decoder allocates one final `Image`: the root is either the first timed sample or the separately decoded primary item, and each visible timed sample is decoded directly into a frame owned by that image. Exact quarter-turn presentation uses one frame-sized reusable pre-rotation buffer rather than a second image or a separately built frame collection. Color and optional auxiliary alpha use independently configured AV1 tracks linked by `auxl`. Lossless samples remain independently decodable key pictures and repeat the sequence header required for random access. Lossy continuation samples use LAST_FRAME inter prediction through the existing SIMD translational predictor; one track-scoped encoder session reuses its source allocation, packed-to-planar row storage and color converter, frame-sized coefficient storage, fixed-geometry picture and frame-header syntax state, tile/superblock/entropy cursors, block arithmetic workspace, complete probability graph, bounded tile-output owner, and OBU-header owner, and swaps two complete reconstruction buffers so the preceding decoded frame becomes the next reference without a plane copy. Sequence samples signal `still_picture=0` and use the complete non-reduced sequence and frame-header prefixes required for a multi-frame coded sequence. Frame payloads are written once into contiguous allocator-backed chunks per track. One compact managed table retains only offset, length, and duration for both tracks, and the bounded `moov` owner is patched once after its final size is known, so prefixed and non-seekable destinations require neither seeking nor a file-sized copy. The media timescale uses the exact representable least common multiple of animated frame-delay denominators and a documented microsecond fallback; zero delays become the smallest legal positive duration. Public lossless color-and-alpha round trips preserve all frames, distinct 24, 25, and 30 fps delays, finite or infinite repetition, ICC, Exif, and XMP metadata, while a separate case proves prefixed non-seekable output. Per-tile CDEF preset, preceding-quantizer state, and payload bounds now occupy one aligned region in the reusable allocator-owned picture buffer rather than separate managed arrays for every frame. The last verified net11 Release checkpoint completed with the established 1,005 warnings and zero errors, all 48 HEIF encoder cases passed, and the complete non-HEVC HEIF/AV1 namespace passed 9,317 of 9,317 through foreground VSTest. Current official libaom `main` at `d565eec60f084421fa34fc0534b760c6452b6a6c` accepted all 66 raw AV1 payloads regenerated by that suite. Verification of the current primary-item, separate-root, non-reduced sequence-header, retained-reference continuation, grid implementation, block-local motion search, conversion-row, probability, picture, frame-header, tile-cursor, tile-output, and OBU-header reuse, and multi-tile output is pending. Additional reference roles and compound prediction remain open. diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs b/src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs index 9ec06fc916..ea29c6a2d5 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs @@ -415,6 +415,10 @@ internal sealed class Av1CodecConfiguration bool highBitDepth = colorConfig.BitDepth is Av1BitDepth.TenBit or Av1BitDepth.TwelveBit; bool twelveBit = colorConfig.BitDepth == Av1BitDepth.TwelveBit; + // Chroma sample position is signaled only for 4:2:0. Other layouts have no corresponding + // sequence-header field, so their container value cannot be compared with the parser default. + bool hasChromaSamplePosition = !colorConfig.IsMonochrome && colorConfig.SubSamplingX && colorConfig.SubSamplingY; + if (this.SequenceProfile != (byte)sequenceHeader.SequenceProfile || this.SequenceLevelIndex != operatingPoint.SequenceLevelIndex || this.SequenceTier != (operatingPoint.SequenceTier != 0) @@ -423,7 +427,7 @@ internal sealed class Av1CodecConfiguration || this.IsMonochrome != colorConfig.IsMonochrome || this.ChromaSubsamplingX != colorConfig.SubSamplingX || this.ChromaSubsamplingY != colorConfig.SubSamplingY - || this.ChromaSamplePosition != (byte)colorConfig.ChromaSamplePosition) + || (hasChromaSamplePosition && this.ChromaSamplePosition != (byte)colorConfig.ChromaSamplePosition)) { throw new InvalidImageContentException("The AV1 item configuration does not match its sequence header."); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs index dfdddde5a8..9362df5bc0 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs @@ -15,6 +15,7 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Cicp; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats.Utils; @@ -167,125 +168,14 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable public Av1InterPredictionFeatures DecodedInterPredictionFeatures { get; private set; } /// - /// Gets the native planes of the current retained shown frame, or before one completes. - /// - public Av1FrameBuffer? FrameBuffer => this.referenceFrames.OutputFrame?.FrameBuffer; - - /// - /// Decodes a bounded AV1 image payload and presents its final shown frame. - /// - /// The destination pixel type. - /// The complete AV1 elementary-stream payload. - /// - /// The container color description that supplies unspecified sequence-header color information. - /// - /// - /// The item-associated AV1 codec configuration validated against the coded sequence header. - /// - /// The optional byte boundaries of a layered AV1 image item. - /// The requested item presentation size, or an empty size for the coded dimensions. - /// The decoded image. - public Image Decode( - Span buffer, - CicpProfile? containerColorProfile = null, - Av1CodecConfiguration? codecConfiguration = null, - Av1LayeredImageIndex? layeredImageIndex = null, - Size presentationSize = default) - where TPixel : unmanaged, IPixel - { - ImageFrame frame = this.DecodeFrame( - buffer, - containerColorProfile, - codecConfiguration, - out CicpProfile effectiveColorProfile, - layeredImageIndex, - presentationSize); - - ImageMetadata metadata = new() - { - CicpProfile = effectiveColorProfile - }; - - HeifContentLightLevel? contentLightLevel = this.obuReader.ContentLightLevel; - HeifMasteringDisplayColorVolume? masteringDisplayColorVolume = this.obuReader.MasteringDisplayColorVolume; - if (contentLightLevel is not null || masteringDisplayColorVolume is not null) - { - HeifMetadata heifMetadata = metadata.GetHeifMetadata(); - heifMetadata.ContentLightLevel = contentLightLevel; - heifMetadata.MasteringDisplayColorVolume = masteringDisplayColorVolume; - } - - try - { - return new Image(this.configuration, metadata, [frame]); - } - catch - { - // Ownership transfers only after the image constructor accepts the decoded frame. - frame.Dispose(); - throw; - } - } - - /// - /// Decodes an AV1 elementary-stream payload into one independently owned ImageSharp frame. + /// Gets or sets the chroma reconstruction mode for presented sequence frames. /// - /// The destination pixel type. - /// The complete AV1 elementary-stream payload. - /// - /// The container color description that supplies unspecified sequence-header color information. - /// - /// - /// The AV1 codec configuration validated against the coded sequence header. - /// - /// Receives the effective CICP description used for conversion. - /// The optional byte boundaries of a layered AV1 image item. - /// The requested item presentation size, or an empty size for the coded dimensions. - /// The decoded frame. Ownership transfers to the caller. - public ImageFrame DecodeFrame( - Span buffer, - CicpProfile? containerColorProfile, - Av1CodecConfiguration? codecConfiguration, - out CicpProfile effectiveColorProfile, - Av1LayeredImageIndex? layeredImageIndex = null, - Size presentationSize = default) - where TPixel : unmanaged, IPixel - { - using Av1FrameBuffer frameBuffer = this.DecodeFrameBuffer( - buffer, - containerColorProfile, - codecConfiguration, - out effectiveColorProfile, - out ObuFrameHeader frameHeader, - layeredImageIndex); - - return this.ConvertToFrame(frameBuffer, frameHeader, effectiveColorProfile, presentationSize); - } + public HeifChromaUpsampling ChromaUpsampling { get; set; } /// - /// Decodes the next visible sample in a bounded AV1 image sequence while retaining its reference state. + /// Gets the native planes of the current retained shown frame, or before one completes. /// - /// The destination pixel type. - /// The complete AV1 sample payload. - /// The container color description. - /// The AV1 sample-entry configuration. - /// The independently owned decoded frame. - public ImageFrame DecodeSequenceFrame( - Span buffer, - CicpProfile? containerColorProfile, - Av1CodecConfiguration? codecConfiguration) - where TPixel : unmanaged, IPixel - { - CicpProfile effectiveColorProfile = this.DecodePayload( - buffer, - containerColorProfile, - codecConfiguration, - null, - requireShownFrame: true); - - Av1ReferenceFrame outputFrame = this.referenceFrames.ResolveOutput(); - return this.ConvertToFrame(outputFrame.FrameBuffer, outputFrame.FrameHeader, effectiveColorProfile); - } + public Av1FrameBuffer? FrameBuffer => this.referenceFrames.OutputFrame?.FrameBuffer; /// /// Decodes the next visible sample in a bounded AV1 image sequence directly into a caller-owned frame. @@ -297,13 +187,21 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable /// The coded dimensions declared by the visual sample entry. /// The clean-aperture region mapped to the complete destination frame. /// The caller-owned packed-pixel frame receiving the presented sample. - public void DecodeSequenceFrame( + /// The rotation and mirroring applied within the destination region. + /// The source profile selected for conversion, or null to preserve source colors. + /// The decoder-owned auxiliary frame, or null for opaque pixels. + /// Whether source RGB is associated with alpha. + public CicpProfile DecodeSequenceFrame( Span buffer, CicpProfile? containerColorProfile, Av1CodecConfiguration? codecConfiguration, Size expectedCodedSize, Rectangle sourceRectangle, - ImageFrame destination) + Buffer2DRegion destination, + HeifPixelTransform transform, + IccProfile? profile, + Av1FrameBuffer? alphaFrame, + bool premultiplied) where TPixel : unmanaged, IPixel { CicpProfile effectiveColorProfile = this.DecodePayload( @@ -324,13 +222,23 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable "The decoded image-sequence sample dimensions do not match its visual sample entry."); } - Av1YuvConverter.ConvertRegionToRgb( + // Keep reconstructed reference state unchanged while honoring the container's presentation range. + Av1YuvConverter.ConvertToRgb( this.configuration, outputFrame.FrameBuffer, sourceRectangle, - destination); + destination, + codedSize, + transform, + profile, + alphaFrame, + codedSize, + sourceRectangle, + premultiplied, + this.ChromaUpsampling, + containerColorProfile?.FullRange ?? outputFrame.FrameBuffer.ColorConfig.ColorRange); - destination.Metadata.CicpProfile = effectiveColorProfile.DeepClone(); + return effectiveColorProfile; } /// @@ -351,29 +259,18 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable requireShownFrame: false); /// - /// Decodes the next visible monochrome AV1 sequence sample and composes it into a color frame. + /// Decodes the next visible monochrome AV1 sequence sample into native samples. /// - /// The destination color pixel type. /// The complete AV1 sample payload. /// The container color description. /// The AV1 sample-entry configuration. /// The required coded dimensions. - /// The clean-aperture luma region mapped to the destination. - /// The packed color frame receiving alpha values. - /// The complete presented size of the auxiliary image. - /// The destination region receiving the alpha image. - /// Whether stored color samples must be converted to unassociated alpha. - public void DecodeSequenceAlpha( + /// The auxiliary frame retained by this decoder until the next sample is decoded or the decoder is disposed. + public Av1FrameBuffer DecodeSequenceAlpha( Span buffer, CicpProfile? containerColorProfile, Av1CodecConfiguration? codecConfiguration, - Size expectedCodedSize, - Rectangle sourceRectangle, - ImageFrame destination, - Size outputSize, - Rectangle destinationRectangle, - bool premultiplied) - where TPixel : unmanaged, IPixel + Size expectedCodedSize) { _ = this.DecodePayload( buffer, @@ -383,59 +280,18 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable requireShownFrame: true); Av1ReferenceFrame outputFrame = this.referenceFrames.ResolveOutput(); - this.ComposeAlpha( - outputFrame.FrameBuffer, - expectedCodedSize, - sourceRectangle, - destination, - outputSize, - destinationRectangle, - premultiplied); - } - - /// - /// Converts native AV1 planes into one independently owned packed-pixel frame. - /// - /// The destination pixel type. - /// The decoded native planes. - /// The completed header describing the decoded native planes. - /// The effective CICP description. - /// The requested item presentation size, or an empty size for the coded dimensions. - /// The independently owned packed-pixel frame. - private ImageFrame ConvertToFrame( - Av1FrameBuffer frameBuffer, - ObuFrameHeader frameHeader, - CicpProfile effectiveColorProfile, - Size presentationSize = default) - where TPixel : unmanaged, IPixel - { - ImageFrame? resultFrame = null; - try + Av1FrameBuffer frame = outputFrame.FrameBuffer; + if (frame.Width != expectedCodedSize.Width || frame.Height != expectedCodedSize.Height) { - Size codedSize = new( - frameHeader.FrameSize.SuperResolutionUpscaledWidth, - frameHeader.FrameSize.FrameHeight); - - // A selected lower spatial layer can only be scaled upward to the image item's ispe extent here. - // Other item-size corrections keep using the shared packed-pixel presentation path after decoding. - Size outputSize = presentationSize.Width >= codedSize.Width && presentationSize.Height >= codedSize.Height - ? presentationSize - : codedSize; - - resultFrame = new ImageFrame( - this.configuration, - outputSize.Width, - outputSize.Height); - - Av1YuvConverter.ConvertToRgb(this.configuration, frameBuffer, resultFrame); - resultFrame.Metadata.CicpProfile = effectiveColorProfile.DeepClone(); - return resultFrame; + throw new InvalidImageContentException("The decoded alpha sample dimensions do not match its visual sample entry."); } - catch + + if (frame.ColorFormat != Av1ColorFormat.Yuv400) { - resultFrame?.Dispose(); - throw; + throw new InvalidImageContentException("An AV1 alpha sample must be monochrome."); } + + return frame; } /// @@ -452,16 +308,18 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable /// The complete presented size of the auxiliary image or grid tile. /// The destination region receiving the top-left portion of the presented alpha image. /// Whether stored color samples must be converted to unassociated alpha. + /// The rotation and mirroring applied within the destination region. /// The optional byte boundaries of a layered AV1 image item. public void DecodeAlpha( Span buffer, CicpProfile? containerColorProfile, Av1CodecConfiguration? codecConfiguration, Size expectedCodedSize, - ImageFrame destination, + Buffer2DRegion destination, Size outputSize, Rectangle destinationRectangle, bool premultiplied, + HeifPixelTransform transform, Av1LayeredImageIndex? layeredImageIndex = null) where TPixel : unmanaged, IPixel { @@ -478,7 +336,8 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable destination, outputSize, destinationRectangle, - premultiplied); + premultiplied, + transform); } /// @@ -491,13 +350,15 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable /// The complete presented size of the auxiliary image. /// The destination region receiving the alpha image. /// Whether stored color samples must be converted to unassociated alpha. + /// The rotation and mirroring applied within the destination region. private void ComposeAlpha( Av1FrameBuffer frameBuffer, Size expectedCodedSize, - ImageFrame destination, + Buffer2DRegion destination, Size outputSize, Rectangle destinationRectangle, - bool premultiplied) + bool premultiplied, + HeifPixelTransform transform) where TPixel : unmanaged, IPixel => this.ComposeAlpha( frameBuffer, @@ -506,7 +367,8 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable destination, outputSize, destinationRectangle, - premultiplied); + premultiplied, + transform); /// /// Composes one decoded monochrome region into a packed color frame. @@ -519,14 +381,16 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable /// The complete presented size of the auxiliary image. /// The destination region receiving the alpha image. /// Whether stored color samples must be converted to unassociated alpha. + /// The rotation and mirroring applied within the destination region. private void ComposeAlpha( Av1FrameBuffer frameBuffer, Size expectedCodedSize, Rectangle sourceRectangle, - ImageFrame destination, + Buffer2DRegion destination, Size outputSize, Rectangle destinationRectangle, - bool premultiplied) + bool premultiplied, + HeifPixelTransform transform) where TPixel : unmanaged, IPixel { if (expectedCodedSize != default && (frameBuffer.Width != expectedCodedSize.Width || frameBuffer.Height != expectedCodedSize.Height)) @@ -548,7 +412,8 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable destination, outputSize, destinationRectangle, - premultiplied); + premultiplied, + transform); } /// @@ -758,11 +623,6 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable { throw new InvalidImageContentException("The HEIF CICP matrix coefficients do not match the AV1 sequence header."); } - - if (colorConfig.ColorRange != colorProfile.FullRange) - { - throw new InvalidImageContentException("The HEIF CICP color range does not match the AV1 sequence header."); - } } // The same sequence header governs subsequent layered frames until another header OBU replaces it. @@ -1046,7 +906,6 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable // A non-shown frame or failed reconstruction never escapes this callback. The tile reader releases the // reconstruction lease; a retained frame keeps only its compact reference state after neighbor contexts // and the remaining frame-sized syntax are returned. - frameDecoder?.Dispose(); presentationBuffer?.Dispose(); frameBuffer?.Dispose(); tileReader?.Dispose(); @@ -1103,7 +962,6 @@ internal sealed class Av1Decoder : IAv1TileReader, IDisposable /// public void Dispose() { - this.FrameDecoder.Dispose(); this.FrameBuffer.Dispose(); this.TileReader.Dispose(); } diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs b/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs index b46381fbe3..502c99afef 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1FrameBuffer.cs @@ -271,6 +271,26 @@ internal sealed class Av1FrameBuffer : IDisposable source.Height, FrameBufferKind.Presentation); + /// + /// Creates a compact monochrome plane for a composed auxiliary image. + /// + /// The configuration providing sample storage. + /// The sample precision and range of the auxiliary items. + /// The complete auxiliary image extent. + /// The compact writable auxiliary plane. + public static Av1FrameBuffer CreateAuxiliary( + Configuration configuration, + ObuColorConfig colorConfig, + Size size) + => new( + configuration.MemoryAllocator, + new ObuSequenceHeader { ColorConfig = colorConfig }, + Av1ColorFormat.Yuv400, + colorConfig.BitDepth > Av1BitDepth.EightBit, + size.Width, + size.Height, + FrameBufferKind.Presentation); + /// /// Creates an empty restoration frame with the source's visible dimensions and sample format. /// diff --git a/src/ImageSharp/Formats/Heif/Av1/Color/Av1YuvConverter.cs b/src/ImageSharp/Formats/Heif/Av1/Color/Av1YuvConverter.cs index 5fe0b0cfc4..fb507dfa04 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Color/Av1YuvConverter.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Color/Av1YuvConverter.cs @@ -4,7 +4,9 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Components; using SixLabors.ImageSharp.Formats.Heif.Components.Alpha; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata.Profiles.Cicp; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Color; @@ -15,40 +17,54 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Color; internal static class Av1YuvConverter { /// - /// Converts the reconstructed component planes to packed pixels. + /// Converts a rectangular region of reconstructed component planes directly to packed pixels. /// /// The destination pixel type. /// The configuration used for allocation and pixel conversion. /// The reconstructed AV1 frame. - /// The destination image frame. - public static void ConvertToRgb(Configuration configuration, Av1FrameBuffer frameBuffer, ImageFrame image) + /// The luma-sample region mapped to the complete destination frame. + /// The destination pixel region. + /// The spatial extent of the presented component planes. + /// The rotation and mirroring applied within the destination region. + /// The source profile selected for conversion, or null to preserve source colors. + /// The auxiliary plane, or null for opaque pixels. + /// The complete color extent covered by alpha. + /// The exact matching auxiliary presentation region. + /// Whether source RGB is associated with alpha. + /// The chroma reconstruction mode. + /// Whether RGB conversion interprets the color planes as full-range samples. + public static void ConvertToRgb( + Configuration configuration, + Av1FrameBuffer frameBuffer, + Rectangle sourceRectangle, + Buffer2DRegion destination, + Size presentationSize, + HeifPixelTransform transform, + IccProfile? profile, + Av1FrameBuffer? alphaFrame, + Size alphaOutputSize, + Rectangle alphaRectangle, + bool premultiplied, + HeifChromaUpsampling chromaUpsampling, + bool isFullRange) where TPixel : unmanaged, IPixel { - HeifColorConversionParameters parameters = GetConversionParameters(frameBuffer.ColorConfig, out HeifColorConversionMode mode); + using HeifAlphaRowSource? alpha = alphaFrame is null + ? null + : CreateAlphaRowSource(configuration, alphaFrame, alphaOutputSize, alphaRectangle); + + HeifColorConversionParameters parameters = GetConversionParameters(frameBuffer.ColorConfig, isFullRange, out HeifColorConversionMode mode); if (frameBuffer.BitDepth == Av1BitDepth.EightBit) { Av1PlanarSampleBuffer buffer = new(frameBuffer); - if (buffer.Width != image.Width || buffer.Height != image.Height) + if (presentationSize != new Size(buffer.Width, buffer.Height)) { - // AVIF spatial-layer selection scales native YUV planes before color conversion. The retained - // reconstruction remains untouched because later dependent layers can still reference its coded - // dimensions, while this short-lived owner contains only the presented sample grid. - using Av1PresentationSampleBuffer> presentationBuffer = new( - configuration, - buffer, - image.Width, - image.Height); + using Av1PresentationSampleBuffer> presented = new( + configuration, buffer, presentationSize.Width, presentationSize.Height); HeifPlanarColorConverter.ConvertToRgb< - TPixel, - Av1PresentationSampleBufferView>, - byte, - HeifByteSampleConverter>( - configuration, - presentationBuffer.View, - image, - in parameters, - mode); + TPixel, Av1PresentationSampleBufferView>, byte, HeifByteSampleConverter>( + configuration, presented.View, destination, in parameters, mode, sourceRectangle.X, sourceRectangle.Y, sourceRectangle.Size, transform, profile, alpha, premultiplied, chromaUpsampling); return; } @@ -56,30 +72,29 @@ internal static class Av1YuvConverter HeifPlanarColorConverter.ConvertToRgb, byte, HeifByteSampleConverter>( configuration, buffer, - image, + destination, in parameters, - mode); + mode, + sourceRectangle.X, + sourceRectangle.Y, + sourceRectangle.Size, + transform, + profile, + alpha, + premultiplied, + chromaUpsampling); return; } Av1PlanarSampleBuffer highBitDepthBuffer = new(frameBuffer); - if (highBitDepthBuffer.Width != image.Width || highBitDepthBuffer.Height != image.Height) + if (presentationSize != new Size(highBitDepthBuffer.Width, highBitDepthBuffer.Height)) { - using Av1PresentationSampleBuffer> presentationBuffer = new( - configuration, - highBitDepthBuffer, - image.Width, - image.Height); + using Av1PresentationSampleBuffer> presented = new( + configuration, highBitDepthBuffer, presentationSize.Width, presentationSize.Height); - HeifPlanarColorConverter.ConvertToRgb< - TPixel, - Av1PresentationSampleBufferView>>( - configuration, - presentationBuffer.View, - image, - in parameters, - mode); + HeifPlanarColorConverter.ConvertToRgb>>( + configuration, presented.View, destination, in parameters, mode, sourceRectangle.X, sourceRectangle.Y, sourceRectangle.Size, transform, profile, alpha, premultiplied, chromaUpsampling); return; } @@ -87,51 +102,51 @@ internal static class Av1YuvConverter HeifPlanarColorConverter.ConvertToRgb>( configuration, highBitDepthBuffer, - image, + destination, in parameters, - mode); + mode, + sourceRectangle.X, + sourceRectangle.Y, + sourceRectangle.Size, + transform, + profile, + alpha, + premultiplied, + chromaUpsampling); } /// - /// Converts a rectangular region of reconstructed component planes directly to packed pixels. + /// Selects a native alpha row reader for the exact color region. /// - /// The destination pixel type. - /// The configuration used for allocation and pixel conversion. - /// The reconstructed AV1 frame. - /// The luma-sample region mapped to the complete destination frame. - /// The destination image frame. - public static void ConvertRegionToRgb( + /// The configuration providing scratch storage. + /// The native auxiliary samples retained by the caller. + /// The complete color extent before cropping and orientation. + /// The color region within that extent. + /// The row reader whose scratch storage must be disposed after conversion. + public static HeifAlphaRowSource CreateAlphaRowSource( Configuration configuration, - Av1FrameBuffer frameBuffer, - Rectangle sourceRectangle, - ImageFrame image) - where TPixel : unmanaged, IPixel + Av1FrameBuffer frame, + Size outputSize, + Rectangle window) { - HeifColorConversionParameters parameters = GetConversionParameters(frameBuffer.ColorConfig, out HeifColorConversionMode mode); - if (frameBuffer.BitDepth == Av1BitDepth.EightBit) + HeifColorConversionParameters parameters = GetConversionParameters(frame.ColorConfig, frame.ColorConfig.ColorRange, out _); + Rectangle source = new(0, 0, frame.Width, frame.Height); + if (frame.BitDepth == Av1BitDepth.EightBit) { - Av1PlanarSampleBuffer buffer = new(frameBuffer); - HeifPlanarColorConverter.ConvertToRgb, byte, HeifByteSampleConverter>( - configuration, - buffer, - image, - in parameters, - mode, - sourceRectangle.X, - sourceRectangle.Y); - - return; + Av1PlanarSampleBuffer buffer = new(frame); + return source.Size == outputSize + ? new HeifAlphaRowSource, byte, HeifByteSampleConverter>( + configuration, buffer, in parameters, window) + : new HeifPlanarAlphaResizeWorker, byte, HeifByteSampleConverter>( + configuration, buffer, in parameters, source, window, outputSize); } - Av1PlanarSampleBuffer highBitDepthBuffer = new(frameBuffer); - HeifPlanarColorConverter.ConvertToRgb>( - configuration, - highBitDepthBuffer, - image, - in parameters, - mode, - sourceRectangle.X, - sourceRectangle.Y); + Av1PlanarSampleBuffer highBitDepthBuffer = new(frame); + return source.Size == outputSize + ? new HeifAlphaRowSource, ushort, HeifUShortSampleConverter>( + configuration, highBitDepthBuffer, in parameters, window) + : new HeifPlanarAlphaResizeWorker, ushort, HeifUShortSampleConverter>( + configuration, highBitDepthBuffer, in parameters, source, window, outputSize); } /// @@ -144,13 +159,15 @@ internal static class Av1YuvConverter /// The complete presented size of the auxiliary image or grid tile. /// The destination region receiving the top-left portion of the presented alpha image. /// Whether stored color samples must be converted to unassociated alpha. + /// The rotation and mirroring applied within the destination region. public static void ComposeAlpha( Configuration configuration, Av1FrameBuffer frameBuffer, - ImageFrame destination, + Buffer2DRegion destination, Size outputSize, Rectangle destinationRectangle, - bool premultiplied) + bool premultiplied, + HeifPixelTransform transform) where TPixel : unmanaged, IPixel => ComposeAlpha( configuration, @@ -159,7 +176,8 @@ internal static class Av1YuvConverter destination, outputSize, destinationRectangle, - premultiplied); + premultiplied, + transform); /// /// Composes a rectangular reconstructed luma region into a packed color frame as auxiliary alpha. @@ -172,17 +190,19 @@ internal static class Av1YuvConverter /// The complete presented size of the auxiliary image or grid tile. /// The destination region receiving the presented alpha image. /// Whether stored color samples must be converted to unassociated alpha. + /// The rotation and mirroring applied within the destination region. public static void ComposeAlpha( Configuration configuration, Av1FrameBuffer frameBuffer, Rectangle sourceRectangle, - ImageFrame destination, + Buffer2DRegion destination, Size outputSize, Rectangle destinationRectangle, - bool premultiplied) + bool premultiplied, + HeifPixelTransform transform) where TPixel : unmanaged, IPixel { - HeifColorConversionParameters parameters = GetConversionParameters(frameBuffer.ColorConfig, out _); + HeifColorConversionParameters parameters = GetConversionParameters(frameBuffer.ColorConfig, frameBuffer.ColorConfig.ColorRange, out _); if (frameBuffer.BitDepth == Av1BitDepth.EightBit) { Av1PlanarSampleBuffer buffer = new(frameBuffer); @@ -194,7 +214,8 @@ internal static class Av1YuvConverter sourceRectangle, outputSize, destinationRectangle, - premultiplied); + premultiplied, + transform); return; } @@ -208,7 +229,8 @@ internal static class Av1YuvConverter sourceRectangle, outputSize, destinationRectangle, - premultiplied); + premultiplied, + transform); } /// @@ -221,7 +243,7 @@ internal static class Av1YuvConverter public static void ConvertFromRgb(Configuration configuration, ImageFrame image, Av1FrameBuffer frameBuffer) where TPixel : unmanaged, IPixel { - HeifColorConversionParameters parameters = GetConversionParameters(frameBuffer.ColorConfig, out HeifColorConversionMode mode); + HeifColorConversionParameters parameters = GetConversionParameters(frameBuffer.ColorConfig, frameBuffer.ColorConfig.ColorRange, out HeifColorConversionMode mode); if (frameBuffer.BitDepth == Av1BitDepth.EightBit) { Av1PlanarSampleBuffer buffer = new(frameBuffer); @@ -248,10 +270,12 @@ internal static class Av1YuvConverter /// Resolves the H.273 conversion mode, matrix coefficients, and sample range for a frame. /// /// The signaled AV1 color configuration. + /// Whether conversion interprets the samples as full range. /// The resolved conversion mode. /// The resolved conversion parameters. public static HeifColorConversionParameters GetConversionParameters( ObuColorConfig colorConfig, + bool isFullRange, out HeifColorConversionMode mode) { if (colorConfig.ChromaSamplePosition == ObuChromoSamplePosition.Reserved) @@ -265,7 +289,7 @@ internal static class Av1YuvConverter (CicpColorPrimaries)(byte)colorConfig.ColorPrimaries, (CicpTransferCharacteristics)(byte)colorConfig.TransferCharacteristics, (CicpMatrixCoefficients)(byte)colorConfig.MatrixCoefficients, - colorConfig.ColorRange, + isFullRange, colorConfig.BitDepth.GetBitCount(), colorConfig.BitDepth.GetBitCount(), isMonochrome, diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs index 3afbb98b1f..f354fb467e 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameDecoder.cs @@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; /// /// Reconstructs the coded blocks of one AV1 image frame into planar sample buffers. /// -internal sealed class Av1FrameDecoder : IAv1FrameDecoder, IDisposable +internal sealed class Av1FrameDecoder : IAv1FrameDecoder { /// /// The sequence-level superblock and color configuration. @@ -42,11 +42,6 @@ internal sealed class Av1FrameDecoder : IAv1FrameDecoder, IDisposable /// private readonly Av1ReferenceFrameStore referenceFrames; - /// - /// The transform-size map populated during reconstruction and consumed by deblocking. - /// - private readonly Av1LoopFilterContext loopFilterContext; - /// /// The block reconstruction stage that applies prediction and inverse transforms. /// @@ -76,31 +71,13 @@ internal sealed class Av1FrameDecoder : IAv1FrameDecoder, IDisposable this.frameInfo = frameInfo; this.frameBuffer = frameBuffer; this.referenceFrames = referenceFrames; - this.loopFilterContext = new(frameBuffer.MemoryAllocator, sequenceHeader, frameHeader); - try - { - this.blockDecoder = new( - this.sequenceHeader, - this.frameHeader, - this.frameBuffer, - this.loopFilterContext, - this.referenceFrames, - reconstructionWorkspace, - paletteColorIndexMaps); - } - catch - { - this.loopFilterContext.Dispose(); - throw; - } - } - - /// - /// Releases the pooled block-reconstruction workspaces owned by this decoder. - /// - public void Dispose() - { - this.loopFilterContext.Dispose(); + this.blockDecoder = new( + this.sequenceHeader, + this.frameHeader, + this.frameBuffer, + this.referenceFrames, + reconstructionWorkspace, + paletteColorIndexMaps); } /// @@ -120,8 +97,7 @@ internal sealed class Av1FrameDecoder : IAv1FrameDecoder, IDisposable this.sequenceHeader, this.frameHeader, this.frameInfo, - this.frameBuffer, - this.loopFilterContext); + this.frameBuffer); loopFilterDecoder.DecodeFrame(); diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameEncoder.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameEncoder.cs index 8da318fb82..08084e8f62 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameEncoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/Av1FrameEncoder.cs @@ -1083,6 +1083,7 @@ internal static class Av1FrameEncoder { HeifColorConversionParameters parameters = Av1YuvConverter.GetConversionParameters( colorConfig, + colorConfig.ColorRange, out HeifColorConversionMode mode); // Conversion writes into the final bordered analysis planes. Later coding stages consume the native @@ -1412,7 +1413,7 @@ internal static class Av1FrameEncoder { // Resolve conversion before renting storage: a rejected color description must not strand an owner // in a constructor that never returns to the sequence encoder's disposal boundary. - this.parameters = Av1YuvConverter.GetConversionParameters(colorConfig, out HeifColorConversionMode mode); + this.parameters = Av1YuvConverter.GetConversionParameters(colorConfig, colorConfig.ColorRange, out HeifColorConversionMode mode); this.colorConverter = HeifColorConverterBase.Create(mode, in this.parameters, colorConfig.IsMonochrome); int subsamplingY = colorConfig.SubSamplingY ? 1 : 0; int componentLength = encodeAlpha diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterContext.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterContext.cs deleted file mode 100644 index 0f5afc2b52..0000000000 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterContext.cs +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; -using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; -using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; -using SixLabors.ImageSharp.Memory; - -namespace SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.LoopFilter; - -/// -/// Stores the transform-size map consumed by the AV1 deblocking loop filter. -/// -internal sealed class Av1LoopFilterContext : IDisposable -{ - /// - /// Stores luma transform sizes at plane-relative 4x4 granularity. - /// - private readonly MemoryGroup transformSizesY; - - /// - /// The active luma transform-map dimensions in plane-relative 4x4 units. - /// - private readonly Size transformSizesYSize; - - /// - /// Stores shared-chroma transform sizes at plane-relative 4x4 granularity. - /// - private readonly MemoryGroup? transformSizesUv; - - /// - /// The active shared-chroma transform-map dimensions in plane-relative 4x4 units. - /// - private readonly Size transformSizesUvSize; - - /// - /// Initializes a new instance of the class. - /// - /// The allocator that owns the frame-sized transform maps. - /// The sequence header defining superblock and chroma geometry. - /// The frame header defining active coded dimensions. - public Av1LoopFilterContext( - MemoryAllocator memoryAllocator, - ObuSequenceHeader sequenceHeader, - ObuFrameHeader frameHeader) - { - int modeInfoWidth = frameHeader.ModeInfoColumnCount; - int modeInfoHeight = frameHeader.ModeInfoRowCount; - MemoryGroup? transformSizesY = null; - MemoryGroup? transformSizesUv = null; - this.transformSizesUvSize = default; - - try - { - long lumaLength = (long)modeInfoWidth * modeInfoHeight; - transformSizesY = memoryAllocator.AllocateGroup( - lumaLength, - 1, - AllocationOptions.Clean); - - if (!sequenceHeader.ColorConfig.IsMonochrome) - { - int subX = sequenceHeader.ColorConfig.SubSamplingX ? 1 : 0; - int subY = sequenceHeader.ColorConfig.SubSamplingY ? 1 : 0; - int chromaWidth = Av1Math.DivideLog2Ceiling(modeInfoWidth, subX); - int chromaHeight = Av1Math.DivideLog2Ceiling(modeInfoHeight, subY); - long chromaLength = (long)chromaWidth * chromaHeight; - transformSizesUv = memoryAllocator.AllocateGroup( - chromaLength, - 1, - AllocationOptions.Clean); - this.transformSizesUvSize = new Size(chromaWidth, chromaHeight); - } - - this.transformSizesY = transformSizesY; - this.transformSizesYSize = new Size(modeInfoWidth, modeInfoHeight); - this.transformSizesUv = transformSizesUv; - } - catch - { - transformSizesUv?.Dispose(); - transformSizesY?.Dispose(); - throw; - } - } - - /// - /// Stores a transform size across every 4x4 position covered by one transform block. - /// - /// The luma or chroma plane. - /// The transform origin in plane-relative 4x4 units. - /// The transform size. - public void SetTransformSize(Av1Plane plane, Point position, Av1TransformSize transformSize) - { - int planeType = Math.Min((int)plane, (int)Av1PlaneType.Uv); - MemoryGroup transformSizeMap; - Size transformSizeMapSize; - if (planeType == (int)Av1PlaneType.Y) - { - transformSizeMap = this.transformSizesY; - transformSizeMapSize = this.transformSizesYSize; - } - else - { - transformSizeMap = this.transformSizesUv - ?? throw new InvalidOperationException("A monochrome AV1 frame has no chroma transform-size map."); - - transformSizeMapSize = this.transformSizesUvSize; - } - - int width = Math.Min(transformSize.Get4x4WideCount(), transformSizeMapSize.Width - position.X); - int height = Math.Min(transformSize.Get4x4HighCount(), transformSizeMapSize.Height - position.Y); - - // libaom clips transform coverage to the active plane mi dimensions at frame edges. Each logical row may cross - // allocator segments, so fill only the current segment before continuing at the same logical map offset. - for (int y = 0; y < height; y++) - { - long offset = ((long)(position.Y + y) * transformSizeMapSize.Width) + position.X; - int remaining = width; - while (remaining > 0) - { - Span destination = transformSizeMap.GetRemainingSliceOfBuffer(offset); - int count = Math.Min(remaining, destination.Length); - destination[..count].Fill(transformSize); - offset += count; - remaining -= count; - } - } - } - - /// - /// Gets the transform size covering a plane-relative 4x4 position. - /// - /// The luma or chroma plane. - /// The position in plane-relative 4x4 units. - /// The transform size covering the position. - public Av1TransformSize GetTransformSize(Av1Plane plane, Point position) - { - int planeType = Math.Min((int)plane, (int)Av1PlaneType.Uv); - MemoryGroup transformSizeMap; - int width; - if (planeType == (int)Av1PlaneType.Y) - { - transformSizeMap = this.transformSizesY; - width = this.transformSizesYSize.Width; - } - else - { - transformSizeMap = this.transformSizesUv - ?? throw new InvalidOperationException("A monochrome AV1 frame has no chroma transform-size map."); - - width = this.transformSizesUvSize.Width; - } - - long offset = ((long)position.Y * width) + position.X; - return transformSizeMap.GetRemainingSliceOfBuffer(offset)[0]; - } - - /// - /// Returns the allocator-owned transform-size maps. - /// - public void Dispose() - { - this.transformSizesUv?.Dispose(); - this.transformSizesY.Dispose(); - } -} diff --git a/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterDecoder.cs index 3af3dc4d05..8e455f833a 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Pipeline/LoopFilter/Av1LoopFilterDecoder.cs @@ -34,11 +34,6 @@ internal sealed class Av1LoopFilterDecoder /// private readonly Av1FrameBuffer frameBuffer; - /// - /// The per-plane transform-size map populated during reconstruction. - /// - private readonly Av1LoopFilterContext loopFilterContext; - /// /// Initializes a new instance of the class. /// @@ -46,19 +41,16 @@ internal sealed class Av1LoopFilterDecoder /// The frame header defining dimensions and filter parameters. /// The decoded block-mode and superblock delta information. /// The reconstructed frame samples to filter. - /// The transform-size map populated during reconstruction. public Av1LoopFilterDecoder( ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameInfo frameInfo, - Av1FrameBuffer frameBuffer, - Av1LoopFilterContext loopFilterContext) + Av1FrameBuffer frameBuffer) { this.sequenceHeader = sequenceHeader; this.frameHeader = frameHeader; this.frameInfo = frameInfo; this.frameBuffer = frameBuffer; - this.loopFilterContext = loopFilterContext; } /// @@ -155,7 +147,7 @@ internal sealed class Av1LoopFilterDecoder } /// - /// Reads deblocking parameters from decoded modes and the reconstructed transform map. + /// Reads deblocking parameters from the decoded block metadata. /// private readonly struct FrameOperator : Av1LoopFilterBase.IFrameOperator { @@ -175,7 +167,19 @@ internal sealed class Av1LoopFilterDecoder mode = state.frameInfo.GetModeInfoAt(position); blockIndex = mode.ModeInfoIndex; skippedTransform = mode.Skip && mode.ReferenceFrames[0] > Av1ReferenceFrameType.Intra; - transformSize = state.loopFilterContext.GetTransformSize(plane, new Point(position.X >> subX, position.Y >> subY)); + transformSize = state.frameHeader.LosslessArray[mode.SegmentId] + ? Av1TransformSize.Size4x4 + : plane == Av1Plane.Y + ? mode.TransformSize + : mode.BlockSize.GetMaxUvTransformSize(subX != 0, subY != 0); + + if (plane == Av1Plane.Y && mode.ReferenceFrames[0] > Av1ReferenceFrameType.Intra && !mode.Skip && + !state.frameHeader.LosslessArray[mode.SegmentId]) + { + int row = position.Y & (mode.BlockSize.Get4x4HighCount() - 1); + int column = position.X & (mode.BlockSize.Get4x4WideCount() - 1); + transformSize = mode.InterTransformSizes[mode.GetInterTransformSizeIndex(row, column)]; + } } /// diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs index ba9453aa29..65b021049f 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1BlockModeInfo.cs @@ -5,6 +5,7 @@ using System.Diagnostics.CodeAnalysis; using SixLabors.ImageSharp.Formats.Heif.Av1.Motion; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; +using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; using SixLabors.ImageSharp.Memory; namespace SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; @@ -19,6 +20,11 @@ internal struct Av1BlockModeInfo /// private InlineArray2 referenceFrames; + /// + /// Stores variable luma transform sizes on the coding block's compact transform grid. + /// + private InlineArray16 interTransformSizes; + /// /// Stores the motion vector associated with each reference-frame label. /// @@ -120,6 +126,17 @@ internal struct Av1BlockModeInfo /// public int ModeInfoIndex { get; set; } + /// + /// Gets or sets the selected luma transform size. + /// + public Av1TransformSize TransformSize { get; set; } + + /// + /// Gets the variable luma transform sizes retained with this coding block. + /// + [UnscopedRef] + public Span InterTransformSizes => this.interTransformSizes; + /// /// Gets or sets the for the luminance channel. /// @@ -281,6 +298,23 @@ internal struct Av1BlockModeInfo /// public Av1FilterIntraMode FilterIntraMode { get; set; } + /// + /// Finds the compact transform-grid entry covering a position relative to the coding block. + /// + /// The row in luma 4x4 units. + /// The column in luma 4x4 units. + /// The entry index in the block's sixteen-element transform grid. + public int GetInterTransformSizeIndex(int row, int column) + { + // One subdivision of the maximum transform defines the storage cell. A final split into + // 4x4 transforms shares one entry because all children of that split have the same size. + Av1TransformSize cellSize = this.BlockSize.GetMaximumTransformSize().GetSubSize(); + int cellWidth = cellSize.Get4x4WideCount(); + int cellHeight = cellSize.Get4x4HighCount(); + int stride = this.BlockSize.Get4x4WideCount() / cellWidth; + return ((row / cellHeight) * stride) + (column / cellWidth); + } + /// /// Gets the directional prediction angle adjustment for a color plane. /// diff --git a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs index c577efc1ef..f8fa900c14 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Tiling/Av1TileReader.cs @@ -1639,7 +1639,8 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable if (usesInterTransformSyntax && !modeInfo.Skip && this.FrameHeader.TransformMode == Av1TransformMode.Select && - blockSize > Av1BlockSize.Block4x4) + blockSize > Av1BlockSize.Block4x4 && + !this.FrameHeader.LosslessArray[modeInfo.SegmentId]) { this.ReadVariableTransformInfo( ref reader, @@ -1661,6 +1662,12 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable tileInfo, allowSelect); + modeInfo.TransformSize = transformSize; + if (usesInterTransformSyntax) + { + modeInfo.InterTransformSizes.Fill(transformSize); + } + bool skippedInterBlock = usesInterTransformSyntax && modeInfo.Skip; this.aboveNeighborContext.UpdateTransformation(modeInfoLocation, tileInfo, transformSize, blockSize, skippedInterBlock); this.leftNeighborContext.UpdateTransformation(modeInfoLocation, superblockInfo, transformSize, blockSize, skippedInterBlock); @@ -1804,6 +1811,18 @@ internal sealed class Av1TileReader : IAv1TileReader, IDisposable return; } + ref Av1BlockModeInfo modeInfo = ref partitionInfo.ModeInfo; + modeInfo.TransformSize = transformSize; + Av1TransformSize cellSize = blockSize.GetMaximumTransformSize().GetSubSize(); + for (int row = 0; row < transformSize.Get4x4HighCount(); row += cellSize.Get4x4HighCount()) + { + for (int column = 0; column < transformSize.Get4x4WideCount(); column += cellSize.Get4x4WideCount()) + { + int index = modeInfo.GetInterTransformSizeIndex(blockRow + row, blockColumn + column); + modeInfo.InterTransformSizes[index] = transformSize; + } + } + Span transformInfo = superblockInfo.GetTransformInfoY(); transformInfo[transformInfoIndex] = new Av1TransformInfo(transformSize, blockColumn, blockRow); transformInfoIndex++; diff --git a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs index a010830271..7cc2b96c28 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Transform/Av1BlockDecoder.cs @@ -4,7 +4,6 @@ using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats.Heif.Av1.Motion; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; -using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.LoopFilter; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.ChromaFromLuma; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; @@ -34,11 +33,6 @@ internal sealed class Av1BlockDecoder /// private readonly Av1FrameBuffer frameBuffer; - /// - /// The per-plane transform-size map consumed after reconstruction by the deblocking stage. - /// - private readonly Av1LoopFilterContext loopFilterContext; - /// /// The retained reconstructed frames addressable by inter prediction. /// @@ -64,11 +58,6 @@ internal sealed class Av1BlockDecoder /// private readonly Av1PredictionDecoder predictionDecoder; - /// - /// Indicates whether transform traversal must also populate loop-filter parameters. - /// - private readonly bool isLoopFilterEnabled; - /// /// The next raster coefficient region for each plane in the current superblock. /// @@ -85,7 +74,6 @@ internal sealed class Av1BlockDecoder /// The decoded sequence header. /// The decoded frame header. /// The frame buffer receiving reconstructed samples. - /// The transform-size map populated while reconstructing blocks. /// The retained reconstructed frames selected by inter blocks. /// The reconstruction storage available for the lifetime of this frame. /// The complete decoder-session palette map state. @@ -93,7 +81,6 @@ internal sealed class Av1BlockDecoder ObuSequenceHeader sequenceHeader, ObuFrameHeader frameHeader, Av1FrameBuffer frameBuffer, - Av1LoopFilterContext loopFilterContext, Av1ReferenceFrameStore referenceFrames, Memory workspace, Av1TileReader.PaletteColorIndexMaps? paletteColorIndexMaps = null) @@ -101,7 +88,6 @@ internal sealed class Av1BlockDecoder this.sequenceHeader = sequenceHeader; this.frameHeader = frameHeader; this.frameBuffer = frameBuffer; - this.loopFilterContext = loopFilterContext; this.referenceFrames = referenceFrames; this.workspace = workspace; int maximumBlockLength = 1 << sequenceHeader.SuperblockSizeLog2; @@ -122,8 +108,6 @@ internal sealed class Av1BlockDecoder frameHeader, predictionScratch.Slice(predictorWorkingOffset, predictorWorkingLength), paletteColorIndexMaps); - this.isLoopFilterEnabled = frameHeader.LoopFilterParameters.FilterLevel[0] != 0 || - frameHeader.LoopFilterParameters.FilterLevel[1] != 0; this.chromaFromLumaContext = new( sequenceHeader.ColorConfig, @@ -1357,20 +1341,6 @@ internal sealed class Av1BlockDecoder transformBlockReconstructionBuffer = blockReconstructionBuffer[transformBlockOffset..]; } - if (this.isLoopFilterEnabled) - { - // U and V share transform geometry. Store the chroma map once so the later plane passes consume - // identical sizes without retaining duplicate state. - if (plane != 2) - { - Point transformPosition = new( - (modeInfoPosition.X >> subX) + transformInfo.OffsetX, - (modeInfoPosition.Y >> subY) + transformInfo.OffsetY); - - this.loopFilterContext.SetTransformSize((Av1Plane)plane, transformPosition, transformSize); - } - } - if (!isInterBlock && !modeInfo.UseIntraBlockCopy) { // Conventional intra prediction consumes the reference-prefixed destination span before the diff --git a/src/ImageSharp/Formats/Heif/Av1HeifItemDecoder.cs b/src/ImageSharp/Formats/Heif/Av1HeifItemDecoder.cs index eb496c9f2e..c402e5f0fa 100644 --- a/src/ImageSharp/Formats/Heif/Av1HeifItemDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1HeifItemDecoder.cs @@ -2,8 +2,12 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.Formats.Heif.Av1.Color; using SixLabors.ImageSharp.Formats.Heif.Components.Alpha; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Cicp; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Heif; @@ -12,7 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Heif; /// Decodes a single AV1-coded HEIF image item. /// /// The destination pixel type. -internal sealed class Av1HeifItemDecoder : IHeifItemDecoder, IHeifAlphaItemDecoder +internal sealed class Av1HeifItemDecoder : IHeifItemDecoder, IHeifAlphaItemDecoder where TPixel : unmanaged, IPixel { /// @@ -20,27 +24,41 @@ internal sealed class Av1HeifItemDecoder : IHeifItemDecoder, IHe /// public Heif4CharCode Type => Heif4CharCode.Av01; - /// - /// Gets the AV1 compression method. - /// - public HeifCompressionMethod CompressionMethod => HeifCompressionMethod.Av1; - /// /// Decodes the encoded AV1 payload of an image item. /// /// The general options governing the containing HEIF decode. + /// The chroma reconstruction mode. /// The HEIF item whose encoded payload is being decoded. /// The encoded AV1 payload. /// /// The container color description that supplies unspecified color information in the AV1 sequence header. /// + /// The source ICC profile selected for conversion, or null to preserve source colors. + /// The native auxiliary plane, or null for an opaque image. + /// The complete color extent covered by the auxiliary plane. + /// The matching region within the auxiliary presentation. + /// Whether source RGB is associated with alpha. + /// The source area of interest in luma-sample coordinates. + /// The rotation and mirroring applied within the destination region. + /// The destination pixel region. + /// The metadata receiving the decoded image properties. /// The token used to cancel the payload decode. - /// The decoded image. - public Image DecodeItemData( + public void DecodeItemData( DecoderOptions options, + HeifChromaUpsampling chromaUpsampling, HeifItem item, Span data, CicpProfile? colorProfile, + IccProfile? profile, + Av1FrameBuffer? alphaFrame, + Size alphaOutputSize, + Rectangle alphaRectangle, + bool premultiplied, + Rectangle sourceRectangle, + HeifPixelTransform transform, + Buffer2DRegion destination, + ImageMetadata metadata, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -55,31 +73,42 @@ internal sealed class Av1HeifItemDecoder : IHeifItemDecoder, IHe byte operatingPointIndex = item.Av1OperatingPointSelector?.Index ?? 0; using Av1Decoder decoder = new(options.Configuration, operatingPointIndex); - Image image = decoder.Decode( + using Av1FrameBuffer frameBuffer = decoder.DecodeFrameBuffer( itemData, colorProfile, codecConfiguration, - item.Av1LayeredImageIndex, - item.Extent); - - HeifMetadata metadata = image.Metadata.GetHeifMetadata(); - metadata.CompressionMethod = this.CompressionMethod; - metadata.BitDepth = codecConfiguration.BitDepth; - metadata.IsMonochrome = codecConfiguration.IsMonochrome; - metadata.ContentLightLevel = item.ContentLightLevel ?? obuContentLightLevel; - metadata.MasteringDisplayColorVolume = item.MasteringDisplayColorVolume ?? obuMasteringDisplayColorVolume; - return image; + out CicpProfile effectiveColorProfile, + item.Av1LayeredImageIndex); + + // Container range describes presentation; the bitstream range remains attached to the decoded planes. + Av1YuvConverter.ConvertToRgb( + options.Configuration, + frameBuffer, + sourceRectangle, + destination, + item.Extent, + transform, + profile, + alphaFrame, + alphaOutputSize, + alphaRectangle, + premultiplied, + chromaUpsampling, + colorProfile?.FullRange ?? frameBuffer.ColorConfig.ColorRange); + + metadata.CicpProfile = effectiveColorProfile; + HeifMetadata heifMetadata = metadata.GetHeifMetadata(); + heifMetadata.BitDepth = codecConfiguration.BitDepth; + heifMetadata.IsMonochrome = codecConfiguration.IsMonochrome; + heifMetadata.ContentLightLevel = item.ContentLightLevel ?? obuContentLightLevel; + heifMetadata.MasteringDisplayColorVolume = item.MasteringDisplayColorVolume ?? obuMasteringDisplayColorVolume; } /// - public void DecodeAlphaItemData( + public Av1FrameBuffer DecodeAlphaItemData( DecoderOptions options, HeifItem item, Span data, - ImageFrame destination, - Size outputSize, - Rectangle destinationRectangle, - bool premultiplied, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); @@ -93,15 +122,11 @@ internal sealed class Av1HeifItemDecoder : IHeifItemDecoder, IHe byte operatingPointIndex = item.Av1OperatingPointSelector?.Index ?? 0; using Av1Decoder decoder = new(options.Configuration, operatingPointIndex); - decoder.DecodeAlpha( + return decoder.DecodeFrameBuffer( itemData, item.CicpProfile, codecConfiguration, - default, - destination, - outputSize, - destinationRectangle, - premultiplied, + out _, item.Av1LayeredImageIndex); } diff --git a/src/ImageSharp/Formats/Heif/Components/Alpha/HeifAlphaRowSource.cs b/src/ImageSharp/Formats/Heif/Components/Alpha/HeifAlphaRowSource.cs new file mode 100644 index 0000000000..a271bf73a3 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Components/Alpha/HeifAlphaRowSource.cs @@ -0,0 +1,74 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Buffers; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Heif.Components.Alpha; + +/// +/// Supplies normalized auxiliary samples for the exact color region being converted. +/// +internal abstract class HeifAlphaRowSource : IDisposable +{ + /// + /// Reads the next row in increasing source-row order. + /// + /// The row relative to the selected color region. + /// The normalized samples, valid until the next row is read. + public abstract Span ReadRow(int y); + + /// + public abstract void Dispose(); +} + +/// +/// Reads or resamples native auxiliary samples without packing them into image pixels. +/// +/// The native component-plane view. +/// The native unsigned sample type. +/// The sample widening operator. +internal sealed class HeifAlphaRowSource : HeifAlphaRowSource + where TBuffer : struct, IHeifPlanarSampleBuffer + where TSample : unmanaged + where TLoader : struct, IHeifSampleConverter +{ + private readonly TBuffer buffer; + private readonly HeifColorConversionParameters parameters; + private readonly Rectangle window; + private readonly IMemoryOwner rowOwner; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration providing scratch storage. + /// The native auxiliary plane retained by the decoder. + /// The auxiliary sample range. + /// The exact color region within that extent. + public HeifAlphaRowSource( + Configuration configuration, + TBuffer buffer, + in HeifColorConversionParameters parameters, + Rectangle window) + { + this.buffer = buffer; + this.parameters = parameters; + this.window = window; + this.rowOwner = configuration.MemoryAllocator.Allocate(window.Width); + } + + /// + public override Span ReadRow(int y) + { + Span row = this.rowOwner.GetSpan()[..this.window.Width]; + ReadOnlySpan source = this.buffer.GetLumaRowSpan(this.window.Y + y).Slice(this.window.X, this.window.Width); + HeifPlanarAlphaCompositor.NormalizeAlphaRow(source, row, in this.parameters); + return row; + } + + /// + public override void Dispose() + { + this.rowOwner.Dispose(); + } +} diff --git a/src/ImageSharp/Formats/Heif/Components/Alpha/HeifPlanarAlphaCompositor.cs b/src/ImageSharp/Formats/Heif/Components/Alpha/HeifPlanarAlphaCompositor.cs index d37b5eabe4..4d670ec572 100644 --- a/src/ImageSharp/Formats/Heif/Components/Alpha/HeifPlanarAlphaCompositor.cs +++ b/src/ImageSharp/Formats/Heif/Components/Alpha/HeifPlanarAlphaCompositor.cs @@ -2,11 +2,11 @@ // Licensed under the Six Labors Split License. using System.Buffers; +using System.Numerics; using System.Numerics.Tensors; using SixLabors.ImageSharp.Formats.Heif.Components; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing.Processors.Transforms; namespace SixLabors.ImageSharp.Formats.Heif.Components.Alpha; @@ -30,15 +30,17 @@ internal static class HeifPlanarAlphaCompositor /// The complete presented size of the auxiliary image or grid tile. /// The destination region receiving the top-left portion of the presented alpha image. /// Whether stored color samples must be converted to unassociated alpha. + /// The rotation and mirroring applied within the destination region. public static void Compose( Configuration configuration, TBuffer buffer, - ImageFrame destination, + Buffer2DRegion destination, in HeifColorConversionParameters parameters, Rectangle sourceRectangle, Size outputSize, Rectangle destinationRectangle, - bool premultiplied) + bool premultiplied, + HeifPixelTransform transform) where TPixel : unmanaged, IPixel where TBuffer : struct, IHeifPlanarSampleBuffer where TSample : unmanaged @@ -50,6 +52,7 @@ internal static class HeifPlanarAlphaCompositor int outputHeight = outputSize.Height; int composedWidth = destinationRectangle.Width; int composedHeight = destinationRectangle.Height; + Matrix3x2 matrix = transform.GetMatrix(destinationRectangle.Size); if (sourceWidth == outputWidth && sourceHeight == outputHeight) { @@ -64,32 +67,34 @@ internal static class HeifPlanarAlphaCompositor // destination row. No resize maps or full-plane staging are required. for (int y = 0; y < composedHeight; y++) { - ReadOnlySpan source = buffer.GetLumaRowSpan(sourceRectangle.Y + y).Slice(sourceRectangle.X, composedWidth); + ReadOnlySpan source = buffer.GetLumaRowSpan(sourceRectangle.Y + destinationRectangle.Y + y) + .Slice(sourceRectangle.X + destinationRectangle.X, composedWidth); + NormalizeAlphaRow(source, alpha, in parameters); - ApplyAlphaRow(configuration, destination, destinationRectangle.X, destinationRectangle.Y + y, alpha, packedAlpha, packedColor, premultiplied); + ApplyAlphaRow(configuration, destination, y, alpha, packedAlpha, packedColor, premultiplied, matrix); } return; } - // Alpha scaling must match KnownResamplers.Box. That public instance is exposed as IResampler, while - // ResizeKernelMap requires the concrete struct so Radius and GetValue remain statically dispatched. - // BoxResampler is stateless, making its default value behaviorally identical to the known instance. - BoxResampler boxResampler = default; - using ResizeKernelMap horizontalKernels = ResizeKernelMap.Calculate(in boxResampler, outputWidth, sourceWidth, configuration.MemoryAllocator); - using ResizeKernelMap verticalKernels = ResizeKernelMap.Calculate(in boxResampler, outputHeight, sourceHeight, configuration.MemoryAllocator); - using HeifPlanarAlphaResizeWorker worker = new( + using HeifPlanarAlphaResizeWorker worker = new( configuration, buffer, - destination, in parameters, sourceRectangle, destinationRectangle, - horizontalKernels, - verticalKernels, - premultiplied); + outputSize); + + using IMemoryOwner resizedAlphaOwner = configuration.MemoryAllocator.Allocate(composedWidth); + using IMemoryOwner resizedColorOwner = configuration.MemoryAllocator.Allocate(composedWidth); + Span resizedPackedAlpha = resizedAlphaOwner.GetSpan()[..composedWidth]; + Span resizedPackedColor = resizedColorOwner.GetSpan()[..composedWidth]; - worker.Compose(); + for (int y = 0; y < composedHeight; y++) + { + ApplyAlphaRow( + configuration, destination, y, worker.ReadRow(y), resizedPackedAlpha, resizedPackedColor, premultiplied, matrix); + } } /// @@ -107,13 +112,10 @@ internal static class HeifPlanarAlphaCompositor where TSample : unmanaged where TLoader : struct, IHeifSampleConverter { - HeifSampleConversion.ConvertSamplesToFloat(source, destination); + HeifSampleConversion.ConvertSamplesToFloat(source, destination, parameters.LumaBias, parameters.LumaScale); - // Alpha auxiliaries use the luma code-value range but no color matrix. TensorPrimitives keeps this bulk - // normalization SIMD-first on every supported architecture and clamps before resampling, matching the - // established conversion to a bounded L16 plane. - TensorPrimitives.Subtract(destination, parameters.LumaBias, destination); - TensorPrimitives.Multiply(destination, 1F / parameters.LumaScale, destination); + // Normalization divides by the encoded range during widening. Multiplication by a rounded + // reciprocal can move alpha across a final half-unit boundary. Clamp before resampling. TensorPrimitives.Clamp(destination, 0F, 1F, destination); } @@ -123,29 +125,44 @@ internal static class HeifPlanarAlphaCompositor /// The destination pixel type. /// The configuration used for pixel conversion. /// The packed destination frame receiving alpha values. - /// The horizontal start of the destination region. - /// The destination row receiving alpha values. + /// The source row mapped into the destination region. /// The normalized alpha samples. /// The reusable 16-bit alpha packing row. /// The reusable high-bit-depth destination color row. /// Whether stored color samples must be converted to unassociated alpha. + /// The matrix resolved once for the destination region. public static void ApplyAlphaRow( Configuration configuration, - ImageFrame destination, - int destinationX, + Buffer2DRegion destination, int destinationY, ReadOnlySpan alpha, Span packedAlpha, Span packedColor, - bool premultiplied) + bool premultiplied, + Matrix3x2 matrix) where TPixel : unmanaged, IPixel { int width = alpha.Length; - Span destinationRow = destination.PixelBuffer.DangerousGetRowSpan(destinationY).Slice(destinationX, width); + Point rowStart = HeifPixelTransform.Transform(0, destinationY, matrix); + Size rowStep = new((int)matrix.M11, (int)matrix.M12); PixelOperations pixelOperations = PixelOperations.Instance; HeifSampleConversion.PackL16(alpha, packedAlpha); - pixelOperations.ToRgba64(configuration, destinationRow, packedColor); + if (matrix.IsIdentity) + { + pixelOperations.ToRgba64(configuration, destination.DangerousGetRowSpan(destinationY), packedColor); + } + else + { + // Gather only this alpha row's color pixels from their final coordinates. No second image is needed. + Point point = rowStart; + for (int x = 0; x < width; x++) + { + packedColor[x] = Rgba64.FromScaledVector4(destination.DangerousGetRowSpan(point.Y)[point.X].ToScaledVector4()); + point += rowStep; + } + } + if (premultiplied) { for (int x = 0; x < width; x++) @@ -168,6 +185,18 @@ internal static class HeifPlanarAlphaCompositor } } - pixelOperations.FromRgba64(configuration, packedColor, destinationRow); + if (matrix.IsIdentity) + { + pixelOperations.FromRgba64(configuration, packedColor, destination.DangerousGetRowSpan(destinationY)); + } + else + { + Point point = rowStart; + for (int x = 0; x < width; x++) + { + destination.DangerousGetRowSpan(point.Y)[point.X] = TPixel.FromRgba64(packedColor[x]); + point += rowStep; + } + } } } diff --git a/src/ImageSharp/Formats/Heif/Components/Alpha/HeifPlanarAlphaResizeWorker.cs b/src/ImageSharp/Formats/Heif/Components/Alpha/HeifPlanarAlphaResizeWorker.cs index 4b54240927..1bcd760494 100644 --- a/src/ImageSharp/Formats/Heif/Components/Alpha/HeifPlanarAlphaResizeWorker.cs +++ b/src/ImageSharp/Formats/Heif/Components/Alpha/HeifPlanarAlphaResizeWorker.cs @@ -14,12 +14,10 @@ namespace SixLabors.ImageSharp.Formats.Heif.Components.Alpha; /// /// Resizes a native HEIF luma plane and composes the result as alpha using a bounded sliding window. /// -/// The destination pixel type. /// The codec adapter exposing the reconstructed component planes. /// The native unsigned sample storage type. /// The SIMD widening operations for the sample type. -internal sealed class HeifPlanarAlphaResizeWorker : IDisposable - where TPixel : unmanaged, IPixel +internal sealed class HeifPlanarAlphaResizeWorker : HeifAlphaRowSource where TBuffer : struct, IHeifPlanarSampleBuffer where TSample : unmanaged where TLoader : struct, IHeifSampleConverter @@ -34,11 +32,6 @@ internal sealed class HeifPlanarAlphaResizeWorker private readonly TBuffer buffer; - /// - /// The packed color frame receiving alpha values. - /// - private readonly ImageFrame destination; - /// /// The resolved H.273 component-range parameters. /// @@ -84,16 +77,6 @@ internal sealed class HeifPlanarAlphaResizeWorker private readonly IMemoryOwner alphaOwner; - /// - /// The reusable high-bit-depth destination color row. - /// - private readonly IMemoryOwner colorOwner; - - /// - /// Whether stored color samples must be converted to unassociated alpha. - /// - private readonly bool premultiplied; - /// /// The number of source rows retained when the window advances. /// @@ -110,38 +93,31 @@ internal sealed class HeifPlanarAlphaResizeWorker - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The configuration used for pooled allocation and pixel conversion. /// The codec-native component planes. - /// The packed color frame receiving alpha values. /// The resolved H.273 component-range parameters. /// The visible luma rectangle within the reconstructed plane. /// The destination region receiving the top-left portion of the presented alpha plane. - /// The horizontal box-filter kernels for the full presented width. - /// The vertical box-filter kernels for the full presented height. - /// Whether stored color samples must be converted to unassociated alpha. + /// The complete presentation extent before cropping. public HeifPlanarAlphaResizeWorker( Configuration configuration, TBuffer buffer, - ImageFrame destination, in HeifColorConversionParameters parameters, Rectangle sourceRectangle, Rectangle destinationRectangle, - ResizeKernelMap horizontalKernels, - ResizeKernelMap verticalKernels, - bool premultiplied) + Size outputSize) { this.configuration = configuration; this.buffer = buffer; - this.destination = destination; this.parameters = parameters; this.sourceRectangle = sourceRectangle; this.destinationRectangle = destinationRectangle; - this.premultiplied = premultiplied; - this.horizontalKernels = horizontalKernels; - this.verticalKernels = verticalKernels; + BoxResampler resampler = default; + this.horizontalKernels = ResizeKernelMap.Calculate(in resampler, outputSize.Width, sourceRectangle.Width, configuration.MemoryAllocator); + this.verticalKernels = ResizeKernelMap.Calculate(in resampler, outputSize.Height, sourceRectangle.Height, configuration.MemoryAllocator); // Retaining one complete maximum-diameter band is sufficient for every vertical kernel that crosses a // window boundary. Those first-pass rows can be copied forward instead of normalized and filtered again. @@ -170,35 +146,32 @@ internal sealed class HeifPlanarAlphaResizeWorker(Math.Max(sourceRectangle.Width, destinationRectangle.Width)); this.sourceVectorOwner = configuration.MemoryAllocator.Allocate(sourceRectangle.Width); this.alphaOwner = configuration.MemoryAllocator.Allocate(Math.Max(sourceRectangle.Width, destinationRectangle.Width)); - this.colorOwner = configuration.MemoryAllocator.Allocate(destinationRectangle.Width); this.currentWindow = new RowInterval(0, this.workerHeight); + this.CalculateFirstPassValues(this.currentWindow); } /// /// Releases all allocator-owned working buffers. /// - public void Dispose() + public override void Dispose() { this.transposedFirstPassBuffer.Dispose(); this.componentOwner.Dispose(); this.sourceVectorOwner.Dispose(); this.alphaOwner.Dispose(); - this.colorOwner.Dispose(); + this.horizontalKernels.Dispose(); + this.verticalKernels.Dispose(); } /// - /// Resizes and composes the complete requested destination rectangle. + /// Resizes the next row of the requested destination rectangle. /// - public void Compose() + /// The next destination row, in increasing order starting at zero. + /// The normalized row, valid until the next row is read. + public override Span ReadRow(int y) { - // Populate the horizontal first pass for the initial bounded source-row interval. Later windows retain their - // overlap and calculate only newly entering rows. - this.CalculateFirstPassValues(this.currentWindow); - Span transposed = this.transposedFirstPassBuffer.DangerousGetSingleSpan(); Span resizedAlpha = this.componentOwner.GetSpan()[..this.destinationRectangle.Width]; - Span packedAlpha = this.alphaOwner.GetSpan()[..this.destinationRectangle.Width]; - Span packedColor = this.colorOwner.GetSpan()[..this.destinationRectangle.Width]; ReadOnlySpan verticalKernelSpan = this.verticalKernels.GetKernelSpan(); ref ResizeKernel verticalKernelBase = ref MemoryMarshal.GetReference(verticalKernelSpan); ref float resizedAlphaBase = ref MemoryMarshal.GetReference(resizedAlpha); @@ -208,47 +181,36 @@ internal sealed class HeifPlanarAlphaResizeWorker currentWindowMax) - { - this.Slide(); - currentWindowMin = this.currentWindow.Min; - currentWindowMax = this.currentWindow.Max; - } + ref ResizeKernel kernel = ref Unsafe.Add(ref verticalKernelBase, this.destinationRectangle.Y + y); + int kernelEnd = kernel.StartIndex + kernel.Length; - // Values for one destination X are contiguous along source Y in the transposed buffer. ConvolveCore - // therefore reads the vertical kernel without gathers, while workerHeight advances to the next X column. - ref Vector4 column = ref transposed[kernel.StartIndex - currentWindowMin]; - nuint x = 0; - for (; x + 1 < width; x += 2) - { - Unsafe.Add(ref resizedAlphaBase, x) = kernel.ConvolveCore(ref column).X; - ref Vector4 nextColumn = ref Unsafe.Add(ref column, workerHeight); - Unsafe.Add(ref resizedAlphaBase, x + 1) = kernel.ConvolveCore(ref nextColumn).X; - column = ref Unsafe.Add(ref column, twoWorkerHeights); - } + // Destination kernels advance monotonically through source Y. Slide until the complete kernel lies in + // the cached first-pass interval; the retained overlap prevents any shared source row being recalculated. + while (kernelEnd > currentWindowMax) + { + this.Slide(); + currentWindowMin = this.currentWindow.Min; + currentWindowMax = this.currentWindow.Max; + } - if (x < width) - { - Unsafe.Add(ref resizedAlphaBase, x) = kernel.ConvolveCore(ref column).X; - } + // Values for one destination X are contiguous along source Y in the transposed buffer. ConvolveCore + // therefore reads the vertical kernel without gathers, while workerHeight advances to the next X column. + ref Vector4 column = ref transposed[kernel.StartIndex - currentWindowMin]; + nuint x = 0; + for (; x + 1 < width; x += 2) + { + Unsafe.Add(ref resizedAlphaBase, x) = kernel.ConvolveCore(ref column).X; + ref Vector4 nextColumn = ref Unsafe.Add(ref column, workerHeight); + Unsafe.Add(ref resizedAlphaBase, x + 1) = kernel.ConvolveCore(ref nextColumn).X; + column = ref Unsafe.Add(ref column, twoWorkerHeights); + } - HeifPlanarAlphaCompositor.ApplyAlphaRow( - this.configuration, - this.destination, - this.destinationRectangle.X, - this.destinationRectangle.Y + y, - resizedAlpha, - packedAlpha, - packedColor, - this.premultiplied); + if (x < width) + { + Unsafe.Add(ref resizedAlphaBase, x) = kernel.ConvolveCore(ref column).X; } + + return resizedAlpha; } /// @@ -307,15 +269,15 @@ internal sealed class HeifPlanarAlphaResizeWorker -/// Decodes one coded HEIF auxiliary alpha item directly into a packed color frame. +/// Decodes an auxiliary item into native samples for joint color and alpha conversion. /// -/// The destination color pixel type. -internal interface IHeifAlphaItemDecoder - where TPixel : unmanaged, IPixel +internal interface IHeifAlphaItemDecoder { /// - /// Decodes and composes one coded auxiliary alpha item. + /// Decodes the auxiliary item and transfers its native sample plane to the caller. /// - /// The general options governing the containing HEIF decode. - /// The auxiliary image item whose encoded payload is being decoded. + /// The options governing payload validation and allocation. + /// The auxiliary image item. /// The encoded auxiliary payload. - /// The packed color frame receiving alpha values. - /// The complete presented size of the auxiliary image or grid tile. - /// The destination region receiving the top-left portion of the presented alpha image. - /// Whether stored color samples must be converted to unassociated alpha. - /// The token used to cancel the payload decode. - public void DecodeAlphaItemData( + /// The cancellation token. + /// The native plane owned by the caller. + public Av1FrameBuffer DecodeAlphaItemData( DecoderOptions options, HeifItem item, Span data, - ImageFrame destination, - Size outputSize, - Rectangle destinationRectangle, - bool premultiplied, CancellationToken cancellationToken); } diff --git a/src/ImageSharp/Formats/Heif/Components/ColorConverters/HeifColorConverter.Alpha.cs b/src/ImageSharp/Formats/Heif/Components/ColorConverters/HeifColorConverter.Alpha.cs new file mode 100644 index 0000000000..7a368e3221 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Components/ColorConverters/HeifColorConverter.Alpha.cs @@ -0,0 +1,100 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; + +namespace SixLabors.ImageSharp.Formats.Heif.Components; + +internal abstract partial class HeifColorConverterBase +{ + /// + /// Unassociates normalized RGB components before color-profile conversion. + /// + /// The associated red components. + /// The associated green components. + /// The associated blue components. + /// The normalized auxiliary samples. + public static void UnassociateRgb(Span red, Span green, Span blue, ReadOnlySpan alpha) + { + ref float r = ref MemoryMarshal.GetReference(red); + ref float g = ref MemoryMarshal.GetReference(green); + ref float b = ref MemoryMarshal.GetReference(blue); + ref float a = ref MemoryMarshal.GetReference(alpha); + int x = 0; + + // Each lane is one pixel, with the same lane index in all four planes. Divide the + // three color vectors by the alpha vector directly; no interleaving or lane broadcast + // is needed. Zero alpha preserves hidden RGB, matching the shared unassociation contract. + if (Vector512.IsHardwareAccelerated) + { + for (; x <= red.Length - Vector512.Count; x += Vector512.Count) + { + Vector512 av = Vector512.LoadUnsafe(ref a, (nuint)x); + Vector512 rv = Vector512.LoadUnsafe(ref r, (nuint)x); + Vector512 gv = Vector512.LoadUnsafe(ref g, (nuint)x); + Vector512 bv = Vector512.LoadUnsafe(ref b, (nuint)x); + Vector512 zeroAlpha = Vector512.Equals(av, Vector512.Zero); + rv = Vector512.ConditionalSelect(zeroAlpha, rv, rv / av); + gv = Vector512.ConditionalSelect(zeroAlpha, gv, gv / av); + bv = Vector512.ConditionalSelect(zeroAlpha, bv, bv / av); + Vector512.Clamp(rv, Vector512.Zero, Vector512.Create(1F)).StoreUnsafe(ref r, (nuint)x); + Vector512.Clamp(gv, Vector512.Zero, Vector512.Create(1F)).StoreUnsafe(ref g, (nuint)x); + Vector512.Clamp(bv, Vector512.Zero, Vector512.Create(1F)).StoreUnsafe(ref b, (nuint)x); + } + } + + if (Vector256.IsHardwareAccelerated) + { + for (; x <= red.Length - Vector256.Count; x += Vector256.Count) + { + Vector256 av = Vector256.LoadUnsafe(ref a, (nuint)x); + Vector256 rv = Vector256.LoadUnsafe(ref r, (nuint)x); + Vector256 gv = Vector256.LoadUnsafe(ref g, (nuint)x); + Vector256 bv = Vector256.LoadUnsafe(ref b, (nuint)x); + Vector256 zeroAlpha = Vector256.Equals(av, Vector256.Zero); + rv = Vector256.ConditionalSelect(zeroAlpha, rv, rv / av); + gv = Vector256.ConditionalSelect(zeroAlpha, gv, gv / av); + bv = Vector256.ConditionalSelect(zeroAlpha, bv, bv / av); + Vector256.Clamp(rv, Vector256.Zero, Vector256.Create(1F)).StoreUnsafe(ref r, (nuint)x); + Vector256.Clamp(gv, Vector256.Zero, Vector256.Create(1F)).StoreUnsafe(ref g, (nuint)x); + Vector256.Clamp(bv, Vector256.Zero, Vector256.Create(1F)).StoreUnsafe(ref b, (nuint)x); + } + } + + if (Vector128.IsHardwareAccelerated) + { + for (; x <= red.Length - Vector128.Count; x += Vector128.Count) + { + Vector128 av = Vector128.LoadUnsafe(ref a, (nuint)x); + Vector128 rv = Vector128.LoadUnsafe(ref r, (nuint)x); + Vector128 gv = Vector128.LoadUnsafe(ref g, (nuint)x); + Vector128 bv = Vector128.LoadUnsafe(ref b, (nuint)x); + Vector128 zeroAlpha = Vector128.Equals(av, Vector128.Zero); + rv = Vector128.ConditionalSelect(zeroAlpha, rv, rv / av); + gv = Vector128.ConditionalSelect(zeroAlpha, gv, gv / av); + bv = Vector128.ConditionalSelect(zeroAlpha, bv, bv / av); + Vector128.Clamp(rv, Vector128.Zero, Vector128.Create(1F)).StoreUnsafe(ref r, (nuint)x); + Vector128.Clamp(gv, Vector128.Zero, Vector128.Create(1F)).StoreUnsafe(ref g, (nuint)x); + Vector128.Clamp(bv, Vector128.Zero, Vector128.Create(1F)).StoreUnsafe(ref b, (nuint)x); + } + } + + // Successively narrower vectors consume their own tails. The remaining zero to three + // pixels use the same division and clamp without touching the auxiliary plane. + for (; x < red.Length; x++) + { + float value = alpha[x]; + if (value != 0F) + { + red[x] /= value; + green[x] /= value; + blue[x] /= value; + } + + red[x] = Math.Clamp(red[x], 0F, 1F); + green[x] = Math.Clamp(green[x], 0F, 1F); + blue[x] = Math.Clamp(blue[x], 0F, 1F); + } + } +} diff --git a/src/ImageSharp/Formats/Heif/Components/ColorConverters/HeifColorConverter.Icc.cs b/src/ImageSharp/Formats/Heif/Components/ColorConverters/HeifColorConverter.Icc.cs new file mode 100644 index 0000000000..a0be934f60 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Components/ColorConverters/HeifColorConverter.Icc.cs @@ -0,0 +1,235 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using System.Numerics.Tensors; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.ColorProfiles; +using SixLabors.ImageSharp.Common.Helpers; + +namespace SixLabors.ImageSharp.Formats.Heif.Components; + +internal abstract partial class HeifColorConverterBase +{ + /// + /// Converts normalized unassociated source components to sRGB before pixel packing. + /// + /// The normalized red or monochrome component row. + /// The normalized green component row. + /// The normalized blue component row. + /// The profile converter selected for this image region. + /// The reusable interleaved RGB row. + public void ConvertRgbToSrgbInPlace( + Span red, + Span green, + Span blue, + ColorProfileConverter converter, + Span packed) + { + // A monochrome profile consumes one normalized channel. Color profiles consume the + // RGB produced by the signaled H.273 operator, not the encoded YUV components. + if (this.IsMonochrome) + { + // Limited-range samples can reconstruct outside the nominal interval. ICC device + // curves address that interval, so clip before looking up their transfer functions. + TensorPrimitives.Clamp(red, 0F, 1F, red); + converter.Convert(MemoryMarshal.Cast(red), packed); + } + else + { + Interleave3(red, green, blue, MemoryMarshal.Cast(packed)); + converter.Convert(packed, packed); + } + + UnpackDeinterleave3(MemoryMarshal.Cast(packed), red, green, blue); + } + + /// + /// Interleaves three planar component lanes into packed XYZ values. + /// + /// The planar X components. + /// The planar Y components. + /// The planar Z components. + /// The destination ordered as consecutive XYZ triples. + private static void Interleave3( + ReadOnlySpan xLane, + ReadOnlySpan yLane, + ReadOnlySpan zLane, + Span packed) + { + DebugGuard.IsTrue(packed.Length % 3 == 0, "Packed length must be divisible by 3."); + DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!"); + DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!"); + DebugGuard.MustBeLessThanOrEqualTo(packed.Length / 3, xLane.Length, nameof(packed)); + + ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); + ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); + ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); + ref float packedRef = ref MemoryMarshal.GetReference(packed); + int i = 0; + + if (Vector128.IsHardwareAccelerated) + { + int oneVectorFromEnd = xLane.Length - Vector128.Count; + + for (; i <= oneVectorFromEnd; i += Vector128.Count) + { + // Each source vector contains four consecutive samples from one plane: + // x = [X0 X1 X2 X3] + // y = [Y0 Y1 Y2 Y3] + // z = [Z0 Z1 Z2 Z3] + // Shifting X by one sample supplies the value that follows each XYZ triple: + // shiftedX = [X1 X2 X3 0] + // The transpose therefore produces overlapping rows [Xn Yn Zn Xn+1]. + // AlignRight joins those rows into three complete destination vectors, avoiding + // the scalar-sized stores that writing four independent Vector3 values requires. + Vector128 x = Unsafe.As>(ref Unsafe.Add(ref xLaneRef, i)); + Vector128 y = Unsafe.As>(ref Unsafe.Add(ref yLaneRef, i)); + Vector128 z = Unsafe.As>(ref Unsafe.Add(ref zLaneRef, i)); + + // YUV reconstruction can overshoot the device RGB range. Clip while loading + // the planes, before ICC curve lookup, without quantizing through packed pixels. + x = Vector128.Clamp(x, Vector128.Zero, Vector128.Create(1F)); + y = Vector128.Clamp(y, Vector128.Zero, Vector128.Create(1F)); + z = Vector128.Clamp(z, Vector128.Zero, Vector128.Create(1F)); + Vector128 shiftedX = Vector128_.ShiftRightBytesInVector(x.AsByte(), sizeof(float)).AsSingle(); + + Transpose4( + x, + y, + z, + shiftedX, + out Vector128 pixel0, + out Vector128 pixel1, + out Vector128 pixel2, + out Vector128 pixel3); + + // Dropping pixel2.X lets [Y2] complete [Y1 Z1 X2] from pixel1. + Vector128 shiftedPixel2 = Vector128_.ShiftRightBytesInVector(pixel2.AsByte(), sizeof(float)); + Vector128 packed1 = Vector128_.AlignRight(shiftedPixel2, pixel1.AsByte(), sizeof(float)).AsSingle(); + + // Dropping pixel3.X leaves [Y3 Z3] to complete [Z2 X3] from pixel2. + Vector128 shiftedPixel3 = Vector128_.ShiftRightBytesInVector(pixel3.AsByte(), sizeof(float)); + Vector128 packed2 = Vector128_.AlignRight(shiftedPixel3, pixel2.AsByte(), sizeof(float) * 2).AsSingle(); + + ref Vector128 destination = ref Unsafe.As>(ref Unsafe.Add(ref packedRef, (uint)i * 3)); + + destination = pixel0; + Unsafe.Add(ref destination, 1) = packed1; + Unsafe.Add(ref destination, 2) = packed2; + } + } + + // Fewer than four pixels remain after SIMD, or every pixel reaches this path + // when the runtime cannot accelerate the cross-vector transpose. + for (; i < xLane.Length; i++) + { + nuint sourceOffset = (uint)i; + nuint packedOffset = sourceOffset * 3; + Unsafe.Add(ref packedRef, packedOffset) = Math.Clamp(Unsafe.Add(ref xLaneRef, sourceOffset), 0F, 1F); + Unsafe.Add(ref packedRef, packedOffset + 1) = Math.Clamp(Unsafe.Add(ref yLaneRef, sourceOffset), 0F, 1F); + Unsafe.Add(ref packedRef, packedOffset + 2) = Math.Clamp(Unsafe.Add(ref zLaneRef, sourceOffset), 0F, 1F); + } + } + + /// + /// Deinterleaves packed XYZ values into three planar component lanes. + /// + /// The source ordered as consecutive XYZ triples. + /// The destination X components. + /// The destination Y components. + /// The destination Z components. + private static void UnpackDeinterleave3(ReadOnlySpan packed, Span xLane, Span yLane, Span zLane) + { + DebugGuard.IsTrue(packed.Length == xLane.Length, nameof(packed), "Channels must be of same size!"); + DebugGuard.IsTrue(yLane.Length == xLane.Length, nameof(yLane), "Channels must be of same size!"); + DebugGuard.IsTrue(zLane.Length == xLane.Length, nameof(zLane), "Channels must be of same size!"); + + ref float packedRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast(packed)); + ref float xLaneRef = ref MemoryMarshal.GetReference(xLane); + ref float yLaneRef = ref MemoryMarshal.GetReference(yLane); + ref float zLaneRef = ref MemoryMarshal.GetReference(zLane); + int i = 0; + + if (Vector128.IsHardwareAccelerated) + { + int oneVectorFromEnd = packed.Length - Vector128.Count; + + for (; i <= oneVectorFromEnd; i += Vector128.Count) + { + // A Vector3 occupies twelve contiguous bytes, so a sixteen-byte load beginning + // at one pixel also reads the X component of the following pixel: + // pixel0 = [X0 Y0 Z0 X1] + // pixel1 = [X1 Y1 Z1 X2] + // The transpose discards this fourth column, making the overlap useful padding + // and avoiding two insert instructions per pixel. The final row needs explicit + // zero padding only when pixel3 is the last element in the source span. + nuint packedOffset = (uint)i * 3; + Vector128 pixel0 = Unsafe.As>(ref Unsafe.Add(ref packedRef, packedOffset)); + Vector128 pixel1 = Unsafe.As>(ref Unsafe.Add(ref packedRef, packedOffset + 3)); + Vector128 pixel2 = Unsafe.As>(ref Unsafe.Add(ref packedRef, packedOffset + 6)); + ref float pixel3Ref = ref Unsafe.Add(ref packedRef, packedOffset + 9); + Vector128 pixel3 = i + Vector128.Count < packed.Length + ? Unsafe.As>(ref pixel3Ref) + : Unsafe.As(ref pixel3Ref).AsVector128(); + + Transpose4(pixel0, pixel1, pixel2, pixel3, out Vector128 x, out Vector128 y, out Vector128 z, out _); + + Unsafe.As>(ref Unsafe.Add(ref xLaneRef, i)) = x; + Unsafe.As>(ref Unsafe.Add(ref yLaneRef, i)) = y; + Unsafe.As>(ref Unsafe.Add(ref zLaneRef, i)) = z; + } + } + + // The scalar remainder preserves the original scatter behavior for zero to + // three pixels and provides the complete fallback on unsupported hardware. + for (; i < packed.Length; i++) + { + nuint packedOffset = (uint)i * 3; + Unsafe.Add(ref xLaneRef, i) = Unsafe.Add(ref packedRef, packedOffset); + Unsafe.Add(ref yLaneRef, i) = Unsafe.Add(ref packedRef, packedOffset + 1); + Unsafe.Add(ref zLaneRef, i) = Unsafe.Add(ref packedRef, packedOffset + 2); + } + } + + /// + /// Transposes four four-lane rows into four four-lane columns. + /// + /// The first matrix row. + /// The second matrix row. + /// The third matrix row. + /// The fourth matrix row. + /// The first matrix column. + /// The second matrix column. + /// The third matrix column. + /// The fourth matrix column. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Transpose4( + Vector128 row0, + Vector128 row1, + Vector128 row2, + Vector128 row3, + out Vector128 column0, + out Vector128 column1, + out Vector128 column2, + out Vector128 column3) + { + // The first unpack interleaves adjacent 32-bit lanes from rows 0/1 and 2/3: + // row01Low = [r0c0 r1c0 r0c1 r1c1] + // row23Low = [r2c0 r3c0 r2c1 r3c1] + // A second unpack treats each adjacent pair as one 64-bit lane and combines + // the row01 and row23 pairs into complete columns. The integer views only + // expose the cross-platform unpack helpers; every floating-point bit is preserved. + Vector128 row01Low = Vector128_.UnpackLow(row0.AsInt32(), row1.AsInt32()); + Vector128 row01High = Vector128_.UnpackHigh(row0.AsInt32(), row1.AsInt32()); + Vector128 row23Low = Vector128_.UnpackLow(row2.AsInt32(), row3.AsInt32()); + Vector128 row23High = Vector128_.UnpackHigh(row2.AsInt32(), row3.AsInt32()); + + column0 = Vector128_.UnpackLow(row01Low.AsInt64(), row23Low.AsInt64()).AsSingle(); + column1 = Vector128_.UnpackHigh(row01Low.AsInt64(), row23Low.AsInt64()).AsSingle(); + column2 = Vector128_.UnpackLow(row01High.AsInt64(), row23High.AsInt64()).AsSingle(); + column3 = Vector128_.UnpackHigh(row01High.AsInt64(), row23High.AsInt64()).AsSingle(); + } +} diff --git a/src/ImageSharp/Formats/Heif/Components/ColorConverters/HeifColorConverter.Operator.cs b/src/ImageSharp/Formats/Heif/Components/ColorConverters/HeifColorConverter.Operator.cs index 9b118a3112..7fbc6f8712 100644 --- a/src/ImageSharp/Formats/Heif/Components/ColorConverters/HeifColorConverter.Operator.cs +++ b/src/ImageSharp/Formats/Heif/Components/ColorConverters/HeifColorConverter.Operator.cs @@ -185,7 +185,7 @@ internal abstract partial class HeifColorConverterBase { HeifColorConversionParameters parameters = this.Parameters; - // Row reconstruction owns equally sized planar buffers. As in JPEG, first-element byrefs let each + // Row reconstruction owns equally sized planar buffers. First-element byrefs let each // SIMD width share one offset while the closed operator type keeps color-model dispatch out of the loop. ref float component0Base = ref MemoryMarshal.GetReference(component0); ref float component1Base = ref MemoryMarshal.GetReference(component1); @@ -195,16 +195,14 @@ internal abstract partial class HeifColorConverterBase if (this.IsMonochrome) { - // Monochrome has no operator arithmetic: expanding the luma range once and copying each SIMD + // Monochrome is already normalized by the sample loader. Copying each SIMD // vector to all three planes is cheaper than routing it through a three-component operator. if (Vector512.IsHardwareAccelerated && i <= length - Vector512.Count) { - Vector512 bias = Vector512.Create(parameters.LumaBias); - Vector512 scale = Vector512.Create(parameters.LumaScale); int oneVectorFromEnd = length - Vector512.Count; for (; i <= oneVectorFromEnd; i += Vector512.Count) { - Vector512 value = (Unsafe.As>(ref Unsafe.Add(ref component0Base, i)) - bias) / scale; + Vector512 value = Unsafe.As>(ref Unsafe.Add(ref component0Base, i)); Unsafe.As>(ref Unsafe.Add(ref component0Base, i)) = value; Unsafe.As>(ref Unsafe.Add(ref component1Base, i)) = value; Unsafe.As>(ref Unsafe.Add(ref component2Base, i)) = value; @@ -213,12 +211,10 @@ internal abstract partial class HeifColorConverterBase if (Vector256.IsHardwareAccelerated && i <= length - Vector256.Count) { - Vector256 bias = Vector256.Create(parameters.LumaBias); - Vector256 scale = Vector256.Create(parameters.LumaScale); int oneVectorFromEnd = length - Vector256.Count; for (; i <= oneVectorFromEnd; i += Vector256.Count) { - Vector256 value = (Unsafe.As>(ref Unsafe.Add(ref component0Base, i)) - bias) / scale; + Vector256 value = Unsafe.As>(ref Unsafe.Add(ref component0Base, i)); Unsafe.As>(ref Unsafe.Add(ref component0Base, i)) = value; Unsafe.As>(ref Unsafe.Add(ref component1Base, i)) = value; Unsafe.As>(ref Unsafe.Add(ref component2Base, i)) = value; @@ -227,12 +223,10 @@ internal abstract partial class HeifColorConverterBase if (Vector128.IsHardwareAccelerated && i <= length - Vector128.Count) { - Vector128 bias = Vector128.Create(parameters.LumaBias); - Vector128 scale = Vector128.Create(parameters.LumaScale); int oneVectorFromEnd = length - Vector128.Count; for (; i <= oneVectorFromEnd; i += Vector128.Count) { - Vector128 value = (Unsafe.As>(ref Unsafe.Add(ref component0Base, i)) - bias) / scale; + Vector128 value = Unsafe.As>(ref Unsafe.Add(ref component0Base, i)); Unsafe.As>(ref Unsafe.Add(ref component0Base, i)) = value; Unsafe.As>(ref Unsafe.Add(ref component1Base, i)) = value; Unsafe.As>(ref Unsafe.Add(ref component2Base, i)) = value; @@ -241,7 +235,7 @@ internal abstract partial class HeifColorConverterBase for (; i < length; i++) { - float value = (Unsafe.Add(ref component0Base, i) - parameters.LumaBias) / parameters.LumaScale; + float value = Unsafe.Add(ref component0Base, i); Unsafe.Add(ref component0Base, i) = value; Unsafe.Add(ref component1Base, i) = value; Unsafe.Add(ref component2Base, i) = value; @@ -250,26 +244,16 @@ internal abstract partial class HeifColorConverterBase return; } - float chromaBias = this.ChromaBias; - float chromaScale = this.ChromaScale; - - // Descending widths preserve vector execution for the remainder left by a wider register. Divide by the - // signaled ranges directly because multiplying by rounded reciprocals changes exact output-code boundaries. + // Samples are normalized before chroma interpolation. This traversal applies only the color + // model, preserving those fractional values through the descending SIMD widths. if (Vector512.IsHardwareAccelerated && i <= length - Vector512.Count) { - Vector512 lumaBias = Vector512.Create(parameters.LumaBias); - Vector512 lumaScale = Vector512.Create(parameters.LumaScale); - Vector512 chromaBiasVector = Vector512.Create(chromaBias); - Vector512 chromaScaleVector = Vector512.Create(chromaScale); int oneVectorFromEnd = length - Vector512.Count; for (; i <= oneVectorFromEnd; i += Vector512.Count) { ref Vector512 c0 = ref Unsafe.As>(ref Unsafe.Add(ref component0Base, i)); ref Vector512 c1 = ref Unsafe.As>(ref Unsafe.Add(ref component1Base, i)); ref Vector512 c2 = ref Unsafe.As>(ref Unsafe.Add(ref component2Base, i)); - c0 = (c0 - lumaBias) / lumaScale; - c1 = (c1 - chromaBiasVector) / chromaScaleVector; - c2 = (c2 - chromaBiasVector) / chromaScaleVector; TOperator.ConvertToRgb(ref c0, ref c1, ref c2, in parameters); } @@ -277,19 +261,12 @@ internal abstract partial class HeifColorConverterBase if (Vector256.IsHardwareAccelerated && i <= length - Vector256.Count) { - Vector256 lumaBias = Vector256.Create(parameters.LumaBias); - Vector256 lumaScale = Vector256.Create(parameters.LumaScale); - Vector256 chromaBiasVector = Vector256.Create(chromaBias); - Vector256 chromaScaleVector = Vector256.Create(chromaScale); int oneVectorFromEnd = length - Vector256.Count; for (; i <= oneVectorFromEnd; i += Vector256.Count) { ref Vector256 c0 = ref Unsafe.As>(ref Unsafe.Add(ref component0Base, i)); ref Vector256 c1 = ref Unsafe.As>(ref Unsafe.Add(ref component1Base, i)); ref Vector256 c2 = ref Unsafe.As>(ref Unsafe.Add(ref component2Base, i)); - c0 = (c0 - lumaBias) / lumaScale; - c1 = (c1 - chromaBiasVector) / chromaScaleVector; - c2 = (c2 - chromaBiasVector) / chromaScaleVector; TOperator.ConvertToRgb(ref c0, ref c1, ref c2, in parameters); } @@ -297,19 +274,12 @@ internal abstract partial class HeifColorConverterBase if (Vector128.IsHardwareAccelerated && i <= length - Vector128.Count) { - Vector128 lumaBias = Vector128.Create(parameters.LumaBias); - Vector128 lumaScale = Vector128.Create(parameters.LumaScale); - Vector128 chromaBiasVector = Vector128.Create(chromaBias); - Vector128 chromaScaleVector = Vector128.Create(chromaScale); int oneVectorFromEnd = length - Vector128.Count; for (; i <= oneVectorFromEnd; i += Vector128.Count) { ref Vector128 c0 = ref Unsafe.As>(ref Unsafe.Add(ref component0Base, i)); ref Vector128 c1 = ref Unsafe.As>(ref Unsafe.Add(ref component1Base, i)); ref Vector128 c2 = ref Unsafe.As>(ref Unsafe.Add(ref component2Base, i)); - c0 = (c0 - lumaBias) / lumaScale; - c1 = (c1 - chromaBiasVector) / chromaScaleVector; - c2 = (c2 - chromaBiasVector) / chromaScaleVector; TOperator.ConvertToRgb(ref c0, ref c1, ref c2, in parameters); } @@ -318,9 +288,9 @@ internal abstract partial class HeifColorConverterBase // Scalar conversion is reserved for the zero-to-three samples left after the SIMD cascade. for (; i < length; i++) { - float c0 = (Unsafe.Add(ref component0Base, i) - parameters.LumaBias) / parameters.LumaScale; - float c1 = (Unsafe.Add(ref component1Base, i) - chromaBias) / chromaScale; - float c2 = (Unsafe.Add(ref component2Base, i) - chromaBias) / chromaScale; + float c0 = Unsafe.Add(ref component0Base, i); + float c1 = Unsafe.Add(ref component1Base, i); + float c2 = Unsafe.Add(ref component2Base, i); TOperator.ConvertToRgb(ref c0, ref c1, ref c2, in parameters); Unsafe.Add(ref component0Base, i) = c0; Unsafe.Add(ref component1Base, i) = c1; @@ -338,7 +308,7 @@ internal abstract partial class HeifColorConverterBase HeifColorConversionParameters parameters = this.Parameters; // The unpacker supplies three planar RGB rows. These same buffers become the destination component - // rows after each operator call, so encoding retains JPEG's planar contract without another allocation. + // rows after each operator call, so encoding retains the planar contract without another allocation. ref float component0Base = ref MemoryMarshal.GetReference(component0); ref float component1Base = ref MemoryMarshal.GetReference(component1); ref float component2Base = ref MemoryMarshal.GetReference(component2); @@ -346,7 +316,7 @@ internal abstract partial class HeifColorConverterBase int i = 0; // RGB normalization is part of the vector load, and each operator returns planar components through - // out parameters. This is the same input/output shape used by JPEG's encoder-side color operators. + // out parameters. The input and output planes share the same row coordinates. if (Vector512.IsHardwareAccelerated) { int oneVectorFromEnd = length - Vector512.Count; diff --git a/src/ImageSharp/Formats/Heif/Components/ColorConverters/HeifPlanarColorConverter.cs b/src/ImageSharp/Formats/Heif/Components/ColorConverters/HeifPlanarColorConverter.cs index 472abc44a3..44c53e94dc 100644 --- a/src/ImageSharp/Formats/Heif/Components/ColorConverters/HeifPlanarColorConverter.cs +++ b/src/ImageSharp/Formats/Heif/Components/ColorConverters/HeifPlanarColorConverter.cs @@ -2,8 +2,14 @@ // Licensed under the Six Labors Split License. using System.Buffers; +using System.Numerics; using System.Runtime.InteropServices; +using System.Runtime.Intrinsics; +using SixLabors.ImageSharp.ColorProfiles; +using SixLabors.ImageSharp.ColorProfiles.Icc; +using SixLabors.ImageSharp.Formats.Heif.Components.Alpha; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Heif.Components; @@ -33,19 +39,6 @@ internal static class HeifPlanarColorConverter /// private const float UShortMaximum = ushort.MaxValue; - /// - /// Converts complete native unsigned 16-bit component planes to packed pixels. - /// - public static void ConvertToRgb( - Configuration configuration, - TBuffer buffer, - ImageFrame image, - in HeifColorConversionParameters parameters, - HeifColorConversionMode mode) - where TPixel : unmanaged, IPixel - where TBuffer : struct, IHeifPlanarSampleBuffer - => ConvertToRgb(configuration, buffer, image, in parameters, mode, 0, 0); - /// /// Converts a region of native unsigned 16-bit component storage to packed pixels and selects eligible exact integer kernels. /// @@ -53,23 +46,35 @@ internal static class HeifPlanarColorConverter /// The codec adapter exposing the native component planes. /// The configuration used for allocation and pixel conversion. /// The native component-plane buffer. - /// The destination image frame. + /// The exact destination pixel region. /// The resolved H.273 conversion parameters. /// The resolved H.273 conversion mode. /// The horizontal luma-sample offset of the first converted pixel. /// The vertical luma-sample offset of the first converted pixel. + /// The extent of the source region before rotation and mirroring. + /// The rotation and mirroring applied within the destination region. + /// The source profile selected for conversion, or null to preserve source colors. + /// The matching normalized auxiliary rows, or null for opaque pixels. + /// Whether source RGB is associated with alpha. + /// The chroma reconstruction mode. public static void ConvertToRgb( Configuration configuration, TBuffer buffer, - ImageFrame image, + Buffer2DRegion destination, in HeifColorConversionParameters parameters, HeifColorConversionMode mode, int sourceX, - int sourceY) + int sourceY, + Size sourceSize, + HeifPixelTransform transform, + IccProfile? profile, + HeifAlphaRowSource? alpha, + bool premultiplied, + HeifChromaUpsampling chromaUpsampling) where TPixel : unmanaged, IPixel where TBuffer : struct, IHeifPlanarSampleBuffer { - if (HeifYuvToRgb8Converter.SupportsFixedPointConversion( + if (chromaUpsampling != HeifChromaUpsampling.Bilinear && profile is null && alpha is null && HeifYuvToRgb8Converter.SupportsFixedPointConversion( buffer.ChromaSubsamplingX, buffer.ChromaSubsamplingY, buffer.LumaBitDepth, @@ -80,35 +85,26 @@ internal static class HeifPlanarColorConverter { // The fixed-point operator preserves the exact code-value rounding used by the verified eight-bit // presentation path. Selection belongs here so no codec can acquire a private color-conversion route. - HeifYuvToRgb8Converter.ConvertFixedPoint(configuration, buffer, image, in parameters, sourceX, sourceY); + HeifYuvToRgb8Converter.ConvertFixedPoint(configuration, buffer, destination, in parameters, sourceX, sourceY, sourceSize, transform); return; } ConvertToRgb( configuration, buffer, - image, + destination, in parameters, mode, sourceX, - sourceY); + sourceY, + sourceSize, + transform, + profile, + alpha, + premultiplied, + chromaUpsampling); } - /// - /// Converts complete native component planes to packed pixels. - /// - public static void ConvertToRgb( - Configuration configuration, - TBuffer buffer, - ImageFrame image, - in HeifColorConversionParameters parameters, - HeifColorConversionMode mode) - where TPixel : unmanaged, IPixel - where TBuffer : struct, IHeifPlanarSampleBuffer - where TSample : unmanaged - where TLoader : struct, IHeifSampleConverter - => ConvertToRgb(configuration, buffer, image, in parameters, mode, 0, 0); - /// /// Converts a region of native component planes to packed pixels. /// @@ -118,38 +114,71 @@ internal static class HeifPlanarColorConverter /// The SIMD widening operations for the sample type. /// The configuration used for allocation and pixel conversion. /// The native component-plane buffer. - /// The destination image frame. + /// The exact destination pixel region. /// The resolved H.273 conversion parameters. /// The resolved H.273 conversion mode. /// The horizontal luma-sample offset of the first converted pixel. /// The vertical luma-sample offset of the first converted pixel. + /// The extent of the source region before rotation and mirroring. + /// The rotation and mirroring applied within the destination region. + /// The source profile selected for conversion, or null to preserve source colors. + /// The matching normalized auxiliary rows, or null for opaque pixels. + /// Whether source RGB is associated with alpha. + /// The chroma reconstruction mode. public static void ConvertToRgb( Configuration configuration, TBuffer buffer, - ImageFrame image, + Buffer2DRegion destination, in HeifColorConversionParameters parameters, HeifColorConversionMode mode, int sourceX, - int sourceY) + int sourceY, + Size sourceSize, + HeifPixelTransform transform, + IccProfile? profile, + HeifAlphaRowSource? alpha, + bool premultiplied, + HeifChromaUpsampling chromaUpsampling) where TPixel : unmanaged, IPixel where TBuffer : struct, IHeifPlanarSampleBuffer where TSample : unmanaged where TLoader : struct, IHeifSampleConverter { HeifColorConverterBase colorConverter = HeifColorConverterBase.Create(mode, in parameters, buffer.IsMonochrome); + ColorProfileConverter? profileConverter = profile is null + ? null + : new ColorProfileConverter(new ColorConversionOptions + { + MemoryAllocator = configuration.MemoryAllocator, + SourceIccProfile = profile, + TargetIccProfile = CompactSrgbV4Profile.Profile, + }); + YuvToRgbRowConverter converter = new( configuration, buffer, - image, + sourceSize.Width, colorConverter, + profileConverter, + alpha, + premultiplied, sourceX, - sourceY); + sourceY, + chromaUpsampling); using IMemoryOwner scratchOwner = configuration.MemoryAllocator.Allocate(converter.BufferLength); Span scratch = scratchOwner.GetSpan(); - for (int y = 0; y < image.Height; y++) + Matrix3x2 matrix = transform.GetMatrix(sourceSize); + Point origin = HeifPixelTransform.Transform(0, 0, matrix); + Size columnStep = new((int)matrix.M11, (int)matrix.M12); + Size rowStep = new((int)matrix.M21, (int)matrix.M22); + + // The decoder's region bounds already include crop and tile placement. Resolve orientation once; + // each row advances by an integer basis vector and packing writes directly into that region. + for (int y = 0; y < sourceSize.Height; y++) { - converter.Convert(y, scratch); + converter.Convert(y, scratch, destination, origin, columnStep); + origin += rowStep; } } @@ -343,15 +372,23 @@ internal static class HeifPlanarColorConverter private TBuffer buffer; /// - /// The destination image frame. + /// The exact destination pixel region. /// - private readonly ImageFrame image; + private readonly int width; /// /// The selected H.273 color converter. /// private readonly HeifColorConverterBase colorConverter; + /// + /// The profile transform reused by every component row in the region. + /// + private readonly ColorProfileConverter? profileConverter; + + private readonly HeifAlphaRowSource? alpha; + private readonly bool premultiplied; + /// /// The horizontal luma-sample offset of the output window. /// @@ -403,7 +440,7 @@ internal static class HeifPlanarColorConverter private readonly bool isMonochrome; /// - /// Whether the destination uses JPEG-compatible eight-bit RGB plane packing. + /// Whether the destination uses eight-bit RGB plane packing. /// private readonly bool usesBytePacking; @@ -412,27 +449,43 @@ internal static class HeifPlanarColorConverter /// private readonly bool reconstructCompleteRow; + /// + /// Whether subsampled chroma is reconstructed with bilinear interpolation. + /// + private readonly bool useBilinear; + /// /// Initializes a new instance of the struct. /// /// The configuration used for pixel conversion. /// The codec adapter exposing the reconstructed component planes. - /// The destination image frame. + /// The source row width. /// The selected H.273 color converter. + /// The profile transform for this region, or null to preserve source colors. + /// The auxiliary rows, or null for opaque pixels. + /// Whether source RGB is associated with alpha. /// The horizontal luma-sample offset of the output window. /// The vertical luma-sample offset of the output window. + /// The chroma reconstruction mode. public YuvToRgbRowConverter( Configuration configuration, TBuffer buffer, - ImageFrame image, + int width, HeifColorConverterBase colorConverter, + ColorProfileConverter? profileConverter, + HeifAlphaRowSource? alpha, + bool premultiplied, int sourceX, - int sourceY) + int sourceY, + HeifChromaUpsampling chromaUpsampling) { this.configuration = configuration; this.buffer = buffer; - this.image = image; + this.width = width; this.colorConverter = colorConverter; + this.profileConverter = profileConverter; + this.alpha = alpha; + this.premultiplied = premultiplied; this.sourceX = sourceX; this.sourceY = sourceY; this.bufferWidth = buffer.Width; @@ -444,11 +497,13 @@ internal static class HeifPlanarColorConverter this.usesBytePacking = buffer.LumaBitDepth == 8 && (buffer.IsMonochrome || buffer.ChromaBitDepth == 8); this.chromaWidth = (buffer.Width + (1 << this.subsamplingX) - 1) >> this.subsamplingX; this.chromaHeight = (buffer.Height + (1 << this.subsamplingY) - 1) >> this.subsamplingY; - this.reconstructCompleteRow = sourceX != 0 || image.Width != buffer.Width; + this.reconstructCompleteRow = sourceX != 0 || width != buffer.Width; + this.useBilinear = chromaUpsampling == HeifChromaUpsampling.Bilinear + || (chromaUpsampling == HeifChromaUpsampling.Auto && buffer.ChromaBitDepth > 8); } /// - /// Gets a value indicating whether the destination uses JPEG-compatible eight-bit RGB plane packing. + /// Gets a value indicating whether the destination uses eight-bit RGB plane packing. /// public readonly bool UsesBytePacking => this.usesBytePacking; @@ -459,16 +514,18 @@ internal static class HeifPlanarColorConverter { get { - int componentLength = this.image.Width * 3; - if (!this.isMonochrome && this.subsamplingX != 0) + int componentLength = this.width * 3; + if (this.useBilinear && !this.isMonochrome && this.subsamplingX != 0) { - // Full-image conversion reconstructs directly into the component rows. Cropped conversion + // Full-destination conversion reconstructs directly into the component rows. Cropped conversion // retains the complete coded row so interpolation phase is preserved at the window boundary. componentLength += (this.chromaWidth * 2) + (this.reconstructCompleteRow ? this.bufferWidth : 0); } - int packedRowCount = this.UsesBytePacking ? 1 : 2; - return componentLength + (this.image.Width * packedRowCount); + // ICC interleaving and final pixel packing occur sequentially, so the same scratch + // storage holds both. No additional buffer or allocation is needed per row. + int packedRowCount = this.profileConverter is not null ? 4 : this.UsesBytePacking && this.alpha is null ? 1 : 2; + return componentLength + (this.width * packedRowCount); } } @@ -477,88 +534,193 @@ internal static class HeifPlanarColorConverter /// /// The zero-based output row. /// The reusable pooled row buffer. - public void Convert(int y, Span scratch) + /// The exact output region receiving the converted pixels. + /// The final location of the first pixel in this source row. + /// The destination increment for each source pixel. + public void Convert(int y, Span scratch, Buffer2DRegion destination, Point origin, Size step) { - int width = this.image.Width; + int width = this.width; Span red = scratch[..width]; Span green = scratch.Slice(width, width); Span blue = scratch.Slice(width * 2, width); int sourceY = y + this.sourceY; ReadOnlySpan luma = this.buffer.GetLumaRowSpan(sourceY).Slice(this.sourceX, width); - HeifSampleConversion.ConvertSamplesToFloat(luma, red); + HeifSampleConversion.ConvertSamplesToFloat(luma, red, this.colorConverter.LumaBias, this.colorConverter.LumaScale); int packedOffset = width * 3; if (!this.isMonochrome) { - GetChromaCoordinates( - sourceY, - this.subsamplingY, - this.chromaPositionY, - this.chromaHeight - 1, - out int y0, - out int y1, - out int y1Weight); - - ReadOnlySpan cb0 = this.buffer.GetChromaBlueRowSpan(y0); - ReadOnlySpan cb1 = this.buffer.GetChromaBlueRowSpan(y1); - ReadOnlySpan cr0 = this.buffer.GetChromaRedRowSpan(y0); - ReadOnlySpan cr1 = this.buffer.GetChromaRedRowSpan(y1); - if (this.subsamplingX == 0) + if (this.useBilinear) { - HeifSampleConversion.ConvertSamplesToFloat(cb0.Slice(this.sourceX, width), green); - HeifSampleConversion.ConvertSamplesToFloat(cr0.Slice(this.sourceX, width), blue); + GetChromaCoordinates( + sourceY, + this.subsamplingY, + this.chromaPositionY, + this.chromaHeight - 1, + out int y0, + out int y1, + out int y1Weight); + + ReadOnlySpan cb0 = this.buffer.GetChromaBlueRowSpan(y0); + ReadOnlySpan cb1 = this.buffer.GetChromaBlueRowSpan(y1); + ReadOnlySpan cr0 = this.buffer.GetChromaRedRowSpan(y0); + ReadOnlySpan cr1 = this.buffer.GetChromaRedRowSpan(y1); + if (this.subsamplingX == 0) + { + HeifSampleConversion.ConvertSamplesToFloat( + cb0.Slice(this.sourceX, width), green, this.colorConverter.ChromaBias, this.colorConverter.ChromaScale); + HeifSampleConversion.ConvertSamplesToFloat( + cr0.Slice(this.sourceX, width), blue, this.colorConverter.ChromaBias, this.colorConverter.ChromaScale); + } + else + { + Span chroma0 = scratch.Slice(packedOffset, this.chromaWidth); + Span chroma1 = scratch.Slice(packedOffset + this.chromaWidth, this.chromaWidth); + Span reconstructed = this.reconstructCompleteRow + ? scratch.Slice(packedOffset + (this.chromaWidth * 2), this.bufferWidth) + : green; + + bool isCenteredX = this.chromaPositionX == 1; + + HeifSampleConversion.ReconstructChromaRowBilinear( + cb0, + cb1, + y1Weight, + this.subsamplingX, + isCenteredX, + reconstructed, + chroma0, + chroma1, + this.colorConverter.ChromaBias, + this.colorConverter.ChromaScale); + + if (this.reconstructCompleteRow) + { + reconstructed.Slice(this.sourceX, width).CopyTo(green); + } + + reconstructed = this.reconstructCompleteRow ? reconstructed : blue; + HeifSampleConversion.ReconstructChromaRowBilinear( + cr0, + cr1, + y1Weight, + this.subsamplingX, + isCenteredX, + reconstructed, + chroma0, + chroma1, + this.colorConverter.ChromaBias, + this.colorConverter.ChromaScale); + + if (this.reconstructCompleteRow) + { + reconstructed.Slice(this.sourceX, width).CopyTo(blue); + } + + packedOffset += (this.chromaWidth * 2) + (this.reconstructCompleteRow ? this.bufferWidth : 0); + } } else { - Span chroma0 = scratch.Slice(packedOffset, this.chromaWidth); - Span chroma1 = scratch.Slice(packedOffset + this.chromaWidth, this.chromaWidth); - Span reconstructed = this.reconstructCompleteRow - ? scratch.Slice(packedOffset + (this.chromaWidth * 2), this.bufferWidth) - : green; - - bool isCenteredX = this.chromaPositionX == 1; - - HeifSampleConversion.ReconstructChromaRow( - cb0, - cb1, - y1Weight, - this.subsamplingX, - isCenteredX, - reconstructed, - chroma0, - chroma1); - - if (this.reconstructCompleteRow) + // Each chroma row serves two luma rows when vertically subsampled. Use absolute + // source coordinates so cropped regions retain the coded sample-replication phase. + int chromaY = sourceY >> this.subsamplingY; + ReadOnlySpan cb = this.buffer.GetChromaBlueRowSpan(chromaY); + ReadOnlySpan cr = this.buffer.GetChromaRedRowSpan(chromaY); + if (this.subsamplingX == 0) { - reconstructed.Slice(this.sourceX, width).CopyTo(green); + HeifSampleConversion.ConvertSamplesToFloat( + cb.Slice(this.sourceX, width), green, this.colorConverter.ChromaBias, this.colorConverter.ChromaScale); + HeifSampleConversion.ConvertSamplesToFloat( + cr.Slice(this.sourceX, width), blue, this.colorConverter.ChromaBias, this.colorConverter.ChromaScale); } - - reconstructed = this.reconstructCompleteRow ? reconstructed : blue; - HeifSampleConversion.ReconstructChromaRow( - cr0, - cr1, - y1Weight, - this.subsamplingX, - isCenteredX, - reconstructed, - chroma0, - chroma1); - - if (this.reconstructCompleteRow) + else { - reconstructed.Slice(this.sourceX, width).CopyTo(blue); + HeifSampleConversion.ReconstructChromaRow( + cb, this.sourceX, green, this.colorConverter.ChromaBias, this.colorConverter.ChromaScale); + HeifSampleConversion.ReconstructChromaRow( + cr, this.sourceX, blue, this.colorConverter.ChromaBias, this.colorConverter.ChromaScale); } - - packedOffset += (this.chromaWidth * 2) + (this.reconstructCompleteRow ? this.bufferWidth : 0); } } this.colorConverter.ConvertToRgbInPlace(red, green, blue); - Span destination = this.image.PixelBuffer.DangerousGetRowSpan(y); + ReadOnlySpan alpha = this.alpha is null ? default : this.alpha.ReadRow(y); + if (this.premultiplied) + { + HeifColorConverterBase.UnassociateRgb(red, green, blue, alpha); + } + Span packedStorage = scratch[packedOffset..]; - if (this.UsesBytePacking) + if (this.profileConverter is not null) + { + this.colorConverter.ConvertRgbToSrgbInPlace( + red, green, blue, this.profileConverter, MemoryMarshal.Cast(packedStorage)[..width]); + + // Keep the converted RGB and auxiliary alpha in floating point until TPixel performs its + // final conversion. An integer intermediate would discard precision for floating-point pixels. + // ICC's interleaved RGB is no longer live, so its scratch storage now holds RGBA vectors. + Span vectors = MemoryMarshal.Cast(packedStorage)[..width]; + int x = 0; + if (Vector128.IsHardwareAccelerated) + { + // Load four samples per plane and transpose them into four RGBA pixels. Opaque alpha + // supplies four ones; auxiliary alpha has already been normalized by its row source. + for (; x <= width - Vector128.Count; x += Vector128.Count) + { + Vector128 r = Vector128.LoadUnsafe(ref MemoryMarshal.GetReference(red), (nuint)x); + Vector128 g = Vector128.LoadUnsafe(ref MemoryMarshal.GetReference(green), (nuint)x); + Vector128 b = Vector128.LoadUnsafe(ref MemoryMarshal.GetReference(blue), (nuint)x); + Vector128 a = this.alpha is null + ? Vector128.Create(1F) + : Vector128.LoadUnsafe(ref MemoryMarshal.GetReference(alpha), (nuint)x); + + HeifColorConverterBase.Transpose4( + r, + g, + b, + a, + out Vector128 p0, + out Vector128 p1, + out Vector128 p2, + out Vector128 p3); + + vectors[x] = p0.AsVector4(); + vectors[x + 1] = p1.AsVector4(); + vectors[x + 2] = p2.AsVector4(); + vectors[x + 3] = p3.AsVector4(); + } + } + + for (; x < width; x++) + { + vectors[x] = new Vector4(red[x], green[x], blue[x], this.alpha is null ? 1F : alpha[x]); + } + + if (step.Width == 1) + { + PixelOperations.Instance.FromVector4Destructive( + this.configuration, + vectors, + destination.DangerousGetRowSpan(origin.Y).Slice(origin.X, width), + PixelConversionModifiers.Scale); + } + else + { + // Reversed rows and rotated columns write directly into their final locations. + for (x = 0; x < width; x++) + { + destination.DangerousGetRowSpan(origin.Y)[origin.X] = TPixel.FromUnassociatedScaledVector4(vectors[x]); + origin += step; + } + } + + return; + } + + if (this.UsesBytePacking && this.alpha is null) { - // This is JPEG's planar packing contract. Existing pixel-specific SIMD packers therefore own the + // The shared planar packing contract lets existing pixel-specific SIMD packers own the // final RGB-to-TPixel conversion rather than a HEIF-codec-specific per-pixel implementation. Span byteStorage = MemoryMarshal.AsBytes(packedStorage)[..(width * 3)]; Span redBytes = byteStorage[..width]; @@ -568,18 +730,59 @@ internal static class HeifPlanarColorConverter SimdUtils.NormalizedFloatToByteSaturate(green, greenBytes); SimdUtils.NormalizedFloatToByteSaturate(blue, blueBytes); - PixelOperations.Instance.PackFromRgbPlanes(redBytes, greenBytes, blueBytes, destination); + if (step.Width == 1) + { + PixelOperations.Instance.PackFromRgbPlanes( + redBytes, greenBytes, blueBytes, destination.DangerousGetRowSpan(origin.Y).Slice(origin.X, width)); + } + else + { + // A reversed row or column is not a contiguous span. Construct the final pixel at its + // destination instead of packing a temporary TPixel row and reading it back to scatter. + for (int x = 0; x < width; x++) + { + destination.DangerousGetRowSpan(origin.Y)[origin.X] = TPixel.FromRgb24(new Rgb24(redBytes[x], greenBytes[x], blueBytes[x])); + origin += step; + } + } + return; } Span packed = MemoryMarshal.Cast(packedStorage)[..width]; HeifSampleConversion.PackRgba64(red, green, blue, packed); - PixelOperations.Instance.FromRgba64(this.configuration, packed, destination); + if (this.alpha is not null) + { + // RGB has been packed, so its float row is no longer live. Reuse that storage for + // alpha narrowing instead of allocating a fourth component buffer or revisiting the image. + Span packedAlpha = MemoryMarshal.Cast(red)[..width]; + HeifSampleConversion.PackL16(alpha, packedAlpha); + for (int x = 0; x < width; x++) + { + packed[x].A = packedAlpha[x].PackedValue; + } + } + + if (step.Width == 1) + { + PixelOperations.Instance.FromRgba64( + this.configuration, packed, destination.DangerousGetRowSpan(origin.Y).Slice(origin.X, width)); + } + else + { + // Keep the same component narrowing as contiguous output, including alpha, while avoiding + // a second packed pixel buffer for reversed rows and quarter-turn destination columns. + for (int x = 0; x < width; x++) + { + destination.DangerousGetRowSpan(origin.Y)[origin.X] = TPixel.FromRgba64(packed[x]); + origin += step; + } + } } } /// - /// Converts packed image rows to native component planes using pooled planar storage. + /// Converts packed destination rows to native component planes using pooled planar storage. /// /// The source pixel type. /// The codec adapter exposing the native component planes. @@ -652,7 +855,7 @@ internal static class HeifPlanarColorConverter private readonly bool isMonochrome; /// - /// Whether source pixels use JPEG-compatible eight-bit RGB plane unpacking. + /// Whether source pixels use eight-bit RGB plane unpacking. /// private readonly bool usesByteInput; @@ -689,7 +892,7 @@ internal static class HeifPlanarColorConverter } /// - /// Gets a value indicating whether source pixels use JPEG-compatible eight-bit RGB plane unpacking. + /// Gets a value indicating whether source pixels use eight-bit RGB plane unpacking. /// public readonly bool UsesByteInput => this.usesByteInput; @@ -796,7 +999,7 @@ internal static class HeifPlanarColorConverter if (this.UsesByteInput) { - // JPEG's planar unpack contract reaches the existing pixel-specific SIMD implementation before + // The shared planar unpack contract reaches the existing pixel-specific SIMD implementation before // the closed H.273 operator transforms the component rows in place. PixelOperations.Instance.UnpackIntoRgbPlanes(luma, chromaBlue, chromaRed, source); this.colorConverter.ConvertFromRgbInPlace(luma, chromaBlue, chromaRed, ByteMaximum); diff --git a/src/ImageSharp/Formats/Heif/Components/ColorConverters/HeifSampleConversion.cs b/src/ImageSharp/Formats/Heif/Components/ColorConverters/HeifSampleConversion.cs index 39bcbc4360..bd32757e1f 100644 --- a/src/ImageSharp/Formats/Heif/Components/ColorConverters/HeifSampleConversion.cs +++ b/src/ImageSharp/Formats/Heif/Components/ColorConverters/HeifSampleConversion.cs @@ -21,13 +21,19 @@ internal static class HeifSampleConversion private const float UShortMaximum = ushort.MaxValue; /// - /// Widens reconstructed integer samples into a pooled float component row. + /// Widens and normalizes reconstructed integer samples into a pooled float component row. /// /// The reconstructed sample type. /// The widening operations for the sample type. /// The reconstructed samples. /// The destination component row. - public static void ConvertSamplesToFloat(ReadOnlySpan source, Span destination) + /// The encoded value corresponding to normalized zero. + /// The encoded range corresponding to a normalized interval of one. + public static void ConvertSamplesToFloat( + ReadOnlySpan source, + Span destination, + float bias, + float scale) where TSample : unmanaged where TLoader : struct, IHeifSampleConverter { @@ -36,46 +42,129 @@ internal static class HeifSampleConversion int length = destination.Length; int i = 0; - // Descending vector widths match the JPEG color-converter traversal. A wide-capable CPU processes + // Descending vector widths use the widest supported lanes first. A wide-capable CPU processes // complete wide batches first while short and irregular rows continue through narrower SIMD tails. if (Vector512.IsHardwareAccelerated) { + Vector512 biasVector = Vector512.Create(bias); + Vector512 scaleVector = Vector512.Create(scale); int oneVectorFromEnd = length - Vector512.Count; for (; i <= oneVectorFromEnd; i += Vector512.Count) { Vector512 samples = TLoader.LoadVector512(ref Unsafe.Add(ref sourceBase, i)); - Unsafe.As>(ref Unsafe.Add(ref destinationBase, i)) = samples; + Unsafe.As>(ref Unsafe.Add(ref destinationBase, i)) = (samples - biasVector) / scaleVector; } } if (Vector256.IsHardwareAccelerated) { + Vector256 biasVector = Vector256.Create(bias); + Vector256 scaleVector = Vector256.Create(scale); int oneVectorFromEnd = length - Vector256.Count; for (; i <= oneVectorFromEnd; i += Vector256.Count) { Vector256 samples = TLoader.LoadVector256(ref Unsafe.Add(ref sourceBase, i)); - Unsafe.As>(ref Unsafe.Add(ref destinationBase, i)) = samples; + Unsafe.As>(ref Unsafe.Add(ref destinationBase, i)) = (samples - biasVector) / scaleVector; } } if (Vector128.IsHardwareAccelerated) { + Vector128 biasVector = Vector128.Create(bias); + Vector128 scaleVector = Vector128.Create(scale); int oneVectorFromEnd = length - Vector128.Count; for (; i <= oneVectorFromEnd; i += Vector128.Count) { Vector128 samples = TLoader.LoadVector128(ref Unsafe.Add(ref sourceBase, i)); - Unsafe.As>(ref Unsafe.Add(ref destinationBase, i)) = samples; + Unsafe.As>(ref Unsafe.Add(ref destinationBase, i)) = (samples - biasVector) / scaleVector; } } for (; i < length; i++) { - Unsafe.Add(ref destinationBase, i) = GetSample(source, i); + Unsafe.Add(ref destinationBase, i) = (GetSample(source, i) - bias) / scale; + } + } + + /// + /// Expands horizontally subsampled chroma by repeating each sample at two luma positions. + /// + /// The reconstructed sample type. + /// The widening operations for the sample type. + /// The complete subsampled chroma row. + /// The first luma coordinate of the output window. + /// The exact output component row. + /// The encoded chroma value corresponding to normalized zero. + /// The encoded chroma range. + public static void ReconstructChromaRow( + ReadOnlySpan source, + int sourceX, + Span destination, + float bias, + float scale) + where TSample : unmanaged + where TLoader : struct, IHeifSampleConverter + { + ref TSample sourceBase = ref MemoryMarshal.GetReference(source); + ref float destinationBase = ref MemoryMarshal.GetReference(destination); + int sourceIndex = sourceX >> 1; + int i = 0; + + // An odd crop origin starts at the second pixel of a replicated pair. Consume that pixel + // before vectorizing complete pairs; the final scalar tail handles an odd right edge. + if ((sourceX & 1) != 0) + { + destination[i++] = (GetSample(source, sourceIndex++) - bias) / scale; + } + + if (Vector512.IsHardwareAccelerated) + { + int lastBatch = destination.Length - 32; + for (; i <= lastBatch; i += 32, sourceIndex += 16) + { + Vector512 samples = TLoader.LoadVector512(ref Unsafe.Add(ref sourceBase, sourceIndex)); + samples = (samples - Vector512.Create(bias)) / Vector512.Create(scale); + + // Each four-lane group [a,b,c,d] becomes [a,a,b,b,c,c,d,d]. Keeping groups + // in source order avoids lane-local shuffles interleaving separate pixel groups. + StoreInterleavedChroma(samples.GetLower().GetLower(), samples.GetLower().GetLower(), ref Unsafe.Add(ref destinationBase, i)); + StoreInterleavedChroma(samples.GetLower().GetUpper(), samples.GetLower().GetUpper(), ref Unsafe.Add(ref destinationBase, i + 8)); + StoreInterleavedChroma(samples.GetUpper().GetLower(), samples.GetUpper().GetLower(), ref Unsafe.Add(ref destinationBase, i + 16)); + StoreInterleavedChroma(samples.GetUpper().GetUpper(), samples.GetUpper().GetUpper(), ref Unsafe.Add(ref destinationBase, i + 24)); + } + } + + if (Vector256.IsHardwareAccelerated) + { + int lastBatch = destination.Length - 16; + for (; i <= lastBatch; i += 16, sourceIndex += 8) + { + Vector256 samples = TLoader.LoadVector256(ref Unsafe.Add(ref sourceBase, sourceIndex)); + samples = (samples - Vector256.Create(bias)) / Vector256.Create(scale); + StoreInterleavedChroma(samples.GetLower(), samples.GetLower(), ref Unsafe.Add(ref destinationBase, i)); + StoreInterleavedChroma(samples.GetUpper(), samples.GetUpper(), ref Unsafe.Add(ref destinationBase, i + 8)); + } + } + + if (Vector128.IsHardwareAccelerated) + { + int lastBatch = destination.Length - 8; + for (; i <= lastBatch; i += 8, sourceIndex += 4) + { + Vector128 samples = TLoader.LoadVector128(ref Unsafe.Add(ref sourceBase, sourceIndex)); + samples = (samples - Vector128.Create(bias)) / Vector128.Create(scale); + StoreInterleavedChroma(samples, samples, ref Unsafe.Add(ref destinationBase, i)); + } + } + + for (; i < destination.Length; i++) + { + destination[i] = (GetSample(source, (sourceX + i) >> 1) - bias) / scale; } } /// - /// Reconstructs one full-width chroma row using the signaled vertical and horizontal sample positions. + /// Reconstructs one normalized chroma row using the signaled sample positions. /// /// The reconstructed sample type. /// The widening operations for the sample type. @@ -87,7 +176,9 @@ internal static class HeifSampleConversion /// The reconstructed full-width chroma row. /// The first pooled chroma scratch row. /// The second pooled chroma scratch row. - public static void ReconstructChromaRow( + /// The encoded chroma value corresponding to normalized zero. + /// The encoded chroma range. + public static void ReconstructChromaRowBilinear( ReadOnlySpan row0, ReadOnlySpan row1, int y1Weight, @@ -95,20 +186,15 @@ internal static class HeifSampleConversion bool isCenteredX, Span destination, Span scratch0, - Span scratch1) + Span scratch1, + float bias, + float scale) where TSample : unmanaged where TLoader : struct, IHeifSampleConverter { int sourceLength = subX == 0 ? destination.Length : (destination.Length + 1) >> 1; Span top = scratch0[..sourceLength]; - ConvertSamplesToFloat(row0, top); - - if (y1Weight != 0) - { - Span bottom = scratch1[..sourceLength]; - ConvertSamplesToFloat(row1, bottom); - InterpolateChromaRows(top, bottom, y1Weight); - } + ConvertSamplesToFloat(row0, top, bias, scale); if (subX == 0) { @@ -116,119 +202,93 @@ internal static class HeifSampleConversion return; } - UpsampleChromaHorizontal(top, destination, isCenteredX); - } - - /// - /// Interpolates two chroma rows in place using quarter-sample weights. - /// - /// The upper row, replaced by the interpolated values. - /// The lower row. - /// The lower-row weight with a denominator of four. - private static void InterpolateChromaRows(Span top, ReadOnlySpan bottom, int bottomWeight) - { - ref float topBase = ref MemoryMarshal.GetReference(top); - ref float bottomBase = ref MemoryMarshal.GetReference(bottom); - int length = top.Length; - int i = 0; - float topWeight = 4 - bottomWeight; - - if (Vector512.IsHardwareAccelerated) - { - Vector512 topWeightVector = Vector512.Create(topWeight); - Vector512 bottomWeightVector = Vector512.Create((float)bottomWeight); - Vector512 scale = Vector512.Create(0.25F); - int oneVectorFromEnd = length - Vector512.Count; - for (; i <= oneVectorFromEnd; i += Vector512.Count) - { - ref Vector512 topVector = ref Unsafe.As>(ref Unsafe.Add(ref topBase, i)); - Vector512 bottomVector = Unsafe.As>(ref Unsafe.Add(ref bottomBase, i)); - topVector = Vector512.MultiplyAddEstimate(bottomWeightVector, bottomVector, topWeightVector * topVector) * scale; - } - } - - if (Vector256.IsHardwareAccelerated) - { - Vector256 topWeightVector = Vector256.Create(topWeight); - Vector256 bottomWeightVector = Vector256.Create((float)bottomWeight); - Vector256 scale = Vector256.Create(0.25F); - int oneVectorFromEnd = length - Vector256.Count; - for (; i <= oneVectorFromEnd; i += Vector256.Count) - { - ref Vector256 topVector = ref Unsafe.As>(ref Unsafe.Add(ref topBase, i)); - Vector256 bottomVector = Unsafe.As>(ref Unsafe.Add(ref bottomBase, i)); - topVector = Vector256.MultiplyAddEstimate(bottomWeightVector, bottomVector, topWeightVector * topVector) * scale; - } - } - - if (Vector128.IsHardwareAccelerated) + ReadOnlySpan bottom = top; + if (y1Weight != 0) { - Vector128 topWeightVector = Vector128.Create(topWeight); - Vector128 bottomWeightVector = Vector128.Create((float)bottomWeight); - Vector128 scale = Vector128.Create(0.25F); - int oneVectorFromEnd = length - Vector128.Count; - for (; i <= oneVectorFromEnd; i += Vector128.Count) - { - ref Vector128 topVector = ref Unsafe.As>(ref Unsafe.Add(ref topBase, i)); - Vector128 bottomVector = Unsafe.As>(ref Unsafe.Add(ref bottomBase, i)); - topVector = Vector128.MultiplyAddEstimate(bottomWeightVector, bottomVector, topWeightVector * topVector) * scale; - } + Span lower = scratch1[..sourceLength]; + ConvertSamplesToFloat(row1, lower, bias, scale); + bottom = lower; } - for (; i < length; i++) - { - Unsafe.Add(ref topBase, i) = ((Unsafe.Add(ref topBase, i) * topWeight) + (Unsafe.Add(ref bottomBase, i) * bottomWeight)) * 0.25F; - } + // Normalize each sample before interpolation. Preserve the four products and their + // addition order instead of combining duplicate rows or performing two separable blends. + // Collapsed vertical boundaries reuse the top row with the same centered weights. + float closestWeight = y1Weight == 0 ? 0.75F : Math.Max(y1Weight, 4 - y1Weight) * 0.25F; + ReadOnlySpan closest = y1Weight > 2 ? bottom : top; + ReadOnlySpan adjacent = y1Weight > 2 ? top : bottom; + UpsampleChromaHorizontal(closest, adjacent, destination, closestWeight, isCenteredX); } /// - /// Expands horizontally subsampled chroma to luma width using the selected sample-position rules. + /// Blends four normalized chroma samples in closest, horizontal, vertical, diagonal order. /// - /// The subsampled chroma values. + /// The vertically closest normalized row. + /// The vertically adjacent normalized row, or the same row at a boundary. /// The full-width chroma values. + /// The weight of the closest row. /// Whether chroma lies between neighboring luma samples. - private static void UpsampleChromaHorizontal(ReadOnlySpan source, Span destination, bool isCentered) + private static void UpsampleChromaHorizontal( + ReadOnlySpan closest, + ReadOnlySpan adjacent, + Span destination, + float closestWeight, + bool isCentered) { - ref float sourceBase = ref MemoryMarshal.GetReference(source); + ref float closestBase = ref MemoryMarshal.GetReference(closest); + ref float adjacentBase = ref MemoryMarshal.GetReference(adjacent); ref float destinationBase = ref MemoryMarshal.GetReference(destination); - int sourceLength = source.Length; + int sourceLength = closest.Length; + float adjacentWeight = 1F - closestWeight; + float evenWeight = isCentered ? 0.75F : 1F; + float oddWeight = isCentered ? 0.75F : 0.5F; + float e0 = evenWeight * closestWeight; + float e1 = (1F - evenWeight) * closestWeight; + float e2 = evenWeight * adjacentWeight; + float e3 = (1F - evenWeight) * adjacentWeight; + float o0 = oddWeight * closestWeight; + float o1 = (1F - oddWeight) * closestWeight; + float o2 = oddWeight * adjacentWeight; + float o3 = (1F - oddWeight) * adjacentWeight; int i = 0; if (isCentered) { - // The first centered pair extends the left edge. Interior vectors can then read one real neighbor - // on each side and use the exact [1,3]/4 and [3,1]/4 interpolation weights. - StoreChromaPair(ref destinationBase, 0, source[0], ((3F * source[0]) + source[Math.Min(1, sourceLength - 1)]) * 0.25F, destination.Length); + // Extend the left edge before SIMD loads can address a previous sample. + // Retain duplicate terms: collapsing their weights changes floating-point rounding. + int next = Math.Min(1, sourceLength - 1); + float even = (((closest[0] * e0) + (closest[0] * e1)) + (adjacent[0] * e2)) + (adjacent[0] * e3); + float odd = (((closest[0] * o0) + (closest[next] * o1)) + (adjacent[0] * o2)) + (adjacent[next] * o3); + StoreChromaPair(ref destinationBase, 0, even, odd, destination.Length); i = 1; } if (Vector512.IsHardwareAccelerated) { int oneVectorBeforeEnd = sourceLength - Vector512.Count - 1; - Vector512 quarter = Vector512.Create(0.25F); - Vector512 half = Vector512.Create(0.5F); - Vector512 three = Vector512.Create(3F); + Vector512 e0Vector = Vector512.Create(e0); + Vector512 e1Vector = Vector512.Create(e1); + Vector512 e2Vector = Vector512.Create(e2); + Vector512 e3Vector = Vector512.Create(e3); + Vector512 o0Vector = Vector512.Create(o0); + Vector512 o1Vector = Vector512.Create(o1); + Vector512 o2Vector = Vector512.Create(o2); + Vector512 o3Vector = Vector512.Create(o3); for (; i <= oneVectorBeforeEnd; i += Vector512.Count) { - Vector512 center = Unsafe.As>(ref Unsafe.Add(ref sourceBase, i)); - Vector512 next = Unsafe.As>(ref Unsafe.Add(ref sourceBase, i + 1)); - Vector512 even; - Vector512 odd; - if (isCentered) - { - Vector512 previous = Unsafe.As>(ref Unsafe.Add(ref sourceBase, i - 1)); - even = Vector512.MultiplyAddEstimate(three, center, previous) * quarter; - odd = Vector512.MultiplyAddEstimate(three, center, next) * quarter; - } - else - { - even = center; - odd = (center + next) * half; - } - - // Vector512 has no cross-platform unpack helper. The interpolation remains 512-bit; four - // established Vector128 unpack operations only transpose the final even/odd lanes for storage. - StoreInterleavedChroma(even.GetLower().GetLower(), odd.GetLower().GetLower(), ref Unsafe.Add(ref destinationBase, i * 2)); + // Each lane represents one chroma column. Even lanes use the previous column + // for centered samples; odd lanes use the next. The two rows remain separate + // until the four weighted terms are added, without fused multiply-add rounding. + int previous = isCentered ? i - 1 : i; + Vector512 center0 = Vector512.LoadUnsafe(ref closestBase, (nuint)i); + Vector512 center1 = Vector512.LoadUnsafe(ref adjacentBase, (nuint)i); + Vector512 previous0 = Vector512.LoadUnsafe(ref closestBase, (nuint)previous); + Vector512 previous1 = Vector512.LoadUnsafe(ref adjacentBase, (nuint)previous); + Vector512 next0 = Vector512.LoadUnsafe(ref closestBase, (nuint)(i + 1)); + Vector512 next1 = Vector512.LoadUnsafe(ref adjacentBase, (nuint)(i + 1)); + Vector512 even = (((center0 * e0Vector) + (previous0 * e1Vector)) + (center1 * e2Vector)) + (previous1 * e3Vector); + Vector512 odd = (((center0 * o0Vector) + (next0 * o1Vector)) + (center1 * o2Vector)) + (next1 * o3Vector); + + StoreInterleavedChroma(even.GetLower().GetLower(), odd.GetLower().GetLower(), ref Unsafe.Add(ref destinationBase, (i * 2) + 0)); StoreInterleavedChroma(even.GetLower().GetUpper(), odd.GetLower().GetUpper(), ref Unsafe.Add(ref destinationBase, (i * 2) + 8)); StoreInterleavedChroma(even.GetUpper().GetLower(), odd.GetUpper().GetLower(), ref Unsafe.Add(ref destinationBase, (i * 2) + 16)); StoreInterleavedChroma(even.GetUpper().GetUpper(), odd.GetUpper().GetUpper(), ref Unsafe.Add(ref destinationBase, (i * 2) + 24)); @@ -238,28 +298,30 @@ internal static class HeifSampleConversion if (Vector256.IsHardwareAccelerated) { int oneVectorBeforeEnd = sourceLength - Vector256.Count - 1; - Vector256 quarter = Vector256.Create(0.25F); - Vector256 half = Vector256.Create(0.5F); - Vector256 three = Vector256.Create(3F); + Vector256 e0Vector = Vector256.Create(e0); + Vector256 e1Vector = Vector256.Create(e1); + Vector256 e2Vector = Vector256.Create(e2); + Vector256 e3Vector = Vector256.Create(e3); + Vector256 o0Vector = Vector256.Create(o0); + Vector256 o1Vector = Vector256.Create(o1); + Vector256 o2Vector = Vector256.Create(o2); + Vector256 o3Vector = Vector256.Create(o3); for (; i <= oneVectorBeforeEnd; i += Vector256.Count) { - Vector256 center = Unsafe.As>(ref Unsafe.Add(ref sourceBase, i)); - Vector256 next = Unsafe.As>(ref Unsafe.Add(ref sourceBase, i + 1)); - Vector256 even; - Vector256 odd; - if (isCentered) - { - Vector256 previous = Unsafe.As>(ref Unsafe.Add(ref sourceBase, i - 1)); - even = Vector256.MultiplyAddEstimate(three, center, previous) * quarter; - odd = Vector256.MultiplyAddEstimate(three, center, next) * quarter; - } - else - { - even = center; - odd = (center + next) * half; - } - - StoreInterleavedChroma(even.GetLower(), odd.GetLower(), ref Unsafe.Add(ref destinationBase, i * 2)); + // Each lane represents one chroma column. Even lanes use the previous column + // for centered samples; odd lanes use the next. The two rows remain separate + // until the four weighted terms are added, without fused multiply-add rounding. + int previous = isCentered ? i - 1 : i; + Vector256 center0 = Vector256.LoadUnsafe(ref closestBase, (nuint)i); + Vector256 center1 = Vector256.LoadUnsafe(ref adjacentBase, (nuint)i); + Vector256 previous0 = Vector256.LoadUnsafe(ref closestBase, (nuint)previous); + Vector256 previous1 = Vector256.LoadUnsafe(ref adjacentBase, (nuint)previous); + Vector256 next0 = Vector256.LoadUnsafe(ref closestBase, (nuint)(i + 1)); + Vector256 next1 = Vector256.LoadUnsafe(ref adjacentBase, (nuint)(i + 1)); + Vector256 even = (((center0 * e0Vector) + (previous0 * e1Vector)) + (center1 * e2Vector)) + (previous1 * e3Vector); + Vector256 odd = (((center0 * o0Vector) + (next0 * o1Vector)) + (center1 * o2Vector)) + (next1 * o3Vector); + + StoreInterleavedChroma(even.GetLower(), odd.GetLower(), ref Unsafe.Add(ref destinationBase, (i * 2) + 0)); StoreInterleavedChroma(even.GetUpper(), odd.GetUpper(), ref Unsafe.Add(ref destinationBase, (i * 2) + 8)); } } @@ -267,55 +329,43 @@ internal static class HeifSampleConversion if (Vector128.IsHardwareAccelerated) { int oneVectorBeforeEnd = sourceLength - Vector128.Count - 1; - Vector128 quarter = Vector128.Create(0.25F); - Vector128 half = Vector128.Create(0.5F); - Vector128 three = Vector128.Create(3F); + Vector128 e0Vector = Vector128.Create(e0); + Vector128 e1Vector = Vector128.Create(e1); + Vector128 e2Vector = Vector128.Create(e2); + Vector128 e3Vector = Vector128.Create(e3); + Vector128 o0Vector = Vector128.Create(o0); + Vector128 o1Vector = Vector128.Create(o1); + Vector128 o2Vector = Vector128.Create(o2); + Vector128 o3Vector = Vector128.Create(o3); for (; i <= oneVectorBeforeEnd; i += Vector128.Count) { - Vector128 center = Unsafe.As>(ref Unsafe.Add(ref sourceBase, i)); - Vector128 next = Unsafe.As>(ref Unsafe.Add(ref sourceBase, i + 1)); - Vector128 even; - Vector128 odd; - if (isCentered) - { - Vector128 previous = Unsafe.As>(ref Unsafe.Add(ref sourceBase, i - 1)); - even = Vector128.MultiplyAddEstimate(three, center, previous) * quarter; - odd = Vector128.MultiplyAddEstimate(three, center, next) * quarter; - } - else - { - even = center; - odd = (center + next) * half; - } - - StoreInterleavedChroma(even, odd, ref Unsafe.Add(ref destinationBase, i * 2)); + // Each lane represents one chroma column. Even lanes use the previous column + // for centered samples; odd lanes use the next. The two rows remain separate + // until the four weighted terms are added, without fused multiply-add rounding. + int previous = isCentered ? i - 1 : i; + Vector128 center0 = Vector128.LoadUnsafe(ref closestBase, (nuint)i); + Vector128 center1 = Vector128.LoadUnsafe(ref adjacentBase, (nuint)i); + Vector128 previous0 = Vector128.LoadUnsafe(ref closestBase, (nuint)previous); + Vector128 previous1 = Vector128.LoadUnsafe(ref adjacentBase, (nuint)previous); + Vector128 next0 = Vector128.LoadUnsafe(ref closestBase, (nuint)(i + 1)); + Vector128 next1 = Vector128.LoadUnsafe(ref adjacentBase, (nuint)(i + 1)); + Vector128 even = (((center0 * e0Vector) + (previous0 * e1Vector)) + (center1 * e2Vector)) + (previous1 * e3Vector); + Vector128 odd = (((center0 * o0Vector) + (next0 * o1Vector)) + (center1 * o2Vector)) + (next1 * o3Vector); + + StoreInterleavedChroma(even, odd, ref Unsafe.Add(ref destinationBase, (i * 2) + 0)); } } for (; i < sourceLength; i++) { - float center = source[i]; - float next = source[Math.Min(i + 1, sourceLength - 1)]; - float even = isCentered ? (source[Math.Max(i - 1, 0)] + (3F * center)) * 0.25F : center; - float odd = isCentered ? ((3F * center) + next) * 0.25F : (center + next) * 0.5F; + int previous = isCentered ? Math.Max(i - 1, 0) : i; + int next = Math.Min(i + 1, sourceLength - 1); + float even = (((closest[i] * e0) + (closest[previous] * e1)) + (adjacent[i] * e2)) + (adjacent[previous] * e3); + float odd = (((closest[i] * o0) + (closest[next] * o1)) + (adjacent[i] * o2)) + (adjacent[next] * o3); StoreChromaPair(ref destinationBase, i * 2, even, odd, destination.Length); } } - /// - /// Stores four even chroma lanes interleaved with their four odd lanes. - /// - /// The even luma-coordinate values. - /// The odd luma-coordinate values. - /// The first destination value. - private static void StoreInterleavedChroma(Vector128 even, Vector128 odd, ref float destination) - { - Vector128 lower = Vector128_.UnpackLow(even.AsInt32(), odd.AsInt32()).AsSingle(); - Vector128 upper = Vector128_.UnpackHigh(even.AsInt32(), odd.AsInt32()).AsSingle(); - Unsafe.As>(ref destination) = lower; - Unsafe.As>(ref Unsafe.Add(ref destination, Vector128.Count)) = upper; - } - /// /// Stores one reconstructed chroma pair without writing beyond an odd-width destination row. /// @@ -333,6 +383,20 @@ internal static class HeifSampleConversion } } + /// + /// Stores four even chroma lanes interleaved with their four odd lanes. + /// + /// The even luma-coordinate values. + /// The odd luma-coordinate values. + /// The first destination value. + private static void StoreInterleavedChroma(Vector128 even, Vector128 odd, ref float destination) + { + Vector128 lower = Vector128_.UnpackLow(even.AsInt32(), odd.AsInt32()).AsSingle(); + Vector128 upper = Vector128_.UnpackHigh(even.AsInt32(), odd.AsInt32()).AsSingle(); + Unsafe.As>(ref destination) = lower; + Unsafe.As>(ref Unsafe.Add(ref destination, Vector128.Count)) = upper; + } + /// /// Deinterleaves high-bit-depth RGB pixels into planar component rows. /// diff --git a/src/ImageSharp/Formats/Heif/Components/ColorConverters/HeifYuvToRgb8Converter.cs b/src/ImageSharp/Formats/Heif/Components/ColorConverters/HeifYuvToRgb8Converter.cs index b900f49f51..8751a517e1 100644 --- a/src/ImageSharp/Formats/Heif/Components/ColorConverters/HeifYuvToRgb8Converter.cs +++ b/src/ImageSharp/Formats/Heif/Components/ColorConverters/HeifYuvToRgb8Converter.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using System.Buffers; +using System.Numerics; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata.Profiles.Cicp; @@ -63,36 +64,42 @@ internal static partial class HeifYuvToRgb8Converter /// The codec adapter that exposes reconstructed component rows. /// The configuration used for allocation and pixel conversion. /// The reconstructed component-plane buffer. - /// The destination image frame. + /// The destination pixel region. /// The resolved H.273 conversion parameters. /// The horizontal luma-sample offset of the output window. /// The vertical luma-sample offset of the output window. + /// The extent of the source region before rotation and mirroring. + /// The rotation and mirroring applied within the destination region. public static void ConvertFixedPoint( Configuration configuration, TBuffer buffer, - ImageFrame image, + Buffer2DRegion destination, in HeifColorConversionParameters parameters, int sourceX, - int sourceY) + int sourceY, + Size sourceSize, + HeifPixelTransform transform) where TPixel : unmanaged, IPixel where TBuffer : struct, IHeifPlanarSampleBuffer { ConversionParameters conversionParameters = new(in parameters); - using IMemoryOwner componentOwner = configuration.MemoryAllocator.Allocate(image.Width * 3); + Matrix3x2 matrix = transform.GetMatrix(sourceSize); + Size step = new((int)matrix.M11, (int)matrix.M12); + using IMemoryOwner componentOwner = configuration.MemoryAllocator.Allocate(sourceSize.Width * 3); Span components = componentOwner.GetSpan(); - Span red = components[..image.Width]; - Span green = components.Slice(image.Width, image.Width); - Span blue = components.Slice(image.Width * 2, image.Width); + Span red = components[..sourceSize.Width]; + Span green = components.Slice(sourceSize.Width, sourceSize.Width); + Span blue = components.Slice(sourceSize.Width * 2, sourceSize.Width); // The value-type buffer closes the row-access contract at the call site. Constrained calls are therefore // devirtualized without boxing while keeping codec-specific buffer ownership outside the color pipeline. - for (int y = 0; y < image.Height; y++) + for (int y = 0; y < sourceSize.Height; y++) { int lumaY = sourceY + y; // The codec boundary validates 4:2:0 crop offsets in complete chroma-sample units. Each native chroma // sample therefore covers one 2x2 luma cell without an alignment branch in the SIMD loop. - ReadOnlySpan luma = buffer.GetLumaRowSpan(lumaY).Slice(sourceX, image.Width); + ReadOnlySpan luma = buffer.GetLumaRowSpan(lumaY).Slice(sourceX, sourceSize.Width); ReadOnlySpan chromaBlue = buffer.GetChromaBlueRowSpan(lumaY >> 1).Slice(sourceX >> 1); ReadOnlySpan chromaRed = buffer.GetChromaRedRowSpan(lumaY >> 1).Slice(sourceX >> 1); @@ -106,8 +113,23 @@ internal static partial class HeifYuvToRgb8Converter 1, in conversionParameters); - Span destination = image.PixelBuffer.DangerousGetRowSpan(y); - PixelOperations.Instance.PackFromRgbPlanes(red, green, blue, destination); + if (transform.IsIdentity) + { + Span output = destination.DangerousGetRowSpan(y); + PixelOperations.Instance.PackFromRgbPlanes(red, green, blue, output); + } + else + { + // Noncontiguous output cannot be passed to the planar SIMD packer. Matrix arithmetic above + // remains vectorized; each packed pixel is written once at its final transformed coordinate. + Point point = HeifPixelTransform.Transform(0, y, matrix); + + for (int x = 0; x < sourceSize.Width; x++) + { + destination.DangerousGetRowSpan(point.Y)[point.X] = TPixel.FromRgb24(new Rgb24(red[x], green[x], blue[x])); + point += step; + } + } } } } diff --git a/src/ImageSharp/Formats/Heif/GridHeifItemDecoder.cs b/src/ImageSharp/Formats/Heif/GridHeifItemDecoder.cs index 996893c1d6..066899d3d2 100644 --- a/src/ImageSharp/Formats/Heif/GridHeifItemDecoder.cs +++ b/src/ImageSharp/Formats/Heif/GridHeifItemDecoder.cs @@ -4,9 +4,13 @@ using System.Buffers; using System.Buffers.Binary; using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.Formats.Heif.Av1.Color; +using SixLabors.ImageSharp.Formats.Heif.Components; using SixLabors.ImageSharp.Formats.Heif.Components.Alpha; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Cicp; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Heif; @@ -15,7 +19,7 @@ namespace SixLabors.ImageSharp.Formats.Heif; /// Decodes the image items referenced by a HEIF grid derived-image item. /// /// The destination pixel type. -internal sealed class GridHeifItemDecoder : IHeifItemDecoder, IHeifAlphaItemDecoder +internal sealed class GridHeifItemDecoder : IHeifItemDecoder, IHeifAlphaItemDecoder where TPixel : unmanaged, IPixel { /// @@ -95,212 +99,128 @@ internal sealed class GridHeifItemDecoder : IHeifItemDecoder, IH /// public Heif4CharCode Type => Heif4CharCode.Grid; - /// - /// Gets the compression method used by the decoded grid tiles. - /// - public HeifCompressionMethod CompressionMethod { get; private set; } - - /// - /// Decodes the tiles referenced by a grid derived-image item. - /// - /// The general options governing the containing HEIF decode. - /// The grid derived-image item. - /// The grid descriptor payload. - /// The container color description inherited by tiles that do not declare one. - /// The token used to cancel between tile payloads. - /// The image reconstructed from the referenced grid tiles. - public Image DecodeItemData( + /// + public void DecodeItemData( DecoderOptions options, + HeifChromaUpsampling chromaUpsampling, HeifItem gridItem, Span data, CicpProfile? colorProfile, + IccProfile? profile, + Av1FrameBuffer? alphaFrame, + Size alphaOutputSize, + Rectangle alphaRectangle, + bool premultiplied, + Rectangle sourceRectangle, + HeifPixelTransform transform, + Buffer2DRegion destination, + ImageMetadata metadata, CancellationToken cancellationToken) { GridDescriptor descriptor = ParseGridDescriptor(data); IReadOnlyList linked = this.GetLinkedTileIds(gridItem, descriptor); - Heif4CharCode tileType = default; Av1CodecConfiguration? av1GridConfiguration = null; - Image result = this.CreateGridResult( - options, - descriptor, - linked[0], - colorProfile, - ref tileType, - ref av1GridConfiguration, - cancellationToken, - out int tileWidth, - out int tileHeight); - - try + Size tileSize = this.items[linked[0]].Extent; + ValidateGridCoverage(descriptor, tileSize.Width, tileSize.Height); + for (int tileIndex = 0; tileIndex < linked.Count; tileIndex++) { - for (int tileIndex = 1; tileIndex < linked.Count; tileIndex++) + cancellationToken.ThrowIfCancellationRequested(); + HeifItem item = this.items[linked[tileIndex]]; + ValidateTileConfiguration(item, ref tileType, ref av1GridConfiguration); + if (tileIndex == 0) { - cancellationToken.ThrowIfCancellationRequested(); - HeifItem item = this.items[linked[tileIndex]]; - using Image tile = this.DecodeGridTile( - options, - item, - colorProfile, - ref tileType, - ref av1GridConfiguration, - cancellationToken); - - Size copySize = GetGridTileCopySize( - descriptor, - tileWidth, - tileHeight, - tileIndex); - - if (!IsGridTileExtentValid(tile.Size, copySize, tileWidth, tileHeight)) - { - throw new InvalidImageContentException( - $"HEIF image grid tile {item.Id} has dimensions {tile.Size}, which cannot cover its {copySize} grid region."); - } + ValidateGridDimensions(descriptor, tileSize, av1GridConfiguration); + } - CopyGridTile(tile, result, descriptor, tileIndex, tileWidth, tileHeight); + Size copySize = GetGridTileCopySize(descriptor, tileSize.Width, tileSize.Height, tileIndex); + if (!IsGridTileExtentValid(item.Extent, copySize, tileSize.Width, tileSize.Height)) + { + throw new InvalidImageContentException( + $"HEIF image grid tile {item.Id} has dimensions {item.Extent}, which cannot cover its {copySize} grid region."); } - return result; - } - catch - { - result.Dispose(); - throw; - } - } + IHeifItemDecoder decoder = HeifCompressionFactory.GetDecoder(item.Type) + ?? throw new ImageFormatException($"The HEIF image grid uses unsupported tile type '{item.Type}'."); - /// - /// Decodes the first validated grid tile, establishes the common tile geometry, and creates the output canvas. - /// - private Image CreateGridResult( - DecoderOptions options, - in GridDescriptor descriptor, - uint firstTileId, - CicpProfile? colorProfile, - ref Heif4CharCode tileType, - ref Av1CodecConfiguration? av1GridConfiguration, - CancellationToken cancellationToken, - out int tileWidth, - out int tileHeight) - { - cancellationToken.ThrowIfCancellationRequested(); - HeifItem item = this.items[firstTileId]; - using Image tile = this.DecodeGridTile( - options, - item, - colorProfile, - ref tileType, - ref av1GridConfiguration, - cancellationToken); - - tileWidth = tile.Width; - tileHeight = tile.Height; - ValidateGridCoverage(descriptor, tileWidth, tileHeight); - ValidateGridDimensions(descriptor, tile.Size, av1GridConfiguration); - Size copySize = GetGridTileCopySize(descriptor, tileWidth, tileHeight, 0); - if (!IsGridTileExtentValid(tile.Size, copySize, tileWidth, tileHeight)) - { - throw new InvalidImageContentException( - $"HEIF image grid tile {item.Id} has dimensions {tile.Size}, which cannot cover its {copySize} grid region."); - } + int x = (tileIndex % descriptor.Columns) * tileSize.Width; + int y = (tileIndex / descriptor.Columns) * tileSize.Height; - Image result = new( - options.Configuration, - descriptor.OutputSize.Width, - descriptor.OutputSize.Height, - tile.Metadata.DeepClone()); + // Edge tiles retain their coded extent while conversion writes only the visible grid region. + // The region refers to the final frame, so no decoded tile image or pixel copy is needed. + Rectangle tileBounds = new(x, y, copySize.Width, copySize.Height); + Rectangle visibleSource = Rectangle.Intersect(tileBounds, sourceRectangle); + if (visibleSource.IsEmpty) + { + continue; + } - try - { - CopyGridTile(tile, result, descriptor, 0, tileWidth, tileHeight); - return result; - } - catch - { - result.Dispose(); - throw; - } - } + Rectangle relativeSource = new( + visibleSource.X - sourceRectangle.X, + visibleSource.Y - sourceRectangle.Y, + visibleSource.Width, + visibleSource.Height); - /// - /// Decodes and scales one grid tile while its encoded payload owner is active. - /// - private Image DecodeGridTile( - DecoderOptions options, - HeifItem item, - CicpProfile? colorProfile, - ref Heif4CharCode tileType, - ref Av1CodecConfiguration? av1GridConfiguration, - CancellationToken cancellationToken) - { - ValidateTileConfiguration(item, ref tileType, ref av1GridConfiguration); - IHeifItemDecoder? decoder = HeifCompressionFactory.GetDecoder(item.Type) - ?? throw new ImageFormatException($"The HEIF image grid uses unsupported tile type '{item.Type}'."); - - using IMemoryOwner itemMemory = this.itemDataReader(item); - this.CompressionMethod = decoder.CompressionMethod; - Image tile = decoder.DecodeItemData( - options, - item, - itemMemory.GetSpan(), - item.CicpProfile ?? colorProfile, - cancellationToken); + Rectangle outputBounds = transform.TransformRectangle(relativeSource, sourceRectangle.Size); + Buffer2DRegion tileDestination = destination.GetSubRegion( + outputBounds.X, outputBounds.Y, outputBounds.Width, outputBounds.Height); - try - { - HeifItemDecoderUtilities.ScaleToItemExtent(tile, item); - return tile; - } - catch - { - // Ownership transfers to the caller only after extent normalization succeeds. - tile.Dispose(); - throw; - } - } + Rectangle tileSource = new( + visibleSource.X - x, visibleSource.Y - y, visibleSource.Width, visibleSource.Height); - /// - /// Copies one decoded tile into its cropped row-major grid position. - /// - private static void CopyGridTile( - Image tile, - Image result, - in GridDescriptor descriptor, - int tileIndex, - int tileWidth, - int tileHeight) - { - int column = tileIndex % descriptor.Columns; - int row = tileIndex / descriptor.Columns; - int destinationX = column * tileWidth; - int destinationY = row * tileHeight; - int copyWidth = Math.Min(tileWidth, descriptor.OutputSize.Width - destinationX); - int copyHeight = Math.Min(tileHeight, descriptor.OutputSize.Height - destinationY); - ImageFrame source = tile.Frames.RootFrame; - ImageFrame destination = result.Frames.RootFrame; - - // Copy before disposing this decoded tile, keeping peak tile storage independent of grid cell count. - // The descriptor may crop only the rightmost column and bottom row. - for (int y = 0; y < copyHeight; y++) - { - Span destinationRow = destination.PixelBuffer - .DangerousGetRowSpan(destinationY + y) - .Slice(destinationX, copyWidth); + // A grid may associate alpha with each coded tile instead of the derived image. + // Resolve association at the tile boundary so its RGB is unassociated before ICC conversion. + bool tilePremultiplied = premultiplied; + if (alphaFrame is not null && !tilePremultiplied) + { + foreach (HeifItemLink link in this.itemLinks) + { + if (link.Type != Heif4CharCode.Prem || link.SourceId != item.Id) + { + continue; + } + + foreach (uint id in link.DestinationIds) + { + if (HeifConstants.IsAlphaAuxiliaryType(this.items[id].AuxiliaryType)) + { + tilePremultiplied = true; + break; + } + } + + if (tilePremultiplied) + { + break; + } + } + } - source.PixelBuffer.DangerousGetRowSpan(y)[..copyWidth].CopyTo(destinationRow); + using IMemoryOwner itemMemory = this.itemDataReader(item); + decoder.DecodeItemData( + options, + chromaUpsampling, + item, + itemMemory.GetSpan(), + item.CicpProfile ?? colorProfile, + profile, + alphaFrame, + alphaOutputSize, + new Rectangle(alphaRectangle.X + relativeSource.X, alphaRectangle.Y + relativeSource.Y, relativeSource.Width, relativeSource.Height), + tilePremultiplied, + tileSource, + transform, + tileDestination, + metadata, + cancellationToken); } } /// - public void DecodeAlphaItemData( + public Av1FrameBuffer DecodeAlphaItemData( DecoderOptions options, HeifItem gridItem, Span data, - ImageFrame destination, - Size outputSize, - Rectangle destinationRectangle, - bool premultiplied, CancellationToken cancellationToken) { GridDescriptor descriptor = ParseGridDescriptor(data); @@ -323,7 +243,7 @@ internal sealed class GridHeifItemDecoder : IHeifItemDecoder, IH uint id = linked[tileIndex]; HeifItem item = this.items[id]; ValidateTileConfiguration(item, ref tileType, ref av1GridConfiguration); - if (HeifCompressionFactory.GetDecoder(item.Type) is not IHeifAlphaItemDecoder) + if (HeifCompressionFactory.GetDecoder(item.Type) is not IHeifAlphaItemDecoder) { throw new ImageFormatException($"The HEIF alpha grid uses unsupported tile type '{item.Type}'."); } @@ -348,45 +268,101 @@ internal sealed class GridHeifItemDecoder : IHeifItemDecoder, IH ValidateGridDimensions(descriptor, tileSize, av1GridConfiguration); - int gridWidth = descriptor.OutputSize.Width; - int gridHeight = descriptor.OutputSize.Height; - if (descriptor.OutputSize != outputSize || destinationRectangle.Size != outputSize) + // The grid needs one assembled native alpha plane because its tile boundaries need not + // coincide with the color grid. Individual decoded tiles are released after composition. + Av1FrameBuffer result; + using (IMemoryOwner firstMemory = this.itemDataReader(firstItem)) { - throw new InvalidImageContentException("The HEIF alpha grid dimensions do not match the color grid dimensions."); + Av1HeifItemDecoder firstDecoder = new(); + using Av1FrameBuffer firstFrame = firstDecoder.DecodeAlphaItemData( + options, firstItem, firstMemory.GetSpan(), cancellationToken); + + result = Av1FrameBuffer.CreateAuxiliary(options.Configuration, firstFrame.ColorConfig, descriptor.OutputSize); + try + { + CopyAlphaTile(options.Configuration, firstFrame, result, firstItem.Extent, descriptor, tileSize, 0); + } + catch + { + result.Dispose(); + throw; + } } - for (int tileIndex = 0; tileIndex < linked.Count; tileIndex++) + try { - cancellationToken.ThrowIfCancellationRequested(); - HeifItem item = this.items[linked[tileIndex]]; - IHeifItemDecoder? itemDecoder = HeifCompressionFactory.GetDecoder(item.Type); - if (itemDecoder is not IHeifAlphaItemDecoder decoder) + for (int tileIndex = 1; tileIndex < linked.Count; tileIndex++) { - throw new InvalidImageContentException($"HEIF alpha grid tile {item.Id} uses unsupported coding format '{item.Type}'."); + cancellationToken.ThrowIfCancellationRequested(); + HeifItem item = this.items[linked[tileIndex]]; + Av1HeifItemDecoder decoder = new(); + using IMemoryOwner itemMemory = this.itemDataReader(item); + using Av1FrameBuffer frame = decoder.DecodeAlphaItemData( + options, item, itemMemory.GetSpan(), cancellationToken); + + CopyAlphaTile(options.Configuration, frame, result, item.Extent, descriptor, tileSize, tileIndex); } - using IMemoryOwner itemMemory = this.itemDataReader(item); - int column = tileIndex % descriptor.Columns; - int row = tileIndex / descriptor.Columns; - int destinationX = destinationRectangle.X + (column * tileSize.Width); - int destinationY = destinationRectangle.Y + (row * tileSize.Height); - Size copySize = GetGridTileCopySize( - descriptor, - tileSize.Width, - tileSize.Height, - tileIndex); + return result; + } + catch + { + result.Dispose(); + throw; + } + } - Rectangle tileDestination = new(destinationX, destinationY, copySize.Width, copySize.Height); + /// + /// Writes one decoded auxiliary tile into the native grid plane. + /// + /// The configuration providing row scratch storage. + /// The decoded tile samples. + /// The native grid plane. + /// The tile's presentation extent. + /// The validated grid layout. + /// The nominal grid-cell size. + /// The tile's row-major index. + private static void CopyAlphaTile( + Configuration configuration, + Av1FrameBuffer source, + Av1FrameBuffer destination, + Size extent, + GridDescriptor descriptor, + Size tileSize, + int tileIndex) + { + Size copySize = GetGridTileCopySize(descriptor, tileSize.Width, tileSize.Height, tileIndex); + int left = (tileIndex % descriptor.Columns) * tileSize.Width; + int top = (tileIndex / descriptor.Columns) * tileSize.Height; + using HeifAlphaRowSource rows = Av1YuvConverter.CreateAlphaRowSource( + configuration, source, extent, new Rectangle(Point.Empty, copySize)); - decoder.DecodeAlphaItemData( - options, - item, - itemMemory.GetSpan(), - destination, - item.Extent, - tileDestination, - premultiplied, - cancellationToken); + HeifColorConversionParameters parameters = Av1YuvConverter.GetConversionParameters(destination.ColorConfig, destination.ColorConfig.ColorRange, out _); + if (destination.BitDepth == Av1BitDepth.EightBit) + { + Av1PlanarSampleBuffer buffer = new(destination); + for (int y = 0; y < copySize.Height; y++) + { + HeifSampleConversion.WriteSamples( + rows.ReadRow(y), + buffer.GetLumaRowSpan(top + y).Slice(left, copySize.Width), + parameters.LumaScale, + parameters.LumaBias, + parameters.LumaSampleMaximum); + } + + return; + } + + Av1PlanarSampleBuffer highBitDepthBuffer = new(destination); + for (int y = 0; y < copySize.Height; y++) + { + HeifSampleConversion.WriteSamples( + rows.ReadRow(y), + highBitDepthBuffer.GetLumaRowSpan(top + y).Slice(left, copySize.Width), + parameters.LumaScale, + parameters.LumaBias, + parameters.LumaSampleMaximum); } } diff --git a/src/ImageSharp/Formats/Heif/Heif4CharCode.cs b/src/ImageSharp/Formats/Heif/Heif4CharCode.cs index 8fc0fa93d0..13f2ddacd1 100644 --- a/src/ImageSharp/Formats/Heif/Heif4CharCode.cs +++ b/src/ImageSharp/Formats/Heif/Heif4CharCode.cs @@ -353,16 +353,6 @@ public enum Heif4CharCode : uint /// Iso8 = 0x69736F38U, - /// - /// Legacy JPEG coded tile. - /// - Jpeg = 0x6A706567U, - - /// - /// JPEG image sequence brand. - /// - Jpgs = 0x6A706773U, - /// /// AOMedia Video Coding tile. /// diff --git a/src/ImageSharp/Formats/Heif/Heif4CharCode.tt b/src/ImageSharp/Formats/Heif/Heif4CharCode.tt index a983a8ad5c..e5abaea9d0 100644 --- a/src/ImageSharp/Formats/Heif/Heif4CharCode.tt +++ b/src/ImageSharp/Formats/Heif/Heif4CharCode.tt @@ -74,8 +74,6 @@ "avio", "AVIF intra-only image sequence brand", "msf1", "HEIF image sequence structural brand", "iso8", "ISO base media version 8 structural brand", - "jpeg", "Legacy JPEG coded tile", - "jpgs", "JPEG image sequence brand", "av01", "AOMedia Video Coding tile", "dinf", "Data Information", "grpl", "Group list", diff --git a/src/ImageSharp/Formats/Heif/HeifChromaUpsampling.cs b/src/ImageSharp/Formats/Heif/HeifChromaUpsampling.cs new file mode 100644 index 0000000000..db3ab609c9 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/HeifChromaUpsampling.cs @@ -0,0 +1,25 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif; + +/// +/// Specifies how subsampled chroma is expanded when decoding HEIF images. +/// +public enum HeifChromaUpsampling +{ + /// + /// Uses nearest-neighbour sampling for 8-bit source samples and bilinear interpolation for higher bit depths. + /// + Auto, + + /// + /// Repeats the nearest chroma sample. + /// + NearestNeighbor, + + /// + /// Interpolates between neighboring chroma samples. + /// + Bilinear +} diff --git a/src/ImageSharp/Formats/Heif/HeifCompressionFactory.cs b/src/ImageSharp/Formats/Heif/HeifCompressionFactory.cs index df00957036..aa1ec3092a 100644 --- a/src/ImageSharp/Formats/Heif/HeifCompressionFactory.cs +++ b/src/ImageSharp/Formats/Heif/HeifCompressionFactory.cs @@ -19,7 +19,6 @@ internal static class HeifCompressionFactory public static IHeifItemDecoder? GetDecoder(Heif4CharCode type) where TPixel : unmanaged, IPixel => type switch { - Heif4CharCode.Jpeg => new JpegHeifItemDecoder(), Heif4CharCode.Av01 => new Av1HeifItemDecoder(), _ => null }; diff --git a/src/ImageSharp/Formats/Heif/HeifCompressionMethod.cs b/src/ImageSharp/Formats/Heif/HeifCompressionMethod.cs deleted file mode 100644 index 2accbeec3c..0000000000 --- a/src/ImageSharp/Formats/Heif/HeifCompressionMethod.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Heif; - -/// -/// Identifies the compression method used by a coded image item in a HEIF file. -/// -public enum HeifCompressionMethod -{ - /// - /// Legacy JPEG coding. - /// - LegacyJpeg, - - /// - /// AOMedia Video 1 (AV1) coding. - /// - Av1, -} diff --git a/src/ImageSharp/Formats/Heif/HeifConstants.cs b/src/ImageSharp/Formats/Heif/HeifConstants.cs index ed184edcc1..bf2f5bcec9 100644 --- a/src/ImageSharp/Formats/Heif/HeifConstants.cs +++ b/src/ImageSharp/Formats/Heif/HeifConstants.cs @@ -45,12 +45,6 @@ internal static class HeifConstants } Heif4CharCode majorBrand = (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(boxContent); - if (IsUnsupportedSequenceBrand(majorBrand)) - { - // A sequence major brand controls the presentation even if a still-image compatible brand is present. - return false; - } - if (IsSupportedSequenceBrand(majorBrand)) { fileType = HeifFileType.ImageSequence; @@ -99,8 +93,7 @@ internal static class HeifConstants /// when the brand identifies a supported still-image container. private static bool IsSupportedStillImageBrand(Heif4CharCode brand) => brand is Heif4CharCode.Mif1 - or Heif4CharCode.Avif - or Heif4CharCode.Jpeg; + or Heif4CharCode.Avif; /// /// Determines whether identifies a supported timed image sequence. @@ -109,12 +102,4 @@ internal static class HeifConstants /// when the brand identifies a supported timed image sequence. private static bool IsSupportedSequenceBrand(Heif4CharCode brand) => brand is Heif4CharCode.Avis; - - /// - /// Determines whether requires an image-sequence profile outside the implemented scope. - /// - /// The registered file-type brand. - /// when the major brand requires unsupported JPEG sequence support. - private static bool IsUnsupportedSequenceBrand(Heif4CharCode brand) - => brand is Heif4CharCode.Jpgs; } diff --git a/src/ImageSharp/Formats/Heif/HeifDecoder.cs b/src/ImageSharp/Formats/Heif/HeifDecoder.cs index 8f7e7a2c3f..9710c0c085 100644 --- a/src/ImageSharp/Formats/Heif/HeifDecoder.cs +++ b/src/ImageSharp/Formats/Heif/HeifDecoder.cs @@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Heif; /// /// Image decoder for reading HEIF images from a stream. /// -public sealed class HeifDecoder : ImageDecoder +public sealed class HeifDecoder : SpecializedImageDecoder { /// /// Initializes a new instance of the class. @@ -28,23 +28,27 @@ public sealed class HeifDecoder : ImageDecoder Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); - return new HeifDecoderCore(options).Identify(options.Configuration, stream, cancellationToken); + return new HeifDecoderCore(new HeifDecoderOptions { GeneralOptions = options }).Identify(options.Configuration, stream, cancellationToken); } /// - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + protected override Image Decode(HeifDecoderOptions options, Stream stream, CancellationToken cancellationToken) { Guard.NotNull(options, nameof(options)); Guard.NotNull(stream, nameof(stream)); HeifDecoderCore decoder = new(options); - Image image = decoder.Decode(options.Configuration, stream, cancellationToken); - ScaleToTargetSize(options, image); + Image image = decoder.Decode(options.GeneralOptions.Configuration, stream, cancellationToken); + ScaleToTargetSize(options.GeneralOptions, image); return image; } /// - protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) + protected override Image Decode(HeifDecoderOptions options, Stream stream, CancellationToken cancellationToken) => this.Decode(options, stream, cancellationToken); + + /// + protected override HeifDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options) + => new() { GeneralOptions = options }; } diff --git a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs index 80f622af43..895f16861e 100644 --- a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs @@ -4,7 +4,6 @@ using System.Buffers; using System.Buffers.Binary; using System.Text; -using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Components.Alpha; using SixLabors.ImageSharp.IO; @@ -15,8 +14,6 @@ using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Xmp; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Processing.Processors.Transforms; namespace SixLabors.ImageSharp.Formats.Heif; @@ -40,6 +37,11 @@ internal sealed class HeifDecoderCore : ImageDecoderCore /// private readonly Configuration configuration; + /// + /// The chroma reconstruction mode selected for this decode. + /// + private readonly HeifChromaUpsampling chromaUpsampling; + /// /// The general options passed to nested coded-image decoders without presentation-level target scaling. /// @@ -98,10 +100,12 @@ internal sealed class HeifDecoderCore : ImageDecoderCore /// /// Initializes a new instance of the class. /// - /// The decoder options. - public HeifDecoderCore(DecoderOptions options) - : base(options) + /// The decoder options. + public HeifDecoderCore(HeifDecoderOptions decoderOptions) + : base(decoderOptions.GeneralOptions) { + DecoderOptions options = decoderOptions.GeneralOptions; + this.chromaUpsampling = decoderOptions.ChromaUpsampling; this.configuration = options.Configuration; // HEIF owns final presentation resizing and ICC conversion after item/grid composition and container-profile @@ -225,11 +229,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore } } - HeifItem? item = this.FindItemById(this.primaryItem); - if (item is null) - { - throw new ImageFormatException("No primary item found"); - } + HeifItem? item = this.FindItemById(this.primaryItem) ?? throw new ImageFormatException("No primary item found"); this.UpdateMetadata(this.metadata, item); @@ -316,10 +316,6 @@ internal sealed class HeifDecoderCore : ImageDecoderCore if (!animateRootFrame) { this.Dimensions = GetPresentationExtent(primaryItem); - if (this.Dimensions != sequenceExtent) - { - throw new InvalidImageContentException("The primary image and image sequence have different presentation dimensions."); - } } this.UpdateMetadata(this.metadata, primaryItem); @@ -356,18 +352,8 @@ internal sealed class HeifDecoderCore : ImageDecoderCore ? colorTrack.CleanAperture.Value.ToRectangle(codedSize) : new Rectangle(Point.Empty, codedSize); - // HEIF stores counter-clockwise quarter turns; ImageSharp's exact modes are clockwise. - RotateMode rotation = colorTrack.RotationAngle switch - { - 1 => RotateMode.Rotate270, - 2 => RotateMode.Rotate180, - 3 => RotateMode.Rotate90, - _ => RotateMode.None - }; - - Size presentationSize = rotation is RotateMode.Rotate90 or RotateMode.Rotate270 - ? new Size(sourceRectangle.Height, sourceRectangle.Width) - : sourceRectangle.Size; + HeifPixelTransform transform = new(colorTrack.RotationAngle ?? 0, colorTrack.MirrorAxis); + Size presentationSize = transform.GetDestinationSize(sourceRectangle.Size); // The returned image owns every presented frame from the outset. A separate primary becomes its root; // otherwise the first successfully decoded timed sample fills the root allocated here. @@ -381,11 +367,15 @@ internal sealed class HeifDecoderCore : ImageDecoderCore try { - if (image.Size != presentationSize) - { - throw new InvalidImageContentException( - "The primary image and image sequence have different presentation dimensions."); - } + Rectangle outputBounds = new( + 0, 0, Math.Min(presentationSize.Width, image.Width), Math.Min(presentationSize.Height, image.Height)); + + Rectangle visibleSource = transform.GetSourceRectangle(outputBounds, sourceRectangle.Size); + sourceRectangle = new Rectangle( + sourceRectangle.X + visibleSource.X, + sourceRectangle.Y + visibleSource.Y, + visibleSource.Width, + visibleSource.Height); using Av1Decoder colorDecoder = new(this.configuration); @@ -396,15 +386,6 @@ internal sealed class HeifDecoderCore : ImageDecoderCore using Av1Decoder? alphaDecoder = alphaState?.Decoder; - // Quarter turns need source and destination frames with opposite dimensions. Reuse one source frame - // across the sequence, then rotate each completed color-and-alpha sample into its final owned frame. - using ImageFrame? rotationSource = rotation == RotateMode.None - ? null - : new ImageFrame( - this.configuration, - sourceRectangle.Width, - sourceRectangle.Height); - int decodedFrameCount = 0; for (int sampleIndex = 0; sampleIndex < colorTrack.Samples.Length; sampleIndex++) { @@ -432,11 +413,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore bool appendedDestination = false; ImageFrame decodedFrame; - if (rotationSource is not null) - { - decodedFrame = rotationSource; - } - else if (animateRootFrame && decodedFrameCount == 0) + if (animateRootFrame && decodedFrameCount == 0) { decodedFrame = image.Frames.RootFrame; } @@ -446,43 +423,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore appendedDestination = true; } - bool colorDecoded = false; - this.ExecuteImageDataSegmentAction( - () => - { - this.DecodeSequenceFrame( - stream, - colorTrack, - colorSample, - colorDecoder, - sourceRectangle, - decodedFrame); - - colorDecoded = true; - }); - - if (!colorDecoded) - { - if (alphaState is not null) - { - (HeifSequenceTrack Track, Av1Decoder Decoder) currentAlphaState = alphaState.Value; - HeifSequenceSample alphaSample = currentAlphaState.Track.Samples[sampleIndex]; - this.ExecuteImageDataSegmentAction( - () => this.DecodeSequenceReference( - stream, - currentAlphaState.Track, - alphaSample, - currentAlphaState.Decoder)); - } - - if (appendedDestination) - { - image.Frames.RemoveFrame(image.Frames.Count - 1); - } - - continue; - } - + Av1FrameBuffer? alphaFrame = null; bool alphaDecoded = true; if (alphaState is not null) { @@ -492,14 +433,8 @@ internal sealed class HeifDecoderCore : ImageDecoderCore this.ExecuteImageDataSegmentAction( () => { - this.DecodeSequenceAlphaFrame( - stream, - currentAlphaState.Track, - alphaSample, - currentAlphaState.Decoder, - sourceRectangle, - decodedFrame, - colorTrack.IsPremultiplied); + alphaFrame = this.DecodeSequenceAlphaFrame( + stream, currentAlphaState.Track, alphaSample, currentAlphaState.Decoder); alphaDecoded = true; }); @@ -507,6 +442,9 @@ internal sealed class HeifDecoderCore : ImageDecoderCore if (!alphaDecoded) { + this.ExecuteImageDataSegmentAction( + () => this.DecodeSequenceReference(stream, colorTrack, colorSample, colorDecoder)); + if (appendedDestination) { image.Frames.RemoveFrame(image.Frames.Count - 1); @@ -515,29 +453,40 @@ internal sealed class HeifDecoderCore : ImageDecoderCore continue; } - ImageFrame presentedFrame = decodedFrame; - if (rotationSource is not null) - { - presentedFrame = animateRootFrame && decodedFrameCount == 0 - ? image.Frames.RootFrame - : image.Frames.CreateFrame(); + bool colorDecoded = false; + this.ExecuteImageDataSegmentAction( + () => + { + CicpProfile? outputProfile = this.DecodeSequenceFrame( + stream, + colorTrack, + colorSample, + colorDecoder, + sourceRectangle, + decodedFrame.PixelBuffer.GetRegion(outputBounds), + transform, + alphaFrame, + alphaFrame is not null && colorTrack.IsPremultiplied); - RotateProcessor.ApplyQuarterTurn( - rotation, - rotationSource, - presentedFrame, - this.configuration); + if (!this.Options.SkipMetadata) + { + decodedFrame.Metadata.CicpProfile = outputProfile; + } - presentedFrame.Metadata.CicpProfile = rotationSource.Metadata.CicpProfile; - } + colorDecoded = true; + }); - if (colorTrack.MirrorAxis is not null) + if (!colorDecoded) { - // Axis zero reflects top-to-bottom around the horizontal axis; axis one reflects left-to-right. - FlipMode flip = colorTrack.MirrorAxis.Value == 0 ? FlipMode.Vertical : FlipMode.Horizontal; - FlipProcessor.Apply(flip, presentedFrame, this.configuration); + if (appendedDestination) + { + image.Frames.RemoveFrame(image.Frames.Count - 1); + } + + continue; } + ImageFrame presentedFrame = decodedFrame; presentedFrame.Metadata.GetHeifMetadata().FrameDelay = new Rational( colorSample.Duration, colorTrack.MediaTimescale); @@ -545,7 +494,6 @@ internal sealed class HeifDecoderCore : ImageDecoderCore if (!animateRootFrame && !this.Options.SkipMetadata) { presentedFrame.Metadata.IccProfile = colorTrack.IccProfile; - _ = this.TryConvertIccProfile(presentedFrame); } decodedFrameCount++; @@ -559,18 +507,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore if (!this.Options.SkipMetadata) { - if (animateRootFrame) - { - image.Metadata.CicpProfile ??= image.Frames.RootFrame.Metadata.CicpProfile?.DeepClone(); - _ = this.TryConvertIccProfile(image); - } - } - else - { - foreach (ImageFrame frame in image.Frames) - { - frame.Metadata.CicpProfile = null; - } + image.Metadata.CicpProfile = image.Frames.RootFrame.Metadata.CicpProfile; } HeifMetadata resultMetadata = image.Metadata.GetHeifMetadata(); @@ -597,13 +534,20 @@ internal sealed class HeifDecoderCore : ImageDecoderCore /// The decoder retaining earlier sequence references. /// The clean-aperture region mapped to the destination frame. /// The caller-owned frame receiving the presented sample. - private void DecodeSequenceFrame( + /// The rotation and mirroring within the destination region. + /// The decoder-owned auxiliary frame, or null for opaque pixels. + /// Whether source RGB is associated with alpha. + /// The output color description, or null when metadata is skipped. + private CicpProfile? DecodeSequenceFrame( BufferedReadStream stream, HeifSequenceTrack track, HeifSequenceSample sample, Av1Decoder decoder, Rectangle sourceRectangle, - ImageFrame destination) + Buffer2DRegion destination, + HeifPixelTransform transform, + Av1FrameBuffer? alphaFrame, + bool premultiplied) where TPixel : unmanaged, IPixel { if (track.CodecType != Heif4CharCode.Av01) @@ -617,35 +561,54 @@ internal sealed class HeifDecoderCore : ImageDecoderCore using IMemoryOwner sampleOwner = this.ReadSequenceSample(stream, track, sample); Span sampleData = sampleOwner.GetSpan()[..sample.Length]; - decoder.DecodeSequenceFrame( + IccProfile? profile = null; + if (!this.Options.SkipMetadata && this.Options.ColorProfileHandling == ColorProfileHandling.Convert) + { + this.Options.TryGetIccProfileForColorConversion(track.IccProfile, out profile); + } + + decoder.ChromaUpsampling = this.chromaUpsampling; + CicpProfile sourceProfile = decoder.DecodeSequenceFrame( sampleData, track.CicpProfile, codecConfiguration, new Size(track.CodedWidth, track.CodedHeight), sourceRectangle, - destination); + destination, + transform, + profile, + alphaFrame, + premultiplied); + + if (this.Options.SkipMetadata) + { + return null; + } + + // Sequence output has the same packed RGB description as a still image. ICC conversion + // changes primaries and transfer to sRGB; otherwise those source characteristics remain. + return profile is null + ? new CicpProfile( + (byte)sourceProfile.ColorPrimaries, + (byte)sourceProfile.TransferCharacteristics, + (byte)CicpMatrixCoefficients.Identity, + true) + : new CicpProfile(1, 13, 0, true); } /// - /// Decodes one AV1 auxiliary sample and composes its native luma plane directly into a color frame. + /// Decodes one AV1 auxiliary sample and returns its native plane. /// - /// The destination color pixel type. /// The complete seekable HEIF stream. /// The alpha track supplying the codec configuration and color description. /// The validated alpha sample range. /// The decoder retaining earlier alpha-sequence references. - /// The clean-aperture region mapped to the destination frame. - /// The decoded color frame receiving alpha values. - /// Whether stored color samples must be converted to unassociated alpha. - private void DecodeSequenceAlphaFrame( + /// The auxiliary plane retained by the sequence decoder until the next sample is decoded. + private Av1FrameBuffer DecodeSequenceAlphaFrame( BufferedReadStream stream, HeifSequenceTrack track, HeifSequenceSample sample, - Av1Decoder decoder, - Rectangle sourceRectangle, - ImageFrame destination, - bool premultiplied) - where TPixel : unmanaged, IPixel + Av1Decoder decoder) { if (track.CodecType != Heif4CharCode.Av01) { @@ -662,16 +625,11 @@ internal sealed class HeifDecoderCore : ImageDecoderCore using IMemoryOwner sampleOwner = this.ReadSequenceSample(stream, track, sample); Span sampleData = sampleOwner.GetSpan()[..sample.Length]; - decoder.DecodeSequenceAlpha( + return decoder.DecodeSequenceAlpha( sampleData, track.CicpProfile, codecConfiguration, - new Size(track.CodedWidth, track.CodedHeight), - sourceRectangle, - destination, - destination.Size, - destination.Bounds, - premultiplied); + new Size(track.CodedWidth, track.CodedHeight)); } /// @@ -760,7 +718,6 @@ internal sealed class HeifDecoderCore : ImageDecoderCore if (animateRootFrame) { - heifMetadata.CompressionMethod = HeifCompressionMethod.Av1; heifMetadata.BitDepth = av1Configuration.BitDepth; heifMetadata.IsMonochrome = av1Configuration.IsMonochrome; } @@ -865,22 +822,19 @@ internal sealed class HeifDecoderCore : ImageDecoderCore } /// - /// Updates identification metadata from the primary item or its decodable thumbnail fallback. + /// Updates identification metadata from the primary item. /// /// The destination image metadata. /// The primary item whose visible representation is being identified. private void UpdateMetadata(ImageMetadata metadata, HeifItem item) { - HeifItem presentationItem = item; HeifItem metadataItem = item; if (item.Type == Heif4CharCode.Grid) { // A grid is a derived image rather than a compression method. Its dimg references identify the coded // tile items whose decoder determines the compression reported for the primary presentation. HeifItem? gridTile = this.FindDecodableGridTile(item); - HeifItem? thumbnail = gridTile is null ? this.FindDecodableThumbnail(item) : null; - metadataItem = gridTile ?? thumbnail ?? item; - presentationItem = thumbnail ?? item; + metadataItem = gridTile ?? item; if (gridTile is not null) { Av1CodecConfiguration? gridConfiguration = gridTile.Type == Heif4CharCode.Av01 @@ -919,42 +873,29 @@ internal sealed class HeifDecoderCore : ImageDecoderCore } } } - else if (HeifCompressionFactory.GetDecoder(item.Type) is null) - { - HeifItem? thumbnail = this.FindDecodableThumbnail(item); - metadataItem = thumbnail ?? item; - presentationItem = thumbnail ?? item; - } HeifMetadata meta = metadata.GetHeifMetadata(); - HeifCompressionMethod compressionMethod; if (metadataItem.Type == Heif4CharCode.Av01) { Av1CodecConfiguration codecConfiguration = metadataItem.Av1CodecConfiguration ?? throw new InvalidImageContentException($"AV1 image item {metadataItem.Id} has no codec configuration property."); - compressionMethod = HeifCompressionMethod.Av1; meta.BitDepth = codecConfiguration.BitDepth; meta.IsMonochrome = codecConfiguration.IsMonochrome; } - else if (metadataItem.Type == Heif4CharCode.Jpeg) - { - compressionMethod = HeifCompressionMethod.LegacyJpeg; - } else { throw new InvalidImageContentException($"Image item {metadataItem.Id} uses unsupported item type '{metadataItem.Type}'."); } - meta.CompressionMethod = compressionMethod; - meta.HasAlpha = this.FindAlphaItem(presentationItem) is not null - || (presentationItem.Type == Heif4CharCode.Grid && this.FindGridAlphaTiles(presentationItem) is not null); + meta.HasAlpha = this.FindAlphaItem(item) is not null + || (item.Type == Heif4CharCode.Grid && this.FindGridAlphaTiles(item) is not null); if (!this.Options.SkipMetadata) { - this.ApplyItemColorMetadata(metadata, presentationItem); - this.ApplyItemHdrMetadata(metadata, presentationItem); - this.ApplyItemPixelAspectRatioMetadata(metadata, presentationItem); + this.ApplyItemColorMetadata(metadata, item); + this.ApplyItemHdrMetadata(metadata, item); + this.ApplyItemPixelAspectRatioMetadata(metadata, item); } } @@ -1172,8 +1113,11 @@ internal sealed class HeifDecoderCore : ImageDecoderCore Heif4CharCode itemType = (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(entryBuffer[bytesRead..]); bytesRead += 4; - item = new HeifItem(itemType, itemId); - item.Name = ReadNullTerminatedString(entryBuffer[bytesRead..], out int nameLength); + item = new HeifItem(itemType, itemId) + { + Name = ReadNullTerminatedString(entryBuffer[bytesRead..], out int nameLength) + }; + bytesRead += nameLength; if (item.Type == Heif4CharCode.Mime) { @@ -1289,15 +1233,15 @@ internal sealed class HeifDecoderCore : ImageDecoderCore this.itemLinks.Add(link); } - catch (Exception ex) when (linkType == Heif4CharCode.Cdsc && HeifDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex)) + catch (Exception ex) when (linkType == Heif4CharCode.Cdsc && ShouldIgnoreAncillarySegmentError(this.Options, ex)) { // A malformed descriptive link cannot change reconstructed pixels, so non-strict modes omit it. bytesRead = referenceEnd; } - catch (Exception ex) when (linkType != Heif4CharCode.Cdsc && HeifDecoderCore.ShouldIgnoreImageDataSegmentError(this.Options, ex)) + catch (Exception ex) when (linkType != Heif4CharCode.Cdsc && ShouldIgnoreImageDataSegmentError(this.Options, ex)) { // IgnoreImageData permits a malformed optional image relationship to be omitted while retaining - // independently reconstructable items and thumbnail fallbacks. + // independently reconstructable items. bytesRead = referenceEnd; } } @@ -1337,7 +1281,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore { // Property types may repeat, and ipma can physically precede ipco. Index the bounded // children first so associations are always resolved after the ordered property table. - List> properties = new(); + List> properties = []; long endBoxPosition = stream.Position + boxLength; (long Offset, long Length)? propertyContainer = null; List<(long Offset, long Length)> associations = []; @@ -1369,10 +1313,10 @@ internal sealed class HeifDecoderCore : ImageDecoderCore stream.Position = propertyContainer.Value.Offset; this.ParsePropertyContainer(stream, propertyContainer.Value.Length, properties); - foreach ((long Offset, long Length) association in associations) + foreach ((long offset, long length) in associations) { - stream.Position = association.Offset; - this.ParsePropertyAssociation(stream, association.Length, properties); + stream.Position = offset; + this.ParsePropertyAssociation(stream, length, properties); } stream.Position = endBoxPosition; @@ -1440,7 +1384,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore { iccProfile = HeifPropertyParser.ParseIccProfile(profileData); } - catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex)) + catch (Exception ex) when (ShouldIgnoreAncillarySegmentError(this.Options, ex)) { // Keep the understood property index without retaining invalid ancillary metadata. } @@ -1482,7 +1426,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore { pixelAspectRatio = HeifPropertyParser.ParsePixelAspectRatio(boxBuffer); } - catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex)) + catch (Exception ex) when (ShouldIgnoreAncillarySegmentError(this.Options, ex)) { // Keep the understood property index without retaining invalid ancillary metadata. } @@ -1555,7 +1499,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore { contentLightLevel = HeifPropertyParser.ParseContentLightLevel(boxBuffer); } - catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex)) + catch (Exception ex) when (ShouldIgnoreAncillarySegmentError(this.Options, ex)) { // Keep the understood property index without retaining invalid ancillary metadata. } @@ -1568,7 +1512,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore { masteringDisplayColorVolume = HeifPropertyParser.ParseMasteringDisplayColorVolume(boxBuffer); } - catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex)) + catch (Exception ex) when (ShouldIgnoreAncillarySegmentError(this.Options, ex)) { // Keep the understood property index without retaining invalid ancillary metadata. } @@ -1581,7 +1525,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore { contentColorVolume = HeifPropertyParser.ParseContentColorVolume(boxBuffer); } - catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex)) + catch (Exception ex) when (ShouldIgnoreAncillarySegmentError(this.Options, ex)) { // Keep the understood property index without retaining invalid ancillary metadata. } @@ -1594,7 +1538,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore { ambientViewingEnvironment = HeifPropertyParser.ParseAmbientViewingEnvironment(boxBuffer); } - catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex)) + catch (Exception ex) when (ShouldIgnoreAncillarySegmentError(this.Options, ex)) { // Keep the understood property index without retaining invalid ancillary metadata. } @@ -1607,7 +1551,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore { referenceViewingEnvironment = HeifPropertyParser.ParseReferenceViewingEnvironment(boxBuffer); } - catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex)) + catch (Exception ex) when (ShouldIgnoreAncillarySegmentError(this.Options, ex)) { // Keep the understood property index without retaining invalid ancillary metadata. } @@ -1620,7 +1564,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore { nominalDiffuseWhite = HeifPropertyParser.ParseNominalDiffuseWhite(boxBuffer); } - catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreAncillarySegmentError(this.Options, ex)) + catch (Exception ex) when (ShouldIgnoreAncillarySegmentError(this.Options, ex)) { // Keep the understood property index without retaining invalid ancillary metadata. } @@ -1683,7 +1627,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore break; } } - catch (Exception ex) when (HeifDecoderCore.ShouldIgnoreImageDataSegmentError(this.Options, ex)) + catch (Exception ex) when (ShouldIgnoreImageDataSegmentError(this.Options, ex)) { // Invalid image properties retain their physical association index. Typed association handling ignores // the placeholder so another decodable item or the coded-image defaults can remain usable. @@ -1700,6 +1644,13 @@ internal sealed class HeifDecoderCore : ImageDecoderCore /// The properties in the order used by association indices. private void ParsePropertyAssociation(BufferedReadStream stream, long boxLength, List> properties) { + // Each association stores the essential flag in its highest bit and the one-based property + // index in the remaining bits: seven index bits in the byte form, fifteen in the ushort form. + const uint smallPropertyIndexMask = 0x7FU; + const uint smallEssentialMask = 0x80U; + const uint largePropertyIndexMask = 0x7FFFU; + const uint largeEssentialMask = 0x8000U; + using IMemoryOwner boxMemory = this.boxReader.ReadPayload(stream, boxLength); Span boxBuffer = boxMemory.GetSpan(); EnsureBufferRemaining(boxBuffer, 0, 8, "item property association"); @@ -1710,39 +1661,32 @@ internal sealed class HeifDecoderCore : ImageDecoderCore } bool largePropertyIndex = (boxBuffer[3] & 1) != 0; + uint propertyIndexMask = largePropertyIndex ? largePropertyIndexMask : smallPropertyIndexMask; + uint essentialMask = largePropertyIndex ? largeEssentialMask : smallEssentialMask; int bytesRead = 4; uint entryCount = BinaryPrimitives.ReadUInt32BigEndian(boxBuffer[bytesRead..]); bytesRead += 4; for (uint entryIndex = 0; entryIndex < entryCount; entryIndex++) { uint itemId = ReadUInt16Or32(boxBuffer, version == 1, ref bytesRead); - HeifItem? item = this.FindItemById(itemId); - if (item is null) - { - throw new InvalidImageContentException($"Item property association references unknown item ID {itemId}."); - } + HeifItem? item = this.FindItemById(itemId) ?? throw new InvalidImageContentException($"Item property association references unknown item ID {itemId}."); EnsureBufferRemaining(boxBuffer, bytesRead, 1, "item property association"); int associationCount = boxBuffer[bytesRead++]; for (int i = 0; i < associationCount; i++) { uint association; - uint propertyIndexMask; - uint essentialMask; + if (largePropertyIndex) { EnsureBufferRemaining(boxBuffer, bytesRead, 2, "item property association"); association = BinaryPrimitives.ReadUInt16BigEndian(boxBuffer[bytesRead..]); bytesRead += 2; - propertyIndexMask = 0x7FFFU; - essentialMask = 0x8000U; } else { EnsureBufferRemaining(boxBuffer, bytesRead, 1, "item property association"); association = boxBuffer[bytesRead++]; - propertyIndexMask = 0x7FU; - essentialMask = 0x80U; } uint propertyIndex = association & propertyIndexMask; @@ -2173,11 +2117,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore { EnsureBufferRemaining(boxBuffer, bytesRead, version == 2 ? 4 : 2, "item location"); uint itemId = ReadUInt16Or32(boxBuffer, version == 2, ref bytesRead); - HeifItem? item = this.FindItemById(itemId); - if (item is null) - { - throw new InvalidImageContentException($"The item location box references unknown item ID {itemId}."); - } + HeifItem? item = this.FindItemById(itemId) ?? throw new InvalidImageContentException($"The item location box references unknown item ID {itemId}."); if (!locatedItemIds.Add(itemId)) { @@ -2387,7 +2327,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore } /// - /// Resolves item extents, selects the primary or supported thumbnail decoder, and reconstructs the image. + /// Resolves item extents and reconstructs the primary image. /// /// The destination pixel format. /// The complete seekable HEIF container stream. @@ -2396,84 +2336,52 @@ internal sealed class HeifDecoderCore : ImageDecoderCore private Image DecodePrimaryItem(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - Func> itemDataReader = item => this.ReadItemData(stream, item); + IMemoryOwner ItemDataReader(HeifItem item) => this.ReadItemData(stream, item); - HeifItem? rootItem = this.FindItemById(this.primaryItem); - if (rootItem is null) - { - throw new ImageFormatException("No primary HEIF item defined."); - } + HeifItem? rootItem = this.FindItemById(this.primaryItem) ?? throw new ImageFormatException("No primary HEIF item defined."); - Image? image = null; - HeifItem itemToDecode = rootItem; - IHeifItemDecoder? itemDecoder = this.GetItemDecoder(rootItem, itemDataReader); - bool supportedItemFound = itemDecoder is not null; - if (itemDecoder is not null) - { - this.ExecuteImageDataSegmentAction( - () => image = this.DecodeImageItem(rootItem, itemDecoder, itemDataReader, cancellationToken)); - } + IHeifItemDecoder itemDecoder = this.GetItemDecoder(rootItem, ItemDataReader) + ?? throw new ImageFormatException($"The primary HEIF item uses unsupported item type '{rootItem.Type}'."); - if (image is null) - { - // An unsupported primary item always permits its registered thumbnail fallback. IgnoreImageData also - // reaches this branch after a recoverable primary payload failure, matching other multi-image decoders. - HeifItem? thumbnailItem = this.FindDecodableThumbnail(rootItem); - if (thumbnailItem is not null) - { - itemDecoder = HeifCompressionFactory.GetDecoder(thumbnailItem.Type); - supportedItemFound |= itemDecoder is not null; - if (itemDecoder is not null) - { - itemToDecode = thumbnailItem; - this.ExecuteImageDataSegmentAction( - () => image = this.DecodeImageItem(thumbnailItem, itemDecoder, itemDataReader, cancellationToken)); - } - } - } + Image image = this.DecodeImageItem(rootItem, itemDecoder, ItemDataReader, cancellationToken); - if (image is null || itemDecoder is null) + try { - if (!supportedItemFound) + if (!this.Options.SkipMetadata) { - throw new ImageFormatException("No supported image item was found inside this HEIF container."); + this.ApplyItemColorMetadata(image.Metadata, rootItem); + this.ApplyItemHdrMetadata(image.Metadata, rootItem); + this.ApplyAssociatedMetadata(image.Metadata, rootItem, ItemDataReader); } - throw new InvalidImageContentException("The HEIF container does not contain a decodable image item."); - } - - try - { - bool hasAlpha = false; - this.ExecuteImageDataSegmentAction( - () => hasAlpha = this.DecodeAlphaPlane(itemToDecode, itemDataReader, image.Frames.RootFrame, cancellationToken)); - - if (!this.Options.SkipMetadata) + if (this.Options.ColorProfileHandling == ColorProfileHandling.Convert + && this.Options.TryGetIccProfileForColorConversion(image.Metadata.IccProfile, out _)) { - this.ApplyItemColorMetadata(image.Metadata, itemToDecode); - this.ApplyItemHdrMetadata(image.Metadata, itemToDecode); - this.ApplyAssociatedMetadata(image.Metadata, rootItem, itemDataReader); + // ICC conversion has already produced full-range sRGB pixels. The source YUV matrix and + // transfer description must not be reapplied when those pixels are written to another format. + image.Metadata.CicpProfile = new CicpProfile(1, 13, 0, true); + image.Frames.RootFrame.Metadata.CicpProfile = image.Metadata.CicpProfile; } + else if (image.Metadata.CicpProfile is not null) + { + // The packed pixels are full-range RGB even when the source ICC profile is preserved. + // Keep the primaries and transfer description, but do not label RGB as encoded YUV. + CicpProfile sourceProfile = image.Metadata.CicpProfile; + image.Metadata.CicpProfile = new CicpProfile( + (byte)sourceProfile.ColorPrimaries, + (byte)sourceProfile.TransferCharacteristics, + (byte)CicpMatrixCoefficients.Identity, + true); - // MIAF defines crop, rotation, and mirror as presentation operations in that order. Applying the - // container transforms after item composition keeps every composed plane in the same coordinate space. - ApplyPresentationTransforms(image, itemToDecode); + image.Frames.RootFrame.Metadata.CicpProfile = image.Metadata.CicpProfile; + } if (!this.Options.SkipMetadata) { - this.ApplyItemPixelAspectRatioMetadata(image.Metadata, itemToDecode); - - // ICC conversion belongs to the presented RGB image. Running it after alpha, grid composition, crop, - // rotation, and mirroring keeps still images aligned with the sequence path and avoids converting - // pixels removed by a clean-aperture crop. - _ = this.TryConvertIccProfile(image); + this.ApplyItemPixelAspectRatioMetadata(image.Metadata, rootItem); } - // The decoder determines the compression of the pixels that were actually returned, including grid tiles - // and a thumbnail fallback when the primary image compression is not available. HeifMetadata meta = image.Metadata.GetHeifMetadata(); - meta.CompressionMethod = itemDecoder.CompressionMethod; - meta.HasAlpha = hasAlpha; if (this.Options.SkipMetadata) { // AV1 item decoders still parse encoded metadata to enforce codec/container equivalence and select @@ -2762,16 +2670,49 @@ internal sealed class HeifDecoderCore : ImageDecoderCore where TPixel : unmanaged, IPixel { using IMemoryOwner itemMemory = itemDataReader(item); - Image image = decoder.DecodeItemData( - this.payloadOptions, - item, - itemMemory.GetSpan(), - item.CicpProfile, - cancellationToken); + Rectangle sourceRectangle = item.CleanAperture?.ToRectangle(item.Extent) ?? new Rectangle(Point.Empty, item.Extent); + HeifPixelTransform transform = new(item.RotationAngle ?? 0, item.MirrorAxis); + Size outputSize = transform.GetDestinationSize(sourceRectangle.Size); + Image image = new(this.configuration, outputSize.Width, outputSize.Height); try { - HeifItemDecoderUtilities.ScaleToItemExtent(image, item); + if (!this.Options.SkipMetadata) + { + this.ApplyItemColorMetadata(image.Metadata, item); + } + + IccProfile? profile = null; + if (this.Options.ColorProfileHandling == ColorProfileHandling.Convert) + { + this.Options.TryGetIccProfileForColorConversion(image.Metadata.IccProfile, out profile); + } + + Av1FrameBuffer? alpha = null; + bool premultiplied = false; + this.ExecuteImageDataSegmentAction( + () => alpha = this.DecodeAlphaPlane(item, itemDataReader, cancellationToken, out premultiplied)); + + using Av1FrameBuffer? alphaFrame = alpha; + decoder.DecodeItemData( + this.payloadOptions, + this.chromaUpsampling, + item, + itemMemory.GetSpan(), + item.CicpProfile, + profile, + alphaFrame, + item.Extent, + sourceRectangle, + alphaFrame is not null && premultiplied, + sourceRectangle, + transform, + image.Frames.RootFrame.PixelBuffer.GetRegion(new Rectangle(Point.Empty, outputSize)), + image.Metadata, + cancellationToken); + + image.Metadata.GetHeifMetadata().HasAlpha = alphaFrame is not null; + image.Frames.RootFrame.Metadata.CicpProfile = image.Metadata.CicpProfile; return image; } catch @@ -2808,81 +2749,23 @@ internal sealed class HeifDecoderCore : ImageDecoderCore : extent; } - /// - /// Applies the clean-aperture, rotation, and mirror properties associated with an image item. - /// - /// The image pixel format. - /// The decoded image item. - /// The item carrying the presentation properties. - private static void ApplyPresentationTransforms(Image image, HeifItem item) - where TPixel : unmanaged, IPixel - => ApplyPresentationTransforms(image, item.CleanAperture, item.RotationAngle, item.MirrorAxis); - - /// - /// Applies shared clean-aperture, rotation, and mirror properties to every frame of an image presentation. - /// - /// The image pixel format. - /// The decoded image presentation. - /// The optional clean-aperture crop. - /// The optional counter-clockwise quarter-turn count. - /// The optional horizontal or vertical mirror axis. - private static void ApplyPresentationTransforms( - Image image, - HeifCleanAperture? cleanAperture, - byte? rotationAngle, - byte? mirrorAxis) - where TPixel : unmanaged, IPixel - { - if (cleanAperture is not null) - { - Rectangle cropRectangle = cleanAperture.Value.ToRectangle(image.Size); - if (cropRectangle != image.Bounds) - { - image.Mutate(context => context.Crop(cropRectangle)); - } - } - - if (rotationAngle is not null) - { - // HEIF angles count quarter turns counter-clockwise, while ImageSharp's optimized rotate modes are clockwise. - RotateMode rotation = rotationAngle.Value switch - { - 1 => RotateMode.Rotate270, - 2 => RotateMode.Rotate180, - 3 => RotateMode.Rotate90, - _ => RotateMode.None - }; - - if (rotation != RotateMode.None) - { - image.Mutate(context => context.Rotate(rotation)); - } - } - - if (mirrorAxis is not null) - { - // Axis zero reflects top-to-bottom around the horizontal axis; axis one reflects left-to-right. - FlipMode flip = mirrorAxis.Value == 0 ? FlipMode.Vertical : FlipMode.Horizontal; - image.Mutate(context => context.Flip(flip)); - } - } - /// /// Decodes and composes the direct or per-grid-tile alpha auxiliary associated with a color image item. /// /// The destination color pixel type. /// The color image item whose alpha plane is requested. /// Reads one selected item payload on demand. - /// The decoded color frame receiving alpha values. /// The token used to cancel the auxiliary payload decode. - /// when an auxiliary alpha plane was decoded and composed. - private bool DecodeAlphaPlane( + /// Whether the auxiliary relationship declares associated RGB. + /// The caller-owned auxiliary plane, or null when the item has no complete alpha plane. + private Av1FrameBuffer? DecodeAlphaPlane( HeifItem colorItem, Func> itemDataReader, - ImageFrame destination, - CancellationToken cancellationToken) + CancellationToken cancellationToken, + out bool premultiplied) where TPixel : unmanaged, IPixel { + premultiplied = false; HeifItem? alphaItem = this.FindAlphaItem(colorItem); if (alphaItem is not null) { @@ -2903,39 +2786,33 @@ internal sealed class HeifDecoderCore : ImageDecoderCore } IHeifItemDecoder? itemDecoder = this.GetItemDecoder(alphaItem, itemDataReader); - if (itemDecoder is not IHeifAlphaItemDecoder decoder) + if (itemDecoder is not IHeifAlphaItemDecoder decoder) { throw new ImageFormatException($"The alpha auxiliary item uses unsupported item type '{alphaItem.Type}'."); } - bool premultiplied = this.itemLinks.Any( + premultiplied = this.itemLinks.Any( link => link.Type == Heif4CharCode.Prem && link.SourceId == colorItem.Id && link.DestinationIds.Contains(alphaItem.Id)); using IMemoryOwner itemMemory = itemDataReader(alphaItem); - decoder.DecodeAlphaItemData( + return decoder.DecodeAlphaItemData( this.payloadOptions, alphaItem, itemMemory.GetSpan(), - destination, - destination.Size, - destination.Bounds, - premultiplied, cancellationToken); - - return true; } if (colorItem.Type != Heif4CharCode.Grid) { - return false; + return null; } List? alphaTileIds = this.FindGridAlphaTiles(colorItem); if (alphaTileIds is null) { - return false; + return null; } // The color grid descriptor defines the same row/column layout and output canvas for per-tile alpha @@ -2947,17 +2824,11 @@ internal sealed class HeifDecoderCore : ImageDecoderCore alphaTileIds); using IMemoryOwner gridMemory = itemDataReader(colorItem); - gridDecoder.DecodeAlphaItemData( + return gridDecoder.DecodeAlphaItemData( this.payloadOptions, colorItem, gridMemory.GetSpan(), - destination, - destination.Size, - destination.Bounds, - false, cancellationToken); - - return true; } /// @@ -3096,8 +2967,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore HeifItem tile = this.FindRequiredItemById(itemId); if (HeifCompressionFactory.GetDecoder(tile.Type) is null) { - // A partially decodable grid cannot yield the requested canvas. Returning no tile lets the - // caller select a thumbnail of the complete primary presentation when one is available. + // Every tile must be decodable to reconstruct the primary grid. return null; } @@ -3108,34 +2978,6 @@ internal sealed class HeifDecoderCore : ImageDecoderCore return firstTile; } - /// - /// Finds a decodable thumbnail that represents the specified master image item. - /// - /// The destination pixel format used to select item decoders. - /// The master image item referenced by the thumbnail. - /// A decodable thumbnail item, or when no matching thumbnail is available. - private HeifItem? FindDecodableThumbnail(HeifItem masterItem) - where TPixel : unmanaged, IPixel - { - // A thumbnail reference points from the thumbnail item to the master image. Restrict fallback to this - // presentation rather than allowing an unrelated thumbnail elsewhere in the file to be selected. - HeifItemLink? thumbnailReference = this.itemLinks.FirstOrDefault( - link => link.Type == Heif4CharCode.Thmb && link.DestinationIds.Contains(masterItem.Id)); - - if (thumbnailReference is null) - { - return null; - } - - HeifItem thumbnailItem = this.FindRequiredItemById(thumbnailReference.SourceId); - if (HeifCompressionFactory.GetDecoder(thumbnailItem.Type) is null) - { - return null; - } - - return thumbnailItem; - } - /// /// Decodes the UTF-8 bytes preceding the first null terminator. /// @@ -3176,7 +3018,6 @@ internal sealed class HeifDecoderCore : ImageDecoderCore private static bool IsRecoverableBoxError(Exception exception) => exception is ImageFormatException or InvalidIccProfileException - or InvalidImageContentException or InvalidOperationException or NotSupportedException; diff --git a/src/ImageSharp/Formats/Heif/HeifDecoderOptions.cs b/src/ImageSharp/Formats/Heif/HeifDecoderOptions.cs new file mode 100644 index 0000000000..99a6e98e4b --- /dev/null +++ b/src/ImageSharp/Formats/Heif/HeifDecoderOptions.cs @@ -0,0 +1,18 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif; + +/// +/// Configuration options for decoding HEIF images. +/// +public sealed class HeifDecoderOptions : ISpecializedDecoderOptions +{ + /// + public DecoderOptions GeneralOptions { get; init; } = new(); + + /// + /// Gets the chroma upsampling mode. The default is . + /// + public HeifChromaUpsampling ChromaUpsampling { get; init; } +} diff --git a/src/ImageSharp/Formats/Heif/HeifEncoder.cs b/src/ImageSharp/Formats/Heif/HeifEncoder.cs index 540f902c44..b8a74cbdf4 100644 --- a/src/ImageSharp/Formats/Heif/HeifEncoder.cs +++ b/src/ImageSharp/Formats/Heif/HeifEncoder.cs @@ -28,12 +28,6 @@ public sealed class HeifEncoder : AnimatedImageEncoder /// private HeifEncodingSpeed speed; - /// - /// Gets the compression method used for the primary image item. - /// The default is . - /// - public HeifCompressionMethod CompressionMethod { get; init; } = HeifCompressionMethod.Av1; - /// /// Gets the lossy compression quality, or to use the compression method's default quality. /// Valid values range from 0 for the lowest quality to 100 for the highest quality. A value of 100 does not @@ -76,8 +70,7 @@ public sealed class HeifEncoder : AnimatedImageEncoder /// /// Gets the encoding effort in the range 0 to 10. A value of 0 selects the fastest encoding and 10 selects the - /// slowest encoding with the greatest compression effort. The default is 5. Legacy JPEG image items use a fixed - /// encoding effort, so this option does not affect them. + /// slowest encoding with the greatest compression effort. The default is 5. /// /// The effort is outside the range 0 to 10. public int Effort @@ -97,7 +90,7 @@ public sealed class HeifEncoder : AnimatedImageEncoder /// /// Gets a value indicating whether the primary and auxiliary alpha images are encoded without loss. When /// , and do not affect the encoded image. - /// This option has no effect on legacy JPEG image items. The default is . + /// The default is . /// public bool Lossless { get; init; } @@ -122,8 +115,7 @@ public sealed class HeifEncoder : AnimatedImageEncoder /// /// Gets the encoded precision of each image component, or to use the HEIF metadata bit - /// depth. Metadata that does not specify a bit depth defaults to . Legacy JPEG - /// image items are always encoded with . + /// depth. Metadata that does not specify a bit depth defaults to . /// public HeifBitDepth? BitDepth { get; init; } diff --git a/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs b/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs index 09b8cabfbb..b8ae213e12 100644 --- a/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs @@ -8,7 +8,6 @@ using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantizers; -using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Cicp; @@ -106,26 +105,13 @@ internal sealed partial class HeifEncoderCore Guard.NotNull(image, nameof(image)); Guard.NotNull(stream, nameof(stream)); - switch (this.encoder.CompressionMethod) + if (image.Frames.Count > 1 && (image.Width > ushort.MaxValue || image.Height > ushort.MaxValue)) { - case HeifCompressionMethod.LegacyJpeg: - break; - case HeifCompressionMethod.Av1: - if (image.Frames.Count > 1) - { - if (image.Width > ushort.MaxValue || image.Height > ushort.MaxValue) - { - throw new NotSupportedException("AV1 image-sequence dimensions cannot exceed 65535 pixels."); - } - } - - break; - default: - throw new NotSupportedException($"HEIF compression method '{this.encoder.CompressionMethod}' is not supported."); + throw new NotSupportedException("AV1 image-sequence dimensions cannot exceed 65535 pixels."); } using ChunkedMemoryStream compressedPixels = new(this.configuration.MemoryAllocator); - if (this.encoder.CompressionMethod == HeifCompressionMethod.Av1 && image.Frames.Count > 1) + if (image.Frames.Count > 1) { Av1EncodingSettings settings = this.ResolveAv1Encoding(image); bool animateRootFrame = this.encoder.AnimateRootFrame @@ -200,16 +186,7 @@ internal sealed partial class HeifEncoderCore List items = new(); List links = new(); - switch (this.encoder.CompressionMethod) - { - case HeifCompressionMethod.LegacyJpeg: - this.CompressPixels(image, compressedPixels, cancellationToken); - GenerateLegacyJpegItem(image, compressedPixels.Length, items); - break; - case HeifCompressionMethod.Av1: - this.CompressAv1Pixels(image, compressedPixels, items, links, cancellationToken); - break; - } + this.CompressAv1Pixels(image, compressedPixels, items, links, cancellationToken); // Write out the generated header and pixels. long metadataBoxOffset = this.WriteFileTypeBox(stream); @@ -218,27 +195,6 @@ internal sealed partial class HeifEncoderCore stream.Flush(); } - /// - /// Builds the item declarations and relationships for the encoded image payload. - /// - /// The source pixel format. - /// The source image. - /// The encoded primary-item payload length. - /// The destination item collection. - private static void GenerateLegacyJpegItem(Image image, long pixelDataLength, List items) - where TPixel : unmanaged, IPixel - { - HeifItem primaryItem = new(Heif4CharCode.Jpeg, 1u); - primaryItem.DataLocations.Add(new HeifLocation(HeifLocationOffsetOrigin.FileOffset, 0L, 0L, pixelDataLength)); - primaryItem.BitsPerPixel = 24; - primaryItem.ChannelCount = 3; - primaryItem.SetExtent(image.Size); - items.Add(primaryItem); - - // No item relationship is emitted until the writer has a distinct derived image, - // thumbnail, auxiliary image, or metadata item to reference. - } - /// /// Writes an eight-byte ISO BMFF basic box header with a placeholder size. /// @@ -290,24 +246,18 @@ internal sealed partial class HeifEncoderCore { Span buffer = stackalloc byte[28]; int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.Ftyp); - Heif4CharCode majorBrand = this.encoder.CompressionMethod == HeifCompressionMethod.Av1 - ? Heif4CharCode.Avif - : Heif4CharCode.Mif1; - - BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)majorBrand); + BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.Avif); bytesWritten += 4; BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], 0); bytesWritten += 4; - if (majorBrand == Heif4CharCode.Avif) - { - // A still AVIF is also a MIAF image collection, so advertise both structural brands with the codec brand. - BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.Avif); - bytesWritten += 4; - BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.Mif1); - bytesWritten += 4; - BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.Miaf); - bytesWritten += 4; - } + + // A still AVIF is also a MIAF image collection, so advertise both structural brands with the codec brand. + BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.Avif); + bytesWritten += 4; + BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.Mif1); + bytesWritten += 4; + BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.Miaf); + bytesWritten += 4; BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten); stream.Write(buffer[..bytesWritten]); @@ -1546,42 +1496,6 @@ internal sealed partial class HeifEncoderCore throw new ImageFormatException("The Exif profile does not contain a TIFF header."); } - /// - /// Encodes the source pixels as the current legacy JPEG item payload. - /// - /// The source pixel format. - /// The source image. - /// The destination for the encoded JPEG item bytes. - /// The token used to cancel payload encoding. - private void CompressPixels( - Image image, - ChunkedMemoryStream stream, - CancellationToken cancellationToken) - where TPixel : unmanaged, IPixel - { - JpegColorType colorType = this.encoder.ChromaSubsampling switch - { - null or HeifChromaSubsampling.Yuv420 => JpegColorType.YCbCrRatio420, - HeifChromaSubsampling.Yuv422 => JpegColorType.YCbCrRatio422, - HeifChromaSubsampling.Yuv444 => JpegColorType.YCbCrRatio444, - HeifChromaSubsampling.Monochrome => JpegColorType.Luminance, - _ => throw new NotSupportedException($"HEIF chroma sampling '{this.encoder.ChromaSubsampling}' is not supported.") - }; - - JpegEncoder encoder = new() - { - // The HEIF quality scale includes zero while the JPEG payload encoder starts at one. - // Map the lowest HEIF setting to the lowest representable JPEG setting. - Quality = this.encoder.Quality == 0 ? 1 : this.encoder.Quality, - ColorType = colorType, - SkipMetadata = this.encoder.SkipMetadata - }; - - // ImageEncoder is a synchronous contract. Wait for the cancellable JPEG operation so HEIF encoding - // cannot return while its pooled item payload is still being produced. - image.SaveAsJpegAsync(stream, encoder, cancellationToken).GetAwaiter().GetResult(); - } - /// /// Describes the already-written color and optional alpha extents backing one AV1 image item. /// diff --git a/src/ImageSharp/Formats/Heif/HeifItemDecoderUtilities.cs b/src/ImageSharp/Formats/Heif/HeifItemDecoderUtilities.cs deleted file mode 100644 index 53418a0fcd..0000000000 --- a/src/ImageSharp/Formats/Heif/HeifItemDecoderUtilities.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; - -namespace SixLabors.ImageSharp.Formats.Heif; - -/// -/// Provides shared presentation operations for decoded HEIF image items. -/// -internal static class HeifItemDecoderUtilities -{ - /// - /// Scales a decoded image to the spatial extent associated with its image item. - /// - /// The decoded pixel format. - /// The decoded image. - /// The image item that defines the presented spatial extent. - public static void ScaleToItemExtent(Image image, HeifItem item) - where TPixel : unmanaged, IPixel - { - Size extent = item.Extent; - if (extent == default || (image.Width == extent.Width && image.Height == extent.Height)) - { - return; - } - - // libavif applies box filtering when coded dimensions differ from an item's ispe dimensions. Reusing the - // same ImageSharp resampler keeps direct images and grid tiles on one presentation path. - image.Mutate(context => context.Resize(extent.Width, extent.Height, KnownResamplers.Box)); - } -} diff --git a/src/ImageSharp/Formats/Heif/HeifMetadata.cs b/src/ImageSharp/Formats/Heif/HeifMetadata.cs index c11f42ca14..225e42274e 100644 --- a/src/ImageSharp/Formats/Heif/HeifMetadata.cs +++ b/src/ImageSharp/Formats/Heif/HeifMetadata.cs @@ -24,7 +24,6 @@ public class HeifMetadata : IFormatMetadata /// The metadata to create an instance from. private HeifMetadata(HeifMetadata other) { - this.CompressionMethod = other.CompressionMethod; this.BitDepth = other.BitDepth; this.IsMonochrome = other.IsMonochrome; this.HasAlpha = other.HasAlpha; @@ -38,11 +37,6 @@ public class HeifMetadata : IFormatMetadata this.NominalDiffuseWhite = other.NominalDiffuseWhite; } - /// - /// Gets or sets the compression method used for the primary frame. - /// - public HeifCompressionMethod CompressionMethod { get; set; } = HeifCompressionMethod.LegacyJpeg; - /// /// Gets or sets the encoded precision of each color component. The default is . /// diff --git a/src/ImageSharp/Formats/Heif/HeifPixelTransform.cs b/src/ImageSharp/Formats/Heif/HeifPixelTransform.cs new file mode 100644 index 0000000000..deba9766b6 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/HeifPixelTransform.cs @@ -0,0 +1,113 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using System.Numerics; +using SixLabors.ImageSharp.Memory; + +namespace SixLabors.ImageSharp.Formats.Heif; + +/// +/// Maps cropped source pixels to their rotated and mirrored destination coordinates. +/// +internal readonly struct HeifPixelTransform +{ + private readonly Matrix3x2 orientation; + + /// + /// Initializes a new instance of the struct. + /// + /// The counter-clockwise quarter-turn count. + /// The optional destination mirror axis. + public HeifPixelTransform(byte rotation, byte? mirrorAxis) + { + this.orientation = Matrix3x2.CreateRotation(-rotation * MathF.PI / 2); + this.orientation *= Matrix3x2.CreateScale(mirrorAxis == 1 ? -1 : 1, mirrorAxis == 0 ? -1 : 1); + } + + /// + /// Gets a value indicating whether source rows map directly to destination rows. + /// + public bool IsIdentity => this.orientation == default || this.orientation.IsIdentity; + + /// + /// Gets the destination extent for a cropped source extent. + /// + /// The cropped source extent. + /// The destination extent. + public Size GetDestinationSize(Size sourceSize) + => Rectangle.Round(Rectangle.Transform(new Rectangle(Point.Empty, sourceSize), this.GetMatrix(sourceSize))).Size; + + /// + /// Finds the source rectangle corresponding to a destination window. + /// + /// The window in transformed coordinates. + /// The complete cropped source extent. + /// The source window before rotation and mirroring. + public Rectangle GetSourceRectangle(Rectangle destination, Size sourceSize) + { + Matrix3x2.Invert(this.GetMatrix(sourceSize), out Matrix3x2 inverse); + return Rectangle.Round(Rectangle.Transform(destination, inverse)); + } + + /// + /// Maps one source pixel to the destination. + /// + /// The source column relative to the crop. + /// The source row relative to the crop. + /// The matrix resolved once for the source region. + /// The destination pixel coordinate. + public static Point Transform(int x, int y, Matrix3x2 matrix) + { + Vector2 point = Vector2.Transform(new Vector2(x + 0.5F, y + 0.5F), matrix); + return new Point((int)MathF.Floor(point.X), (int)MathF.Floor(point.Y)); + } + + /// + /// Maps a source rectangle to its exact destination rectangle. + /// + /// The rectangle relative to the cropped source. + /// The complete cropped source extent. + /// The destination rectangle. + public Rectangle TransformRectangle(Rectangle source, Size sourceSize) + { + return Rectangle.Round(Rectangle.Transform(source, this.GetMatrix(sourceSize))); + } + + /// + /// Writes a converted source row into its destination coordinates. + /// + /// The packed pixel type. + /// The converted source row. + /// The source row index relative to the crop. + /// The matrix resolved once for the source region. + /// The exact destination region. + public static void WriteRow(ReadOnlySpan row, int y, Matrix3x2 matrix, Buffer2DRegion destination) + where TPixel : unmanaged + { + // The matrix's first basis vector is the integer destination step for one source column. + // Quarter turns and mirrors require no matrix operations inside the pixel loop. + Point first = Transform(0, y, matrix); + int stepX = (int)matrix.M11; + int stepY = (int)matrix.M12; + + for (int x = 0; x < row.Length; x++) + { + destination.DangerousGetRowSpan(first.Y)[first.X] = row[x]; + first.X += stepX; + first.Y += stepY; + } + } + + /// + /// Gets the transform translated into the positive destination bounds. + /// + /// The source extent. + /// The transform of rectangle edges and pixel centers. + public Matrix3x2 GetMatrix(Size sourceSize) + { + Matrix3x2 matrix = this.IsIdentity ? Matrix3x2.Identity : this.orientation; + RectangleF bounds = Rectangle.Transform(new Rectangle(Point.Empty, sourceSize), matrix); + matrix.Translation = new Vector2(-bounds.X, -bounds.Y); + return matrix; + } +} diff --git a/src/ImageSharp/Formats/Heif/IHeifItemDecoder.cs b/src/ImageSharp/Formats/Heif/IHeifItemDecoder.cs index 9835a47086..7c73ea675e 100644 --- a/src/ImageSharp/Formats/Heif/IHeifItemDecoder.cs +++ b/src/ImageSharp/Formats/Heif/IHeifItemDecoder.cs @@ -1,7 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Cicp; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Heif; @@ -18,26 +22,40 @@ internal interface IHeifItemDecoder /// public Heif4CharCode Type { get; } - /// - /// Gets the compression method used by the image item. - /// - public HeifCompressionMethod CompressionMethod { get; } - /// /// Decodes the compressed payload of an image item. /// /// The general options governing the containing HEIF decode. + /// The chroma reconstruction mode. /// The HEIF item whose encoded payload is being decoded. /// The encoded image payload. /// /// The container color description that overrides matching color information in the encoded image payload. /// + /// The source ICC profile selected for conversion, or null to preserve source colors. + /// The native auxiliary plane, or null for an opaque image. + /// The complete color extent covered by the auxiliary plane. + /// The matching region within the auxiliary presentation. + /// Whether source RGB is associated with alpha. + /// The source area of interest in luma-sample coordinates. + /// The rotation and mirroring applied within the destination region. + /// The destination pixel region. + /// The metadata receiving the decoded image properties. /// The token used to cancel the payload decode. - /// The decoded image. - public Image DecodeItemData( + public void DecodeItemData( DecoderOptions options, + HeifChromaUpsampling chromaUpsampling, HeifItem item, Span data, CicpProfile? colorProfile, + IccProfile? profile, + Av1FrameBuffer? alphaFrame, + Size alphaOutputSize, + Rectangle alphaRectangle, + bool premultiplied, + Rectangle sourceRectangle, + HeifPixelTransform transform, + Buffer2DRegion destination, + ImageMetadata metadata, CancellationToken cancellationToken); } diff --git a/src/ImageSharp/Formats/Heif/JpegHeifItemDecoder.cs b/src/ImageSharp/Formats/Heif/JpegHeifItemDecoder.cs deleted file mode 100644 index 831f62c164..0000000000 --- a/src/ImageSharp/Formats/Heif/JpegHeifItemDecoder.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Metadata.Profiles.Cicp; -using SixLabors.ImageSharp.PixelFormats; - -namespace SixLabors.ImageSharp.Formats.Heif; - -/// -/// Decodes a single JPEG-coded HEIF image item. -/// -/// The destination pixel type. -internal class JpegHeifItemDecoder : IHeifItemDecoder - where TPixel : unmanaged, IPixel -{ - /// - /// Gets the JPEG-coded image item type. - /// - public Heif4CharCode Type => Heif4CharCode.Jpeg; - - /// - /// Gets the legacy JPEG compression method. - /// - public HeifCompressionMethod CompressionMethod => HeifCompressionMethod.LegacyJpeg; - - /// - /// Decodes the encoded JPEG payload of an image item. - /// - /// The general options governing the containing HEIF decode. - /// The HEIF item whose encoded payload is being decoded. - /// The encoded JPEG payload. - /// The container color description associated with the image item. - /// The token used to cancel the payload decode. - /// The decoded image. - public unsafe Image DecodeItemData( - DecoderOptions options, - HeifItem item, - Span data, - CicpProfile? colorProfile, - CancellationToken cancellationToken) - { - // The JPEG decoder owns the payload's JPEG color coding. The containing decoder attaches HEIF CICP as - // presentation metadata after payload decode, so it must not be mistaken for JPEG component-transform syntax. - fixed (byte* dataPointer = data) - { - using UnmanagedMemoryStream stream = new(dataPointer, data.Length); - using JpegDecoderCore decoder = new(new JpegDecoderOptions { GeneralOptions = options }); - return decoder.Decode(options.Configuration, stream, cancellationToken); - } - } -} diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs index d4999220f1..86ba2f0f9a 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/FlipProcessor{TPixel}.cs @@ -60,23 +60,15 @@ internal class FlipProcessor : ImageProcessor /// protected override void OnFrameApply(ImageFrame source) - => Apply(this.definition.FlipMode, source, this.Configuration); - - /// - /// Applies an exact axis-aligned reflection to an existing frame. - /// - /// The reflection direction. - /// The frame modified in place. - /// The configuration controlling row parallelism and scratch allocation. - internal static void Apply(FlipMode flipMode, ImageFrame source, Configuration configuration) { - switch (flipMode) + switch (this.definition.FlipMode) { + // No default needed as we have already set the pixels. case FlipMode.Vertical: - FlipX(source.PixelBuffer, configuration); + FlipX(source.PixelBuffer, this.Configuration); break; case FlipMode.Horizontal: - FlipY(source, configuration); + FlipY(source, this.Configuration); break; } } diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs index ed376872a6..e9d0ecf57d 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/Linear/RotateProcessor{TPixel}.cs @@ -103,52 +103,25 @@ internal class RotateProcessor : AffineTransformProcessor if (MathF.Abs(degrees - 90) < Constants.Epsilon) { - ApplyQuarterTurn(RotateMode.Rotate90, source, destination, configuration); + Rotate90(source, destination, configuration); return true; } if (MathF.Abs(degrees - 180) < Constants.Epsilon) { - ApplyQuarterTurn(RotateMode.Rotate180, source, destination, configuration); + Rotate180(source, destination, configuration); return true; } if (MathF.Abs(degrees - 270) < Constants.Epsilon) { - ApplyQuarterTurn(RotateMode.Rotate270, source, destination, configuration); + Rotate270(source, destination, configuration); return true; } return false; } - /// - /// Applies an exact quarter-turn rotation between already allocated frames. - /// - /// The clockwise quarter-turn rotation. - /// The source frame. - /// The destination frame with the rotated dimensions. - /// The configuration controlling row parallelism. - internal static void ApplyQuarterTurn( - RotateMode rotation, - ImageFrame source, - ImageFrame destination, - Configuration configuration) - { - switch (rotation) - { - case RotateMode.Rotate90: - Rotate90(source, destination, configuration); - break; - case RotateMode.Rotate180: - Rotate180(source, destination, configuration); - break; - case RotateMode.Rotate270: - Rotate270(source, destination, configuration); - break; - } - } - /// /// Rotates the image 180 degrees clockwise at the centre point. /// diff --git a/tests/ImageSharp.Benchmarks/Codecs/Heif/Av1ColorConversionBenchmarks.cs b/tests/ImageSharp.Benchmarks/Codecs/Heif/Av1ColorConversionBenchmarks.cs index 668ca4196e..6d62d48350 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Heif/Av1ColorConversionBenchmarks.cs +++ b/tests/ImageSharp.Benchmarks/Codecs/Heif/Av1ColorConversionBenchmarks.cs @@ -2,9 +2,11 @@ // Licensed under the Six Labors Split License. using BenchmarkDotNet.Attributes; +using SixLabors.ImageSharp.Formats.Heif; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.Color; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Benchmarks.Codecs.Heif; @@ -113,7 +115,20 @@ public class Av1ColorConversionBenchmarks { Av1FrameBuffer frameBuffer = this.frameBuffer; Image destination = this.destination; - Av1YuvConverter.ConvertToRgb(Configuration.Default, frameBuffer, destination.Frames.RootFrame); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + frameBuffer, + new Rectangle(Point.Empty, destination.Frames.RootFrame.Size), + destination.Frames.RootFrame.PixelBuffer.GetRegion(), + destination.Frames.RootFrame.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + frameBuffer.ColorConfig.ColorRange); return destination.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(Height - 1)[Width - 1]; } diff --git a/tests/ImageSharp.Benchmarks/Codecs/Heif/Av1SequenceEncoderBenchmarks.cs b/tests/ImageSharp.Benchmarks/Codecs/Heif/Av1SequenceEncoderBenchmarks.cs deleted file mode 100644 index 0ef5d2264f..0000000000 --- a/tests/ImageSharp.Benchmarks/Codecs/Heif/Av1SequenceEncoderBenchmarks.cs +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -using System.Numerics; -using BenchmarkDotNet.Attributes; -using SixLabors.ImageSharp.Formats.Heif; -using SixLabors.ImageSharp.Formats.Heif.Av1; -using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; -using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; -using SixLabors.ImageSharp.Formats.Heif.Components; -using SixLabors.ImageSharp.Memory; -using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Tests; - -namespace SixLabors.ImageSharp.Benchmarks.Codecs.Heif; - -/// -/// Measures encoding a photographic sequence with fractional motion at the interpolation-search effort boundaries. -/// -[MemoryDiagnoser] -public class Av1SequenceEncoderBenchmarks -{ - /// - /// The number of displayed pictures in each independently encoded sequence. - /// - private const int FrameCount = 3; - - /// - /// The native AV1 quantizer index corresponding to libaom's public constant-quality level 30. - /// - private const int QIndex = 120; - - /// - /// The fixed native speed baseline, independent of ImageSharp's effort scale. - /// - private const int NativeCpuUsed = 6; - - /// - /// The native public quantizer corresponding to , also used for both rate-control bounds. - /// - private const int NativeQuality = 30; - - private Image sequence; - private Configuration configuration; - private ObuColorConfig colorConfig; - private MemoryStream output; - private string outputDirectory; - - /// - /// Gets or sets the square frame dimension. - /// - [Params(256, 512)] - public int Dimension { get; set; } - - /// - /// Gets or sets the effort controlling fixed, common switchable, or independently switchable filters. - /// - [Params(7, 8, 9)] - public int Effort { get; set; } - - /// - /// Prepares identical photographic RGB frames and a planar source file for checking reconstructed output quality. - /// - [GlobalSetup] - public void Setup() - { - this.configuration = Configuration.Default.Clone(); - this.configuration.MaxDegreeOfParallelism = 1; - this.colorConfig = new ObuColorConfig - { - BitDepth = Av1BitDepth.EightBit, - IsColorDescriptionPresent = true, - ColorPrimaries = ObuColorPrimaries.Bt601, - TransferCharacteristics = ObuTransferCharacteristics.Bt601, - MatrixCoefficients = ObuMatrixCoefficients.Bt601, - ColorRange = true, - SubSamplingX = true, - SubSamplingY = true, - ChromaSamplePosition = ObuChromoSamplePosition.Unknown - }; - - this.output = new MemoryStream(); - this.outputDirectory = TestEnvironment.CreateOutputDirectory("Heif", "Av1", nameof(Av1SequenceEncoderBenchmarks)); - string inputPath = Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Png.Bike); - using Image photograph = Image.Load(inputPath); - - // Leave a source margin for the half-pixel translations. Resampling is setup work, not encoder time; - // every invocation consumes the same three images rather than repeatedly translating a previous result. - photograph.Mutate(context => context.Resize(new ResizeOptions - { - Size = new Size(this.Dimension + FrameCount, this.Dimension + FrameCount), - Mode = ResizeMode.Crop - })); - - Rectangle sourceBounds = new(0, 0, photograph.Width, photograph.Height); - Size targetSize = new(this.Dimension, this.Dimension); - this.sequence = photograph.Clone(context => context.Crop(new Rectangle(Point.Empty, targetSize))); - for (int frameIndex = 1; frameIndex < FrameCount; frameIndex++) - { - Matrix3x2 translation = Matrix3x2.CreateTranslation(-0.5F * frameIndex, -0.5F * frameIndex); - using Image translated = photograph.Clone(context => - context.Transform(sourceBounds, translation, targetSize, KnownResamplers.Bicubic)); - - this.sequence.Frames.AddFrame(translated.Frames.RootFrame); - } - - // Export the production-converted source planes only for checking reconstructed output quality. - // Neither timed encoder reads this file: both convert the original RGB frames during each operation. - using Av1EncoderFrameBuffer planar = new(this.configuration, this.Dimension, this.Dimension, 8, Av1ColorFormat.Yuv420, 0, 0, lumaBorder: 64); - using FileStream raw = File.Create(Path.Combine(this.outputDirectory, $"bike-{this.Dimension}-3frames.source.yuv")); - foreach (ImageFrame frame in this.sequence.Frames) - { - Av1FrameEncoder.PrepareSource(this.configuration, frame, planar.Frame, this.colorConfig); - for (int planeIndex = 0; planeIndex < this.colorConfig.PlaneCount; planeIndex++) - { - Buffer2DRegion plane = planar.Frame.View.GetPlane((Av1Plane)planeIndex); - for (int y = 0; y < plane.Height; y++) - { - raw.Write(plane.DangerousGetRowSpan(y)); - } - } - } - } - - /// - /// Encodes one key picture and two dependent pictures, returning the complete OBU payload length. - /// - /// The encoded sequence length. - [Benchmark] - public long ImageSharp() - { - this.output.SetLength(0); - using Av1FrameEncoder.SequenceEncoder encoder = Av1FrameEncoder.CreateColorSequenceEncoder( - this.configuration, - this.Dimension, - this.Dimension, - this.colorConfig, - QIndex, - this.Effort, - speed: HeifEncodingSpeed.Level0); - - // One operation owns the real sequence lifetime: allocation, conversion, key/inter coding, and disposal. - // The caller's destination is reused, excluding filesystem and MemoryStream growth from steady-state timing. - encoder.EncodeKeyFrame(this.sequence.Frames.RootFrame, this.output); - for (int frameIndex = 1; frameIndex < FrameCount; frameIndex++) - { - encoder.EncodeInterFrame(this.sequence.Frames[frameIndex], this.output); - } - - return this.output.Length; - } - - /// - /// Encodes the same RGB sequence with current-main libaom, including conversion, allocation, output, and disposal. - /// - /// The encoded sequence length. - [Benchmark(Baseline = true)] - public long Libaom() - { - // cpu-used is a separate speed scale, not an ImageSharp effort mapping. Keep the reference at - // good-quality speed six while comparing the three managed interpolation-search boundaries. - this.output.SetLength(0); - using LibaomBenchmarkEncoder encoder = LibaomBenchmarkEncoder.Open(this.Dimension, this.Dimension, NativeQuality, NativeCpuUsed); - using Av1EncoderFrameBuffer planar = new(this.configuration, this.Dimension, this.Dimension, 8, Av1ColorFormat.Yuv420, 0, 0, lumaBorder: 64); - using Av1FrameEncoder.Av1EncoderConversionWorkspace conversion = new(this.configuration, this.Dimension, this.colorConfig, false, false); - Rectangle bounds = new(0, 0, this.Dimension, this.Dimension); - for (int frameIndex = 0; frameIndex < FrameCount; frameIndex++) - { - // Conversion belongs inside both measured paths. Reuse the same row workspace and SIMD converter - // as the managed sequence encoder, writing directly into the planes passed to native libaom. - conversion.Convert.PlanarView, byte, HeifByteSampleConverter>( - this.configuration, this.sequence.Frames[frameIndex], bounds, planar.Frame.View); - - encoder.Encode(planar.Frame, frameIndex, this.output); - } - - encoder.Finish(this.output); - return this.output.Length; - } - - /// - /// Retains the measured managed encoder output and releases the input images and destination stream. - /// - [GlobalCleanup(Target = nameof(ImageSharp))] - public void CleanupImageSharp() => this.Cleanup($"bike-{this.Dimension}-q{QIndex}-effort{this.Effort}.obu"); - - /// - /// Retains the measured reference encoder output and releases the input images and destination stream. - /// - [GlobalCleanup(Target = nameof(Libaom))] - public void CleanupLibaom() => this.Cleanup($"bike-{this.Dimension}-q{QIndex}-libaom-cpu{NativeCpuUsed}.obu"); - - /// - /// Writes the measured payload without another encoding pass and releases the shared benchmark resources. - /// - /// The codec-specific payload file name. - private void Cleanup(string outputName) - { - // Output validation and quality measurement use the actual measured payload, with no encode or decode - // hidden inside the timed operation and no file-sized ToArray copy. - using FileStream encoded = File.Create(Path.Combine(this.outputDirectory, outputName)); - this.output.Position = 0; - this.output.CopyTo(encoded); - this.output.Dispose(); - this.sequence.Dispose(); - } -} diff --git a/tests/ImageSharp.Benchmarks/Codecs/Heif/README.md b/tests/ImageSharp.Benchmarks/Codecs/Heif/README.md index 5bc9e1d613..5314e09853 100644 --- a/tests/ImageSharp.Benchmarks/Codecs/Heif/README.md +++ b/tests/ImageSharp.Benchmarks/Codecs/Heif/README.md @@ -1,7 +1,23 @@ -# AV1 sequence encoder comparison +# AV1 encoder and decoder comparisons + +The 2026-09-05 measurements below are historical results from earlier trees. The takeover source audit has not +accepted encoder or decoder completeness. The first decoder comparison following that audit's corrections ran +on 2026-09-07; its current results and limitations are recorded in the implementation plan. +Encoder comparisons require identical source samples and explicitly reconciled settings, with a maximum +component difference of one unit. Report component maxima and counts exceeding one. +Decoder comparisons for identical bitstreams must be byte exact: maximum error zero and zero differing samples. +Historical Y/U/V maxima of 40/30/53 fail that requirement; their exceedance counts were not recorded. +Agreement between two decoders on the same bitstream is a separate claim from agreement between two encoders. `Av1SequenceEncoderBenchmarks` compares the managed AV1 sequence encoder with an optimized build of official libaom `main`. -The native adapter belongs only to this benchmark project. ImageSharp production code remains fully managed. +The native adapter is temporary local comparison tooling already present in this project. It is excluded from the +codec deliverable and future checkpoint commits. ImageSharp production code remains fully managed. + +`Av1DecoderBenchmarks` compares complete AV1 elementary-stream decoding to `Rgb48` against that same optimized native build. +It includes decoder construction, parsing, reconstruction, output allocation, color conversion, and disposal on both sides. +File reads and correctness checks are setup work. HEIF container parsing is outside this elementary-stream comparison. +Native output planes are borrowed only during synchronous conversion through the existing HEIF converter; no plane copy or per-row interop call is added. +Setup compares every packed RGB sample with ImageSharp and fails before timing on any disagreement. ## Measurement boundary @@ -15,6 +31,7 @@ MemoryStream capacity is retained between operations for both methods. These are Do not compare them with `aomenc`'s internal encode-time report, which excludes the conversion boundary. The source is the existing `TestImages.Png.Bike` photograph. Frames one and two are independently translated by half a pixel and one pixel on both axes. +Encoder setup also checks all three encoded photographic frames with independent ImageSharp and libaom decoder contexts, including their retained references. The parameter matrix contains 256x256 and 512x512 frames at ImageSharp efforts seven, eight, and nine. Input is full-range BT.601, eight-bit 4:2:0; each sequence contains one key frame and two dependent frames. Both codecs use one coding thread. Libaom uses good-quality mode, no lookahead, disabled automatic key frames, and `cpu-used=6`. @@ -135,3 +152,100 @@ Final OBU SHA-256 values: The final Release benchmark build has zero errors and 39 existing benchmark warnings outside the new files. Roslyn compiler and analyzer diagnostics contain no errors or warnings in the new benchmark files. No production file changed in this benchmark checkpoint; the preceding 2,524-case encoder/entropy verification remains the latest production test run. + +## Decoder baseline and sparse-transform checkpoint: 2026-09-05 + +Run the two existing conformance inputs with the same in-process toolchain: + +```powershell +dotnet $benchmarkAssembly --inProcess --job Dry --filter '*Av1DecoderBenchmarks*' ` + --stopOnFirstError --noOverwrite --artifacts artifacts/BenchmarkDotNet/av1-decoder-rgb-dry +dotnet $benchmarkAssembly --inProcess --job Short --filter '*Av1DecoderBenchmarks*' ` + --stopOnFirstError --noOverwrite --artifacts artifacts/BenchmarkDotNet/av1-decoder-rgb-short +``` + +Both complete `Rgb48` outputs match libaom exactly. Initial warmed measurements were: + +| Input | ImageSharp | Libaom | +| --- | ---: | ---: | +| `libavif-kodim23-8b.bit` | 15.130 ms | 5.423 ms | +| `libavif-cosmos1650-10b.bit` | 27.655 ms | 10.246 ms | + +The lossy inverse dispatcher previously ignored EOB and transformed every coefficient, including DC-only blocks. +The first sparse path now evaluates the two DC cosine stages once, preserving rectangular normalization, axis rounding, input clamps, and final clipping. +The existing reconstruction output operators handle Vector512, Vector256, Vector128, and scalar stores without an extra sample buffer. +After this change the short measurements were 14.658/5.416 ms for the eight-bit pair and 26.722/9.992 ms for the ten-bit pair. +These small changes are not a statistically established decoder speedup; the approximately 2.7x gap remains open. +The encoder's measured output at this checkpoint remains byte-identical to the baseline hash above. + +Evidence: `artifacts/BenchmarkDotNet/av1-decoder-rgb-short-20260905` and `artifacts/BenchmarkDotNet/av1-dc-short-20260905/20260905-134350`. +DC regression cases compare the sparse and full transforms for every size, both destination layouts, signed rounding boundaries, clipping, and untouched row padding. +The final focused screening/DC/native-profile/moving-color set passes 25 cases in each of the normal, AVX512-disabled, AVX-disabled, and all-intrinsics-disabled VSTest processes. +Reports are under `artifacts/TestResults/av1-screen-20260905`. +The matching optimized current-main decoder also reproduces every native Y/U/V sample in all twelve two-frame moving-color streams regenerated by this tree. + +## Historical intra screening experiment: 2026-09-05 + +The current decoder-only comparison after source-led corrections is recorded at the end of this document. + +The experimental fixed 8x8 luma search imported libaom's unnormalized Hadamard magnitude and 1.5x threshold +without its full candidate ordering, top-ranked list, neighbor/quantizer policy, or surrounding search controller. +The implementation reuses block scratch and existing vectorized tensor/transpose APIs, retains Int32 precision for twelve-bit residuals, and omits coefficient permutations irrelevant to SATD. +Lossless search is unchanged. Screening for larger blocks, top-ranked candidate pruning, transform bounds, and winner refinement remain open. +The first three-iteration run measured 1,437.53 ms versus 82.34 ms, with considerable run variance. +The managed output is 11.577 KiB at Y/U/V PSNR 38.942/38.613/32.755 dB and aggregate 37.069 dB. +Libaom remains 8.272 KiB at aggregate 38.942 dB. Both decode to three complete frames. +This experiment is not an accepted improvement: aggregate PSNR fell 0.055 dB and the payload grew slightly. +Neither its isolated primitive tests nor these timing results validate the imported pruning policy. +The exact photographic sequence also passes independent packed-RGB decoder comparison before either timed method runs. + +A repeat with five warmup iterations and ten measurement iterations gives 1,363.09 ms for ImageSharp and 71.73 ms for libaom. +The payload and quality are unchanged from the screening result above. The native baseline is stable relative to the original run; +the managed reduction is about 34%, but the remaining encoding gap is still approximately 19x. +Evidence: `artifacts/BenchmarkDotNet/av1-screen-verified-short-20260905`. +The final managed OBU SHA-256 is `CCA72021F1AC0BBEA5E69F536589F859B76D2CF293ECD6BA86A644A57B56686E`. + +The focused production run passes 84 screening/DC/superblock cases and 165 frame/public encoder cases, with no failures or skips. +Release builds report zero errors, 1,009 existing test warnings, and 39 existing benchmark warnings; the changed source files have no build warnings. +One initial narrower-tier VSTest launch aborted before tests because the inherited environment contained both `Path` and `PATH`. +Subsequent serialized runs use a case-insensitively deduplicated child-process environment. No machine or user environment setting was changed. +The independent Hadamard matrix oracle and zero-allocation test pass at all tested hardware tiers; no golden output was changed to accept a mismatch. + +## Decoder source-correction measurement: 2026-09-07 + +The initial takeover investigation, reconstruction/filter/scratch corrections, and final production verification +preceded this measurement. No encoder timing or separately encoded output comparison was run. +The existing decoder benchmark uses the elementary-stream boundary described above, with one coding thread, +construction and disposal per operation, and independently allocated RGB48 output. Both paths use ImageSharp's +color converter, so this is not an independent color-conversion oracle or a HEIF container-load measurement. + +| Input | Encoded size | RGB48 output size | ImageSharp mean | Native mean | ImageSharp managed allocation | +| --- | ---: | ---: | ---: | ---: | ---: | +| Kodak, 768x512, 8-bit 4:2:0 | 20.264 KiB | 2.250 MiB | 13.296 ms | 5.099 ms | 715.97 KiB | +| Cosmos, 1024x428, 10-bit 4:4:4 | 36.298 KiB | 2.508 MiB | 25.419 ms | 9.448 ms | 797.24 KiB | + +Setup verifies all 2,494,464 packed RGB48 components exactly: maximum error zero, no differing samples, and +zero errors above one. Fresh optimized native decoding also matches the 1,904,640 retained native plane samples +used by the current conformance tests, with the same zero-error counts. Decoder agreement on these inputs does +not establish complete decoder conformance or separately encoded output parity. + +Immediately before the CDF-width correction, the same Short job reported 13.739/5.133 ms for Kodak and +24.848/9.263 ms for Cosmos. Replacing 32-bit CDF threshold storage with its required 16-bit representation +reduced managed allocation by about 217 KiB per decode. These three-iteration timing samples do not establish +a speedup. The current managed/native gap remains approximately 2.61x and 2.69x. The native allocation footprint +is not included in the managed allocation column. + +The benchmark uses BenchmarkDotNet 0.15.8, its existing in-process toolchain, .NET 11.0.0-preview.7.26381.103, +x64 RyuJIT x86-64-v4, three warmups and three measured iterations. CPU-model lookup and power-plan changes +were denied by the environment. Current 99.9% error half-widths are 2.800/1.959 ms for Kodak and +26.309/6.052 ms for Cosmos (managed/native); the latter results have substantial variance. + +The optimized reference remains `d565eec60f084421fa34fc0534b760c6452b6a6c` with the Release configuration above. +The existing local adapter SHA-256 is `B4FCB9028C15F0F7961A4CEF77FDFF265A11720132F5B23562673E5A65635B23`. +All 9,997 AV1 and selected HEIF production cases pass after the final CDF edit, and fresh comparisons preserve +exact agreement across the established 8,521,435-sample restoration, film-grain, and moving-color corpus. +These are bounded verification results, not codec completion. + +Reports remain outside the repository under `D:\GitHub\ynse01\av1-takeover-20260905`: +`decoder-current-short\20260907-011627` before CDF narrowing and `decoder-current-short\20260907-012459` +afterward, with matching Dry jobs and `decoder-benchmark-inputs.json`. No native material was staged or committed. diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs index 766b11e9b5..dd354bca9a 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CoefficientsEntropyTests.cs @@ -69,7 +69,7 @@ public class Av1CoefficientsEntropyTests } TestMemoryAllocator.ReturnRequest returned = Assert.Single(allocator.ReturnLog); - Assert.Equal(allocation.AllocationId, returned.AllocationId); + Assert.Equal(allocation.HashCodeOfBuffer, returned.HashCodeOfBuffer); } [Theory] @@ -398,7 +398,7 @@ public class Av1CoefficientsEntropyTests } TestMemoryAllocator.ReturnRequest returned = Assert.Single(allocator.ReturnLog); - Assert.Equal(allocation.AllocationId, returned.AllocationId); + Assert.Equal(allocation.HashCodeOfBuffer, returned.HashCodeOfBuffer); } [Fact] @@ -458,7 +458,7 @@ public class Av1CoefficientsEntropyTests } TestMemoryAllocator.ReturnRequest returned = Assert.Single(allocator.ReturnLog); - Assert.Equal(allocation.AllocationId, returned.AllocationId); + Assert.Equal(allocation.HashCodeOfBuffer, returned.HashCodeOfBuffer); } [Fact] diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundBlockDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundBlockDecoderTests.cs index 3a9f7330dc..3b7c37dbce 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundBlockDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1CompoundBlockDecoderTests.cs @@ -5,7 +5,6 @@ using System.Buffers; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.Motion; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; -using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.LoopFilter; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; using SixLabors.ImageSharp.Formats.Heif.Av1.ReferenceFrames; @@ -82,13 +81,11 @@ public class Av1CompoundBlockDecoderTests partitionInfo.ComputeBoundaryOffsets(sequenceHeader, frameHeader, tileInfo); partitionInfo.PopulateModeInfoNeighbors(sequenceHeader.ColorConfig); - using Av1LoopFilterContext loopFilterContext = new(frameBuffer.MemoryAllocator, sequenceHeader, frameHeader); using IMemoryOwner workspace = frameBuffer.MemoryAllocator.Allocate(Av1BlockDecoder.GetWorkspaceLength(sequenceHeader)); Av1BlockDecoder decoder = new( sequenceHeader, frameHeader, frameBuffer, - loopFilterContext, referenceFrames, workspace.Memory); decoder.UpdateSuperblock(superblockInfo); @@ -202,15 +199,11 @@ public class Av1CompoundBlockDecoderTests modeInfo.InterpolationFilters.Clear(); modeInfo.SetTransformUnitCount(Av1PlaneType.Y, 1); - using Av1LoopFilterContext loopFilterContext = - new(frameBuffer.MemoryAllocator, sequenceHeader, frameHeader); - using IMemoryOwner workspace = frameBuffer.MemoryAllocator.Allocate(Av1BlockDecoder.GetWorkspaceLength(sequenceHeader)); Av1BlockDecoder decoder = new( sequenceHeader, frameHeader, frameBuffer, - loopFilterContext, referenceFrames, workspace.Memory); @@ -322,15 +315,11 @@ public class Av1CompoundBlockDecoderTests modeInfo.InterpolationFilters.Fill(Av1InterpolationFilter.Bilinear); modeInfo.SetTransformUnitCount(Av1PlaneType.Y, 1); - using Av1LoopFilterContext loopFilterContext = - new(frameBuffer.MemoryAllocator, sequenceHeader, frameHeader); - using IMemoryOwner workspace = frameBuffer.MemoryAllocator.Allocate(Av1BlockDecoder.GetWorkspaceLength(sequenceHeader)); Av1BlockDecoder decoder = new( sequenceHeader, frameHeader, frameBuffer, - loopFilterContext, referenceFrames, workspace.Memory); @@ -449,15 +438,11 @@ public class Av1CompoundBlockDecoderTests modeInfo.InterpolationFilters.Clear(); modeInfo.SetTransformUnitCount(Av1PlaneType.Y, 1); - using Av1LoopFilterContext loopFilterContext = - new(frameBuffer.MemoryAllocator, sequenceHeader, frameHeader); - using IMemoryOwner workspace = frameBuffer.MemoryAllocator.Allocate(Av1BlockDecoder.GetWorkspaceLength(sequenceHeader)); Av1BlockDecoder decoder = new( sequenceHeader, frameHeader, frameBuffer, - loopFilterContext, referenceFrames, workspace.Memory); @@ -541,15 +526,11 @@ public class Av1CompoundBlockDecoderTests modeInfo.InterpolationFilters.Clear(); modeInfo.SetTransformUnitCount(Av1PlaneType.Y, 1); - using Av1LoopFilterContext loopFilterContext = - new(frameBuffer.MemoryAllocator, sequenceHeader, frameHeader); - using IMemoryOwner workspace = frameBuffer.MemoryAllocator.Allocate(Av1BlockDecoder.GetWorkspaceLength(sequenceHeader)); Av1BlockDecoder decoder = new( sequenceHeader, frameHeader, frameBuffer, - loopFilterContext, referenceFrames, workspace.Memory); @@ -625,15 +606,11 @@ public class Av1CompoundBlockDecoderTests current.SetTransformUnitCount(Av1PlaneType.Y, 1); frameInfo.UpdateModeInfo(current, superblockInfo); - using Av1LoopFilterContext loopFilterContext = - new(frameBuffer.MemoryAllocator, sequenceHeader, frameHeader); - using IMemoryOwner workspace = frameBuffer.MemoryAllocator.Allocate(Av1BlockDecoder.GetWorkspaceLength(sequenceHeader)); Av1BlockDecoder decoder = new( sequenceHeader, frameHeader, frameBuffer, - loopFilterContext, referenceFrames, workspace.Memory); @@ -722,15 +699,11 @@ public class Av1CompoundBlockDecoderTests current.SetTransformUnitCount(Av1PlaneType.Uv, 1); frameInfo.UpdateModeInfo(current, superblockInfo); - using Av1LoopFilterContext loopFilterContext = - new(frameBuffer.MemoryAllocator, sequenceHeader, frameHeader); - using IMemoryOwner workspace = frameBuffer.MemoryAllocator.Allocate(Av1BlockDecoder.GetWorkspaceLength(sequenceHeader)); Av1BlockDecoder decoder = new( sequenceHeader, frameHeader, frameBuffer, - loopFilterContext, referenceFrames, workspace.Memory); @@ -979,15 +952,11 @@ public class Av1CompoundBlockDecoderTests using Av1FrameInfo frameInfo = new(sequenceHeader); Av1SuperblockInfo superblockInfo = frameInfo.GetSuperblock(Point.Empty); superblockInfo.GetTransformInfoY()[0] = new Av1TransformInfo(Av1TransformSize.Size8x8, 0, 0); - using Av1LoopFilterContext loopFilterContext = - new(frameBuffer.MemoryAllocator, sequenceHeader, frameHeader); - using IMemoryOwner workspace = frameBuffer.MemoryAllocator.Allocate(Av1BlockDecoder.GetWorkspaceLength(sequenceHeader)); Av1BlockDecoder decoder = new( sequenceHeader, frameHeader, frameBuffer, - loopFilterContext, referenceFrames, workspace.Memory); @@ -1375,15 +1344,11 @@ public class Av1CompoundBlockDecoderTests using Av1FrameInfo frameInfo = new(sequenceHeader); Av1SuperblockInfo superblockInfo = frameInfo.GetSuperblock(Point.Empty); superblockInfo.GetTransformInfoY()[0] = new Av1TransformInfo(Av1TransformSize.Size8x8, 0, 0); - using Av1LoopFilterContext loopFilterContext = - new(frameBuffer.MemoryAllocator, sequenceHeader, frameHeader); - using IMemoryOwner workspace = frameBuffer.MemoryAllocator.Allocate(Av1BlockDecoder.GetWorkspaceLength(sequenceHeader)); Av1BlockDecoder decoder = new( sequenceHeader, frameHeader, frameBuffer, - loopFilterContext, referenceFrames, workspace.Memory); @@ -1585,15 +1550,11 @@ public class Av1CompoundBlockDecoderTests modeInfo.InterpolationFilters.Clear(); modeInfo.SetTransformUnitCount(Av1PlaneType.Y, 1); - using Av1LoopFilterContext loopFilterContext = - new(frameBuffer.MemoryAllocator, sequenceHeader, frameHeader); - using IMemoryOwner workspace = frameBuffer.MemoryAllocator.Allocate(Av1BlockDecoder.GetWorkspaceLength(sequenceHeader)); Av1BlockDecoder decoder = new( sequenceHeader, frameHeader, frameBuffer, - loopFilterContext, referenceFrames, workspace.Memory); diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1DeblockingFilterTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1DeblockingFilterTests.cs index df8237eb2c..4d1bb4fc4a 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1DeblockingFilterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1DeblockingFilterTests.cs @@ -196,12 +196,14 @@ public class Av1DeblockingFilterTests { Skip = true, YMode = mode, + TransformSize = Av1TransformSize.Size8x8, }; Av1BlockModeInfo rightModeInfo = new(Av1BlockSize.Block16x8, new Point(4, 0)) { Skip = true, YMode = mode, + TransformSize = Av1TransformSize.Size8x8, }; leftModeInfo.ReferenceFrames[0] = referenceFrame; @@ -209,14 +211,6 @@ public class Av1DeblockingFilterTests frameInfo.UpdateModeInfo(leftModeInfo, superblock); frameInfo.UpdateModeInfo(rightModeInfo, superblock); - using Av1LoopFilterContext loopFilterContext = - new(frameBuffer.MemoryAllocator, sequenceHeader, frameHeader); - - loopFilterContext.SetTransformSize(Av1Plane.Y, Point.Empty, Av1TransformSize.Size8x8); - loopFilterContext.SetTransformSize(Av1Plane.Y, new Point(2, 0), Av1TransformSize.Size8x8); - loopFilterContext.SetTransformSize(Av1Plane.Y, new Point(4, 0), Av1TransformSize.Size8x8); - loopFilterContext.SetTransformSize(Av1Plane.Y, new Point(6, 0), Av1TransformSize.Size8x8); - byte[] expected = new byte[width * height]; for (int row = 0; row < height; row++) { @@ -234,7 +228,7 @@ public class Av1DeblockingFilterTests ApplyReference(expected, true, edge, 8, limit, boundaryLimit, highEdgeVarianceThreshold, 8); ApplyReference(expected, true, (4 * width) + edge, 8, limit, boundaryLimit, highEdgeVarianceThreshold, 8); - Av1LoopFilterDecoder decoder = new(sequenceHeader, frameHeader, frameInfo, frameBuffer, loopFilterContext); + Av1LoopFilterDecoder decoder = new(sequenceHeader, frameHeader, frameInfo, frameBuffer); decoder.DecodeFrame(); for (int row = 0; row < height; row++) diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EncoderFrameTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EncoderFrameTests.cs index cca45bdd63..db8ff34954 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EncoderFrameTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EncoderFrameTests.cs @@ -5,6 +5,7 @@ using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Heif; using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.Formats.Heif.Av1.Color; using SixLabors.ImageSharp.Formats.Heif.Av1.Motion; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; @@ -159,7 +160,22 @@ public class Av1EncoderFrameTests effort: 0); using Av1Decoder decoder = new(Configuration.Default); - using Image decoded = decoder.Decode(stream.ToArray()); + using Av1FrameBuffer decodedPlanes = decoder.DecodeFrameBuffer(stream.ToArray(), null, null, out _); + using Image decoded = new(Configuration.Default, decodedPlanes.Width, decodedPlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + decodedPlanes, + decoded.Bounds, + decoded.Frames.RootFrame.PixelBuffer.GetRegion(decoded.Bounds), + decoded.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + decodedPlanes.ColorConfig.ColorRange); Assert.Equal(2, decoder.FrameHeader.TilesInfo.TileColumnCount); Assert.Equal(1, decoder.FrameHeader.TilesInfo.TileRowCount); @@ -236,21 +252,24 @@ public class Av1EncoderFrameTests effort: 5); byte[] payload = stream.ToArray(); - string outputDirectory = Path.Combine( - TestEnvironment.ActualOutputDirectoryFullPath, - "Formats", - "Heif", - "Av1"); - - Directory.CreateDirectory(outputDirectory); - string contentName = hasGradient ? "gradient" : "constant"; - int bitCount = bitDepth.GetBitCount(); - string colorName = colorFormat.ToString()[3..]; - string fileName = $"encoder-frame-{width}x{height}-{bitCount}b-{colorName}-{contentName}.obu"; - File.WriteAllBytes(Path.Combine(outputDirectory, fileName), payload); using Av1Decoder decoder = new(Configuration.Default); - using Image decoded = decoder.Decode(payload); + using Av1FrameBuffer decodedPlanes = decoder.DecodeFrameBuffer(payload, null, null, out _); + using Image decoded = new(Configuration.Default, decodedPlanes.Width, decodedPlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + decodedPlanes, + decoded.Bounds, + decoded.Frames.RootFrame.PixelBuffer.GetRegion(decoded.Bounds), + decoded.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + decodedPlanes.ColorConfig.ColorRange); Assert.Equal(width, decoded.Width); Assert.Equal(height, decoded.Height); @@ -332,7 +351,23 @@ public class Av1EncoderFrameTests ObuSequenceHeader encodedHeader = encoder.SequenceHeader; byte[] payload = stream.ToArray(); using Av1Decoder decoder = new(Configuration.Default); - using Image decoded = decoder.Decode(payload); + using Av1FrameBuffer decodedPlanes = decoder.DecodeFrameBuffer(payload, null, null, out _); + using Image decoded = new(Configuration.Default, decodedPlanes.Width, decodedPlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + decodedPlanes, + decoded.Bounds, + decoded.Frames.RootFrame.PixelBuffer.GetRegion(decoded.Bounds), + decoded.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + decodedPlanes.ColorConfig.ColorRange); + ObuSequenceHeader decodedHeader = decoder.SequenceHeader; Assert.False(encodedHeader.IsStillPicture); @@ -427,14 +462,6 @@ public class Av1EncoderFrameTests effort, speed); - string outputFolder = width == 23 ? nameof(SequenceEncoderPreservesNativeColorPlanesWithSubpixelMotion) - : nameof(SequenceEncoderPreservesNativeColorPlanesAcrossMotionCostRefreshRows); - - string outputDirectory = TestEnvironment.CreateOutputDirectory("Heif", "Av1", outputFolder); - - string outputName = $"{bitDepth.GetBitCount()}-{colorFormat}-effort{effort}-speed{(int)speed}"; - 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 < 3; frameIndex++) @@ -466,8 +493,6 @@ public class Av1EncoderFrameTests encoder.EncodeInterFrame(source.Frames.RootFrame, sample); } - sample.Position = 0; - sample.CopyTo(output); decoder.DecodeSequenceReference(sample.ToArray(), null, null); Assert.True(Assert.IsType(decoder.SequenceHeader).EnableIntraEdgeFilter); Av1FrameBuffer decoded = Assert.IsType>(decoder.FrameBuffer); @@ -484,33 +509,6 @@ public class Av1EncoderFrameTests Assert.False(mode.Skip); } } - - 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); @@ -570,21 +568,19 @@ public class Av1EncoderFrameTests encoder.EncodeKeyFrame(source.Frames.RootFrame, firstSample); encoder.EncodeInterFrame(source.Frames.RootFrame, secondSample); - // Retain the exact two-sample elementary stream for independent reference-decoder acceptance. - string outputDirectory = TestEnvironment.CreateOutputDirectory("Heif", "Av1", nameof(this.SequenceEncoderUsesRetainedReconstructionForInterFrame)); - using (FileStream output = File.Create(Path.Combine(outputDirectory, $"effort-{effort}.obu"))) - { - firstSample.Position = 0; - firstSample.CopyTo(output); - secondSample.Position = 0; - secondSample.CopyTo(output); - } - using Av1Decoder decoder = new(Configuration.Default); - using ImageFrame decodedFirst = decoder.DecodeSequenceFrame( + using ImageFrame decodedFirst = new(Configuration.Default, Width, Height); + decoder.DecodeSequenceFrame( firstSample.ToArray(), null, - null); + null, + decodedFirst.Size, + decodedFirst.Bounds, + decodedFirst.PixelBuffer.GetRegion(decodedFirst.Bounds), + default, + null, + null, + false); Av1FrameInfo firstFrameInfo = Assert.IsType(decoder.FrameInfo); foreach (Av1BlockModeInfo mode in firstFrameInfo.GetModeInfos(Point.Empty, firstFrameInfo.GetModeInfoCount(Point.Empty))) @@ -594,10 +590,18 @@ public class Av1EncoderFrameTests Assert.False(mode.Skip); } - using ImageFrame decodedSecond = decoder.DecodeSequenceFrame( + using ImageFrame decodedSecond = new(Configuration.Default, Width, Height); + decoder.DecodeSequenceFrame( secondSample.ToArray(), null, - null); + null, + decodedSecond.Size, + decodedSecond.Bounds, + decodedSecond.PixelBuffer.GetRegion(decodedSecond.Bounds), + default, + null, + null, + false); ObuFrameHeader frameHeader = decoder.FrameHeader; Assert.Equal(ObuFrameType.InterFrame, frameHeader.FrameType); @@ -669,15 +673,31 @@ public class Av1EncoderFrameTests encoder.EncodeInterFrame(second.Frames.RootFrame, secondSample); using Av1Decoder decoder = new(Configuration.Default); - using ImageFrame decodedFirst = decoder.DecodeSequenceFrame( + using ImageFrame decodedFirst = new(Configuration.Default, Width, Height); + decoder.DecodeSequenceFrame( firstSample.ToArray(), null, - null); + null, + decodedFirst.Size, + decodedFirst.Bounds, + decodedFirst.PixelBuffer.GetRegion(decodedFirst.Bounds), + default, + null, + null, + false); - using ImageFrame decodedSecond = decoder.DecodeSequenceFrame( + using ImageFrame decodedSecond = new(Configuration.Default, Width, Height); + decoder.DecodeSequenceFrame( secondSample.ToArray(), null, - null); + null, + decodedSecond.Size, + decodedSecond.Bounds, + decodedSecond.PixelBuffer.GetRegion(decodedSecond.Bounds), + default, + null, + null, + false); ObuFrameHeader frameHeader = decoder.FrameHeader; Av1GlobalMotionParameters globalMotion = frameHeader.GetGlobalMotionParameters()[0]; @@ -767,7 +787,7 @@ public class Av1EncoderFrameTests Assert.Equal(failureIndex, allocator.AllocationLog.Count); Assert.All( allocator.AllocationLog, - allocation => Assert.Single(allocator.ReturnLog, returned => returned.AllocationId == allocation.AllocationId)); + allocation => Assert.Single(allocator.ReturnLog, returned => returned.HashCodeOfBuffer == allocation.HashCodeOfBuffer)); Assert.Equal(allocator.AllocationLog.Count, allocator.ReturnLog.Count); } @@ -835,7 +855,7 @@ public class Av1EncoderFrameTests Assert.Equal(expectedRowStorageLength, rowStorage.Length); Assert.Contains( allocator.ReturnLog, - returned => returned.AllocationId == rowStorage.AllocationId); + returned => returned.HashCodeOfBuffer == rowStorage.HashCodeOfBuffer); } [Theory] @@ -897,17 +917,6 @@ public class Av1EncoderFrameTests effort); byte[] payload = stream.ToArray(); - string outputDirectory = Path.Combine( - TestEnvironment.ActualOutputDirectoryFullPath, - "Formats", - "Heif", - "Av1"); - - Directory.CreateDirectory(outputDirectory); - string outputName = $"encoder-frame-{width}x{height}-{bitDepth.GetBitCount()}b-444-lossless-effort{effort}"; - File.WriteAllBytes( - Path.Combine(outputDirectory, outputName + ".obu"), - payload); using Av1Decoder decoder = new(Configuration.Default); using Av1FrameBuffer actual = decoder.DecodeFrameBuffer(payload, null, null, out _); @@ -919,9 +928,6 @@ public class Av1EncoderFrameTests Assert.True(frameHeader.AllLossless); Assert.Equal(Av1TransformMode.Only4x4, frameHeader.TransformMode); - // Lossless native planes are the oracle for external decoding, not the packed RGB conversion on return. - // UInt16 raw samples are explicitly little-endian even when these tests run on a different host byte order. - using BinaryWriter rawOutput = new(File.Create(Path.Combine(outputDirectory, outputName + ".source.yuv"))); foreach (Av1Plane plane in new[] { Av1Plane.Y, Av1Plane.U, Av1Plane.V }) { Buffer2DRegion expectedPlane = expected.Frame.View.GetPlane(plane); @@ -929,10 +935,6 @@ public class Av1EncoderFrameTests { ReadOnlySpan expectedRow = expectedPlane.DangerousGetRowSpan(row); Assert.Equal(expectedRow, actual.GetHighBitDepthRowSpan(plane, row, 0, 0)); - foreach (ushort sample in expectedRow) - { - rawOutput.Write(sample); - } } } } @@ -967,20 +969,15 @@ public class Av1EncoderFrameTests using MemoryStream stream = new(); Av1FrameEncoder.Encode(Configuration.Default, source.Frames.RootFrame, stream, colorConfig, qIndex: 0, effort); byte[] payload = stream.ToArray(); - string outputDirectory = TestEnvironment.CreateOutputDirectory("Heif", "Av1", nameof(this.EncodeLosslessPartitionSearchAcrossClippedSuperblocks)); - string outputName = $"{width}x{height}-effort{effort}"; - File.WriteAllBytes(Path.Combine(outputDirectory, outputName + ".obu"), payload); using Av1Decoder decoder = new(Configuration.Default); using Av1FrameBuffer decoded = decoder.DecodeFrameBuffer(payload, null, null, out _); Assert.Equal(width, decoded.Width); Assert.Equal(height, decoded.Height); Buffer2DRegion actual = decoded.DeriveBlockPointer(Av1Plane.Y, 0, 0); - using FileStream rawOutput = File.Create(Path.Combine(outputDirectory, outputName + ".source.yuv")); for (int y = 0; y < height; y++) { ReadOnlySpan expectedRow = MemoryMarshal.AsBytes(source.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(y)); Assert.Equal(expectedRow, actual.DangerousGetRowSpan(y)); - rawOutput.Write(expectedRow); } } @@ -1027,7 +1024,23 @@ public class Av1EncoderFrameTests byte[] payload = stream.ToArray(); using Av1Decoder decoder = new(Configuration.Default); - using Image decoded = decoder.Decode(payload); + using Av1FrameBuffer decodedPlanes = decoder.DecodeFrameBuffer(payload, null, null, out _); + using Image decoded = new(Configuration.Default, decodedPlanes.Width, decodedPlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + decodedPlanes, + decoded.Bounds, + decoded.Frames.RootFrame.PixelBuffer.GetRegion(decoded.Bounds), + decoded.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + decodedPlanes.ColorConfig.ColorRange); + Av1FrameInfo frameInfo = Assert.IsType(decoder.FrameInfo); Point[] leafPositions = [ @@ -1090,7 +1103,23 @@ public class Av1EncoderFrameTests byte[] payload = stream.ToArray(); using Av1Decoder decoder = new(Configuration.Default); - using Image decoded = decoder.Decode(payload); + using Av1FrameBuffer decodedPlanes = decoder.DecodeFrameBuffer(payload, null, null, out _); + using Image decoded = new(Configuration.Default, decodedPlanes.Width, decodedPlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + decodedPlanes, + decoded.Bounds, + decoded.Frames.RootFrame.PixelBuffer.GetRegion(decoded.Bounds), + decoded.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + decodedPlanes.ColorConfig.ColorRange); + Av1FrameInfo frameInfo = Assert.IsType(decoder.FrameInfo); for (int modeInfoY = 4; modeInfoY < 8; modeInfoY++) { @@ -1130,7 +1159,23 @@ public class Av1EncoderFrameTests byte[] payload = stream.ToArray(); using Av1Decoder decoder = new(Configuration.Default); - using Image decoded = decoder.Decode(payload); + using Av1FrameBuffer decodedPlanes = decoder.DecodeFrameBuffer(payload, null, null, out _); + using Image decoded = new(Configuration.Default, decodedPlanes.Width, decodedPlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + decodedPlanes, + decoded.Bounds, + decoded.Frames.RootFrame.PixelBuffer.GetRegion(decoded.Bounds), + decoded.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + decodedPlanes.ColorConfig.ColorRange); + Av1FrameInfo frameInfo = Assert.IsType(decoder.FrameInfo); for (int modeInfoY = 0; modeInfoY < 8; modeInfoY++) { @@ -1173,7 +1218,23 @@ public class Av1EncoderFrameTests byte[] payload = stream.ToArray(); using Av1Decoder decoder = new(Configuration.Default); - using Image decoded = decoder.Decode(payload); + using Av1FrameBuffer decodedPlanes = decoder.DecodeFrameBuffer(payload, null, null, out _); + using Image decoded = new(Configuration.Default, decodedPlanes.Width, decodedPlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + decodedPlanes, + decoded.Bounds, + decoded.Frames.RootFrame.PixelBuffer.GetRegion(decoded.Bounds), + decoded.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + decodedPlanes.ColorConfig.ColorRange); + Av1FrameInfo frameInfo = Assert.IsType(decoder.FrameInfo); for (int modeInfoY = 0; modeInfoY < 16; modeInfoY++) { @@ -1217,7 +1278,23 @@ public class Av1EncoderFrameTests Assert.True(sequenceHeader.Use128x128Superblock); byte[] payload = stream.ToArray(); using Av1Decoder decoder = new(Configuration.Default); - using Image decoded = decoder.Decode(payload); + using Av1FrameBuffer decodedPlanes = decoder.DecodeFrameBuffer(payload, null, null, out _); + using Image decoded = new(Configuration.Default, decodedPlanes.Width, decodedPlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + decodedPlanes, + decoded.Bounds, + decoded.Frames.RootFrame.PixelBuffer.GetRegion(decoded.Bounds), + decoded.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + decodedPlanes.ColorConfig.ColorRange); + Av1FrameInfo frameInfo = Assert.IsType(decoder.FrameInfo); for (int modeInfoY = 0; modeInfoY < 32; modeInfoY++) { @@ -1258,7 +1335,23 @@ public class Av1EncoderFrameTests Assert.True(sequenceHeader.Use128x128Superblock); byte[] payload = stream.ToArray(); using Av1Decoder decoder = new(Configuration.Default); - using Image decoded = decoder.Decode(payload); + using Av1FrameBuffer decodedPlanes = decoder.DecodeFrameBuffer(payload, null, null, out _); + using Image decoded = new(Configuration.Default, decodedPlanes.Width, decodedPlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + decodedPlanes, + decoded.Bounds, + decoded.Frames.RootFrame.PixelBuffer.GetRegion(decoded.Bounds), + decoded.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + decodedPlanes.ColorConfig.ColorRange); + Assert.Equal(new Size(Size, Size), decoded.Size); } @@ -1293,7 +1386,22 @@ public class Av1EncoderFrameTests byte[] payload = stream.ToArray(); using Av1Decoder decoder = new(Configuration.Default); - using Image decoded = decoder.Decode(payload); + using Av1FrameBuffer decodedPlanes = decoder.DecodeFrameBuffer(payload, null, null, out _); + using Image decoded = new(Configuration.Default, decodedPlanes.Width, decodedPlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + decodedPlanes, + decoded.Bounds, + decoded.Frames.RootFrame.PixelBuffer.GetRegion(decoded.Bounds), + decoded.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + decodedPlanes.ColorConfig.ColorRange); Assert.True(encodedHeader.ColorConfig.IsMonochrome); Assert.Equal( @@ -1305,17 +1413,6 @@ public class Av1EncoderFrameTests Assert.Equal(decoded[0, 0].R, decoded[0, 0].G); Assert.Equal(decoded[0, 0].R, decoded[0, 0].B); Assert.Equal(ushort.MaxValue, decoded[0, 0].A); - - string outputDirectory = Path.Combine( - TestEnvironment.ActualOutputDirectoryFullPath, - "Formats", - "Heif", - "Av1"); - - Directory.CreateDirectory(outputDirectory); - File.WriteAllBytes( - Path.Combine(outputDirectory, $"encoder-alpha-{Width}x{Height}-{bitDepth.GetBitCount()}b.obu"), - payload); } [Fact] @@ -1363,7 +1460,7 @@ public class Av1EncoderFrameTests Assert.Equal(typeof(float), allocation.ElementType); Assert.Equal(Width * 3, allocation.Length); TestMemoryAllocator.ReturnRequest returned = Assert.Single(allocator.ReturnLog); - Assert.Equal(allocation.AllocationId, returned.AllocationId); + Assert.Equal(allocation.HashCodeOfBuffer, returned.HashCodeOfBuffer); } [Theory] @@ -1588,17 +1685,24 @@ public class Av1EncoderFrameTests Assert.True(frameHeader.AllowScreenContentTools); Assert.True(frameHeader.AllowIntraBlockCopy); using Av1Decoder decoder = new(Configuration.Default); - using Image decoded = decoder.Decode(payload); - Assert.Equal(new Size(width, height), decoded.Size); - - string outputDirectory = Path.Combine( - TestEnvironment.ActualOutputDirectoryFullPath, - "Formats", - "Heif", - "Av1"); + using Av1FrameBuffer decodedPlanes = decoder.DecodeFrameBuffer(payload, null, null, out _); + using Image decoded = new(Configuration.Default, decodedPlanes.Width, decodedPlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + decodedPlanes, + decoded.Bounds, + decoded.Frames.RootFrame.PixelBuffer.GetRegion(decoded.Bounds), + decoded.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + decodedPlanes.ColorConfig.ColorRange); - Directory.CreateDirectory(outputDirectory); - File.WriteAllBytes(Path.Combine(outputDirectory, "encoder-frame-16x16-8b-444-palette.obu"), payload); + Assert.Equal(new Size(width, height), decoded.Size); } [Theory] @@ -1644,7 +1748,23 @@ public class Av1EncoderFrameTests byte[] payload = stream.ToArray(); using Av1Decoder decoder = new(Configuration.Default); - using Image decoded = decoder.Decode(payload); + using Av1FrameBuffer decodedPlanes = decoder.DecodeFrameBuffer(payload, null, null, out _); + using Image decoded = new(Configuration.Default, decodedPlanes.Width, decodedPlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + decodedPlanes, + decoded.Bounds, + decoded.Frames.RootFrame.PixelBuffer.GetRegion(decoded.Bounds), + decoded.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + decodedPlanes.ColorConfig.ColorRange); + ObuSequenceHeader sequenceHeader = Assert.IsType(decoder.SequenceHeader); ObuFrameHeader frameHeader = Assert.IsType(decoder.FrameHeader); Av1FrameInfo frameInfo = Assert.IsType(decoder.FrameInfo); @@ -1686,15 +1806,6 @@ public class Av1EncoderFrameTests } Assert.NotEqual(0, modeCount); - - string outputDirectory = Path.Combine( - TestEnvironment.ActualOutputDirectoryFullPath, - "Formats", - "Heif", - "Av1"); - - Directory.CreateDirectory(outputDirectory); - File.WriteAllBytes(Path.Combine(outputDirectory, $"encoder-frame-16x16-8b-444-effort-{effort}.obu"), payload); } [Fact] @@ -1725,7 +1836,23 @@ public class Av1EncoderFrameTests byte[] payload = stream.ToArray(); using Av1Decoder decoder = new(Configuration.Default); - using Image decoded = decoder.Decode(payload); + using Av1FrameBuffer decodedPlanes = decoder.DecodeFrameBuffer(payload, null, null, out _); + using Image decoded = new(Configuration.Default, decodedPlanes.Width, decodedPlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + decodedPlanes, + decoded.Bounds, + decoded.Frames.RootFrame.PixelBuffer.GetRegion(decoded.Bounds), + decoded.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + decodedPlanes.ColorConfig.ColorRange); + Assert.NotNull(decoder.FrameHeader); Assert.Equal(Av1TransformMode.Select, decoder.FrameHeader.TransformMode); Assert.NotNull(decoder.FrameInfo); @@ -1737,17 +1864,6 @@ public class Av1EncoderFrameTests Assert.True(foundSplitTransform); Assert.Equal(new Size(Width, Height), decoded.Size); - - string outputDirectory = Path.Combine( - TestEnvironment.ActualOutputDirectoryFullPath, - "Formats", - "Heif", - "Av1"); - - Directory.CreateDirectory(outputDirectory); - File.WriteAllBytes( - Path.Combine(outputDirectory, "encoder-frame-16x16-8b-400-transform-size-select.obu"), - payload); } [Theory] @@ -1785,7 +1901,23 @@ public class Av1EncoderFrameTests byte[] payload = stream.ToArray(); using Av1Decoder decoder = new(Configuration.Default); - using Image decoded = decoder.Decode(payload); + using Av1FrameBuffer decodedPlanes = decoder.DecodeFrameBuffer(payload, null, null, out _); + using Image decoded = new(Configuration.Default, decodedPlanes.Width, decodedPlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + decodedPlanes, + decoded.Bounds, + decoded.Frames.RootFrame.PixelBuffer.GetRegion(decoded.Bounds), + decoded.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + decodedPlanes.ColorConfig.ColorRange); + Assert.NotNull(decoder.FrameHeader); Assert.True(decoder.FrameHeader.AllowScreenContentTools); Assert.True(decoder.FrameHeader.AllowIntraBlockCopy); @@ -1802,19 +1934,6 @@ public class Av1EncoderFrameTests Assert.True(usesIntraBlockCopy); Assert.Equal(new Size(Width, Height), decoded.Size); - - string outputDirectory = Path.Combine( - TestEnvironment.ActualOutputDirectoryFullPath, - "Formats", - "Heif", - "Av1"); - - Directory.CreateDirectory(outputDirectory); - string fileName = effort == 5 - ? "encoder-frame-328x16-8b-444-intrabc.obu" - : "encoder-frame-328x16-8b-444-intrabc-effort-6.obu"; - - File.WriteAllBytes(Path.Combine(outputDirectory, fileName), payload); } [Fact] @@ -1972,7 +2091,7 @@ public class Av1EncoderFrameTests } TestMemoryAllocator.ReturnRequest returned = Assert.Single(allocator.ReturnLog); - Assert.Equal(allocation.AllocationId, returned.AllocationId); + Assert.Equal(allocation.HashCodeOfBuffer, returned.HashCodeOfBuffer); } [Fact] @@ -2019,8 +2138,8 @@ public class Av1EncoderFrameTests Assert.Equal(ExpectedTileOutputLength, tileOutput.Length); Assert.Equal(allocator.AllocationLog.Count, allocator.ReturnLog.Count); Assert.Equal( - allocator.AllocationLog.Select(allocation => allocation.AllocationId).Order(), - allocator.ReturnLog.Select(returned => returned.AllocationId).Order()); + allocator.AllocationLog.Select(allocation => allocation.HashCodeOfBuffer).Order(), + allocator.ReturnLog.Select(returned => returned.HashCodeOfBuffer).Order()); } private static ObuColorConfig CreateColorConfig( diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EncoderModeInfoBufferTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EncoderModeInfoBufferTests.cs index b10d3a153b..69995edf98 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EncoderModeInfoBufferTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EncoderModeInfoBufferTests.cs @@ -45,7 +45,7 @@ public class Av1EncoderModeInfoBufferTests } TestMemoryAllocator.ReturnRequest returned = Assert.Single(allocator.ReturnLog); - Assert.Equal(allocation.AllocationId, returned.AllocationId); + Assert.Equal(allocation.HashCodeOfBuffer, returned.HashCodeOfBuffer); } [Theory] @@ -251,8 +251,8 @@ public class Av1EncoderModeInfoBufferTests Assert.Equal(2, allocator.ReturnLog.Count); Assert.Equal( - allocations.Select(x => x.AllocationId).Order(), - allocator.ReturnLog.Select(x => x.AllocationId).Order()); + allocations.Select(x => x.HashCodeOfBuffer).Order(), + allocator.ReturnLog.Select(x => x.HashCodeOfBuffer).Order()); } [Fact] diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs index fe7710b630..c5645ee8ef 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EntropyTests.cs @@ -1048,8 +1048,8 @@ public class Av1EntropyTests } Assert.Equal( - allocator.AllocationLog.Select(x => x.AllocationId).Order(), - allocator.ReturnLog.Select(x => x.AllocationId).Order()); + allocator.AllocationLog.Select(x => x.HashCodeOfBuffer).Order(), + allocator.ReturnLog.Select(x => x.HashCodeOfBuffer).Order()); } [Theory] @@ -1089,8 +1089,8 @@ public class Av1EntropyTests } Assert.Equal( - Assert.Single(allocator.AllocationLog).AllocationId, - Assert.Single(allocator.ReturnLog).AllocationId); + Assert.Single(allocator.AllocationLog).HashCodeOfBuffer, + Assert.Single(allocator.ReturnLog).HashCodeOfBuffer); } [Fact] @@ -1149,7 +1149,7 @@ public class Av1EntropyTests } TestMemoryAllocator.ReturnRequest returned = Assert.Single(allocator.ReturnLog); - Assert.Equal(allocation.AllocationId, returned.AllocationId); + Assert.Equal(allocation.HashCodeOfBuffer, returned.HashCodeOfBuffer); } /// @@ -1237,8 +1237,8 @@ public class Av1EntropyTests Assert.Equal(3, allocator.ReturnLog.Count); Assert.Equal( - allocator.AllocationLog.Select(x => x.AllocationId).Order(), - allocator.ReturnLog.Select(x => x.AllocationId).Order()); + allocator.AllocationLog.Select(x => x.HashCodeOfBuffer).Order(), + allocator.ReturnLog.Select(x => x.HashCodeOfBuffer).Order()); } [Fact] diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1FrameBufferTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1FrameBufferTests.cs index 219044c607..6efa0adecc 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1FrameBufferTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1FrameBufferTests.cs @@ -6,9 +6,7 @@ using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; -using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.LoopFilter; using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.LoopRestoration; -using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline.Quantizers; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.ChromaFromLuma; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction.Inter; @@ -103,18 +101,18 @@ public class Av1FrameBufferTests { int oldOwner = Assert.Single( allocator.AllocationLog, - x => x.ElementType == typeof(short) && x.Length == workspaceLengths[0]).AllocationId; + x => x.ElementType == typeof(short) && x.Length == workspaceLengths[0]).HashCodeOfBuffer; - Assert.Single(allocator.ReturnLog, x => x.AllocationId == oldOwner); + Assert.Single(allocator.ReturnLog, x => x.HashCodeOfBuffer == oldOwner); } } using Av1FrameBuffer frame = decoder.DecodeFrameBuffer(payload, null, null, out _); retainedLength = Math.Max(retainedLength, workspaceLengths[index]); int owner = Assert.Single( - allocator.AllocationLog, x => x.ElementType == typeof(short) && x.Length == retainedLength).AllocationId; + allocator.AllocationLog, x => x.ElementType == typeof(short) && x.Length == retainedLength).HashCodeOfBuffer; - Assert.DoesNotContain(allocator.ReturnLog, x => x.AllocationId == owner); + Assert.DoesNotContain(allocator.ReturnLog, x => x.HashCodeOfBuffer == owner); Buffer2DRegion luma = frame.DeriveBlockPointer(Av1Plane.Y, 0, 0); for (int row = 0; row < frame.Height; row++) { @@ -124,7 +122,7 @@ public class Av1FrameBufferTests } Assert.Equal(allocator.AllocationLog.Count, allocator.ReturnLog.Count); - Assert.All(allocator.AllocationLog, allocation => Assert.Single(allocator.ReturnLog, x => x.AllocationId == allocation.AllocationId)); + Assert.All(allocator.AllocationLog, allocation => Assert.Single(allocator.ReturnLog, x => x.HashCodeOfBuffer == allocation.HashCodeOfBuffer)); } /// @@ -195,7 +193,7 @@ public class Av1FrameBufferTests Assert.Equal(7, allocator.AllocationAttemptCount); Assert.Equal(6, allocator.AllocationLog.Count); Assert.Equal(allocator.AllocationLog.Count, allocator.ReturnLog.Count); - Assert.All(allocator.AllocationLog, allocation => Assert.Single(allocator.ReturnLog, x => x.AllocationId == allocation.AllocationId)); + Assert.All(allocator.AllocationLog, allocation => Assert.Single(allocator.ReturnLog, x => x.HashCodeOfBuffer == allocation.HashCodeOfBuffer)); } /// @@ -313,7 +311,7 @@ public class Av1FrameBufferTests Assert.Equal(allocator.AllocationLog.Count, allocator.ReturnLog.Count); foreach (TestMemoryAllocator.AllocationRequest allocation in allocator.AllocationLog) { - Assert.Single(allocator.ReturnLog, returned => returned.AllocationId == allocation.AllocationId); + Assert.Single(allocator.ReturnLog, returned => returned.HashCodeOfBuffer == allocation.HashCodeOfBuffer); } } @@ -336,7 +334,7 @@ public class Av1FrameBufferTests using (Av1FrameBuffer restored = Av1FrameBuffer.CreateRestoration(allocator, sequence, small)) { Assert.Throws(() => restored.ResizeRestoration(sequence, large)); - Assert.Equal(Assert.Single(allocator.AllocationLog).AllocationId, Assert.Single(allocator.ReturnLog).AllocationId); + Assert.Equal(Assert.Single(allocator.AllocationLog).HashCodeOfBuffer, Assert.Single(allocator.ReturnLog).HashCodeOfBuffer); restored.ResizeRestoration(sequence, large); Assert.Equal(3, allocator.AllocationAttemptCount); @@ -350,7 +348,7 @@ public class Av1FrameBufferTests Assert.Equal(allocator.AllocationLog.Count, allocator.ReturnLog.Count); foreach (TestMemoryAllocator.AllocationRequest allocation in allocator.AllocationLog) { - Assert.Single(allocator.ReturnLog, returned => returned.AllocationId == allocation.AllocationId); + Assert.Single(allocator.ReturnLog, returned => returned.HashCodeOfBuffer == allocation.HashCodeOfBuffer); } } @@ -497,7 +495,7 @@ public class Av1FrameBufferTests Assert.Equal(allocator.AllocationLog.Count, allocator.ReturnLog.Count); foreach (TestMemoryAllocator.AllocationRequest allocation in allocator.AllocationLog) { - Assert.Single(allocator.ReturnLog, returned => returned.AllocationId == allocation.AllocationId); + Assert.Single(allocator.ReturnLog, returned => returned.HashCodeOfBuffer == allocation.HashCodeOfBuffer); } } @@ -547,7 +545,7 @@ public class Av1FrameBufferTests Assert.Equal(allocator.AllocationLog.Count, allocator.ReturnLog.Count); foreach (TestMemoryAllocator.AllocationRequest allocation in allocator.AllocationLog) { - Assert.Single(allocator.ReturnLog, returned => returned.AllocationId == allocation.AllocationId); + Assert.Single(allocator.ReturnLog, returned => returned.HashCodeOfBuffer == allocation.HashCodeOfBuffer); } } @@ -646,7 +644,7 @@ public class Av1FrameBufferTests } TestMemoryAllocator.ReturnRequest returned = Assert.Single(allocator.ReturnLog); - Assert.Equal(allocation.AllocationId, returned.AllocationId); + Assert.Equal(allocation.HashCodeOfBuffer, returned.HashCodeOfBuffer); } /// @@ -747,7 +745,7 @@ public class Av1FrameBufferTests } TestMemoryAllocator.ReturnRequest returned = Assert.Single(allocator.ReturnLog); - Assert.Equal(allocation.AllocationId, returned.AllocationId); + Assert.Equal(allocation.HashCodeOfBuffer, returned.HashCodeOfBuffer); } /// @@ -793,9 +791,6 @@ public class Av1FrameBufferTests ModeInfoRowCount = 16 }; - using Av1LoopFilterContext loopFilterContext = - new(Configuration.Default.MemoryAllocator, sequenceHeader, frameHeader); - using Av1ReferenceFrameStore referenceFrames = new(); // Reset frame-plane logs so these assertions describe the supplied workspace and the borrowing decoder. @@ -826,7 +821,6 @@ public class Av1FrameBufferTests sequenceHeader, frameHeader, frameBuffer, - loopFilterContext, referenceFrames, workspace.Memory); @@ -837,44 +831,7 @@ public class Av1FrameBufferTests } TestMemoryAllocator.ReturnRequest returned = Assert.Single(allocator.ReturnLog); - Assert.Equal(workspaceAllocation.AllocationId, returned.AllocationId); - } - - /// - /// Verifies that failure to allocate the active chroma transform map releases the preceding luma map. - /// - [Fact] - public void LoopFilterContextAllocationFailureReleasesLumaMap() - { - FailingTestMemoryAllocator allocator = new(failureAllocationNumber: 2); - ObuSequenceHeader sequenceHeader = new() - { - MaxFrameWidth = 64, - MaxFrameHeight = 64, - Use128x128Superblock = false, - ColorConfig = new ObuColorConfig - { - IsMonochrome = false, - SubSamplingX = true, - SubSamplingY = true, - BitDepth = Av1BitDepth.EightBit - } - }; - - ObuFrameHeader frameHeader = new() - { - ModeInfoColumnCount = 16, - ModeInfoRowCount = 16 - }; - - Assert.Throws( - () => new Av1LoopFilterContext(allocator, sequenceHeader, frameHeader)); - - TestMemoryAllocator.AllocationRequest allocation = Assert.Single(allocator.AllocationLog); - TestMemoryAllocator.ReturnRequest returned = Assert.Single(allocator.ReturnLog); - - Assert.Equal(2, allocator.AllocationAttemptCount); - Assert.Equal(allocation.HashCodeOfBuffer, returned.HashCodeOfBuffer); + Assert.Equal(workspaceAllocation.HashCodeOfBuffer, returned.HashCodeOfBuffer); } /// diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraSuperblockEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraSuperblockEncoderTests.cs index c5147d9680..96804cf25d 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraSuperblockEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1IntraSuperblockEncoderTests.cs @@ -6,6 +6,7 @@ using System.Numerics; using System.Runtime.InteropServices; using SixLabors.ImageSharp.Formats.Heif; using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.Formats.Heif.Av1.Color; using SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; using SixLabors.ImageSharp.Formats.Heif.Av1.Motion; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; @@ -165,24 +166,36 @@ public class Av1IntraSuperblockEncoderTests // Sequence decoding retains the first frame's reference slots. The still-image transfer API deliberately // releases those slots, so it cannot be used between dependent samples. Full-range monochrome L8 is exact. - using ImageFrame decodedFirst = decoder.DecodeSequenceFrame(firstSample.ToArray(), null, null); - using ImageFrame decodedSecond = decoder.DecodeSequenceFrame(secondSample.ToArray(), null, null); - - // Preserve both the production stream and every managed reconstructed luma sample for exact libaom comparison. - string outputDirectory = TestEnvironment.CreateOutputDirectory("Heif", "Av1", nameof(this.ProductionTileSelectsNonRegularInterpolation)); - string outputName = $"{filter}-{dualFilter}"; - using FileStream output = File.Create(Path.Combine(outputDirectory, outputName + ".obu")); - firstSample.Position = 0; - firstSample.CopyTo(output); - secondSample.Position = 0; - secondSample.CopyTo(output); - using FileStream rawOutput = File.Create(Path.Combine(outputDirectory, outputName + ".managed.yuv")); + using ImageFrame decodedFirst = new(configuration, Width, Height); + decoder.DecodeSequenceFrame( + firstSample.ToArray(), + null, + null, + decodedFirst.Size, + decodedFirst.Bounds, + decodedFirst.PixelBuffer.GetRegion(decodedFirst.Bounds), + default, + null, + null, + false); + using ImageFrame decodedSecond = new(configuration, Width, Height); + decoder.DecodeSequenceFrame( + secondSample.ToArray(), + null, + null, + decodedSecond.Size, + decodedSecond.Bounds, + decodedSecond.PixelBuffer.GetRegion(decodedSecond.Bounds), + default, + null, + null, + false); + for (int y = 0; y < Height; y++) { ReadOnlySpan expected = reference.Frame.View.GetPlane(Av1Plane.Y).DangerousGetRowSpan(y); ReadOnlySpan actual = MemoryMarshal.AsBytes(decodedFirst.PixelBuffer.DangerousGetRowSpan(y)); Assert.Equal(expected, actual); - rawOutput.Write(actual); } for (int y = 0; y < Height; y++) @@ -190,7 +203,6 @@ public class Av1IntraSuperblockEncoderTests ReadOnlySpan expected = reconstruction.Frame.View.GetPlane(Av1Plane.Y).DangerousGetRowSpan(y); ReadOnlySpan actual = MemoryMarshal.AsBytes(decodedSecond.PixelBuffer.DangerousGetRowSpan(y)); Assert.Equal(expected, actual); - rawOutput.Write(actual); } } @@ -330,19 +342,9 @@ public class Av1IntraSuperblockEncoderTests using MemoryStream secondSample = new(); using ObuWriter obuWriter = new(configuration); obuWriter.WriteFrame(secondSample, sequenceHeader, frameHeader, tileWriter); - string outputDirectory = TestEnvironment.CreateOutputDirectory("Heif", "Av1", nameof(this.ProductionTileSelectsDualAxisInterpolationHighBitDepth)); - string outputName = $"{bitDepth}-{horizontalFilter}-{verticalFilter}"; - using FileStream output = File.Create(Path.Combine(outputDirectory, outputName + ".obu")); - firstSample.Position = 0; - firstSample.CopyTo(output); - secondSample.Position = 0; - secondSample.CopyTo(output); - using BinaryWriter rawOutput = new(File.Create(Path.Combine(outputDirectory, outputName + ".managed.yuv"))); using Av1Decoder decoder = new(configuration); for (int frameIndex = 0; frameIndex < 2; frameIndex++) { - // Consume native retained planes before the next sample can replace them. BinaryWriter emits explicit - // little-endian UInt16 samples, matching the raw reference-decoder output independently of host byte order. decoder.DecodeSequenceReference((frameIndex == 0 ? firstSample : secondSample).ToArray(), null, null); Av1FrameBuffer decoded = Assert.IsType>(decoder.FrameBuffer); Buffer2DRegion expected = (frameIndex == 0 ? reference : reconstruction).Frame.View.GetPlane(Av1Plane.Y); @@ -350,10 +352,6 @@ public class Av1IntraSuperblockEncoderTests { ReadOnlySpan actualRow = decoded.GetHighBitDepthRowSpan(Av1Plane.Y, y, 0, 0); Assert.Equal(expected.DangerousGetRowSpan(y), actualRow); - foreach (ushort sample in actualRow) - { - rawOutput.Write(sample); - } } } } @@ -1945,20 +1943,6 @@ public class Av1IntraSuperblockEncoderTests } } - string outputDirectory = Path.Combine(TestEnvironment.ActualOutputDirectoryFullPath, "Formats", "Heif", "Av1"); - string outputName = $"encoder-palette-chroma-{width}x{height}-{subX}-{subY}-{useLumaPalette}"; - Directory.CreateDirectory(outputDirectory); - File.WriteAllBytes(Path.Combine(outputDirectory, outputName + ".obu"), payload); - using FileStream raw = File.Create(Path.Combine(outputDirectory, outputName + ".retained.yuv")); - for (int plane = 0; plane < 3; plane++) - { - Buffer2DRegion retained = reconstruction.Frame.View.GetPlane((Av1Plane)plane); - for (int row = 0; row < retained.Height; row++) - { - raw.Write(retained.DangerousGetRowSpan(row)); - } - } - Assert.NotEqual(0, tileWriter.GetTileData(0).Length); } @@ -3009,24 +2993,29 @@ public class Av1IntraSuperblockEncoderTests byte[] payload = WriteCompleteTileObu(pictureTemplate, tileWriter, Width, Height); using Av1Decoder decoder = new(Configuration.Default); - using Image decoded = decoder.Decode(payload); + using Av1FrameBuffer decodedPlanes = decoder.DecodeFrameBuffer(payload, null, null, out _); + using Image decoded = new(Configuration.Default, decodedPlanes.Width, decodedPlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + decodedPlanes, + decoded.Bounds, + decoded.Frames.RootFrame.PixelBuffer.GetRegion(decoded.Bounds), + decoded.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + decodedPlanes.ColorConfig.ColorRange); + Assert.NotNull(decoder.FrameInfo); Av1BlockModeInfo decodedBlock = decoder.FrameInfo.GetModeInfoAt(new Point(2, 2)); Assert.True(decodedBlock.UseFilterIntra); Assert.Equal(filterIntraMode, decodedBlock.FilterIntraMode); Assert.Equal(4, decodedBlock.GetTransformUnitCount(Av1Plane.Y)); Assert.Equal(new Size(Width, Height), decoded.Size); - - string outputDirectory = Path.Combine( - TestEnvironment.ActualOutputDirectoryFullPath, - "Formats", - "Heif", - "Av1"); - - Directory.CreateDirectory(outputDirectory); - File.WriteAllBytes( - Path.Combine(outputDirectory, $"encoder-filter-intra-transform-size-select-{bitDepth}b.obu"), - payload); } Assert.NotEqual(0, pilotWriter.GetTileData(0).Length); @@ -3665,16 +3654,6 @@ public class Av1IntraSuperblockEncoderTests } Assert.True(hasMixedPartition); - string directory = Path.Combine( - TestEnvironment.ActualOutputDirectoryFullPath, "Heif", "Av1", nameof(this.ProductionMixedPartitionsPreserveReconstructionOrder)); - - Directory.CreateDirectory(directory); - File.WriteAllBytes(Path.Combine(directory, $"{size}-{transpose}-{enableIntraEdgeFilter}.obu"), payload); - using FileStream raw = File.Create(Path.Combine(directory, $"{size}-{transpose}-{enableIntraEdgeFilter}.retained.yuv")); - for (int y = 0; y < size; y++) - { - raw.Write(retainedPlane.DangerousGetRowSpan(y)); - } } [Theory] @@ -3785,11 +3764,6 @@ public class Av1IntraSuperblockEncoderTests using Av1Decoder decoder = new(Configuration.Default); using Av1FrameBuffer decodedFrame = decoder.DecodeFrameBuffer(payload, null, null, out _); int changedSamples = 0; - string directory = Path.Combine(TestEnvironment.ActualOutputDirectoryFullPath, "Heif", "Av1", "ProductionDeblocking"); - Directory.CreateDirectory(directory); - string name = $"{bitDepth}-{colorFormat}"; - File.WriteAllBytes(Path.Combine(directory, name + ".obu"), payload); - using FileStream raw = File.Create(Path.Combine(directory, name + ".retained.yuv")); for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) { Av1Plane plane = (Av1Plane)planeIndex; @@ -3806,8 +3780,6 @@ public class Av1IntraSuperblockEncoderTests { changedSamples += row[x] != unfiltered[planeIndex][(y * retained.Width) + x] ? 1 : 0; } - - raw.Write(MemoryMarshal.AsBytes(row)); } } @@ -4128,7 +4100,23 @@ public class Av1IntraSuperblockEncoderTests // Decode every payload, including clipped maps: retained reconstruction alone cannot reveal missing map symbols. byte[] payload = WriteCompleteTileObu(pictureTemplate, tileWriter, width, height); using Av1Decoder decoder = new(Configuration.Default); - using Image decoded = decoder.Decode(payload); + using Av1FrameBuffer decodedPlanes = decoder.DecodeFrameBuffer(payload, null, null, out _); + using Image decoded = new(Configuration.Default, decodedPlanes.Width, decodedPlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + decodedPlanes, + decoded.Bounds, + decoded.Frames.RootFrame.PixelBuffer.GetRegion(decoded.Bounds), + decoded.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + decodedPlanes.ColorConfig.ColorRange); + Assert.NotNull(decoder.FrameInfo); Av1BlockModeInfo decodedBlock = decoder.FrameInfo.GetModeInfoAt(default); Assert.True(decodedBlock.GetPaletteSize(Av1Plane.Y) > 0); @@ -4148,19 +4136,6 @@ public class Av1IntraSuperblockEncoderTests Assert.Equal(4, decodedBlock.GetTransformUnitCount(Av1Plane.Y)); } - string outputDirectory = Path.Combine(TestEnvironment.ActualOutputDirectoryFullPath, "Formats", "Heif", "Av1"); - string outputName = useSplitTransform - ? $"encoder-palette-transform-size-select-{bitDepthValue}b" - : $"encoder-palette-luma-{bitDepthValue}b-{width}x{height}"; - - Directory.CreateDirectory(outputDirectory); - File.WriteAllBytes(Path.Combine(outputDirectory, outputName + ".obu"), payload); - using FileStream raw = File.Create(Path.Combine(outputDirectory, outputName + ".retained.yuv")); - for (int row = 0; row < height; row++) - { - raw.Write(MemoryMarshal.AsBytes(reconstructionPlane.DangerousGetRowSpan(row)[..width])); - } - Assert.NotEqual(0, tileWriter.GetTileData(0).Length); } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1MotionSearchTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1MotionSearchTests.cs index f0cf9ffa82..259035ce96 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1MotionSearchTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1MotionSearchTests.cs @@ -52,8 +52,6 @@ public class Av1MotionSearchTests const int QIndex = 90; const int ReferenceStride = 192; const int ReferenceOrigin = (64 * ReferenceStride) + 64; - string directory = Path.Combine(TestEnvironment.ActualOutputDirectoryFullPath, "Heif", "Av1", "SingleReferenceSearch"); - Directory.CreateDirectory(directory); using Av1EncoderBlockWorkspace workspace = new(Configuration.Default, allocateInterMotionCosts: true); using Av1SymbolEncoder writer = new(Configuration.Default, 64, QIndex, updateCdf: true); Av1MotionVectorCosts costs = workspace.GetMotionVectorCosts(Av1MotionVectorPrecision.EighthSample); @@ -142,24 +140,6 @@ public class Av1MotionSearchTests costs); Av1MotionSearchBase.SingleReferenceState state = default; - using FileStream stream = File.Create(Path.Combine(directory, $"{bits}-{width}-{pattern}-{speed}-{forceInteger}.bin")); - using BinaryWriter output = new(stream); - int[] header = - [ - 3, bits, width, height, sourceStride, ReferenceStride, ReferenceOrigin, - bounds.Left, bounds.Top, bounds.Right, bounds.Bottom, QIndex, speed, - (int)settings.GetFullPixelMethod(blockSize), frameStep, spatialMagnitude, 1, searchRange, - forceInteger ? 1 : 0, 1, 0, multiplier, Av1RateDistortion.GetMotionSearchSadPerBit(QIndex, bitDepth), - settings.AutomaticStepSizeLevel, settings.StartCandidatePruningLevel, settings.ReferenceCandidatePruningLevel, - (int)settings.FractionalMethod, settings.FractionalIterationsPerStep, settings.FractionalInterpolationTaps, - (int)settings.SecondCandidateSelection, settings.MeshErrorThreshold, settings.MeshPruningLevel, - settings.DownsampledSadLevel, frameSize.Width, frameSize.Height, 0, 0, 512, 1024, 256, 0, 0, 3, - source.Length, reference.Length, pattern - ]; - - output.Write(MemoryMarshal.AsBytes(header.AsSpan())); - output.Write(MemoryMarshal.AsBytes(source.AsSpan())); - output.Write(MemoryMarshal.AsBytes(reference.AsSpan())); for (int referenceIndex = 0; referenceIndex < 3; referenceIndex++) { Av1MotionVector referenceVector = referenceIndex == 1 @@ -218,17 +198,6 @@ public class Av1MotionSearchTests Assert.Equal(0, result.Vector.Row & 7); Assert.Equal(0, result.Vector.Column & 7); } - - int[] decision = - [ - referenceVector.Row, referenceVector.Column, drlRate, valid ? 1 : 0, - valid ? result.Vector.Row : 0, valid ? result.Vector.Column : 0, - valid ? retained.Rate : 0, retained.Skip ? 1 : 0, state.StartCount, - retained.HasFullResult ? 1 : 0, retained.FullVector.Row, retained.FullVector.Column, - retained.FullRate, retained.FullCost - ]; - - output.Write(MemoryMarshal.AsBytes(decision.AsSpan())); } Assert.Equal(originalSource, source); @@ -241,7 +210,7 @@ public class Av1MotionSearchTests } /// - /// Checks all search methods at each sample precision and exports their inputs for independent reference verification. + /// Checks all search methods at each sample precision. /// /// The coded component precision. [Theory] @@ -307,8 +276,6 @@ public class Av1MotionSearchTests const int ReferenceStride = 192; const int ReferenceOrigin = (64 * ReferenceStride) + 64; int maximum = (1 << bits) - 1; - string directory = Path.Combine(TestEnvironment.ActualOutputDirectoryFullPath, "Heif", "Av1", "FullPixelSearch"); - Directory.CreateDirectory(directory); using Av1EncoderBlockWorkspace workspace = new(Configuration.Default, allocateInterMotionCosts: true); using Av1SymbolEncoder writer = new(Configuration.Default, 64, QIndex, updateCdf: true); Av1MotionVectorCosts costs = workspace.GetMotionVectorCosts(Av1MotionVectorPrecision.EighthSample); @@ -423,34 +390,10 @@ public class Av1MotionSearchTests Assert.Equal(0, result.SquaredError); } - // This export records both source inputs and the published state. A native comparison can - // establish controller agreement without calling native code from the managed test process. - using BinaryWriter output = new(File.Create(Path.Combine(directory, $"{bits}-{width}-{pattern}-{methodIndex}.bin"))); - foreach (int value in new[] - { - 1, bits, width, height, sourceStride, ReferenceStride, ReferenceOrigin, - frameBounds.Left, frameBounds.Top, frameBounds.Right, frameBounds.Bottom, - referenceVector.Row, referenceVector.Column, start.X, start.Y, 5, methodIndex, (int)speed, - frameSize.Width, frameSize.Height, QIndex, 0, screenContent ? 1 : 0, 0, 0, source.Length, reference.Length, - result.Vector.X, result.Vector.Y, result.Variance, result.SquaredError, result.MotionCost, - secondBest.HasValue ? 1 : 0, secondBest.GetValueOrDefault().X, secondBest.GetValueOrDefault().Y, multiplier, sadPerBit - }) - { - output.Write(value); - } - - foreach (int cost in costList) - { - output.Write(cost); - } - - output.Write(MemoryMarshal.AsBytes(source.AsSpan())); - output.Write(MemoryMarshal.AsBytes(reference.AsSpan())); if (method == FullPixelSearchMethod.NStep) { VerifyFractionalSearches( bitDepth, - bits, pattern, width, source, @@ -464,9 +407,7 @@ public class Av1MotionSearchTests multiplier, result, costList, - writer, - directory, - methodIndex); + writer); } } } @@ -478,7 +419,6 @@ public class Av1MotionSearchTests /// private static void VerifyFractionalSearches( Av1BitDepth bitDepth, - int bits, int pattern, int width, TSample[] source, @@ -492,9 +432,7 @@ public class Av1MotionSearchTests int multiplier, Av1MotionSearchBase.FullPixelResult integerResult, int[] costList, - Av1SymbolEncoder writer, - string directory, - int fullPixelMethod) + Av1SymbolEncoder writer) where TSample : unmanaged where TOperator : struct, Av1MotionSearchBase.IMotionSearchOperator { @@ -516,7 +454,6 @@ public class Av1MotionSearchTests bitDepth, multiplier); - using BinaryWriter output = new(File.Create(Path.Combine(directory, $"{bits}-{width}-{pattern}-{fullPixelMethod}.fractional"))); foreach (FractionalSearchMethod method in Enum.GetValues()) { foreach (int taps in new[] { 2, 4, 8 }) @@ -562,16 +499,6 @@ public class Av1MotionSearchTests out Av1MotionSearchBase.FractionalResult repeated); Assert.Equal(precision == SearchPrecision.Integer ? cost : int.MaxValue, repeatedCost); - foreach (int value in new[] - { - (int)method, taps, variant, allowHighPrecision ? 1 : 0, iterations, retainStatistics ? 1 : 0, retainCosts ? 1 : 0, - cost, result.Vector.Column, result.Vector.Row, result.Variance, result.SquaredError, result.MotionCost, - centers[0].Row, centers[0].Column, centers[1].Row, centers[1].Column, centers[2].Row, centers[2].Column, - repeatedCost, repeated.Vector.Column, repeated.Vector.Row, repeated.Variance, repeated.SquaredError, repeated.MotionCost - }) - { - output.Write(value); - } } } } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs index 9e821e7116..dd120fc40e 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReconstructionConformanceTests.cs @@ -7,7 +7,6 @@ using System.Text; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Heif; using SixLabors.ImageSharp.Formats.Heif.Av1; -using SixLabors.ImageSharp.Formats.Heif.Av1.Motion; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Prediction; using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; @@ -591,15 +590,14 @@ public class Av1ReconstructionConformanceTests TestImageProvider provider, HeifBitDepth bitDepth) { - using Image image = provider.GetImage(); + using Image image = provider.GetImage(HeifDecoder.Instance); HeifMetadata metadata = image.Metadata.GetHeifMetadata(); - Assert.Equal(HeifCompressionMethod.Av1, metadata.CompressionMethod); Assert.Equal(bitDepth, metadata.BitDepth); CicpProfile colorProfile = Assert.IsType(image.Metadata.CicpProfile); Assert.Equal(CicpColorPrimaries.ItuRBt709_6, colorProfile.ColorPrimaries); Assert.Equal(CicpTransferCharacteristics.Iec61966_2_1, colorProfile.TransferCharacteristics); - Assert.Equal(CicpMatrixCoefficients.ItuRBt601_7_525, colorProfile.MatrixCoefficients); + Assert.Equal(CicpMatrixCoefficients.Identity, colorProfile.MatrixCoefficients); Assert.True(colorProfile.FullRange); FeatureTestRunner.RunWithHwIntrinsicsFeature( @@ -1035,21 +1033,26 @@ public class Av1ReconstructionConformanceTests continue; } - ImageFrame decodedFrame; + using ImageFrame frame = new(configuration, track.CodedWidth, track.CodedHeight); try { - decodedFrame = decoder.DecodeSequenceFrame( + decoder.DecodeSequenceFrame( sampleData, track.CicpProfile, - track.Av1CodecConfiguration); + track.Av1CodecConfiguration, + frame.Size, + frame.Bounds, + frame.PixelBuffer.GetRegion(frame.Bounds), + default, + null, + null, + false); } catch (InvalidImageContentException exception) { throw new InvalidImageContentException($"The verified compound fixture failed at sample {sampleIndex}.", exception); } - using ImageFrame frame = decodedFrame; - _ = Assert.IsType(decoder.FrameHeader); Av1FrameBuffer frameBuffer = Assert.IsType>(decoder.FrameBuffer); Av1FrameInfo frameInfo = decoder.FrameInfo; @@ -1290,10 +1293,18 @@ public class Av1ReconstructionConformanceTests { int payloadLength = checked((int)BinaryPrimitives.ReadUInt32LittleEndian(ivf.AsSpan(ivfOffset, 4))); ivfOffset += 12; - using ImageFrame frame = decoder.DecodeSequenceFrame( + using ImageFrame frame = new(Configuration.Default, OfficialMotionVectorFixtureWidth, OfficialMotionVectorFixtureHeight); + decoder.DecodeSequenceFrame( ivf.AsSpan(ivfOffset, payloadLength), null, - null); + null, + frame.Size, + frame.Bounds, + frame.PixelBuffer.GetRegion(frame.Bounds), + default, + null, + null, + false); ivfOffset += payloadLength; Assert.Equal(OfficialMotionVectorFixtureWidth, frame.Width); @@ -1408,10 +1419,18 @@ public class Av1ReconstructionConformanceTests { int payloadLength = checked((int)BinaryPrimitives.ReadUInt32LittleEndian(ivf.AsSpan(ivfOffset, 4))); ivfOffset += 12; - using ImageFrame frame = decoder.DecodeSequenceFrame( + using ImageFrame frame = new(Configuration.Default, OfficialIntraBlockCopyFixtureWidth, OfficialIntraBlockCopyFixtureHeight); + decoder.DecodeSequenceFrame( ivf.AsSpan(ivfOffset, payloadLength), null, - null); + null, + frame.Size, + frame.Bounds, + frame.PixelBuffer.GetRegion(frame.Bounds), + default, + null, + null, + false); ivfOffset += payloadLength; Assert.Equal(OfficialIntraBlockCopyFixtureWidth, frame.Width); @@ -2128,10 +2147,18 @@ public class Av1ReconstructionConformanceTests { int payloadLength = checked((int)BinaryPrimitives.ReadUInt32LittleEndian(ivf.AsSpan(ivfOffset, 4))); ivfOffset += 12; - using ImageFrame frame = decoder.DecodeSequenceFrame( + using ImageFrame frame = new(configuration, expectedWidth, expectedHeight); + decoder.DecodeSequenceFrame( ivf.AsSpan(ivfOffset, payloadLength), null, - null); + null, + frame.Size, + frame.Bounds, + frame.PixelBuffer.GetRegion(frame.Bounds), + default, + null, + null, + false); ivfOffset += payloadLength; Assert.Equal(expectedWidth, frame.Width); @@ -2255,10 +2282,18 @@ public class Av1ReconstructionConformanceTests { int payloadLength = checked((int)BinaryPrimitives.ReadUInt32LittleEndian(ivf.AsSpan(ivfOffset, 4))); ivfOffset += 12; - ImageFrame decodedFrame = decoder.DecodeSequenceFrame( + ImageFrame decodedFrame = new(configuration, OfficialMotionVectorFixtureWidth, OfficialMotionVectorFixtureHeight); + decoder.DecodeSequenceFrame( ivf.AsSpan(ivfOffset, payloadLength), null, - null); + null, + decodedFrame.Size, + decodedFrame.Bounds, + decodedFrame.PixelBuffer.GetRegion(decodedFrame.Bounds), + default, + null, + null, + false); using ImageFrame frame = decodedFrame; @@ -2489,10 +2524,18 @@ public class Av1ReconstructionConformanceTests continue; } - using ImageFrame frame = decoder.DecodeSequenceFrame( + using ImageFrame frame = new(configuration, track.CodedWidth, track.CodedHeight); + decoder.DecodeSequenceFrame( sampleData, track.CicpProfile, - track.Av1CodecConfiguration); + track.Av1CodecConfiguration, + frame.Size, + frame.Bounds, + frame.PixelBuffer.GetRegion(frame.Bounds), + default, + null, + null, + false); Av1FrameBuffer frameBuffer = Assert.IsType>(decoder.FrameBuffer); coverage |= GetInterPredictionCoverage(decoder); @@ -3836,7 +3879,6 @@ public class Av1ReconstructionConformanceTests Assert.Equal(height, image.Height); Assert.Single(image.Frames); HeifMetadata metadata = image.Metadata.GetHeifMetadata(); - Assert.Equal(HeifCompressionMethod.Av1, metadata.CompressionMethod); Assert.Equal(metadataBitDepth, metadata.BitDepth); if (metadataBitDepth != HeifBitDepth.Bit8) @@ -3877,13 +3919,12 @@ public class Av1ReconstructionConformanceTests int height, HeifBitDepth bitDepth) { - using Image image = provider.GetImage(); + using Image image = provider.GetImage(HeifDecoder.Instance); Assert.Equal(width, image.Width); Assert.Equal(height, image.Height); Assert.Single(image.Frames); HeifMetadata metadata = image.Metadata.GetHeifMetadata(); - Assert.Equal(HeifCompressionMethod.Av1, metadata.CompressionMethod); Assert.Equal(bitDepth, metadata.BitDepth); } @@ -3896,12 +3937,9 @@ public class Av1ReconstructionConformanceTests TestImageProvider provider = FeatureTestRunner.DeserializeForXunit>(providerDump); - using Image image = provider.GetImage(); - - // CICP records the AVIF source component layout, but PNG permits only the identity matrix. The debug image - // is a pixel artifact; the test verifies source metadata independently where that is part of the contract. - image.DebugSave(provider, new PngEncoder { SkipMetadata = true }); + using Image image = provider.GetImage(HeifDecoder.Instance); + image.DebugSave(provider, extension: "png", encoder: new PngEncoder()); image.CompareToReferenceOutput(ImageComparer.Exact, provider); } @@ -3914,13 +3952,10 @@ public class Av1ReconstructionConformanceTests TestImageProvider provider = FeatureTestRunner.DeserializeForXunit>(providerDump); - using Image sequence = provider.GetImage(); + using Image sequence = provider.GetImage(HeifDecoder.Instance); using Image finalFrame = sequence.Frames.CloneFrame(sequence.Frames.Count - 1); - // The retained source CICP matrix cannot be represented in a PNG cICP chunk. Omit metadata only from the - // diagnostic output; the exact reference comparison below still consumes the original decoded image. - finalFrame.DebugSave(provider, new PngEncoder { SkipMetadata = true }); - + finalFrame.DebugSave(provider, extension: "png", encoder: new PngEncoder()); finalFrame.CompareToReferenceOutput(ImageComparer.Exact, provider); } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceFrameStoreTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceFrameStoreTests.cs index 03bbd8b2e1..a73a2a4365 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceFrameStoreTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1ReferenceFrameStoreTests.cs @@ -265,10 +265,10 @@ public class Av1ReferenceFrameStoreTests // Full current-frame state owns the projected temporal field. The retained frame owns only the source field // needed by later projections, irrespective of how many map and output aliases identify the same frame. frameInfo.Dispose(); - Assert.Single(allocator.ReturnLog, returned => returned.AllocationId == temporalMotionField.AllocationId); + Assert.Single(allocator.ReturnLog, returned => returned.HashCodeOfBuffer == temporalMotionField.HashCodeOfBuffer); Av1ReferenceFrame output = store.TakeOutput(); - Assert.DoesNotContain(allocator.ReturnLog, returned => returned.AllocationId == retainedMotionField.AllocationId); + Assert.DoesNotContain(allocator.ReturnLog, returned => returned.HashCodeOfBuffer == retainedMotionField.HashCodeOfBuffer); output.Dispose(); output.Dispose(); @@ -278,7 +278,7 @@ public class Av1ReferenceFrameStoreTests allocator.AllocationLog, allocation => Assert.Single( allocator.ReturnLog, - returned => returned.AllocationId == allocation.AllocationId)); + returned => returned.HashCodeOfBuffer == allocation.HashCodeOfBuffer)); Assert.Equal(2, allocator.ReturnLog.Count); } @@ -343,7 +343,7 @@ public class Av1ReferenceFrameStoreTests allocator.AllocationLog, allocation => Assert.Single( allocator.ReturnLog, - returned => returned.AllocationId == allocation.AllocationId)); + returned => returned.HashCodeOfBuffer == allocation.HashCodeOfBuffer)); Assert.Equal(allocator.AllocationLog.Count, allocator.ReturnLog.Count); } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1RegularQuantizerTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1RegularQuantizerTests.cs index 22ffb8ce99..49c0787f07 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1RegularQuantizerTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1RegularQuantizerTests.cs @@ -30,13 +30,6 @@ public class Av1RegularQuantizerTests /// 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[] @@ -128,17 +121,6 @@ public class Av1RegularQuantizerTests } 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/Formats/Heif/Av1/Av1TilingTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs index c0fb6e5662..b05c0b62a2 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TilingTests.cs @@ -2,7 +2,9 @@ // Licensed under the Six Labors Split License. using System.Buffers; +using SixLabors.ImageSharp.Formats.Heif; using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.Formats.Heif.Av1.Color; using SixLabors.ImageSharp.Formats.Heif.Av1.Entropy; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; @@ -137,7 +139,7 @@ public class Av1TilingTests Assert.Equal(failureIndex, allocator.AllocationLog.Count); Assert.All( allocator.AllocationLog, - allocation => Assert.Single(allocator.ReturnLog, returned => returned.AllocationId == allocation.AllocationId)); + allocation => Assert.Single(allocator.ReturnLog, returned => returned.HashCodeOfBuffer == allocation.HashCodeOfBuffer)); Assert.Equal(allocator.AllocationLog.Count, allocator.ReturnLog.Count); } @@ -173,7 +175,23 @@ public class Av1TilingTests const int codedItemOffset = 0x17A8; const int codedItemLength = 0x3AE4; using Av1Decoder decoder = new(Configuration.Default); - using Image image = decoder.Decode(content.AsSpan(codedItemOffset, codedItemLength)); + using Av1FrameBuffer imagePlanes = decoder.DecodeFrameBuffer(content.AsSpan(codedItemOffset, codedItemLength), null, null, out _); + using Image image = new(Configuration.Default, imagePlanes.Width, imagePlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + imagePlanes, + image.Bounds, + image.Frames.RootFrame.PixelBuffer.GetRegion(image.Bounds), + image.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + imagePlanes.ColorConfig.ColorRange); + Av1FrameInfo frameInfo = Assert.IsType(decoder.FrameInfo); ObuFrameHeader frameHeader = Assert.IsType(decoder.FrameHeader); Span blockSizeCounts = stackalloc int[(int)Av1BlockSize.AllSizes]; @@ -213,7 +231,22 @@ public class Av1TilingTests byte[] content = File.ReadAllBytes(filePath); Av1Decoder decoder = new(Configuration.Default); - using Image image = decoder.Decode(content.AsSpan(0x010E, 0x001D)); + using Av1FrameBuffer imagePlanes = decoder.DecodeFrameBuffer(content.AsSpan(0x010E, 0x001D), null, null, out _); + using Image image = new(Configuration.Default, imagePlanes.Width, imagePlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + imagePlanes, + image.Bounds, + image.Frames.RootFrame.PixelBuffer.GetRegion(image.Bounds), + image.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + imagePlanes.ColorConfig.ColorRange); Assert.Equal(4, image.Width); Assert.Equal(4, image.Height); @@ -306,7 +339,7 @@ public class Av1TilingTests using IMemoryOwner workspace = frameBuffer.MemoryAllocator.Allocate(Av1BlockDecoder.GetWorkspaceLength(obuReader.SequenceHeader)); - using Av1FrameDecoder frameDecoder = new( + Av1FrameDecoder frameDecoder = new( obuReader.SequenceHeader, obuReader.FrameHeader, frameInfo, @@ -361,7 +394,7 @@ public class Av1TilingTests using IMemoryOwner workspace = frameBuffer.MemoryAllocator.Allocate(Av1BlockDecoder.GetWorkspaceLength(obuReader.SequenceHeader)); - using Av1FrameDecoder frameDecoder = new( + Av1FrameDecoder frameDecoder = new( obuReader.SequenceHeader, obuReader.FrameHeader, frameInfo, diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TransformBlockEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TransformBlockEncoderTests.cs index cf7c36fb37..8f7bc4fe63 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TransformBlockEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TransformBlockEncoderTests.cs @@ -653,7 +653,7 @@ public class Av1TransformBlockEncoderTests } TestMemoryAllocator.ReturnRequest returned = Assert.Single(allocator.ReturnLog); - Assert.Equal(allocation.AllocationId, returned.AllocationId); + Assert.Equal(allocation.HashCodeOfBuffer, returned.HashCodeOfBuffer); } private static void ValidateBlock( diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TransformEstimateTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TransformEstimateTests.cs index 804914a627..7429940a67 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TransformEstimateTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1TransformEstimateTests.cs @@ -31,13 +31,6 @@ public class Av1TransformEstimateTests /// private static void ValidateEstimates() { - string vectorWidth = Vector512.IsHardwareAccelerated ? "512" - : Vector256.IsHardwareAccelerated ? "256" - : Vector128.IsHardwareAccelerated ? "128" : "0"; - - string directory = Path.Combine(TestEnvironment.ActualOutputDirectoryFullPath, "Heif", "Av1", "TransformEstimates", vectorWidth); - - Directory.CreateDirectory(directory); using Av1EncoderBlockWorkspace workspace = new(Configuration.Default); foreach (Av1BitDepth bitDepth in new[] { Av1BitDepth.EightBit, Av1BitDepth.TenBit, Av1BitDepth.TwelveBit }) { @@ -220,27 +213,6 @@ public class Av1TransformEstimateTests Assert.Equal(long.MaxValue, terminated); Assert.Equal(Av1RateDistortionStatistics.Invalid, incomplete); } - - using BinaryWriter output = new(File.Create(Path.Combine( - directory, $"{bits}-{width}-{height}-{(int)transformSize}-{qIndex}-{pattern}.bin"))); - - foreach (int value in new[] - { - bits, width, height, (int)transformSize, qIndex, sharpness, lossless ? 1 : 0, - multiplier, partitionRate, noSkipRate, skipRate, stride - }) - { - output.Write(value); - } - - output.Write(cost); - output.Write((long)statistics.Rate); - output.Write(statistics.Distortion); - output.Write(energy); - output.Write(skip ? 1L : 0L); - output.Write(above); - output.Write(left); - output.Write(MemoryMarshal.AsBytes(residual.AsSpan())); } } } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1YuvConverterTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1YuvConverterTests.cs index 0f0be7062e..422d462526 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1YuvConverterTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1YuvConverterTests.cs @@ -3,6 +3,7 @@ using System; using System.Numerics; +using SixLabors.ImageSharp.Formats.Heif; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.Color; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; @@ -116,7 +117,20 @@ public class Av1YuvConverterTests frameBuffer.DeriveBlockPointer(Av1Plane.V, 0, 0).DangerousGetRowSpan(0)[0] = (byte)v; // Act - Av1YuvConverter.ConvertToRgb(Configuration.Default, frameBuffer, frame); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + frameBuffer, + new Rectangle(Point.Empty, frame.Size), + frame.PixelBuffer.GetRegion(), + frame.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + frameBuffer.ColorConfig.ColorRange); // Assert frame.DangerousTryGetSinglePixelMemory(out Memory memory); @@ -126,6 +140,60 @@ public class Av1YuvConverterTests Assert.Equal(b, actual.B, 1d); } + [Fact] + public void YuvToRgbWritesOnlyRequestedDestinationRegion() + { + Rgb24 sentinel = new(201, 202, 203); + using Image image = new(7, 5, sentinel); + ObuSequenceHeader header = CreateSequenceHeader(4, 3, true, ObuMatrixCoefficients.Identity); + using Av1FrameBuffer buffer = new(Configuration.Default, header, Av1ColorFormat.Yuv444, false); + for (int y = 0; y < 3; y++) + { + Span green = buffer.DeriveBlockPointer(Av1Plane.Y, 0, 0).DangerousGetRowSpan(y); + Span blue = buffer.DeriveBlockPointer(Av1Plane.U, 0, 0).DangerousGetRowSpan(y); + Span red = buffer.DeriveBlockPointer(Av1Plane.V, 0, 0).DangerousGetRowSpan(y); + for (int x = 0; x < 4; x++) + { + red[x] = (byte)(10 + x); + green[x] = (byte)(20 + y); + blue[x] = (byte)(30 + x + (y * 4)); + } + } + + Rectangle source = new(1, 1, 2, 2); + Rectangle target = new(3, 2, 2, 2); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + buffer, + source, + image.Frames.RootFrame.PixelBuffer.GetRegion(target), + new Size(4, 3), + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + buffer.ColorConfig.ColorRange); + + // The identity matrix gives independently known RGB values. Every pixel outside the target must retain + // the sentinel, including the row prefix and suffix surrounding the converted source crop. + for (int y = 0; y < image.Height; y++) + { + for (int x = 0; x < image.Width; x++) + { + int sourceX = x - target.X + source.X; + int sourceY = y - target.Y + source.Y; + Rgb24 expected = target.Contains(x, y) + ? new Rgb24((byte)(10 + sourceX), (byte)(20 + sourceY), (byte)(30 + sourceX + (sourceY * 4))) + : sentinel; + + Assert.Equal(expected, image[x, y]); + } + } + } + /// /// Verifies that limited-range monochrome samples expand to the complete RGB output range. /// @@ -147,7 +215,20 @@ public class Av1YuvConverterTests yRow[1] = 235; // Act - Av1YuvConverter.ConvertToRgb(Configuration.Default, frameBuffer, image.Frames.RootFrame); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + frameBuffer, + new Rectangle(Point.Empty, image.Frames.RootFrame.Size), + image.Frames.RootFrame.PixelBuffer.GetRegion(), + image.Frames.RootFrame.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + frameBuffer.ColorConfig.ColorRange); // Assert Span actual = image.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(0); @@ -220,7 +301,20 @@ public class Av1YuvConverterTests frameBuffer.GetHighBitDepthRowSpan(Av1Plane.V, 0, 0, 0).Fill(neutralChroma); // Act - Av1YuvConverter.ConvertToRgb(Configuration.Default, frameBuffer, image.Frames.RootFrame); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + frameBuffer, + new Rectangle(Point.Empty, image.Frames.RootFrame.Size), + image.Frames.RootFrame.PixelBuffer.GetRegion(), + image.Frames.RootFrame.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + frameBuffer.ColorConfig.ColorRange); // Assert Span actual = image.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(0); @@ -372,7 +466,20 @@ public class Av1YuvConverterTests frameBuffer.DeriveBlockPointer(Av1Plane.V, 1, 0).DangerousGetRowSpan(0).Fill(128); // Act - Av1YuvConverter.ConvertToRgb(Configuration.Default, frameBuffer, image.Frames.RootFrame); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + frameBuffer, + new Rectangle(Point.Empty, image.Frames.RootFrame.Size), + image.Frames.RootFrame.PixelBuffer.GetRegion(), + image.Frames.RootFrame.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Bilinear, + frameBuffer.ColorConfig.ColorRange); // Assert Span actual = image.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(0); @@ -419,7 +526,20 @@ public class Av1YuvConverterTests vPlane.DangerousGetRowSpan(1).Fill(128); // Act - Av1YuvConverter.ConvertToRgb(Configuration.Default, frameBuffer, image.Frames.RootFrame); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + frameBuffer, + new Rectangle(Point.Empty, image.Frames.RootFrame.Size), + image.Frames.RootFrame.PixelBuffer.GetRegion(), + image.Frames.RootFrame.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Bilinear, + frameBuffer.ColorConfig.ColorRange); // Assert Assert.Equal(expectedTopBlue, image.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(0)[1].B); @@ -538,7 +658,20 @@ public class Av1YuvConverterTests CreateTestData(rnd, frameBuffer, Av1Plane.V); // Act - Av1YuvConverter.ConvertToRgb(Configuration.Default, frameBuffer, frame); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + frameBuffer, + new Rectangle(Point.Empty, frame.Size), + frame.PixelBuffer.GetRegion(), + frame.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + frameBuffer.ColorConfig.ColorRange); Span referenceOutput = Av1ReferenceYuvConverter.YuvToRgb(frameBuffer, true); // Assert @@ -643,7 +776,20 @@ public class Av1YuvConverterTests // Act Av1YuvConverter.ConvertFromRgb(Configuration.Default, frame, frameBuffer); - Av1YuvConverter.ConvertToRgb(Configuration.Default, frameBuffer, actual.Frames.RootFrame); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + frameBuffer, + new Rectangle(Point.Empty, actual.Frames.RootFrame.Size), + actual.Frames.RootFrame.PixelBuffer.GetRegion(), + actual.Frames.RootFrame.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + frameBuffer.ColorConfig.ColorRange); // Assert actual.Frames.RootFrame.DangerousTryGetSinglePixelMemory(out Memory actualMemory); @@ -690,7 +836,20 @@ public class Av1YuvConverterTests // Act Av1YuvConverter.ConvertFromRgb(Configuration.Default, image.Frames.RootFrame, frameBuffer); - Av1YuvConverter.ConvertToRgb(Configuration.Default, frameBuffer, actual.Frames.RootFrame); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + frameBuffer, + new Rectangle(Point.Empty, actual.Frames.RootFrame.Size), + actual.Frames.RootFrame.PixelBuffer.GetRegion(), + actual.Frames.RootFrame.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + frameBuffer.ColorConfig.ColorRange); // Assert Span actualPixels = actual.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(0); @@ -729,7 +888,20 @@ public class Av1YuvConverterTests Assert.Equal(198, frameBuffer.DeriveBlockPointer(Av1Plane.V, 0, 0).DangerousGetRowSpan(0)[0]); using Image destination = new(1, 1); - Av1YuvConverter.ConvertToRgb(Configuration.Default, frameBuffer, destination.Frames.RootFrame); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + frameBuffer, + new Rectangle(Point.Empty, destination.Frames.RootFrame.Size), + destination.Frames.RootFrame.PixelBuffer.GetRegion(), + destination.Frames.RootFrame.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + frameBuffer.ColorConfig.ColorRange); Assert.Equal(new Rgb24(150, 100, 51), destination.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(0)[0]); } @@ -794,7 +966,20 @@ public class Av1YuvConverterTests Assert.Equal(expectedV, actualV); using Image destination = new(1, 1); - Av1YuvConverter.ConvertToRgb(Configuration.Default, frameBuffer, destination.Frames.RootFrame); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + frameBuffer, + new Rectangle(Point.Empty, destination.Frames.RootFrame.Size), + destination.Frames.RootFrame.PixelBuffer.GetRegion(), + destination.Frames.RootFrame.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + frameBuffer.ColorConfig.ColorRange); Assert.Equal(new Rgb48(ushort.MaxValue, 0, 0), destination.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(0)[0]); } @@ -865,7 +1050,20 @@ public class Av1YuvConverterTests } using Image destination = new(2, 1); - Av1YuvConverter.ConvertToRgb(Configuration.Default, frameBuffer, destination.Frames.RootFrame); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + frameBuffer, + new Rectangle(Point.Empty, destination.Frames.RootFrame.Size), + destination.Frames.RootFrame.PixelBuffer.GetRegion(), + destination.Frames.RootFrame.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + frameBuffer.ColorConfig.ColorRange); Span destinationPixels = destination.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(0); Assert.Equal(new Rgb48(0, 0, 0), destinationPixels[0]); @@ -896,7 +1094,20 @@ public class Av1YuvConverterTests () => Av1YuvConverter.ConvertFromRgb(Configuration.Default, image.Frames.RootFrame, frameBuffer)); Assert.Throws( - () => Av1YuvConverter.ConvertToRgb(Configuration.Default, frameBuffer, image.Frames.RootFrame)); + () => Av1YuvConverter.ConvertToRgb( + Configuration.Default, + frameBuffer, + new Rectangle(Point.Empty, image.Frames.RootFrame.Size), + image.Frames.RootFrame.PixelBuffer.GetRegion(), + image.Frames.RootFrame.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + frameBuffer.ColorConfig.ColorRange)); } /// @@ -937,7 +1148,20 @@ public class Av1YuvConverterTests using Image destination = new(width, 1); Av1YuvConverter.ConvertFromRgb(Configuration.Default, source.Frames.RootFrame, frameBuffer); - Av1YuvConverter.ConvertToRgb(Configuration.Default, frameBuffer, destination.Frames.RootFrame); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + frameBuffer, + new Rectangle(Point.Empty, destination.Frames.RootFrame.Size), + destination.Frames.RootFrame.PixelBuffer.GetRegion(), + destination.Frames.RootFrame.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + frameBuffer.ColorConfig.ColorRange); AssertPlaneContainsRepeatedSample(frameBuffer, Av1Plane.Y, 0, 0); AssertPlaneContainsRepeatedSample(frameBuffer, Av1Plane.U, 0, 0); @@ -1017,7 +1241,20 @@ public class Av1YuvConverterTests // Act Av1YuvConverter.ConvertFromRgb(Configuration.Default, frame, frameBuffer); - Av1YuvConverter.ConvertToRgb(Configuration.Default, frameBuffer, actual.Frames.RootFrame); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + frameBuffer, + new Rectangle(Point.Empty, actual.Frames.RootFrame.Size), + actual.Frames.RootFrame.PixelBuffer.GetRegion(), + actual.Frames.RootFrame.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + frameBuffer.ColorConfig.ColorRange); // Assert ImageComparer.Tolerant(0.002F).VerifySimilarity(image, actual); @@ -1088,10 +1325,11 @@ public class Av1YuvConverterTests Av1YuvConverter.ComposeAlpha( Configuration.Default, frameBuffer, - destination.Frames.RootFrame, + destination.Frames.RootFrame.PixelBuffer.GetRegion(destination.Frames.RootFrame.Bounds), destination.Size, destination.Bounds, - false); + false, + default); for (int y = 0; y < height; y++) { @@ -1144,10 +1382,11 @@ public class Av1YuvConverterTests Av1YuvConverter.ComposeAlpha( Configuration.Default, frameBuffer, - destination.Frames.RootFrame, + destination.Frames.RootFrame.PixelBuffer.GetRegion(destination.Frames.RootFrame.Bounds), destination.Size, destination.Bounds, - false); + false, + default); for (int y = 0; y < destinationHeight; y++) { @@ -1200,10 +1439,11 @@ public class Av1YuvConverterTests Av1YuvConverter.ComposeAlpha( configuration, frameBuffer, - destination.Frames.RootFrame, + destination.Frames.RootFrame.PixelBuffer.GetRegion(destination.Frames.RootFrame.Bounds), destination.Size, destination.Bounds, - false); + false, + default); for (int y = 0; y < destinationHeight; y++) { @@ -1259,10 +1499,11 @@ public class Av1YuvConverterTests Av1YuvConverter.ComposeAlpha( Configuration.Default, frameBuffer, - destination.Frames.RootFrame, + destination.Frames.RootFrame.PixelBuffer.GetRegion(destination.Frames.RootFrame.Bounds), destination.Size, destination.Bounds, - false); + false, + default); Span actual = destination.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(0); Assert.Equal((ushort)0, actual[0].A); diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs index 5b49a86b7d..58884adce5 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameHeaderTests.cs @@ -134,16 +134,6 @@ public class ObuFrameHeaderTests writer.WriteSequenceFrame(stream, sequence, frame, tiles); byte[] payload = stream.ToArray(); - string outputDirectory = Path.Combine( - TestEnvironment.ActualOutputDirectoryFullPath, - "Heif", - "Av1", - nameof(this.DecodeSmallFrameWithLargeSequenceMaximum)); - - Directory.CreateDirectory(outputDirectory); - File.WriteAllBytes(Path.Combine(outputDirectory, "original.bit"), originalPayload.ToArray()); - File.WriteAllBytes(Path.Combine(outputDirectory, "large-maximum.bit"), payload); - using Av1Decoder originalDecoder = new(Configuration.Default); using Av1FrameBuffer expected = originalDecoder.DecodeFrameBuffer(originalPayload, null, null, out _); using Av1Decoder decoder = new(Configuration.Default); @@ -154,7 +144,6 @@ public class ObuFrameHeaderTests Assert.Equal(4, actual.MaxHeight); Assert.Equal(expected.ColorFormat, actual.ColorFormat); Assert.Equal(expected.BitDepth, actual.BitDepth); - using FileStream decodedPlanes = File.Create(Path.Combine(outputDirectory, "managed.yuv")); for (int planeIndex = 0; planeIndex < sequence.ColorConfig.PlaneCount; planeIndex++) { Av1Plane plane = (Av1Plane)planeIndex; @@ -173,7 +162,6 @@ public class ObuFrameHeaderTests .Slice(actual.OriginX >> subX, width); Assert.True(expectedRow.SequenceEqual(actualRow), $"Plane {planeIndex}, row {row}"); - decodedPlanes.Write(actualRow); } } } @@ -866,7 +854,7 @@ public class ObuFrameHeaderTests Assert.Equal(typeof(byte), headerScratch.ElementType); Assert.InRange(headerScratch.Length, 1, TilePayloadLength - 1); TestMemoryAllocator.ReturnRequest returned = Assert.Single(allocator.ReturnLog); - Assert.Equal(headerScratch.AllocationId, returned.AllocationId); + Assert.Equal(headerScratch.HashCodeOfBuffer, returned.HashCodeOfBuffer); Assert.True(stream.GetBuffer().AsSpan((int)stream.Length - TilePayloadLength, TilePayloadLength).SequenceEqual(tileData)); } diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameLifecycleTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameLifecycleTests.cs index 727061d785..9c5777e554 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameLifecycleTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/ObuFrameLifecycleTests.cs @@ -5,6 +5,7 @@ using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.ReferenceFrames; using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Tests.Formats.Heif.Av1; @@ -105,8 +106,30 @@ public class ObuFrameLifecycleTests // three-bit slot index, and the required trailing-one bit; no tile-group OBU follows it. byte[] showExistingFrame = [0x1A, 0x01, 0x88]; using Av1Decoder decoder = new(Configuration.Default, ProgressiveOperatingPointIndex); - using ImageFrame reconstructed = decoder.DecodeSequenceFrame(bitStream, null, null); - using ImageFrame existing = decoder.DecodeSequenceFrame(showExistingFrame, null, null); + using ImageFrame reconstructed = new(Configuration.Default, ProgressiveImageWidth, ProgressiveImageHeight); + decoder.DecodeSequenceFrame( + bitStream, + null, + null, + reconstructed.Size, + reconstructed.Bounds, + reconstructed.PixelBuffer.GetRegion(reconstructed.Bounds), + default, + null, + null, + false); + using ImageFrame existing = new(Configuration.Default, ProgressiveImageWidth, ProgressiveImageHeight); + decoder.DecodeSequenceFrame( + showExistingFrame, + null, + null, + existing.Size, + existing.Bounds, + existing.PixelBuffer.GetRegion(existing.Bounds), + default, + null, + null, + false); Assert.Equal(reconstructed.Size, existing.Size); for (int row = 0; row < reconstructed.Height; row++) diff --git a/tests/ImageSharp.Tests/Formats/Heif/HeifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/HeifDecoderTests.cs index e43090a06e..da0f255696 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/HeifDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/HeifDecoderTests.cs @@ -2,8 +2,6 @@ // Licensed under the Six Labors Split License. using System.Buffers.Binary; -using SixLabors.ImageSharp.ColorProfiles; -using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Heif; using SixLabors.ImageSharp.Formats.Png; @@ -11,8 +9,8 @@ using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using SixLabors.ImageSharp.Tests.ColorProfiles.Icc; using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; +using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs; namespace SixLabors.ImageSharp.Tests.Formats.Heif; @@ -22,19 +20,86 @@ public class HeifDecoderTests { private const uint UnknownBoxType = 0x74657374U; - private static ReadOnlySpan MalformedJpegApp13 => - [ - 0xFF, 0xED, - 0x00, 0x1D, - (byte)'P', (byte)'h', (byte)'o', (byte)'t', (byte)'o', (byte)'s', (byte)'h', (byte)'o', (byte)'p', (byte)' ', (byte)'3', (byte)'.', - (byte)'0', 0x00, - (byte)'B', (byte)'a', (byte)'d', (byte)'R', (byte)'e', (byte)'s', (byte)'o', (byte)'u', (byte)'r', (byte)'c', (byte)'e', (byte)'!', - (byte)'!' - ]; + /// + /// Decodes the AVIF corpus through the public decoder and compares every frame with the independent decoder. + /// + [Theory] + [WithFile(TestImages.Heif.IrvineAvif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.XnConvert, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Orange4x4, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.ParisIccExifXmpAvif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.PerceptualIccAvif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.PerceptualIccGridAvif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.PerceptualIccSequenceAvif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.DuckyRommIccAlphaAvif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Animated8Bit, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Animated8BitWithAudio, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Animated8BitWithAlphaExifXmp, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Av1Deblocking8BitAvif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Av1Progressive8BitAvif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Av1ScaledReferenceAvif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Av1ScaledReferenceSelectedLayerAvif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Av1AverageCompoundSequenceAvif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Av1DistanceWeightedCompoundSequenceAvif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Av1WedgeCompoundSequenceAvif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Av1DifferenceWeightedCompoundSequenceAvif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Av1InterIntraSequenceAvif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Av1ObmcSequenceAvif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Av1LocalWarpSequenceAvif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Av1GlobalWarpSequenceAvif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Av1Cdef8BitAvif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Av1Profile8BitMonochromeAvif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Av1Profile8Bit420Avif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Av1Profile8Bit422Avif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Av1Profile8Bit444Avif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Av1Palette8BitAvif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Av1IntraBlockCopy8BitAvif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Av1Lossless8BitAvif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Av1SuperResolution8BitAvif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Av1Restoration8BitAvif, PixelTypes.Rgba32)] + [WithFile(TestImages.Heif.Animated12BitWithKeyframes, PixelTypes.Rgba64)] + [WithFile(TestImages.Heif.Av1Deblocking10BitAvif, PixelTypes.Rgba64)] + [WithFile(TestImages.Heif.Av1Deblocking12BitAvif, PixelTypes.Rgba64)] + [WithFile(TestImages.Heif.Av1Cdef10BitAvif, PixelTypes.Rgba64)] + [WithFile(TestImages.Heif.Av1Cdef12BitAvif, PixelTypes.Rgba64)] + [WithFile(TestImages.Heif.Av1Profile10BitMonochromeAvif, PixelTypes.Rgba64)] + [WithFile(TestImages.Heif.Av1Profile10Bit420Avif, PixelTypes.Rgba64)] + [WithFile(TestImages.Heif.Av1Profile10Bit422Avif, PixelTypes.Rgba64)] + [WithFile(TestImages.Heif.Av1Profile10Bit444Avif, PixelTypes.Rgba64)] + [WithFile(TestImages.Heif.Av1Profile12BitMonochromeAvif, PixelTypes.Rgba64)] + [WithFile(TestImages.Heif.Av1Profile12Bit420Avif, PixelTypes.Rgba64)] + [WithFile(TestImages.Heif.Av1Profile12Bit422Avif, PixelTypes.Rgba64)] + [WithFile(TestImages.Heif.Av1Profile12Bit444Avif, PixelTypes.Rgba64)] + [WithFile(TestImages.Heif.Av1IntraBlockCopy10BitAvif, PixelTypes.Rgba64)] + [WithFile(TestImages.Heif.Av1IntraBlockCopy12BitAvif, PixelTypes.Rgba64)] + [WithFile(TestImages.Heif.Av1Lossless10BitAvif, PixelTypes.Rgba64)] + [WithFile(TestImages.Heif.Av1Lossless12BitAvif, PixelTypes.Rgba64)] + [WithFile(TestImages.Heif.Av1SuperResolution10BitAvif, PixelTypes.Rgba64)] + [WithFile(TestImages.Heif.Av1SuperResolution12BitAvif, PixelTypes.Rgba64)] + [WithFile(TestImages.Heif.Av1Restoration10BitAvif, PixelTypes.Rgba64)] + [WithFile(TestImages.Heif.Av1Restoration12BitAvif, PixelTypes.Rgba64)] + public void Decode(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + DecoderOptions options = new() { ColorProfileHandling = ColorProfileHandling.Preserve }; + using Image image = provider.GetImage(HeifDecoder.Instance, options); + + if (image.Frames.Count == 1) + { + image.DebugSave(provider, extension: "png", encoder: new PngEncoder()); + image.CompareToReferenceOutput(ImageComparer.Exact, provider); + } + else + { + image.DebugSaveMultiFrame(provider, encoder: new PngEncoder()); + image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); + } + } + [Theory] - [InlineData(TestImages.Heif.IrvineAvif, HeifCompressionMethod.Av1, HeifBitDepth.Bit8, 480, 640)] - public void Identify(string imagePath, HeifCompressionMethod compressionMethod, HeifBitDepth bitDepth, int width, int height) + [InlineData(TestImages.Heif.IrvineAvif, HeifBitDepth.Bit8, 480, 640)] + public void Identify(string imagePath, HeifBitDepth bitDepth, int width, int height) { TestFile testFile = TestFile.Create(imagePath); using MemoryStream stream = new(testFile.Bytes, false); @@ -44,7 +109,6 @@ public class HeifDecoderTests Assert.NotNull(imageInfo); Assert.Equal(HeifFormat.Instance, imageInfo.Metadata.DecodedImageFormat); - Assert.Equal(compressionMethod, heifMetadata.CompressionMethod); Assert.Equal(bitDepth, heifMetadata.BitDepth); Assert.Equal(width, imageInfo.Width); Assert.Equal(height, imageInfo.Height); @@ -120,7 +184,7 @@ public class HeifDecoderTests /// [Theory] [WithFile(TestImages.Heif.ParisIccExifXmpAvif, PixelTypes.Rgba32)] - public void DecodeAvifPreservesEmbeddedIccProfile(TestImageProvider provider) + public void IccPreserve(TestImageProvider provider) where TPixel : unmanaged, IPixel { DecoderOptions preserveOptions = new() { ColorProfileHandling = ColorProfileHandling.Preserve }; @@ -131,6 +195,8 @@ public class HeifDecoderTests Assert.NotNull(preserved.Metadata.IccProfile); Assert.NotNull(expectedPreserved.Metadata.IccProfile); Assert.Equal(expectedPreserved.Metadata.IccProfile.ToByteArray(), preserved.Metadata.IccProfile.ToByteArray()); + preserved.DebugSave(provider, extension: "png", encoder: new PngEncoder()); + preserved.CompareToReferenceOutput(ImageComparer.Exact, provider); } /// @@ -138,7 +204,7 @@ public class HeifDecoderTests /// [Theory] [WithFile(TestImages.Heif.PerceptualIccAvif, PixelTypes.Rgba32)] - public void DecodeAvifConvertsEmbeddedNonSrgbIccProfile(TestImageProvider provider) + public void IccConvert(TestImageProvider provider) where TPixel : unmanaged, IPixel { DecoderOptions preserveOptions = new() { ColorProfileHandling = ColorProfileHandling.Preserve }; @@ -146,22 +212,13 @@ public class HeifDecoderTests using Image preserved = provider.GetImage(HeifDecoder.Instance, preserveOptions); using Image converted = provider.GetImage(HeifDecoder.Instance, convertOptions); - using Image expected = Image.Load(convertOptions, TestFile.Create(TestImages.Png.Icc.Perceptual).Bytes); Assert.NotNull(preserved.Metadata.IccProfile); Assert.Null(converted.Metadata.IccProfile); Assert.NotEmpty(ImageComparer.Exact.CompareImages(preserved, converted)); - // The decoded metadata retains the AVIF source matrix, which PNG cannot represent. The debug output exists - // only to inspect converted pixels, so omit metadata without altering the image under test. - converted.DebugSave( - provider, - new PngEncoder { SkipMetadata = true }, - testOutputDetails: "IccConverted"); - - // The PNG is the independent RGB source used by libavif's avifenc. A tolerant comparison accounts for the - // AV1 loss while proving the AVIF ICC stage produces the same target-profile interpretation. - ImageComparer.TolerantPercentage(1F, 20).VerifySimilarity(expected, converted); + converted.DebugSave(provider, extension: "png", encoder: new PngEncoder()); + converted.CompareToReferenceOutput(ImageComparer.Exact, provider); } /// @@ -169,7 +226,7 @@ public class HeifDecoderTests /// [Theory] [WithFile(TestImages.Heif.PerceptualIccGridAvif, PixelTypes.Rgba32)] - public void DecodeAvifGridConvertsEmbeddedNonSrgbIccProfile(TestImageProvider provider) + public void IccConvertGrid(TestImageProvider provider) where TPixel : unmanaged, IPixel { DecoderOptions preserveOptions = new() { ColorProfileHandling = ColorProfileHandling.Preserve }; @@ -178,14 +235,14 @@ public class HeifDecoderTests using Image preserved = provider.GetImage(HeifDecoder.Instance, preserveOptions); using Image converted = provider.GetImage(HeifDecoder.Instance, convertOptions); using Image expectedPreserved = Image.Load(preserveOptions, TestFile.Create(TestImages.Png.Icc.Perceptual).Bytes); - using Image expected = Image.Load(convertOptions, TestFile.Create(TestImages.Png.Icc.Perceptual).Bytes); IccProfile preservedIccProfile = Assert.IsType(preserved.Metadata.IccProfile); IccProfile expectedIccProfile = Assert.IsType(expectedPreserved.Metadata.IccProfile); Assert.Null(converted.Metadata.IccProfile); Assert.Equal(expectedIccProfile.ToByteArray(), preservedIccProfile.ToByteArray()); Assert.NotEmpty(ImageComparer.Exact.CompareImages(preserved, converted)); - ImageComparer.TolerantPercentage(1F, 20).VerifySimilarity(expected, converted); + converted.DebugSave(provider, extension: "png", encoder: new PngEncoder()); + converted.CompareToReferenceOutput(ImageComparer.Exact, provider); } /// @@ -193,7 +250,7 @@ public class HeifDecoderTests /// [Theory] [WithFile(TestImages.Heif.PerceptualIccSequenceAvif, PixelTypes.Rgba32)] - public void DecodeAvifSequenceConvertsEveryFrameWithEmbeddedNonSrgbIccProfile(TestImageProvider provider) + public void IccConvertSequence(TestImageProvider provider) where TPixel : unmanaged, IPixel { DecoderOptions preserveOptions = new() { ColorProfileHandling = ColorProfileHandling.Preserve }; @@ -202,7 +259,6 @@ public class HeifDecoderTests using Image preserved = provider.GetImage(HeifDecoder.Instance, preserveOptions); using Image converted = provider.GetImage(HeifDecoder.Instance, convertOptions); using Image expectedPreserved = Image.Load(preserveOptions, TestFile.Create(TestImages.Png.Icc.Perceptual).Bytes); - using Image expected = Image.Load(convertOptions, TestFile.Create(TestImages.Png.Icc.Perceptual).Bytes); Assert.Equal(2, preserved.Frames.Count); Assert.Equal(preserved.Frames.Count, converted.Frames.Count); @@ -211,55 +267,23 @@ public class HeifDecoderTests Assert.Null(converted.Metadata.IccProfile); Assert.Equal(expectedIccProfile.ToByteArray(), preservedIccProfile.ToByteArray()); - for (int i = 0; i < converted.Frames.Count; i++) - { - Assert.False(ImageComparer.Exact.CompareImagesOrFrames(i, preserved.Frames[i], converted.Frames[i]).IsEmpty); - Assert.True(ImageComparer.TolerantPercentage(1F, 20).CompareImagesOrFrames(i, expected.Frames.RootFrame, converted.Frames[i]).IsEmpty); - } + converted.DebugSaveMultiFrame(provider, encoder: new PngEncoder()); + converted.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); } /// /// Verifies that non-sRGB ICC conversion follows auxiliary-alpha composition and preserves the composed alpha values. /// - [Fact] - public void DecodeAvifAlphaImageConvertsEmbeddedIccProfileWithoutChangingAlpha() + [Theory] + [WithFile(TestImages.Heif.DuckyRommIccAlphaAvif, PixelTypes.Rgba32)] + public void IccConvertAlpha(TestImageProvider provider) { - DecoderOptions preserveOptions = new() { ColorProfileHandling = ColorProfileHandling.Preserve }; DecoderOptions convertOptions = new() { ColorProfileHandling = ColorProfileHandling.Convert }; - byte[] encoded = TestFile.Create(TestImages.Heif.DuckyRommIccAlphaAvif).Bytes; - - using Image preserved = Image.Load(preserveOptions, encoded); - using Image converted = Image.Load(convertOptions, encoded); - using Image expected = preserved.Clone(); - - ColorProfileConverter converter = new(new ColorConversionOptions - { - SourceIccProfile = expected.Metadata.IccProfile, - TargetIccProfile = CompactSrgbV4Profile.Profile, - MemoryAllocator = expected.Configuration.MemoryAllocator, - }); + using Image decoded = provider.GetImage(HeifDecoder.Instance, convertOptions); - // Build the oracle from the fully composed preserved decode so that only ICC ordering and alpha retention - // are under test; the independently encoded AV1 color and alpha payloads remain identical in both paths. - converter.Convert(expected); - - Assert.NotNull(preserved.Metadata.IccProfile); - Assert.Null(converted.Metadata.IccProfile); - Assert.Equal(TestIccProfiles.GetProfile(TestIccProfiles.RommRgb).ToByteArray(), preserved.Metadata.IccProfile.ToByteArray()); - Assert.NotEmpty(ImageComparer.Exact.CompareImages(preserved, converted)); - - for (int y = 0; y < converted.Height; y++) - { - Span preservedRow = preserved.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(y); - Span convertedRow = converted.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(y); - - for (int x = 0; x < convertedRow.Length; x++) - { - Assert.Equal(preservedRow[x].A, convertedRow[x].A); - } - } - - ImageComparer.Exact.VerifySimilarity(expected, converted); + decoded.DebugSave(provider, extension: "png", encoder: new PngEncoder()); + decoded.CompareToReferenceOutput(ImageComparer.Exact, provider); + Assert.Null(decoded.Metadata.IccProfile); } /// @@ -270,7 +294,7 @@ public class HeifDecoderTests [WithFile(TestImages.Heif.PerceptualIccGridAvif, PixelTypes.Rgba32)] [WithFile(TestImages.Heif.PerceptualIccSequenceAvif, PixelTypes.Rgba32)] [WithFile(TestImages.Heif.DuckyRommIccAlphaAvif, PixelTypes.Rgba32)] - public void DecodeAvifRetainsNonSrgbIccProfileWhenCompacting(TestImageProvider provider) + public void IccCompactNonSrgb(TestImageProvider provider) where TPixel : unmanaged, IPixel { DecoderOptions preserveOptions = new() { ColorProfileHandling = ColorProfileHandling.Preserve }; @@ -283,6 +307,16 @@ public class HeifDecoderTests Assert.NotNull(compact.Metadata.IccProfile); Assert.Equal(preserved.Metadata.IccProfile.ToByteArray(), compact.Metadata.IccProfile.ToByteArray()); Assert.Empty(ImageComparer.Exact.CompareImages(preserved, compact)); + if (compact.Frames.Count == 1) + { + compact.DebugSave(provider, extension: "png", encoder: new PngEncoder()); + compact.CompareToReferenceOutput(ImageComparer.Exact, provider); + } + else + { + compact.DebugSaveMultiFrame(provider, encoder: new PngEncoder()); + compact.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); + } } /// @@ -290,7 +324,7 @@ public class HeifDecoderTests /// [Theory] [WithFile(TestImages.Heif.ParisIccExifXmpAvif, PixelTypes.Rgba32)] - public void DecodeAvifCompactsCanonicalSrgbIccProfile(TestImageProvider provider) + public void IccCompactSrgb(TestImageProvider provider) where TPixel : unmanaged, IPixel { DecoderOptions preserveOptions = new() { ColorProfileHandling = ColorProfileHandling.Preserve }; @@ -302,6 +336,8 @@ public class HeifDecoderTests Assert.NotNull(preserved.Metadata.IccProfile); Assert.Null(compact.Metadata.IccProfile); Assert.Empty(ImageComparer.Exact.CompareImages(preserved, compact)); + compact.DebugSave(provider, extension: "png", encoder: new PngEncoder()); + compact.CompareToReferenceOutput(ImageComparer.Exact, provider); } /// @@ -312,7 +348,7 @@ public class HeifDecoderTests [WithFile(TestImages.Heif.PerceptualIccGridAvif, PixelTypes.Rgba32)] [WithFile(TestImages.Heif.PerceptualIccSequenceAvif, PixelTypes.Rgba32)] [WithFile(TestImages.Heif.DuckyRommIccAlphaAvif, PixelTypes.Rgba32)] - public void DecodeAvifSkipsEmbeddedIccProfileWithMetadata(TestImageProvider provider) + public void IccSkipMetadata(TestImageProvider provider) where TPixel : unmanaged, IPixel { DecoderOptions options = new() @@ -324,12 +360,22 @@ public class HeifDecoderTests using Image image = provider.GetImage(HeifDecoder.Instance, options); Assert.Null(image.Metadata.IccProfile); + if (image.Frames.Count == 1) + { + image.DebugSave(provider, extension: "png", encoder: new PngEncoder()); + image.CompareToReferenceOutput(ImageComparer.Exact, provider); + } + else + { + image.DebugSaveMultiFrame(provider, encoder: new PngEncoder()); + image.CompareToReferenceOutputMultiFrame(provider, ImageComparer.Exact); + } } [Fact] public void DecodeIgnoresUnknownTopLevelBox() { - byte[] data = CreateLegacyJpegContainer(); + byte[] data = CreateAv1Container(); data = InsertBytes(data, data.Length, CreateUnknownBox()); using Image image = Image.Load(data); @@ -369,58 +415,6 @@ public class HeifDecoderTests } } - [Fact] - public void DecodePropagatesStrictValidationToLegacyJpegItems() - { - byte[] data = CreateContainerWithMalformedJpegMetadata(); - DecoderOptions options = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.Strict }; - - Assert.Throws(() => - { - using Image image = Image.Load(options, data); - }); - } - - [Theory] - [InlineData(SegmentIntegrityHandling.IgnoreAncillary)] - [InlineData(SegmentIntegrityHandling.IgnoreImageData)] - public void DecodePropagatesRecoverableMetadataValidationToLegacyJpegItems(SegmentIntegrityHandling handling) - { - byte[] data = CreateContainerWithMalformedJpegMetadata(); - DecoderOptions options = new() { SegmentIntegrityHandling = handling }; - - using Image image = Image.Load(options, data); - - Assert.Equal(new Size(2, 3), image.Size); - } - - [Fact] - public void DecodePropagatesSkipMetadataToLegacyJpegItems() - { - byte[] data = CreateContainerWithMalformedJpegMetadata(); - DecoderOptions options = new() - { - SkipMetadata = true, - SegmentIntegrityHandling = SegmentIntegrityHandling.Strict - }; - - using Image image = Image.Load(options, data); - - Assert.Equal(new Size(2, 3), image.Size); - } - - [Fact] - public void DecodePropagatesConfigurationToLegacyJpegItems() - { - byte[] data = CreateLegacyJpegContainer(); - Configuration configuration = Configuration.CreateDefaultInstance(); - DecoderOptions options = new() { Configuration = configuration }; - - using Image image = Image.Load(options, data); - - Assert.Same(configuration, image.Configuration); - } - /// /// Verifies that invalid optional alpha payloads remain fatal when image-data errors cannot be ignored. /// @@ -555,7 +549,7 @@ public class HeifDecoderTests [Fact] public void IdentifyIgnoresUnknownMetadataBox() { - byte[] data = CreateLegacyJpegContainer(); + byte[] data = CreateAv1Container(); int metaOffset = FindBoxOffset(data, Heif4CharCode.Meta, 0, data.Length); int metaSize = (int)BinaryPrimitives.ReadUInt32BigEndian(data.AsSpan(metaOffset)); data = InsertBytes(data, metaOffset + metaSize, CreateUnknownBox()); @@ -698,10 +692,9 @@ public class HeifDecoderTests [Theory] [InlineData(Heif4CharCode.Mif1)] [InlineData(Heif4CharCode.Avif)] - [InlineData(Heif4CharCode.Jpeg)] public void DetectorRecognizesSupportedStillImageMajorBrand(Heif4CharCode brand) { - byte[] data = CreateLegacyJpegContainer(); + byte[] data = CreateAv1Container(); BinaryPrimitives.WriteUInt32BigEndian(data.AsSpan(8), (uint)brand); HeifImageFormatDetector detector = new(); @@ -715,7 +708,7 @@ public class HeifDecoderTests [InlineData(Heif4CharCode.Avis)] public void DetectorRecognizesSupportedSequenceMajorBrand(Heif4CharCode brand) { - byte[] data = CreateLegacyJpegContainer(); + byte[] data = CreateAv1Container(); BinaryPrimitives.WriteUInt32BigEndian(data.AsSpan(8), (uint)brand); HeifImageFormatDetector detector = new(); @@ -741,21 +734,10 @@ public class HeifDecoderTests Assert.Same(HeifFormat.Instance, format); } - [Theory] - [InlineData(Heif4CharCode.Jpgs)] - public void DetectorRejectsUnsupportedSequenceMajorBrand(Heif4CharCode brand) - { - byte[] data = CreateLegacyJpegContainer(); - BinaryPrimitives.WriteUInt32BigEndian(data.AsSpan(8), (uint)brand); - HeifImageFormatDetector detector = new(); - - Assert.False(detector.TryDetectFormat(data.AsSpan(0, detector.HeaderSize), out _)); - } - [Fact] public void IdentifyRejectsUnsupportedBrands() { - byte[] data = CreateLegacyJpegContainer(); + byte[] data = CreateAv1Container(); BinaryPrimitives.WriteUInt32BigEndian(data.AsSpan(8), UnknownBoxType); BinaryPrimitives.WriteUInt32BigEndian(data.AsSpan(16), UnknownBoxType); BinaryPrimitives.WriteUInt32BigEndian(data.AsSpan(20), UnknownBoxType); @@ -767,7 +749,7 @@ public class HeifDecoderTests [Fact] public void IdentifyAcceptsExtendedSizeTopLevelBox() { - byte[] data = CreateLegacyJpegContainer(); + byte[] data = CreateAv1Container(); byte[] box = new byte[16]; BinaryPrimitives.WriteUInt32BigEndian(box, 1); BinaryPrimitives.WriteUInt32BigEndian(box.AsSpan(4), UnknownBoxType); @@ -782,7 +764,7 @@ public class HeifDecoderTests [Fact] public void IdentifyAcceptsUuidTopLevelBox() { - byte[] data = CreateLegacyJpegContainer(); + byte[] data = CreateAv1Container(); byte[] box = new byte[24]; BinaryPrimitives.WriteUInt32BigEndian(box, (uint)box.Length); BinaryPrimitives.WriteUInt32BigEndian(box.AsSpan(4), (uint)Heif4CharCode.Uuid); @@ -796,7 +778,7 @@ public class HeifDecoderTests [Fact] public void IdentifyAcceptsSizeZeroTopLevelBox() { - byte[] data = CreateLegacyJpegContainer(); + byte[] data = CreateAv1Container(); byte[] box = CreateUnknownBox(); BinaryPrimitives.WriteUInt32BigEndian(box, 0); data = InsertBytes(data, data.Length, box); @@ -809,7 +791,7 @@ public class HeifDecoderTests [Fact] public void IdentifyAcceptsExtendedSizeItemInfoEntry() { - byte[] data = CreateLegacyJpegContainer(); + byte[] data = CreateAv1Container(); int metaOffset = FindBoxOffset(data, Heif4CharCode.Meta, 0, data.Length); int metaSize = (int)BinaryPrimitives.ReadUInt32BigEndian(data.AsSpan(metaOffset)); int iinfOffset = FindBoxOffset(data, Heif4CharCode.Iinf, metaOffset + 12, metaSize - 12); @@ -829,7 +811,7 @@ public class HeifDecoderTests [Fact] public void IdentifyRejectsSizeZeroMetadataChild() { - byte[] data = CreateLegacyJpegContainer(); + byte[] data = CreateAv1Container(); int metaOffset = FindBoxOffset(data, Heif4CharCode.Meta, 0, data.Length); int metaSize = (int)BinaryPrimitives.ReadUInt32BigEndian(data.AsSpan(metaOffset)); byte[] box = CreateUnknownBox(); @@ -843,7 +825,7 @@ public class HeifDecoderTests [Fact] public void IdentifyRejectsMetadataChildBeyondParent() { - byte[] data = CreateLegacyJpegContainer(); + byte[] data = CreateAv1Container(); int metaOffset = FindBoxOffset(data, Heif4CharCode.Meta, 0, data.Length); int metaSize = (int)BinaryPrimitives.ReadUInt32BigEndian(data.AsSpan(metaOffset)); byte[] box = CreateUnknownBox(); @@ -857,7 +839,7 @@ public class HeifDecoderTests [Fact] public void IdentifyRejectsItemInfoEntryBeyondParent() { - byte[] data = CreateLegacyJpegContainer(); + byte[] data = CreateAv1Container(); int metaOffset = FindBoxOffset(data, Heif4CharCode.Meta, 0, data.Length); int metaSize = (int)BinaryPrimitives.ReadUInt32BigEndian(data.AsSpan(metaOffset)); int iinfOffset = FindBoxOffset(data, Heif4CharCode.Iinf, metaOffset + 12, metaSize - 12); @@ -871,7 +853,7 @@ public class HeifDecoderTests [Fact] public void IdentifyRejectsBoxSmallerThanHeader() { - byte[] data = CreateLegacyJpegContainer(); + byte[] data = CreateAv1Container(); byte[] box = CreateUnknownBox(); BinaryPrimitives.WriteUInt32BigEndian(box, 4); data = InsertBytes(data, data.Length, box); @@ -882,7 +864,7 @@ public class HeifDecoderTests [Fact] public void IdentifyRejectsTruncatedExtendedSizeHeader() { - byte[] data = CreateLegacyJpegContainer(); + byte[] data = CreateAv1Container(); byte[] box = new byte[12]; BinaryPrimitives.WriteUInt32BigEndian(box, 1); BinaryPrimitives.WriteUInt32BigEndian(box.AsSpan(4), UnknownBoxType); @@ -894,7 +876,7 @@ public class HeifDecoderTests [Fact] public void IdentifyRejectsTruncatedUuidHeader() { - byte[] data = CreateLegacyJpegContainer(); + byte[] data = CreateAv1Container(); byte[] box = new byte[16]; BinaryPrimitives.WriteUInt32BigEndian(box, 24); BinaryPrimitives.WriteUInt32BigEndian(box.AsSpan(4), (uint)Heif4CharCode.Uuid); @@ -906,7 +888,7 @@ public class HeifDecoderTests [Fact] public void IdentifyAcceptsItemPropertiesBeforeItemInfo() { - byte[] data = CreateLegacyJpegContainer(); + byte[] data = CreateAv1Container(); int metaOffset = FindBoxOffset(data, Heif4CharCode.Meta, 0, data.Length); int metaSize = (int)BinaryPrimitives.ReadUInt32BigEndian(data.AsSpan(metaOffset)); int iinfOffset = FindBoxOffset(data, Heif4CharCode.Iinf, metaOffset + 12, metaSize - 12); @@ -922,7 +904,7 @@ public class HeifDecoderTests [Fact] public void IdentifyAcceptsItemLocationBeforeItemInfo() { - byte[] data = CreateLegacyJpegContainer(); + byte[] data = CreateAv1Container(); int metaOffset = FindBoxOffset(data, Heif4CharCode.Meta, 0, data.Length); int metaSize = (int)BinaryPrimitives.ReadUInt32BigEndian(data.AsSpan(metaOffset)); int iinfOffset = FindBoxOffset(data, Heif4CharCode.Iinf, metaOffset + 12, metaSize - 12); @@ -938,7 +920,7 @@ public class HeifDecoderTests [Fact] public void IdentifyRejectsDuplicateUniqueMetadataBox() { - byte[] data = CreateLegacyJpegContainer(); + byte[] data = CreateAv1Container(); int metaOffset = FindBoxOffset(data, Heif4CharCode.Meta, 0, data.Length); int metaSize = (int)BinaryPrimitives.ReadUInt32BigEndian(data.AsSpan(metaOffset)); int pitmOffset = FindBoxOffset(data, Heif4CharCode.Pitm, metaOffset + 12, metaSize - 12); @@ -949,11 +931,11 @@ public class HeifDecoderTests Assert.Throws(() => Image.Identify(data)); } - private static byte[] CreateLegacyJpegContainer() + private static byte[] CreateAv1Container() { - using Image image = new(2, 3); + using Image image = new(2, 3); using MemoryStream stream = new(); - image.Save(stream, new HeifEncoder { CompressionMethod = HeifCompressionMethod.LegacyJpeg }); + image.Save(stream, new HeifEncoder()); return stream.ToArray(); } @@ -962,7 +944,7 @@ public class HeifDecoderTests private static byte[] CreateContainerWithProperty(ReadOnlySpan property, bool essential) { - byte[] data = CreateLegacyJpegContainer(); + byte[] data = CreateAv1Container(); int metaOffset = FindBoxOffset(data, Heif4CharCode.Meta, 0, data.Length); int metaSize = (int)BinaryPrimitives.ReadUInt32BigEndian(data.AsSpan(metaOffset)); int iprpOffset = FindBoxOffset(data, Heif4CharCode.Iprp, metaOffset + 12, metaSize - 12); @@ -971,18 +953,26 @@ public class HeifDecoderTests int ipcoSize = (int)BinaryPrimitives.ReadUInt32BigEndian(data.AsSpan(ipcoOffset)); int ipmaOffset = FindBoxOffset(data, Heif4CharCode.Ipma, iprpOffset + 8, iprpSize - 8); - // Insert the property before ipma so its one-based index is 2 and all parent box sizes remain explicit. + // Count existing AV1 properties before appending the test property; its association is one-based. + int propertyIndex = 1; + for (int offset = ipcoOffset + 8; offset < ipcoOffset + ipcoSize;) + { + offset += (int)BinaryPrimitives.ReadUInt32BigEndian(data.AsSpan(offset)); + propertyIndex++; + } + + // Insert before ipma and update each enclosing box size. data = InsertBytes(data, ipcoOffset + ipcoSize, property); IncrementBoxSize(data, metaOffset, property.Length); IncrementBoxSize(data, iprpOffset, property.Length); IncrementBoxSize(data, ipcoOffset, property.Length); ipmaOffset += property.Length; - // The generated container has one item with one property association; append the inserted property to that entry. + // Append the new property association to the sole opaque AV1 item's existing entry. int associationCountOffset = ipmaOffset + 18; data[associationCountOffset]++; int associationOffset = ipmaOffset + (int)BinaryPrimitives.ReadUInt32BigEndian(data.AsSpan(ipmaOffset)); - byte association = (byte)(2 | (essential ? 0x80 : 0)); + byte association = (byte)(propertyIndex | (essential ? 0x80 : 0)); data = InsertBytes(data, associationOffset, new byte[] { association }); IncrementBoxSize(data, metaOffset, 1); IncrementBoxSize(data, iprpOffset, 1); @@ -990,23 +980,6 @@ public class HeifDecoderTests return data; } - private static byte[] CreateContainerWithMalformedJpegMetadata() - { - byte[] data = CreateLegacyJpegContainer(); - int metaOffset = FindBoxOffset(data, Heif4CharCode.Meta, 0, data.Length); - int metaSize = (int)BinaryPrimitives.ReadUInt32BigEndian(data.AsSpan(metaOffset)); - int itemLocationOffset = FindBoxOffset(data, Heif4CharCode.Iloc, metaOffset + 12, metaSize - 12); - int mediaDataOffset = FindBoxOffset(data, Heif4CharCode.Mdat, 0, data.Length); - - // The generated item uses one file-relative extent. Insert the malformed JPEG application segment after its - // start-of-image marker, then update the enclosing media-data size and the exact declared extent length. - data = InsertBytes(data, mediaDataOffset + 10, MalformedJpegApp13); - IncrementBoxSize(data, mediaDataOffset, MalformedJpegApp13.Length); - uint extentLength = BinaryPrimitives.ReadUInt32BigEndian(data.AsSpan(itemLocationOffset + 32)); - BinaryPrimitives.WriteUInt32BigEndian(data.AsSpan(itemLocationOffset + 32), extentLength + (uint)MalformedJpegApp13.Length); - return data; - } - private static byte[] CreateContainerWithDuplicatePropertyAssociation(ReadOnlySpan property, bool essential) { byte[] data = CreateContainerWithProperty(property, essential); @@ -1020,7 +993,7 @@ public class HeifDecoderTests // Repeat the inserted property's one-based index in the existing item entry without changing box structure. data[associationCountOffset]++; int associationOffset = ipmaOffset + (int)BinaryPrimitives.ReadUInt32BigEndian(data.AsSpan(ipmaOffset)); - byte association = (byte)(2 | (essential ? 0x80 : 0)); + byte association = data[associationOffset - 1]; data = InsertBytes(data, associationOffset, new byte[] { association }); IncrementBoxSize(data, metaOffset, 1); IncrementBoxSize(data, iprpOffset, 1); @@ -1137,6 +1110,20 @@ public class HeifDecoderTests /// The complete mutable HEIF container. /// The item whose coded payload is cleared. private static void ClearItemPayload(Span data, uint itemId) + { + foreach (Range extent in GetItemPayloadRanges(data, itemId)) + { + data[extent].Clear(); + } + } + + /// + /// Locates every file-relative extent for an item without decoding its contents. + /// + /// The complete HEIF container. + /// The item whose extents are selected. + /// The item's extents in file order. + private static List GetItemPayloadRanges(ReadOnlySpan data, uint itemId) { int metaOffset = FindBoxOffset(data, Heif4CharCode.Meta, 0, data.Length); Assert.True(metaOffset >= 0); @@ -1161,7 +1148,8 @@ public class HeifDecoderTests : BinaryPrimitives.ReadUInt16BigEndian(data[offset..]); offset += version == 2 ? 4 : 2; - bool found = false; + List extents = new(); + for (uint itemIndex = 0; itemIndex < itemCount; itemIndex++) { uint currentItemId = version == 2 @@ -1189,13 +1177,14 @@ public class HeifDecoderTests ulong extentLength = ReadVariableUnsigned(data, extentLengthSize, ref offset); if (currentItemId == itemId) { - data.Slice(checked((int)(baseOffset + extentOffset)), checked((int)extentLength)).Clear(); - found = true; + int start = checked((int)(baseOffset + extentOffset)); + extents.Add(new Range(start, checked(start + (int)extentLength))); } } } - Assert.True(found); + Assert.NotEmpty(extents); + return extents; } /// diff --git a/tests/ImageSharp.Tests/Formats/Heif/HeifEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Heif/HeifEncoderTests.cs index 2fec37aaf6..1a0a146065 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/HeifEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/HeifEncoderTests.cs @@ -7,9 +7,11 @@ using System.Text; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Heif; using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.Formats.Heif.Av1.Color; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; using SixLabors.ImageSharp.Formats.Heif.Av1.Transform; +using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata.Profiles.Cicp; using SixLabors.ImageSharp.Metadata.Profiles.Exif; @@ -39,7 +41,6 @@ public class HeifEncoderTests { HeifEncoder encoder = new(); - Assert.Equal(HeifCompressionMethod.Av1, encoder.CompressionMethod); Assert.Null(encoder.Quality); Assert.Null(encoder.AlphaQuality); Assert.Equal(5, encoder.Effort); @@ -85,150 +86,6 @@ public class HeifEncoderTests Assert.Equal(effort, encoder.Effort); } - [Fact] - public void LegacyJpegAcceptsZeroQuality() - { - using Image image = new(1, 1); - image[0, 0] = new Rgba32(10, 20, 30); - using MemoryStream stream = new(); - HeifEncoder encoder = new() - { - CompressionMethod = HeifCompressionMethod.LegacyJpeg, - Quality = 0 - }; - - image.Save(stream, encoder); - - Assert.NotEqual(0, stream.Length); - stream.Position = 0; - using Image decoded = Image.Load(stream); - Assert.Equal(image.Size, decoded.Size); - } - - [Fact] - public void LegacyJpegWritesNonSeekableStream() - { - using Image image = new(1, 1); - image[0, 0] = new Rgba32(10, 20, 30); - using MemoryStream storage = new(); - using NonSeekableStream destination = new(storage); - - image.Save( - destination, - new HeifEncoder { CompressionMethod = HeifCompressionMethod.LegacyJpeg }); - - Assert.NotEqual(0, storage.Length); - storage.Position = 0; - using Image decoded = Image.Load(storage); - Assert.Equal(image.Size, decoded.Size); - } - - [Fact] - public void LegacyJpegWritesAtCurrentStreamPosition() - { - using Image image = new(1, 1); - image[0, 0] = new Rgba32(10, 20, 30); - using MemoryStream stream = new(); - stream.Write([1, 2, 3, 4]); - long fileStart = stream.Position; - - image.Save( - stream, - new HeifEncoder { CompressionMethod = HeifCompressionMethod.LegacyJpeg }); - - stream.Position = fileStart; - using Image decoded = Image.Load(stream); - Assert.Equal(image.Size, decoded.Size); - } - - [Theory] - [InlineData(false, true)] - [InlineData(true, false)] - public void LegacyJpegHonorsSkipMetadataForEmbeddedProfiles(bool skipMetadata, bool expectedIccProfile) - { - using Image image = new(8, 8); - image.Metadata.IccProfile = new IccProfile(IccTestDataProfiles.ProfileRandomArray); - using MemoryStream stream = new(); - HeifEncoder encoder = new() - { - CompressionMethod = HeifCompressionMethod.LegacyJpeg, - SkipMetadata = skipMetadata - }; - - image.Save(stream, encoder); - - DecoderOptions preserveOptions = new() { ColorProfileHandling = ColorProfileHandling.Preserve }; - stream.Position = 0; - using Image decoded = Image.Load(preserveOptions, stream); - Assert.Equal(expectedIccProfile, decoded.Metadata.IccProfile is not null); - if (expectedIccProfile) - { - Assert.Equal( - IccTestDataProfiles.ProfileRandomArray, - Assert.IsType(decoded.Metadata.IccProfile).ToByteArray()); - } - } - - [Fact] - public void LegacyJpegIgnoresLosslessOption() - { - using Image image = new(1, 1); - using MemoryStream stream = new(); - HeifEncoder encoder = new() - { - CompressionMethod = HeifCompressionMethod.LegacyJpeg, - Lossless = true - }; - - image.Save(stream, encoder); - stream.Position = 0; - using Image decoded = Image.Load(stream); - Assert.Equal(image.Size, decoded.Size); - Assert.Equal( - HeifCompressionMethod.LegacyJpeg, - decoded.Metadata.GetHeifMetadata().CompressionMethod); - } - - [Theory] - [InlineData(HeifBitDepth.Bit10)] - [InlineData(HeifBitDepth.Bit12)] - public void LegacyJpegNormalizesHighBitDepthToEightBit(HeifBitDepth bitDepth) - { - using Image image = new(1, 1); - using MemoryStream stream = new(); - HeifEncoder encoder = new() - { - CompressionMethod = HeifCompressionMethod.LegacyJpeg, - BitDepth = bitDepth - }; - - image.Save(stream, encoder); - stream.Position = 0; - using Image decoded = Image.Load(stream); - Assert.Equal(HeifBitDepth.Bit8, decoded.Metadata.GetHeifMetadata().BitDepth); - } - - [Fact] - public void LegacyJpegEncodesRootFrameFromImageSequence() - { - using Image image = new(1, 1); - image.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(0)[0] = new Rgba32(255, 255, 255); - image.Frames.AddFrame(image.Frames.RootFrame); - image.Frames[1].PixelBuffer.DangerousGetRowSpan(0)[0] = new Rgba32(0, 0, 0); - using MemoryStream stream = new(); - HeifEncoder encoder = new() - { - CompressionMethod = HeifCompressionMethod.LegacyJpeg, - Quality = 100 - }; - - image.Save(stream, encoder); - stream.Position = 0; - using Image decoded = Image.Load(stream); - Assert.Single(decoded.Frames); - Assert.Equal(new Rgba32(255, 255, 255), decoded.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(0)[0]); - } - [Fact] public void Av1ImageSequencePreservesSeparateRootFrame() { @@ -250,7 +107,6 @@ public class HeifEncoderTests using MemoryStream stream = new(); HeifEncoder encoder = new() { - CompressionMethod = HeifCompressionMethod.Av1, AnimateRootFrame = false, Lossless = true, Effort = 0 @@ -342,11 +198,22 @@ public class HeifEncoderTests List links = [gridLink]; GridHeifItemDecoder decoder = new(items, links, ReadItem); Span descriptor = [0, 0, 1, 1, 0, outputWidth, 0, outputHeight]; - using Image result = decoder.DecodeItemData( + using Image result = new(outputWidth, outputHeight); + decoder.DecodeItemData( new DecoderOptions { Configuration = Configuration.Default }, + HeifChromaUpsampling.Auto, gridItem, descriptor, null, + null, + null, + default, + default, + false, + result.Bounds, + default, + result.Frames.RootFrame.PixelBuffer.GetRegion(result.Bounds), + result.Metadata, TestContext.Current.CancellationToken); for (int y = 0; y < outputHeight; y++) @@ -360,15 +227,20 @@ public class HeifEncoderTests Rgba32 opaqueColor = new(7, 11, 13); using Image alphaResult = new(outputWidth, outputHeight, opaqueColor); - decoder.DecodeAlphaItemData( + using Av1FrameBuffer alphaFrame = decoder.DecodeAlphaItemData( new DecoderOptions { Configuration = Configuration.Default }, gridItem, descriptor, - alphaResult.Frames.RootFrame, + TestContext.Current.CancellationToken); + + Av1YuvConverter.ComposeAlpha( + Configuration.Default, + alphaFrame, + alphaResult.Frames.RootFrame.PixelBuffer.GetRegion(alphaResult.Bounds), alphaResult.Size, new Rectangle(Point.Empty, alphaResult.Size), false, - TestContext.Current.CancellationToken); + default); for (int y = 0; y < outputHeight; y++) { @@ -469,7 +341,6 @@ public class HeifEncoderTests using MemoryStream stream = new(); HeifEncoder encoder = new() { - CompressionMethod = HeifCompressionMethod.Av1, Lossless = true, ChromaSubsampling = chromaSubsampling, Effort = 0 @@ -482,9 +353,40 @@ public class HeifEncoderTests GetItemPayload(file, 1).ToArray()); using Av1Decoder firstCellDecoder = new(Configuration.Default); - using Image firstCell = firstCellDecoder.Decode(GetItemPayload(file, 2)); + using Av1FrameBuffer firstCellPlanes = firstCellDecoder.DecodeFrameBuffer(GetItemPayload(file, 2), null, null, out _); + using Image firstCell = new(Configuration.Default, firstCellPlanes.Width, firstCellPlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + firstCellPlanes, + firstCell.Bounds, + firstCell.Frames.RootFrame.PixelBuffer.GetRegion(firstCell.Bounds), + firstCell.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + firstCellPlanes.ColorConfig.ColorRange); + using Av1Decoder secondCellDecoder = new(Configuration.Default); - using Image secondCell = secondCellDecoder.Decode(GetItemPayload(file, 3)); + using Av1FrameBuffer secondCellPlanes = secondCellDecoder.DecodeFrameBuffer(GetItemPayload(file, 3), null, null, out _); + using Image secondCell = new(Configuration.Default, secondCellPlanes.Width, secondCellPlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + secondCellPlanes, + secondCell.Bounds, + secondCell.Frames.RootFrame.PixelBuffer.GetRegion(secondCell.Bounds), + secondCell.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + secondCellPlanes.ColorConfig.ColorRange); // Odd grid dimensions require full-resolution chroma, including when subsampling was explicitly requested. // The conversion must retain the source profile and signal the resolved sampling on every coded cell. @@ -526,138 +428,69 @@ public class HeifEncoderTests Assert.Empty(ImageComparer.Exact.CompareImages(image, decoded)); } - [Fact] - public void Av1LosslessRoundTripPreservesColorAndAlpha() + [Theory] + [WithFile(TestImages.Png.Ducky, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Bike, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Splash, PixelTypes.Rgba32)] + [WithFile(TestImages.Png.Transparency, PixelTypes.Rgba32)] + public void LosslessRgba(TestImageProvider provider) { - const int width = 8; - const int height = 8; - using Image image = new(width, height); - for (int row = 0; row < height; row++) - { - Span pixels = image.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(row); - for (int column = 0; column < width; column++) - { - pixels[column] = new Rgba32( - (byte)((column * 31) + row), - (byte)((row * 29) + column), - (byte)((column * 17) + (row * 11)), - (byte)((column * 23) + (row * 7))); - } - } + using Image image = provider.GetImage(); - // Identity-matrix 4:4:4 maps the packed RGB channels directly onto AV1 planes, so codec losslessness - // can be asserted against the original pixels without a separate color-conversion tolerance. + // Identity 4:4:4 preserves the source RGB samples without matrix or chroma-subsampling losses. image.Metadata.CicpProfile = new CicpProfile(1, 13, 0, true); - using MemoryStream stream = new(); HeifEncoder encoder = new() { - CompressionMethod = HeifCompressionMethod.Av1, Lossless = true, Effort = 0 }; - image.Save(stream, encoder); - byte[] file = stream.ToArray(); - Span colorPayload = GetItemPayload(file, 1); - using Av1Decoder colorDecoder = new(Configuration.Default); - using Image colorImage = colorDecoder.Decode(colorPayload); - ObuSequenceHeader colorSequenceHeader = Assert.IsType(colorDecoder.SequenceHeader); - ObuFrameHeader colorFrameHeader = Assert.IsType(colorDecoder.FrameHeader); - - Assert.Equal(Av1ColorFormat.Yuv444, colorSequenceHeader.ColorConfig.GetColorFormat()); - Assert.Equal(0, colorFrameHeader.QuantizationParameters.BaseQIndex); - Assert.True(colorFrameHeader.CodedLossless); - Assert.True(colorFrameHeader.AllLossless); - Assert.Equal(Av1TransformMode.Only4x4, colorFrameHeader.TransformMode); - - Span alphaPayload = GetItemPayload(file, 2); - using Av1Decoder alphaDecoder = new(Configuration.Default); - using Image alphaImage = alphaDecoder.Decode(alphaPayload); - ObuFrameHeader alphaFrameHeader = Assert.IsType(alphaDecoder.FrameHeader); - Assert.Equal(0, alphaFrameHeader.QuantizationParameters.BaseQIndex); - Assert.True(alphaFrameHeader.CodedLossless); - - stream.Position = 0; - using Image decoded = Image.Load(stream); - Assert.Empty(ImageComparer.Exact.CompareImages(image, decoded)); - - string outputDirectory = Path.Combine( - TestEnvironment.ActualOutputDirectoryFullPath, - "Formats", - "Heif", - "Av1"); + string outputFile = image.VerifyEncoder( + provider, + "avif", + null, + encoder, + ImageComparer.Exact, + referenceDecoder: MagickReferenceDecoder.Heif); - Directory.CreateDirectory(outputDirectory); - File.WriteAllBytes(Path.Combine(outputDirectory, "encoder-public-lossless-color.obu"), colorPayload.ToArray()); - File.WriteAllBytes(Path.Combine(outputDirectory, "encoder-public-lossless-alpha.obu"), alphaPayload.ToArray()); + using FileStream stream = File.OpenRead(outputFile); + using Image reference = MagickReferenceDecoder.Heif.Decode(DecoderOptions.Default, stream); + reference.DebugSave(provider, extension: "png", encoder: new PngEncoder()); } [Theory] - [InlineData(HeifBitDepth.Bit10)] - [InlineData(HeifBitDepth.Bit12)] - public void Av1LosslessRoundTripPreservesHighBitDepthSourcePixels(HeifBitDepth bitDepth) + [WithFile(TestImages.Tiff.Rgba10BitUnassociatedAlphaBigEndian, PixelTypes.Rgba64, HeifBitDepth.Bit10)] + [WithFile(TestImages.Tiff.Rgba12BitUnassociatedAlphaBigEndian, PixelTypes.Rgba64, HeifBitDepth.Bit12)] + public void Av1LosslessRoundTripPreservesHighBitDepthSourcePixels(TestImageProvider provider, HeifBitDepth bitDepth) { - const int width = 8; - const int height = 8; - int codedMaximum = (1 << (int)bitDepth) - 1; - using Image image = new(width, height); - for (int row = 0; row < height; row++) - { - Span pixels = image.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(row); - for (int column = 0; column < width; column++) - { - int red = ((column * 131) + (row * 37) + 1) & codedMaximum; - int green = ((column * 61) + (row * 173) + 3) & codedMaximum; - int blue = ((column * 211) + (row * 47) + 5) & codedMaximum; - int alpha = ((column * 127) + (row * 89)) & codedMaximum; - pixels[column] = new Rgba64( - ExpandToUShort(red, codedMaximum), - ExpandToUShort(green, codedMaximum), - ExpandToUShort(blue, codedMaximum), - ExpandToUShort(alpha, codedMaximum)); - } - } + using Image image = provider.GetImage(); - // Full-range identity 4:4:4 preserves the requested sample lattice, isolating source precision from a - // deliberately lossy color matrix or chroma subsampling step. + // Identity 4:4:4 retains the source's native 10/12-bit RGB sample precision without a color matrix. image.Metadata.CicpProfile = new CicpProfile(1, 13, 0, true); - using MemoryStream stream = new(); HeifEncoder encoder = new() { - CompressionMethod = HeifCompressionMethod.Av1, BitDepth = bitDepth, ChromaSubsampling = HeifChromaSubsampling.Yuv444, Lossless = true, Effort = 0 }; - image.Save(stream, encoder); - byte[] file = stream.ToArray(); - Span colorPayload = GetItemPayload(file, 1); - Span alphaPayload = GetItemPayload(file, 2); - stream.Position = 0; - using Image decoded = Image.Load(stream); - Assert.Empty(ImageComparer.Exact.CompareImages(image, decoded)); - - string outputDirectory = Path.Combine( - TestEnvironment.ActualOutputDirectoryFullPath, - "Formats", - "Heif", - "Av1"); - - Directory.CreateDirectory(outputDirectory); - File.WriteAllBytes( - Path.Combine(outputDirectory, $"encoder-public-lossless-{(int)bitDepth}b-color.obu"), - colorPayload.ToArray()); - - File.WriteAllBytes( - Path.Combine(outputDirectory, $"encoder-public-lossless-{(int)bitDepth}b-alpha.obu"), - alphaPayload.ToArray()); + string outputFile = image.VerifyEncoder( + provider, + "avif", + bitDepth, + encoder, + ImageComparer.Exact, + referenceDecoder: MagickReferenceDecoder.Heif); + + using FileStream stream = File.OpenRead(outputFile); + using Image reference = MagickReferenceDecoder.Heif.Decode(DecoderOptions.Default, stream); + reference.DebugSave(provider, bitDepth, encoder: new PngEncoder + { + BitDepth = PngBitDepth.Bit16 + }); } - private static ushort ExpandToUShort(int sample, int maximum) - => (ushort)(((sample * (long)ushort.MaxValue) + (maximum / 2)) / maximum); - [Theory] [InlineData((ushort)0, null, (ushort)0)] [InlineData((ushort)3, null, (ushort)3)] @@ -708,7 +541,6 @@ public class HeifEncoderTests using MemoryStream stream = new(); HeifEncoder encoder = new() { - CompressionMethod = HeifCompressionMethod.Av1, Lossless = true, Effort = 0, RepeatCount = encoderRepeatCount @@ -720,8 +552,24 @@ public class HeifEncoderTests Assert.Equal(1, BinaryPrimitives.ReadUInt16BigEndian(GetMetadataChild(file, Heif4CharCode.Pitm)[12..])); using (Av1Decoder sampleDecoder = new(Configuration.Default)) - using (Image decodedSample = sampleDecoder.Decode(GetItemPayload(file, 1))) { + using Av1FrameBuffer decodedSamplePlanes = sampleDecoder.DecodeFrameBuffer(GetItemPayload(file, 1), null, null, out _); + using Image decodedSample = new(Configuration.Default, decodedSamplePlanes.Width, decodedSamplePlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + decodedSamplePlanes, + decodedSample.Bounds, + decodedSample.Frames.RootFrame.PixelBuffer.GetRegion(decodedSample.Bounds), + decodedSample.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + decodedSamplePlanes.ColorConfig.ColorRange); + ObuSequenceHeader sampleHeader = sampleDecoder.SequenceHeader; Assert.NotNull(sampleHeader); Assert.False(sampleHeader.IsStillPicture); @@ -768,7 +616,6 @@ public class HeifEncoderTests using NonSeekableStream destination = new(storage); HeifEncoder encoder = new() { - CompressionMethod = HeifCompressionMethod.Av1, Effort = 0, SkipMetadata = true }; @@ -795,28 +642,14 @@ public class HeifEncoderTests public void Av1QualityMapsThroughLibaomQuantizers(int quality, int expectedQIndex) => Assert.Equal(expectedQIndex, HeifEncoderCore.GetAv1QuantizerIndex(quality)); - [Fact] - public void Av1WritesStillImageWithRequiredBrandsAndProductionPayload() + [Theory] + [WithFile(TestImages.Png.Bike, PixelTypes.Rgb24)] + public void Av1WritesStillImageWithRequiredBrandsAndColorDescription(TestImageProvider provider) { - const int width = 16; - const int height = 16; - using Image image = new(width, height); - for (int row = 0; row < height; row++) - { - Span pixels = image.Frames.RootFrame.PixelBuffer.DangerousGetRowSpan(row); - for (int column = 0; column < width; column++) - { - pixels[column] = new Rgb24( - (byte)(column * 11), - (byte)(row * 13), - (byte)((column + row) * 7)); - } - } - + using Image image = provider.GetImage(); using MemoryStream stream = new(); HeifEncoder encoder = new() { - CompressionMethod = HeifCompressionMethod.Av1, Quality = 75, Effort = 0 }; @@ -832,34 +665,16 @@ public class HeifEncoderTests Assert.Equal(Heif4CharCode.Mif1, (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(fileType[20..])); Assert.Equal(Heif4CharCode.Miaf, (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(fileType[24..])); - Span payload = GetItemPayload(file, 1); - using Av1Decoder payloadDecoder = new(Configuration.Default); - using Image payloadImage = payloadDecoder.Decode(payload); - ObuFrameHeader frameHeader = Assert.IsType(payloadDecoder.FrameHeader); - Assert.Equal(64, frameHeader.QuantizationParameters.BaseQIndex); - Assert.Equal(image.Size, payloadImage.Size); - stream.Position = 0; - using Image decoded = Image.Load(stream); - HeifMetadata metadata = decoded.Metadata.GetHeifMetadata(); - Assert.Equal(image.Size, decoded.Size); - Assert.Equal(HeifCompressionMethod.Av1, metadata.CompressionMethod); + ImageInfo info = Image.Identify(stream); + HeifMetadata metadata = info.Metadata.GetHeifMetadata(); + Assert.Equal(image.Size, info.Size); Assert.Equal(HeifBitDepth.Bit8, metadata.BitDepth); Assert.False(metadata.IsMonochrome); Assert.False(metadata.HasAlpha); - CicpProfile colorProfile = Assert.IsType(decoded.Metadata.CicpProfile); + CicpProfile colorProfile = Assert.IsType(info.Metadata.CicpProfile); Assert.Equal(CicpMatrixCoefficients.ItuRBt601_7_525, colorProfile.MatrixCoefficients); Assert.False(colorProfile.FullRange); - - string outputDirectory = Path.Combine( - TestEnvironment.ActualOutputDirectoryFullPath, - "Formats", - "Heif", - "Av1"); - - Directory.CreateDirectory(outputDirectory); - File.WriteAllBytes(Path.Combine(outputDirectory, "encoder-public-16x16-8b-420.avif"), file); - File.WriteAllBytes(Path.Combine(outputDirectory, "encoder-public-16x16-8b-420.obu"), payload.ToArray()); } [Fact] @@ -882,7 +697,6 @@ public class HeifEncoderTests using MemoryStream stream = new(); HeifEncoder encoder = new() { - CompressionMethod = HeifCompressionMethod.Av1, Quality = 75, AlphaQuality = 100, Effort = 0 @@ -893,12 +707,44 @@ public class HeifEncoderTests Span colorPayload = GetItemPayload(file, 1); Span alphaPayload = GetItemPayload(file, 2); using Av1Decoder colorDecoder = new(Configuration.Default); - using Image colorImage = colorDecoder.Decode(colorPayload); + using Av1FrameBuffer colorImagePlanes = colorDecoder.DecodeFrameBuffer(colorPayload, null, null, out _); + using Image colorImage = new(Configuration.Default, colorImagePlanes.Width, colorImagePlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + colorImagePlanes, + colorImage.Bounds, + colorImage.Frames.RootFrame.PixelBuffer.GetRegion(colorImage.Bounds), + colorImage.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + colorImagePlanes.ColorConfig.ColorRange); + ObuFrameHeader colorFrameHeader = Assert.IsType(colorDecoder.FrameHeader); Assert.Equal(64, colorFrameHeader.QuantizationParameters.BaseQIndex); using Av1Decoder alphaDecoder = new(Configuration.Default); - using Image alphaImage = alphaDecoder.Decode(alphaPayload); + using Av1FrameBuffer alphaImagePlanes = alphaDecoder.DecodeFrameBuffer(alphaPayload, null, null, out _); + using Image alphaImage = new(Configuration.Default, alphaImagePlanes.Width, alphaImagePlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + alphaImagePlanes, + alphaImage.Bounds, + alphaImage.Frames.RootFrame.PixelBuffer.GetRegion(alphaImage.Bounds), + alphaImage.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + alphaImagePlanes.ColorConfig.ColorRange); + ObuSequenceHeader alphaSequenceHeader = Assert.IsType(alphaDecoder.SequenceHeader); ObuFrameHeader alphaFrameHeader = Assert.IsType(alphaDecoder.FrameHeader); Assert.True(alphaSequenceHeader.ColorConfig.IsMonochrome); @@ -910,16 +756,6 @@ public class HeifEncoderTests Assert.True(metadata.HasAlpha); Assert.InRange(decoded[0, 0].A, (byte)0, (byte)8); Assert.InRange(decoded[width - 1, 0].A, (byte)247, byte.MaxValue); - - string outputDirectory = Path.Combine( - TestEnvironment.ActualOutputDirectoryFullPath, - "Formats", - "Heif", - "Av1"); - - Directory.CreateDirectory(outputDirectory); - File.WriteAllBytes(Path.Combine(outputDirectory, "encoder-public-alpha-color.obu"), colorPayload.ToArray()); - File.WriteAllBytes(Path.Combine(outputDirectory, "encoder-public-alpha-auxiliary.obu"), alphaPayload.ToArray()); } [Theory] @@ -952,7 +788,6 @@ public class HeifEncoderTests using MemoryStream stream = new(); HeifEncoder encoder = new() { - CompressionMethod = HeifCompressionMethod.Av1, BitDepth = bitDepth, ChromaSubsampling = chromaSubsampling, Effort = 0 @@ -962,7 +797,23 @@ public class HeifEncoderTests byte[] file = stream.ToArray(); Span payload = GetItemPayload(file, 1); using Av1Decoder payloadDecoder = new(Configuration.Default); - using Image payloadImage = payloadDecoder.Decode(payload); + using Av1FrameBuffer payloadImagePlanes = payloadDecoder.DecodeFrameBuffer(payload, null, null, out _); + using Image payloadImage = new(Configuration.Default, payloadImagePlanes.Width, payloadImagePlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + payloadImagePlanes, + payloadImage.Bounds, + payloadImage.Frames.RootFrame.PixelBuffer.GetRegion(payloadImage.Bounds), + payloadImage.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + payloadImagePlanes.ColorConfig.ColorRange); + ObuSequenceHeader sequenceHeader = Assert.IsType(payloadDecoder.SequenceHeader); Assert.Equal(expectedAv1BitDepth, sequenceHeader.ColorConfig.BitDepth); Assert.Equal(expectedColorFormat, sequenceHeader.ColorConfig.GetColorFormat()); @@ -973,17 +824,6 @@ public class HeifEncoderTests HeifMetadata metadata = decoded.Metadata.GetHeifMetadata(); Assert.Equal(bitDepth, metadata.BitDepth); Assert.Equal(chromaSubsampling == HeifChromaSubsampling.Monochrome, metadata.IsMonochrome); - - string outputDirectory = Path.Combine( - TestEnvironment.ActualOutputDirectoryFullPath, - "Formats", - "Heif", - "Av1"); - - Directory.CreateDirectory(outputDirectory); - File.WriteAllBytes( - Path.Combine(outputDirectory, $"encoder-public-8x8-{(int)bitDepth}b-{chromaSubsampling}.obu"), - payload.ToArray()); } [Fact] @@ -994,7 +834,6 @@ public class HeifEncoderTests using MemoryStream stream = new(); HeifEncoder encoder = new() { - CompressionMethod = HeifCompressionMethod.Av1, ChromaSubsampling = HeifChromaSubsampling.Yuv444, Effort = 0 }; @@ -1003,7 +842,23 @@ public class HeifEncoderTests byte[] file = stream.ToArray(); Span payload = GetItemPayload(file, 1); using Av1Decoder payloadDecoder = new(Configuration.Default); - using Image payloadImage = payloadDecoder.Decode(payload); + using Av1FrameBuffer payloadImagePlanes = payloadDecoder.DecodeFrameBuffer(payload, null, null, out _); + using Image payloadImage = new(Configuration.Default, payloadImagePlanes.Width, payloadImagePlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + payloadImagePlanes, + payloadImage.Bounds, + payloadImage.Frames.RootFrame.PixelBuffer.GetRegion(payloadImage.Bounds), + payloadImage.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + payloadImagePlanes.ColorConfig.ColorRange); + ObuSequenceHeader sequenceHeader = Assert.IsType(payloadDecoder.SequenceHeader); Assert.Equal(Av1BitDepth.TenBit, sequenceHeader.ColorConfig.BitDepth); } @@ -1062,7 +917,6 @@ public class HeifEncoderTests using MemoryStream stream = new(); image.Save(stream, new HeifEncoder { - CompressionMethod = HeifCompressionMethod.Av1, BitDepth = bitDepth, ChromaSubsampling = HeifChromaSubsampling.Yuv444, Lossless = true, @@ -1073,7 +927,23 @@ public class HeifEncoderTests Assert.Equal(fullRange, profile.FullRange); byte[] file = stream.ToArray(); using Av1Decoder sampleDecoder = new(Configuration.Default); - using Image sample = sampleDecoder.Decode(GetItemPayload(file, 1)); + using Av1FrameBuffer samplePlanes = sampleDecoder.DecodeFrameBuffer(GetItemPayload(file, 1), null, null, out _); + using Image sample = new(Configuration.Default, samplePlanes.Width, samplePlanes.Height); + Av1YuvConverter.ConvertToRgb( + Configuration.Default, + samplePlanes, + sample.Bounds, + sample.Frames.RootFrame.PixelBuffer.GetRegion(sample.Bounds), + sample.Size, + default, + null, + null, + default, + default, + false, + HeifChromaUpsampling.Auto, + samplePlanes.ColorConfig.ColorRange); + ObuSequenceHeader header = Assert.IsType(sampleDecoder.SequenceHeader); Assert.Equal(ObuMatrixCoefficients.Identity, header.ColorConfig.MatrixCoefficients); Assert.Equal((byte)profile.ColorPrimaries, (byte)header.ColorConfig.ColorPrimaries); @@ -1088,7 +958,7 @@ public class HeifEncoderTests Assert.Equal(profile.ColorPrimaries, decodedProfile.ColorPrimaries); Assert.Equal(profile.TransferCharacteristics, decodedProfile.TransferCharacteristics); Assert.Equal(CicpMatrixCoefficients.Identity, decodedProfile.MatrixCoefficients); - Assert.Equal(expectedFullRange, decodedProfile.FullRange); + Assert.True(decodedProfile.FullRange); Assert.Equal(image.Frames.Count, decoded.Frames.Count); for (int frameIndex = 0; frameIndex < image.Frames.Count; frameIndex++) { @@ -1105,39 +975,6 @@ public class HeifEncoderTests } } } - - string directory = TestEnvironment.CreateOutputDirectory("Heif", "Av1", nameof(this.Av1PreservesIdentityMatrixColorDescription)); - string name = $"{(int)bitDepth}-{fullRange}-{sequence}-{srgb}"; - File.WriteAllBytes(Path.Combine(directory, name + ".obu"), GetTopLevelBox(file, Heif4CharCode.Mdat)[8..].ToArray()); - using BinaryWriter expectedSamples = new(File.Create(Path.Combine(directory, name + ".expected.yuv"))); - int depthScale = 1 << ((int)bitDepth - 8); - int bias = expectedFullRange ? 0 : 16 * depthScale; - int range = expectedFullRange ? (1 << (int)bitDepth) - 1 : 219 * depthScale; - for (int frameIndex = 0; frameIndex < image.Frames.Count; frameIndex++) - { - for (int plane = 0; plane < 3; plane++) - { - for (int y = 0; y < Height; y++) - { - ReadOnlySpan row = image.Frames[frameIndex].PixelBuffer.DangerousGetRowSpan(y); - for (int x = 0; x < Width; x++) - { - // The independent reference is G, B, R with the luma range on every plane. - // These integer sample expectations do not call the production color converter. - int channel = plane == 0 ? row[x].G : plane == 1 ? row[x].B : row[x].R; - int value = bias + (((channel * range) + 127) / 255); - if (bitDepth == HeifBitDepth.Bit8) - { - expectedSamples.Write((byte)value); - } - else - { - expectedSamples.Write((ushort)value); - } - } - } - } - } } [Theory] @@ -1158,7 +995,6 @@ public class HeifEncoderTests using MemoryStream stream = new(); HeifEncoder encoder = new() { - CompressionMethod = HeifCompressionMethod.Av1, ChromaSubsampling = subsampling, Effort = 0 }; @@ -1169,10 +1005,10 @@ public class HeifEncoderTests Assert.False(sourceProfile.FullRange); stream.Position = 0; - using Image decoded = Image.Load(stream); - CicpProfile decodedProfile = Assert.IsType(decoded.Metadata.CicpProfile); - Assert.Equal(CicpMatrixCoefficients.ItuRBt601_7_525, decodedProfile.MatrixCoefficients); - Assert.False(decodedProfile.FullRange); + ImageInfo encoded = Image.Identify(stream); + CicpProfile encodedProfile = Assert.IsType(encoded.Metadata.CicpProfile); + Assert.Equal(CicpMatrixCoefficients.ItuRBt601_7_525, encodedProfile.MatrixCoefficients); + Assert.False(encodedProfile.FullRange); // A fallback must change the actual encoded conversion as well as its metadata. Compare with the // same packed pixels explicitly encoded using that fallback matrix and the requested sampling. @@ -1200,7 +1036,6 @@ public class HeifEncoderTests using MemoryStream stream = new(); HeifEncoder encoder = new() { - CompressionMethod = HeifCompressionMethod.Av1, Effort = 0 }; @@ -1280,7 +1115,6 @@ public class HeifEncoderTests using MemoryStream stream = new(); HeifEncoder encoder = new() { - CompressionMethod = HeifCompressionMethod.Av1, Effort = 0, SkipMetadata = true }; @@ -1325,7 +1159,6 @@ public class HeifEncoderTests using NonSeekableStream destination = new(storage); HeifEncoder encoder = new() { - CompressionMethod = HeifCompressionMethod.Av1, Effort = 0 }; @@ -1345,7 +1178,6 @@ public class HeifEncoderTests long fileStart = stream.Position; HeifEncoder encoder = new() { - CompressionMethod = HeifCompressionMethod.Av1, Effort = 0 }; @@ -1632,44 +1464,6 @@ public class HeifEncoderTests Assert.Equal(0x8081, BinaryPrimitives.ReadUInt16BigEndian(propertyBox[(finalEntryOffset + 7)..])); } - [Fact] - public void LegacyJpegEncodingDoesNotMutateSourceHeifMetadata() - { - using Image image = new(1, 1); - image[0, 0] = new Rgba32(10, 20, 30, 255); - HeifMetadata metadata = image.Metadata.GetHeifMetadata(); - metadata.CompressionMethod = HeifCompressionMethod.Av1; - using MemoryStream stream = new(); - HeifEncoder encoder = new() { CompressionMethod = HeifCompressionMethod.LegacyJpeg }; - - image.Save(stream, encoder); - - Assert.Same(metadata, image.Metadata.GetHeifMetadata()); - Assert.Equal(HeifCompressionMethod.Av1, metadata.CompressionMethod); - } - - [Theory] - [WithFile(TestImages.Heif.IrvineAvif, PixelTypes.Rgba32, HeifCompressionMethod.LegacyJpeg)] - public static void Encode(TestImageProvider provider, HeifCompressionMethod compressionMethod) - where TPixel : unmanaged, IPixel - { - using Image image = provider.GetImage(new MagickReferenceDecoder(HeifFormat.Instance)); - using MemoryStream stream = new(); - HeifEncoder encoder = new() { CompressionMethod = compressionMethod }; - image.Save(stream, encoder); - stream.Position = 0; - - ImageInfo imageInfo = Image.Identify(stream); - Assert.Equal(image.Size, imageInfo.Size); - - stream.Position = 0; - using Image encodedImage = Image.Load(stream); - HeifMetadata heifMetadata = encodedImage.Metadata.GetHeifMetadata(); - - ImageComparer.Exact.CompareImages(image, encodedImage); - Assert.Equal(compressionMethod, heifMetadata.CompressionMethod); - } - private static Span GetItemPayload(Span file, ushort itemId) { int offset = 0; @@ -1767,12 +1561,33 @@ public class HeifEncoderTests byte[] descriptor = new byte[8]; BinaryPrimitives.WriteUInt16BigEndian(descriptor.AsSpan(4), (ushort)width); BinaryPrimitives.WriteUInt16BigEndian(descriptor.AsSpan(6), (ushort)height); - return decoder.DecodeItemData( - new DecoderOptions { Configuration = Configuration.Default }, - gridItem, - descriptor, - null, - TestContext.Current.CancellationToken); + Image result = new(width, height); + try + { + decoder.DecodeItemData( + new DecoderOptions { Configuration = Configuration.Default }, + HeifChromaUpsampling.Auto, + gridItem, + descriptor, + null, + null, + null, + default, + default, + false, + result.Bounds, + default, + result.Frames.RootFrame.PixelBuffer.GetRegion(result.Bounds), + result.Metadata, + TestContext.Current.CancellationToken); + + return result; + } + catch + { + result.Dispose(); + throw; + } IMemoryOwner ReadItem(HeifItem item) { diff --git a/tests/ImageSharp.Tests/Formats/Heif/HeifMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Heif/HeifMetadataTests.cs index 7c75dfea09..33c90d794e 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/HeifMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/HeifMetadataTests.cs @@ -15,7 +15,6 @@ public class HeifMetadataTests { HeifMetadata metadata = new(); - Assert.Equal(HeifCompressionMethod.LegacyJpeg, metadata.CompressionMethod); Assert.Equal(HeifBitDepth.Bit8, metadata.BitDepth); Assert.False(metadata.IsMonochrome); Assert.False(metadata.HasAlpha); @@ -28,7 +27,6 @@ public class HeifMetadataTests { HeifMetadata metadata = new() { - CompressionMethod = HeifCompressionMethod.Av1, BitDepth = HeifBitDepth.Bit12, IsMonochrome = true, HasAlpha = true, @@ -38,7 +36,6 @@ public class HeifMetadataTests HeifMetadata clone = metadata.DeepClone(); - Assert.Equal(metadata.CompressionMethod, clone.CompressionMethod); Assert.Equal(metadata.BitDepth, clone.BitDepth); Assert.Equal(metadata.IsMonochrome, clone.IsMonochrome); Assert.Equal(metadata.HasAlpha, clone.HasAlpha); @@ -111,5 +108,4 @@ public class HeifMetadataTests Assert.Equal(expectedComponentCount, componentInfo.ComponentCount); Assert.Equal((int)bitDepth, componentInfo.GetMaximumComponentPrecision()); } - } diff --git a/tests/ImageSharp.Tests/Formats/Heif/HeifSequenceParserTests.cs b/tests/ImageSharp.Tests/Formats/Heif/HeifSequenceParserTests.cs index 1ea41f4e58..121b77f296 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/HeifSequenceParserTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/HeifSequenceParserTests.cs @@ -96,7 +96,6 @@ public class HeifSequenceParserTests Assert.Equal(new Size(LibavifAnimationSize, LibavifAnimationSize), info.Size); Assert.Equal(LibavifAnimationFrameCount, info.FrameCount); - Assert.Equal(HeifCompressionMethod.Av1, metadata.CompressionMethod); Assert.Equal(HeifBitDepth.Bit8, metadata.BitDepth); Assert.Equal(FinitePlayCount, metadata.RepeatCount); Assert.False(metadata.HasAlpha); @@ -116,7 +115,6 @@ public class HeifSequenceParserTests Assert.Equal(new Size(LibavifAnimationSize, LibavifAnimationSize), info.Size); Assert.Equal(LibavifAnimationFrameCount, info.FrameCount); - Assert.Equal(HeifCompressionMethod.Av1, metadata.CompressionMethod); Assert.Equal(HeifBitDepth.Bit8, metadata.BitDepth); Assert.Equal(InfinitePlayCount, metadata.RepeatCount); Assert.True(metadata.HasAlpha); @@ -138,7 +136,6 @@ public class HeifSequenceParserTests Assert.Equal(new Size(LibavifKeyframeAnimationSize, LibavifKeyframeAnimationSize), info.Size); Assert.Equal(LibavifAnimationFrameCount, info.FrameCount); - Assert.Equal(HeifCompressionMethod.Av1, metadata.CompressionMethod); Assert.Equal(HeifBitDepth.Bit12, metadata.BitDepth); } @@ -156,7 +153,6 @@ public class HeifSequenceParserTests Assert.Equal(new Size(SyntheticHeight, SyntheticWidth), info.Size); Assert.Equal(2, info.FrameCount); - Assert.Equal(HeifCompressionMethod.Av1, metadata.CompressionMethod); Assert.Equal(HeifBitDepth.Bit8, metadata.BitDepth); Assert.Equal(3, metadata.RepeatCount); Assert.False(metadata.HasAlpha); @@ -186,7 +182,6 @@ public class HeifSequenceParserTests ImageInfo info = Image.Identify(options, stream); HeifMetadata metadata = info.Metadata.GetHeifMetadata(); - Assert.Equal(HeifCompressionMethod.Av1, metadata.CompressionMethod); Assert.Equal(HeifBitDepth.Bit8, metadata.BitDepth); Assert.Equal(3, metadata.RepeatCount); Assert.Null(info.Metadata.CicpProfile); diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index 1616f3a3ab..e1767d8333 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -4,6 +4,7 @@ using System.Runtime.InteropServices; using ImageMagick; using ImageMagick.Formats; +using SixLabors.ImageSharp.ColorProfiles.Icc; using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Exr; @@ -76,6 +77,14 @@ public class MagickReferenceDecoder : ImageDecoder List> framesList = []; foreach (IMagickImage magicFrame in magickImageCollection) { + if (this.imageFormat is HeifFormat + && options.ColorProfileHandling == ColorProfileHandling.Convert + && magicFrame.GetColorProfile() is not null) + { + // Use the decoder contract's target profile so the comparison isolates conversion behavior. + magicFrame.TransformColorSpace(new ColorProfile(CompactSrgbV4Profile.Profile.ToByteArray())); + } + ImageFrame frame = new(configuration, imageWidth, imageHeight); framesList.Add(frame); diff --git a/tests/Images/External/ReferenceOutput/Av1ReconstructionConformanceTests/DecodeCdefMatchesReference_Rgba32_libavif-cdef-kodim23-8b.png b/tests/Images/External/ReferenceOutput/Av1ReconstructionConformanceTests/DecodeCdefMatchesReference_Rgba32_libavif-cdef-kodim23-8b.png index 7ab2e6b201..6c704a673c 100644 --- a/tests/Images/External/ReferenceOutput/Av1ReconstructionConformanceTests/DecodeCdefMatchesReference_Rgba32_libavif-cdef-kodim23-8b.png +++ b/tests/Images/External/ReferenceOutput/Av1ReconstructionConformanceTests/DecodeCdefMatchesReference_Rgba32_libavif-cdef-kodim23-8b.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:19f3e0d5357df5dacf16dc73501e5bbe280f04e0f0e0831bfc8f2511497dccf4 -size 408944 +oid sha256:568c8dfeb43b36e9f2df3c7edd8a16bcd8fde52a259e61c04d903b0e0767cc62 +size 371942 diff --git a/tests/Images/External/ReferenceOutput/Av1ReconstructionConformanceTests/DecodeLoopRestorationMatchesReference_Rgba32_libavif-restoration-kodim23-8b.png b/tests/Images/External/ReferenceOutput/Av1ReconstructionConformanceTests/DecodeLoopRestorationMatchesReference_Rgba32_libavif-restoration-kodim23-8b.png index f457ec6dbb..dc2517ab8a 100644 --- a/tests/Images/External/ReferenceOutput/Av1ReconstructionConformanceTests/DecodeLoopRestorationMatchesReference_Rgba32_libavif-restoration-kodim23-8b.png +++ b/tests/Images/External/ReferenceOutput/Av1ReconstructionConformanceTests/DecodeLoopRestorationMatchesReference_Rgba32_libavif-restoration-kodim23-8b.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2098b78efb22b27473802e4e9e074d4948eda6d6bb06510069c9497e63f37bf5 -size 383051 +oid sha256:3c5ee8ff354f1c48d5746af02d76708e7be76d3947f318f9db5855909a804f9e +size 346662 diff --git a/tests/Images/External/ReferenceOutput/Av1ReconstructionConformanceTests/DecodeSuperResolutionMatchesReference_Rgba32_libavif-superres-kodim23-8b.png b/tests/Images/External/ReferenceOutput/Av1ReconstructionConformanceTests/DecodeSuperResolutionMatchesReference_Rgba32_libavif-superres-kodim23-8b.png index 1d5e6b5146..d5da7d11ee 100644 --- a/tests/Images/External/ReferenceOutput/Av1ReconstructionConformanceTests/DecodeSuperResolutionMatchesReference_Rgba32_libavif-superres-kodim23-8b.png +++ b/tests/Images/External/ReferenceOutput/Av1ReconstructionConformanceTests/DecodeSuperResolutionMatchesReference_Rgba32_libavif-superres-kodim23-8b.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cd995bb10a9c10eba89584ea49c6a1c853b0168aa3fac324d3d935d9f985851f -size 383407 +oid sha256:c5ed33348681cf5fd780ff38afd21f49651fd8a6256b91c8f31d88a315294100 +size 345399 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_Irvine_CA.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_Irvine_CA.png new file mode 100644 index 0000000000..45c8b5de13 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_Irvine_CA.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8524059e28b1f64e6070108794213bfe0e87b654d37a7c405e6918d72fc312f9 +size 204802 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_Orange4x4.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_Orange4x4.png new file mode 100644 index 0000000000..50d60722cc --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_Orange4x4.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f2cf98bfaacc2ff6a229d433cdc0c7ba068447d25d5cd5ca7fc04ae4f9ea07c +size 352 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-alpha-exif-xmp.avif/00.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-alpha-exif-xmp.avif/00.png new file mode 100644 index 0000000000..23f2e2b2cc --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-alpha-exif-xmp.avif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:528eb9a1e6fe6e5060265a2d02bb2f314f11fd4274efd9971f0a66aeaa403087 +size 695 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-alpha-exif-xmp.avif/01.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-alpha-exif-xmp.avif/01.png new file mode 100644 index 0000000000..500a39c29d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-alpha-exif-xmp.avif/01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e9973399a7c4d670532905dfcda7878b9709f438136233974580e822e355688d +size 697 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-alpha-exif-xmp.avif/02.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-alpha-exif-xmp.avif/02.png new file mode 100644 index 0000000000..6408088f22 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-alpha-exif-xmp.avif/02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d30d25fe8302f64c9764b5cc38f93b68922d45f2fa5c5068d4c2308e5bec16fc +size 716 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-alpha-exif-xmp.avif/03.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-alpha-exif-xmp.avif/03.png new file mode 100644 index 0000000000..e7521defc2 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-alpha-exif-xmp.avif/03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a780bfbfd53e0c14477fe3fffdc7d4e3e571988fc49872c04620555328d86f91 +size 697 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-alpha-exif-xmp.avif/04.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-alpha-exif-xmp.avif/04.png new file mode 100644 index 0000000000..210afcd316 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-alpha-exif-xmp.avif/04.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bbc64e3c45c4f02c334a528be2e87a99ac5f854382ae36d7ce2bce0fe9c492e9 +size 697 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-audio.avif/00.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-audio.avif/00.png new file mode 100644 index 0000000000..9ffa3131d1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-audio.avif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fbf8f4f98d052a9ebc1eeaec5253b545004a8094bd9d70662d269efbd946e333 +size 695 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-audio.avif/01.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-audio.avif/01.png new file mode 100644 index 0000000000..5429486499 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-audio.avif/01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0c4f51868abaac1ad9742c6be51b897319a47d3e8f5be06163896ed3ef5ccc56 +size 697 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-audio.avif/02.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-audio.avif/02.png new file mode 100644 index 0000000000..32ab6eb826 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-audio.avif/02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6443ae807d43ffa6df3e350358081e3ace6fe79fdf2fabf4dae0e79fc7ac90de +size 696 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-audio.avif/03.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-audio.avif/03.png new file mode 100644 index 0000000000..332d8c751a --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-audio.avif/03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6a288fa21457152d6e9b543a679f1cd0b62e08b157c06bc70b71ad33271ec297 +size 697 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-audio.avif/04.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-audio.avif/04.png new file mode 100644 index 0000000000..444fa4fb0f --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc-audio.avif/04.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:32de0769629d5043bfe3219f596eac3c30cbfefa371e0126edb0a4858981f95d +size 697 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc.avif/00.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc.avif/00.png new file mode 100644 index 0000000000..a45be12c43 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc.avif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d911da2c28fec56a4c11badf7a04b03846b86ba214ce6aaeebe43d696bb9668 +size 695 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc.avif/01.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc.avif/01.png new file mode 100644 index 0000000000..3c265e27c1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc.avif/01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e0957ec2347edf15744a3914a471ee0412402b8e034e0311d906234e54e4c441 +size 697 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc.avif/02.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc.avif/02.png new file mode 100644 index 0000000000..8104b2bd59 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc.avif/02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b6c8c30a7551f7fbcfd290f55dbcdb04f93ec0a72cb194c9c0ae9bfcbc2fd6d2 +size 696 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc.avif/03.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc.avif/03.png new file mode 100644 index 0000000000..fe09a49c53 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc.avif/03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ecdf8e090b33738a0d7c5aa484f291f6e9ef8ea8b3467bd63de1a103ca3e40a2 +size 697 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc.avif/04.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc.avif/04.png new file mode 100644 index 0000000000..0a439e4775 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_colors-animated-8bpc.avif/04.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5d939fd84c88f09609286cf606257534fd571e5acf36194589c22a7d904e94c6 +size 697 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_ducky_romm_icc_alpha.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_ducky_romm_icc_alpha.png new file mode 100644 index 0000000000..2d9cf61bf7 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_ducky_romm_icc_alpha.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:176d81c3d9b8fb8760d69f11d0804673e4a55704e4110b5eae163a64c9aee62a +size 34758 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_jpeg444_xnconvert.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_jpeg444_xnconvert.png new file mode 100644 index 0000000000..01c99d72a7 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_jpeg444_xnconvert.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4268ff56bddccd15637c35d7108d59dd8d2c30a54024d123e1f41250a571dbd0 +size 43380 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-cdef-kodim23-8b.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-cdef-kodim23-8b.png new file mode 100644 index 0000000000..6c704a673c --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-cdef-kodim23-8b.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:568c8dfeb43b36e9f2df3c7edd8a16bcd8fde52a259e61c04d903b0e0767cc62 +size 371942 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-intrabc-abc-8b-444.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-intrabc-abc-8b-444.png new file mode 100644 index 0000000000..4f1d508cc6 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-intrabc-abc-8b-444.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4cd476ce81e54eed777e4218f9b0ad38cc509d60d1c38bb0ba2ccc013d4ee053 +size 6462 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-kodim23-8b.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-kodim23-8b.png new file mode 100644 index 0000000000..4b42e8a740 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-kodim23-8b.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97aa2d6df27bb8ba5647d08bd0e3da8f25c2411a7f4cabb2ba490012e218aa2f +size 355642 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-lossless-circle-8b-444.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-lossless-circle-8b-444.png new file mode 100644 index 0000000000..53ef7ef638 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-lossless-circle-8b-444.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a9b2d70a65299c6da81c3b495c54e1eab99c1932e44add57cff2ae00c1f36072 +size 834 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-palette-draw-points-8b.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-palette-draw-points-8b.png new file mode 100644 index 0000000000..d140ba6fdb --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-palette-draw-points-8b.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1148ebf6aa4b0f2d069d5e9b9605f6fb2a315e525f18016cdcae23efdd81da84 +size 157 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-profile-8b-400.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-profile-8b-400.png new file mode 100644 index 0000000000..e55b20c57f --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-profile-8b-400.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:260a860d1f01e56c0154a31a6f4802fd27b5f507f724d55084f5278b388003c2 +size 5294 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-profile-8b-420.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-profile-8b-420.png new file mode 100644 index 0000000000..fae45e031a --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-profile-8b-420.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2b39c31c012ad7e194870fcdd074a83f225dc0dfc40731f0c72fa8847821e0fd +size 9631 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-profile-8b-422.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-profile-8b-422.png new file mode 100644 index 0000000000..abdbafc004 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-profile-8b-422.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:253291bc0bcebde6cc0ef6729c52ec025cba4a7ed046a3231297da7c63df3f4a +size 9748 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-profile-8b-444.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-profile-8b-444.png new file mode 100644 index 0000000000..3e0b2f65d6 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-profile-8b-444.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96423c89e68f6d4e6a349e0693b1c0b106055654681d51c822ab43fd12b86223 +size 9703 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-progressive-draw-points-8b.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-progressive-draw-points-8b.png new file mode 100644 index 0000000000..4e753f21b5 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-progressive-draw-points-8b.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0758c17dc36e38aee9f4389a335c2bf332ab91e4c79d7b0b22994fddd0fd1605 +size 186 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-restoration-kodim23-8b.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-restoration-kodim23-8b.png new file mode 100644 index 0000000000..dc2517ab8a --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-restoration-kodim23-8b.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c5ee8ff354f1c48d5746af02d76708e7be76d3947f318f9db5855909a804f9e +size 346662 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-rotating-grid-global-warp.avif/00.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-rotating-grid-global-warp.avif/00.png new file mode 100644 index 0000000000..36e3759330 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-rotating-grid-global-warp.avif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:87726ba278fe2b20da48da33b1397a23b1b6814ea2cc17b4372f9582413a1b8b +size 2945 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-rotating-grid-global-warp.avif/01.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-rotating-grid-global-warp.avif/01.png new file mode 100644 index 0000000000..53277bca4b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-rotating-grid-global-warp.avif/01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2665eef3c646db7f6ad3e38cae3e60b178ecc07161669d46dadb6cc62a82c82 +size 3962 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-rotating-grid-local-warp.avif/00.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-rotating-grid-local-warp.avif/00.png new file mode 100644 index 0000000000..89b93acaba --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-rotating-grid-local-warp.avif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fb5005e779d122fa8a004c6fd357e8a17b157c327f0c2fb245ec826b2667114d +size 3374 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-rotating-grid-local-warp.avif/01.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-rotating-grid-local-warp.avif/01.png new file mode 100644 index 0000000000..45ee00881e --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-rotating-grid-local-warp.avif/01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:23f62750f99e8d4b27c03b90779aa314ac2689db1372aedaa51e4fdf9ff3bf18 +size 10185 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-superres-kodim23-8b.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-superres-kodim23-8b.png new file mode 100644 index 0000000000..d5da7d11ee --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-superres-kodim23-8b.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5ed33348681cf5fd780ff38afd21f49651fd8a6256b91c8f31d88a315294100 +size 345399 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/00.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/00.png new file mode 100644 index 0000000000..2c0722511b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8ba2eedb768434ca64be1cb9699096ba130e5921633c8ca189d650bf6f93c4d9 +size 5071 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/01.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/01.png new file mode 100644 index 0000000000..726576b643 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53e659e5ee44b314ed9a27eae56a2b6add24bd8d64c56aa48a70815b7131fc23 +size 5411 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/02.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/02.png new file mode 100644 index 0000000000..6d96ab240b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc1ba530e15824544c832a821540eb434a955683bf17896cdeecce640a8d7936 +size 5812 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/03.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/03.png new file mode 100644 index 0000000000..8121a669f3 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da88fbbff90172a99bb2e30b4146ac49f1b6b47d610a2caad8938297b079c5de +size 5866 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/04.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/04.png new file mode 100644 index 0000000000..692c791043 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/04.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e144bb5a3017be933c4a4440cb1709f8eab31ef2804fc63f2389a3a0ffa692a2 +size 6019 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/05.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/05.png new file mode 100644 index 0000000000..b42ebd4b52 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/05.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e47c6774271d61de8dd138522e6df4c513b5a2b7b5e3fc5e4c068dfe26b0570a +size 6446 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/06.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/06.png new file mode 100644 index 0000000000..411b72052b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/06.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:790c8126f8d9c86b0619f366f261163a3bea6368b50359b2a7bd86738aeca426 +size 6328 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/07.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/07.png new file mode 100644 index 0000000000..aa182d7b2d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/07.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:43fc83a9805fb93d647ff94c79ab361eea7ce26c662b72d571ca06a1e81f1110 +size 5471 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/08.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/08.png new file mode 100644 index 0000000000..1dce2754ce --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/08.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8fdd5d6956d8e6335efc99289e7b75543a69b9113bce8fb27e6d1f1a7d407a4b +size 4906 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/09.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/09.png new file mode 100644 index 0000000000..9bcd06d741 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/09.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:258bbc7b4b97ad6924eb100aa920b23c74326ab32f66c447395a60f1f91498ab +size 4834 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/10.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/10.png new file mode 100644 index 0000000000..9bcd06d741 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:258bbc7b4b97ad6924eb100aa920b23c74326ab32f66c447395a60f1f91498ab +size 4834 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/11.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/11.png new file mode 100644 index 0000000000..9bcd06d741 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/11.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:258bbc7b4b97ad6924eb100aa920b23c74326ab32f66c447395a60f1f91498ab +size 4834 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/12.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/12.png new file mode 100644 index 0000000000..9bcd06d741 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/12.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:258bbc7b4b97ad6924eb100aa920b23c74326ab32f66c447395a60f1f91498ab +size 4834 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/13.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/13.png new file mode 100644 index 0000000000..9bcd06d741 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/13.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:258bbc7b4b97ad6924eb100aa920b23c74326ab32f66c447395a60f1f91498ab +size 4834 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/14.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/14.png new file mode 100644 index 0000000000..9bcd06d741 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/14.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:258bbc7b4b97ad6924eb100aa920b23c74326ab32f66c447395a60f1f91498ab +size 4834 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/15.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/15.png new file mode 100644 index 0000000000..9bcd06d741 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/15.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:258bbc7b4b97ad6924eb100aa920b23c74326ab32f66c447395a60f1f91498ab +size 4834 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/16.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/16.png new file mode 100644 index 0000000000..9bcd06d741 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/16.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:258bbc7b4b97ad6924eb100aa920b23c74326ab32f66c447395a60f1f91498ab +size 4834 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/17.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/17.png new file mode 100644 index 0000000000..9bcd06d741 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/17.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:258bbc7b4b97ad6924eb100aa920b23c74326ab32f66c447395a60f1f91498ab +size 4834 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/18.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/18.png new file mode 100644 index 0000000000..9bcd06d741 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-average-compound.avif/18.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:258bbc7b4b97ad6924eb100aa920b23c74326ab32f66c447395a60f1f91498ab +size 4834 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/00.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/00.png new file mode 100644 index 0000000000..22fe0320c7 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:052399e8ef8309dbf028a7352959e0af3f873b3afa1512413827cf29b1274452 +size 4870 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/01.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/01.png new file mode 100644 index 0000000000..f1447399eb --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b4d73f7e72466e7a3dc808c21d066a72625b84338d1aeaf9f2bde144da3e1d2 +size 5386 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/02.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/02.png new file mode 100644 index 0000000000..5a92cd2c10 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a29cf2bed1b0201810c78ca2515ccf5b6bd1b05526adfbc6afaaff947b099f3c +size 5664 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/03.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/03.png new file mode 100644 index 0000000000..c1a2493d63 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34f6e0230c219eea8fcc92373cba9e58d81e1ee329bf8efda2eb856d6ee22682 +size 5785 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/04.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/04.png new file mode 100644 index 0000000000..e8db9789dd --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/04.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ea04b78ea73a5c4b40519538b6bb93d7d2ccab603bb6a87938d5df82ef83c8f0 +size 5722 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/05.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/05.png new file mode 100644 index 0000000000..f1c4ceea8b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/05.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5fff110cdbd3f291cdd929509e539eb070df1e01eb74e4179c6c0ef545a5f13d +size 6215 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/06.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/06.png new file mode 100644 index 0000000000..bb8cac8fac --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/06.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:afbbdff0b903c192316aac017cb70a827c6ff51c7f0e1310f9e0f5f6dbda3220 +size 6193 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/07.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/07.png new file mode 100644 index 0000000000..cd1a76bf39 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/07.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8f5a3c28eba5ed81c2132f84067af3b6960f84e7a83835c3276aff5937f238e +size 5371 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/08.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/08.png new file mode 100644 index 0000000000..d70eab3243 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/08.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c7e31a2f5a0046fb7361ad29105cab6cb37a950b96c41a253652dfceae63d9d +size 4697 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/09.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/09.png new file mode 100644 index 0000000000..9dadec19f0 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/09.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:011d0ff1f9b103765d3ad15ee24e31f6ea3dbd19cb5da348095bdb6c17a3e7d5 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/10.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/10.png new file mode 100644 index 0000000000..9dadec19f0 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:011d0ff1f9b103765d3ad15ee24e31f6ea3dbd19cb5da348095bdb6c17a3e7d5 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/11.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/11.png new file mode 100644 index 0000000000..9dadec19f0 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/11.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:011d0ff1f9b103765d3ad15ee24e31f6ea3dbd19cb5da348095bdb6c17a3e7d5 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/12.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/12.png new file mode 100644 index 0000000000..9dadec19f0 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/12.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:011d0ff1f9b103765d3ad15ee24e31f6ea3dbd19cb5da348095bdb6c17a3e7d5 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/13.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/13.png new file mode 100644 index 0000000000..9dadec19f0 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/13.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:011d0ff1f9b103765d3ad15ee24e31f6ea3dbd19cb5da348095bdb6c17a3e7d5 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/14.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/14.png new file mode 100644 index 0000000000..9dadec19f0 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/14.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:011d0ff1f9b103765d3ad15ee24e31f6ea3dbd19cb5da348095bdb6c17a3e7d5 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/15.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/15.png new file mode 100644 index 0000000000..9dadec19f0 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/15.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:011d0ff1f9b103765d3ad15ee24e31f6ea3dbd19cb5da348095bdb6c17a3e7d5 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/16.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/16.png new file mode 100644 index 0000000000..9dadec19f0 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/16.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:011d0ff1f9b103765d3ad15ee24e31f6ea3dbd19cb5da348095bdb6c17a3e7d5 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/17.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/17.png new file mode 100644 index 0000000000..9dadec19f0 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/17.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:011d0ff1f9b103765d3ad15ee24e31f6ea3dbd19cb5da348095bdb6c17a3e7d5 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/18.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/18.png new file mode 100644 index 0000000000..9dadec19f0 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-difference-weighted-compound.avif/18.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:011d0ff1f9b103765d3ad15ee24e31f6ea3dbd19cb5da348095bdb6c17a3e7d5 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/00.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/00.png new file mode 100644 index 0000000000..4060705d9e --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe88c7a13669f0fe0a1d8d6fbb4237f83a34bb7f7a4533851426ea8071c2f416 +size 4870 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/01.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/01.png new file mode 100644 index 0000000000..80f07d6bcf --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:70fe65136ba6ab921ef3b50777225e3c14c13a299a928de5ecbe13094ed23a8e +size 5321 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/02.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/02.png new file mode 100644 index 0000000000..201ec9aad1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da1126fd448377ad37eb74ce2293c74278e0f20d0bc07e3d6f40ac7c90d9024b +size 5644 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/03.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/03.png new file mode 100644 index 0000000000..076478bbc3 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:734c43d36136f0dd5543ea14c5debcf78ace1ff7a58c7b858ce7fc3eea29e26f +size 5760 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/04.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/04.png new file mode 100644 index 0000000000..dedfac97e8 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/04.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0be740bffaf62376a2b7deb29426f3330c66b53faed61e825d58a57180686a2d +size 5779 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/05.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/05.png new file mode 100644 index 0000000000..ed366bc442 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/05.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa9b01e9cc1c35b965ddb415a331fd50b4fa39a44205eb52ea17296b699d1959 +size 6196 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/06.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/06.png new file mode 100644 index 0000000000..fe9a97b07d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/06.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9eefc8bca51044291759cde6f8e16a61d04f6a4c91f49f0d821ff87ae3eba1a4 +size 6141 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/07.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/07.png new file mode 100644 index 0000000000..b420e6f057 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/07.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e2ec2854c544a9fc84d198507d2295464398b0f3a9a0030fb7e59d0d831ca28a +size 5352 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/08.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/08.png new file mode 100644 index 0000000000..e2840649a4 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/08.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:37443d9a33fef2cc132353758592389f461714dd0ff61a5efb052b802f8718b0 +size 4697 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/09.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/09.png new file mode 100644 index 0000000000..410dcfe3d9 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/09.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53ccff0bac9f4899003693e023d66b98381bb0105ef718f5d50b220f01780733 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/10.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/10.png new file mode 100644 index 0000000000..410dcfe3d9 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53ccff0bac9f4899003693e023d66b98381bb0105ef718f5d50b220f01780733 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/11.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/11.png new file mode 100644 index 0000000000..410dcfe3d9 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/11.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53ccff0bac9f4899003693e023d66b98381bb0105ef718f5d50b220f01780733 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/12.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/12.png new file mode 100644 index 0000000000..410dcfe3d9 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/12.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53ccff0bac9f4899003693e023d66b98381bb0105ef718f5d50b220f01780733 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/13.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/13.png new file mode 100644 index 0000000000..410dcfe3d9 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/13.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53ccff0bac9f4899003693e023d66b98381bb0105ef718f5d50b220f01780733 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/14.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/14.png new file mode 100644 index 0000000000..410dcfe3d9 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/14.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53ccff0bac9f4899003693e023d66b98381bb0105ef718f5d50b220f01780733 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/15.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/15.png new file mode 100644 index 0000000000..410dcfe3d9 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/15.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53ccff0bac9f4899003693e023d66b98381bb0105ef718f5d50b220f01780733 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/16.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/16.png new file mode 100644 index 0000000000..410dcfe3d9 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/16.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53ccff0bac9f4899003693e023d66b98381bb0105ef718f5d50b220f01780733 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/17.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/17.png new file mode 100644 index 0000000000..410dcfe3d9 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/17.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53ccff0bac9f4899003693e023d66b98381bb0105ef718f5d50b220f01780733 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/18.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/18.png new file mode 100644 index 0000000000..410dcfe3d9 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-distance-weighted-compound.avif/18.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53ccff0bac9f4899003693e023d66b98381bb0105ef718f5d50b220f01780733 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/00.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/00.png new file mode 100644 index 0000000000..7266826af9 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8326494a41b46f3e3adc79a8afc4a6ee605876e57e6a71d44c01815d60742e1d +size 4870 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/01.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/01.png new file mode 100644 index 0000000000..299f585e07 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0dfb4c7fd57d1ff024786037d5984b1fc772c2e45fd5266ddfb6105696f55d0 +size 5412 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/02.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/02.png new file mode 100644 index 0000000000..2990a5f46b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f89f66357e790b198907ecee6ef4073a1c434f93def26376c375e3a5b80adc0f +size 5722 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/03.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/03.png new file mode 100644 index 0000000000..9d947dd280 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d37d13711317339bff443cc14e257054aaa21a56e10e7fc5673ac96c2a68c6ad +size 5750 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/04.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/04.png new file mode 100644 index 0000000000..6ecbc2ea03 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/04.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4e3523a7055e0930f1bef2b7077f938d36b2ee089fb793d59617775a58fc7e21 +size 5789 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/05.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/05.png new file mode 100644 index 0000000000..2c96dccaf9 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/05.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14162ea3152af06a95d2b80e88719448b8d04cc756f3efb6919fcb4e7e993ebe +size 6157 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/06.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/06.png new file mode 100644 index 0000000000..2aceabc073 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/06.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4994b47ea0726290f8d5c280ad0f84e41327558fb64905f8442d893b95aaa5a0 +size 6241 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/07.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/07.png new file mode 100644 index 0000000000..032c416370 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/07.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b2f020ad9461505a1a0595ccd2d68440eeb5e11247e0969d5bf88d0d8831173 +size 5357 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/08.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/08.png new file mode 100644 index 0000000000..ffe81d5c0e --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/08.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:789ec0f386430a60085bc3877b28e60469e0c7b081adf175caef56aba3ac397f +size 4713 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/09.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/09.png new file mode 100644 index 0000000000..b1ca73b00d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/09.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65560dda4c21fb93a4748702760a7095c0155fe4a955093572ad7b8de3cd12a9 +size 4638 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/10.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/10.png new file mode 100644 index 0000000000..b1ca73b00d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65560dda4c21fb93a4748702760a7095c0155fe4a955093572ad7b8de3cd12a9 +size 4638 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/11.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/11.png new file mode 100644 index 0000000000..b1ca73b00d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/11.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65560dda4c21fb93a4748702760a7095c0155fe4a955093572ad7b8de3cd12a9 +size 4638 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/12.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/12.png new file mode 100644 index 0000000000..b1ca73b00d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/12.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65560dda4c21fb93a4748702760a7095c0155fe4a955093572ad7b8de3cd12a9 +size 4638 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/13.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/13.png new file mode 100644 index 0000000000..b1ca73b00d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/13.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65560dda4c21fb93a4748702760a7095c0155fe4a955093572ad7b8de3cd12a9 +size 4638 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/14.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/14.png new file mode 100644 index 0000000000..b1ca73b00d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/14.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65560dda4c21fb93a4748702760a7095c0155fe4a955093572ad7b8de3cd12a9 +size 4638 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/15.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/15.png new file mode 100644 index 0000000000..b1ca73b00d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/15.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65560dda4c21fb93a4748702760a7095c0155fe4a955093572ad7b8de3cd12a9 +size 4638 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/16.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/16.png new file mode 100644 index 0000000000..b1ca73b00d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/16.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65560dda4c21fb93a4748702760a7095c0155fe4a955093572ad7b8de3cd12a9 +size 4638 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/17.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/17.png new file mode 100644 index 0000000000..b1ca73b00d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/17.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65560dda4c21fb93a4748702760a7095c0155fe4a955093572ad7b8de3cd12a9 +size 4638 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/18.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/18.png new file mode 100644 index 0000000000..b1ca73b00d --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-inter-intra.avif/18.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65560dda4c21fb93a4748702760a7095c0155fe4a955093572ad7b8de3cd12a9 +size 4638 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/00.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/00.png new file mode 100644 index 0000000000..be3dcd2688 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac23a5717faf09c21b63438154a06d4e623ef817c7ab439bcedc70b456a4c190 +size 4870 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/01.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/01.png new file mode 100644 index 0000000000..1f56c1b8b6 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cbcca7b1c433f29f4d8b479c44fa4ff162d1687b6182919fa1d2446b4a88d19a +size 5307 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/02.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/02.png new file mode 100644 index 0000000000..b8f34e48fd --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c52147c67931a854727e544089768c37d43fcd3bc77eeafba79f01c18ba3d467 +size 5790 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/03.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/03.png new file mode 100644 index 0000000000..97ddd88a8f --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6a74b0d1fe6baba03f2d7e74fc4fb6b054fe7b6c96b23a1f522e8315cd9e36af +size 5776 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/04.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/04.png new file mode 100644 index 0000000000..7bb50df721 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/04.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d2a467f9a1a29242ad06f9802b45f304e6557941ea35229ae14b87deed8cdc77 +size 5867 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/05.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/05.png new file mode 100644 index 0000000000..0aef4759d1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/05.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a74ce2ea86e1e403670bef8f94ac1fc3fcdcff81879694c4b640d4836ba43827 +size 6276 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/06.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/06.png new file mode 100644 index 0000000000..560edfc14b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/06.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3d259c884a22525be02e2f617c9f5c9864123feac2cbdb832c79caea9e3b60e +size 6167 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/07.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/07.png new file mode 100644 index 0000000000..04ba974af7 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/07.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25efd62b84b52e8aac95ae09bfb363216c3c9be8a1281cb3eac3a2f7154ca6ec +size 5432 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/08.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/08.png new file mode 100644 index 0000000000..6d5757ee90 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/08.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed08e5932fb1d7e7f46d588f96a109924f5f3891a424433a28933292895150d1 +size 4697 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/09.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/09.png new file mode 100644 index 0000000000..b59a2a9a6b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/09.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11e569511959ca5d788a1a6eb4b87dc9fa230505c11717958fef3a11af3ad74a +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/10.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/10.png new file mode 100644 index 0000000000..b59a2a9a6b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11e569511959ca5d788a1a6eb4b87dc9fa230505c11717958fef3a11af3ad74a +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/11.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/11.png new file mode 100644 index 0000000000..b59a2a9a6b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/11.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11e569511959ca5d788a1a6eb4b87dc9fa230505c11717958fef3a11af3ad74a +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/12.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/12.png new file mode 100644 index 0000000000..b59a2a9a6b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/12.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11e569511959ca5d788a1a6eb4b87dc9fa230505c11717958fef3a11af3ad74a +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/13.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/13.png new file mode 100644 index 0000000000..b59a2a9a6b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/13.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11e569511959ca5d788a1a6eb4b87dc9fa230505c11717958fef3a11af3ad74a +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/14.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/14.png new file mode 100644 index 0000000000..b59a2a9a6b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/14.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11e569511959ca5d788a1a6eb4b87dc9fa230505c11717958fef3a11af3ad74a +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/15.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/15.png new file mode 100644 index 0000000000..b59a2a9a6b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/15.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11e569511959ca5d788a1a6eb4b87dc9fa230505c11717958fef3a11af3ad74a +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/16.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/16.png new file mode 100644 index 0000000000..b59a2a9a6b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/16.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11e569511959ca5d788a1a6eb4b87dc9fa230505c11717958fef3a11af3ad74a +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/17.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/17.png new file mode 100644 index 0000000000..b59a2a9a6b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/17.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11e569511959ca5d788a1a6eb4b87dc9fa230505c11717958fef3a11af3ad74a +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/18.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/18.png new file mode 100644 index 0000000000..b59a2a9a6b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-obmc.avif/18.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11e569511959ca5d788a1a6eb4b87dc9fa230505c11717958fef3a11af3ad74a +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-scaled-reference-lsel0.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-scaled-reference-lsel0.png new file mode 100644 index 0000000000..1b0c1e4769 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-scaled-reference-lsel0.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:678c5e5d4650ea6f0c590302e7db9e3c6608851bc577453da4a6837bdb4d3af3 +size 6453 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-scaled-reference.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-scaled-reference.png new file mode 100644 index 0000000000..5ac780b8aa --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-scaled-reference.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc4c6dbe6bd92c5fce1e3e23700afa603ef04ed02edd336213ebba1e3bd84ba0 +size 5759 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/00.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/00.png new file mode 100644 index 0000000000..3184129700 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa59741fb2fedb891ed840754e071e7b1d2b9cddef26a6b5f1b37b42926687e4 +size 4870 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/01.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/01.png new file mode 100644 index 0000000000..f48eb88018 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b27b5771ec51c77da594bd545cad66727d805a92dacacf6d340b649e5142353b +size 5280 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/02.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/02.png new file mode 100644 index 0000000000..62a183530e --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01160e78bf0a25f8ceff938fc75e63259d3044cf98ffd0d65960f1eca26c7a1f +size 5711 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/03.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/03.png new file mode 100644 index 0000000000..666bf237a3 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9d3c4a1c852c2dac59532b532538d458ef01209db154d5f47fbe2bfa44d443f5 +size 5818 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/04.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/04.png new file mode 100644 index 0000000000..8c9e1781a9 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/04.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e8df2c6fe6e4591bd4bac1e4c0a495d0150a06b0d506e235ff2d54287ccfe287 +size 5694 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/05.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/05.png new file mode 100644 index 0000000000..eccf8fe8cc --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/05.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a7b9d091ef302331193efc58efa48c81e0c2222ffe7a520ad88863acf773c29 +size 6126 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/06.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/06.png new file mode 100644 index 0000000000..248b55c2f1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/06.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:520a870b71dda3e5e6d76a0a678a8f422ef9b3183c5104c4043eeb65a2be0e29 +size 6210 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/07.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/07.png new file mode 100644 index 0000000000..a76e1a25ab --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/07.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1d23fead652e267b50d761c3fe801510c1c415700864d2239132f60c5cf70107 +size 5411 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/08.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/08.png new file mode 100644 index 0000000000..984e000215 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/08.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33334f9bfd31340e3a03097247d5cbc4ea7ad957b219433e30500c42b7c191ad +size 4697 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/09.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/09.png new file mode 100644 index 0000000000..49f2796d7c --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/09.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c64b0fc7d4d84f16bc5be17d59963ff49c3f7a166a3538d7ebe50613e77d5b22 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/10.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/10.png new file mode 100644 index 0000000000..49f2796d7c --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c64b0fc7d4d84f16bc5be17d59963ff49c3f7a166a3538d7ebe50613e77d5b22 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/11.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/11.png new file mode 100644 index 0000000000..49f2796d7c --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/11.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c64b0fc7d4d84f16bc5be17d59963ff49c3f7a166a3538d7ebe50613e77d5b22 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/12.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/12.png new file mode 100644 index 0000000000..49f2796d7c --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/12.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c64b0fc7d4d84f16bc5be17d59963ff49c3f7a166a3538d7ebe50613e77d5b22 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/13.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/13.png new file mode 100644 index 0000000000..49f2796d7c --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/13.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c64b0fc7d4d84f16bc5be17d59963ff49c3f7a166a3538d7ebe50613e77d5b22 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/14.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/14.png new file mode 100644 index 0000000000..49f2796d7c --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/14.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c64b0fc7d4d84f16bc5be17d59963ff49c3f7a166a3538d7ebe50613e77d5b22 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/15.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/15.png new file mode 100644 index 0000000000..49f2796d7c --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/15.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c64b0fc7d4d84f16bc5be17d59963ff49c3f7a166a3538d7ebe50613e77d5b22 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/16.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/16.png new file mode 100644 index 0000000000..49f2796d7c --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/16.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c64b0fc7d4d84f16bc5be17d59963ff49c3f7a166a3538d7ebe50613e77d5b22 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/17.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/17.png new file mode 100644 index 0000000000..49f2796d7c --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/17.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c64b0fc7d4d84f16bc5be17d59963ff49c3f7a166a3538d7ebe50613e77d5b22 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/18.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/18.png new file mode 100644 index 0000000000..49f2796d7c --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_libavif-webp-logo-wedge-compound.avif/18.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c64b0fc7d4d84f16bc5be17d59963ff49c3f7a166a3538d7ebe50613e77d5b22 +size 4629 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_paris_icc_exif_xmp.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_paris_icc_exif_xmp.png new file mode 100644 index 0000000000..b1083c0601 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_paris_icc_exif_xmp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56582a8fa2426a6f975d336dab9a6b1dceee54af1ae23e7ba13c978f07868531 +size 196748 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_perceptual_icc.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_perceptual_icc.png new file mode 100644 index 0000000000..0617b606f1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_perceptual_icc.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:03112c131bbe53c210594e03eed387511150aa45325f6765dcd4b926c0059163 +size 237768 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_perceptual_icc_grid.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_perceptual_icc_grid.png new file mode 100644 index 0000000000..ca396521b3 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_perceptual_icc_grid.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af91fcd50dd0c2ce2197e1df29d1e0f694a40ee796364ccb6a9795109a3185d4 +size 237832 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_perceptual_icc_sequence.avif/00.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_perceptual_icc_sequence.avif/00.png new file mode 100644 index 0000000000..9de84854a1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_perceptual_icc_sequence.avif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8027baf25dbd10aa08ea6f7f1c5daba8ae4d36f12f799a9a40d525232a045028 +size 64591 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_perceptual_icc_sequence.avif/01.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_perceptual_icc_sequence.avif/01.png new file mode 100644 index 0000000000..9de84854a1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba32_perceptual_icc_sequence.avif/01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8027baf25dbd10aa08ea6f7f1c5daba8ae4d36f12f799a9a40d525232a045028 +size 64591 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_colors-animated-12bpc-keyframes-0-2-3.avif/00.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_colors-animated-12bpc-keyframes-0-2-3.avif/00.png new file mode 100644 index 0000000000..e307b1ea61 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_colors-animated-12bpc-keyframes-0-2-3.avif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad9f169e6442c430d5c01b475923721c8142d6d83023252f65f8c542c3795370 +size 476 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_colors-animated-12bpc-keyframes-0-2-3.avif/01.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_colors-animated-12bpc-keyframes-0-2-3.avif/01.png new file mode 100644 index 0000000000..611300ee0f --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_colors-animated-12bpc-keyframes-0-2-3.avif/01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:52751d9ddaa12cfd191dda0912b9a340a7f50235fb1092a9fb24fe02f142e71c +size 655 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_colors-animated-12bpc-keyframes-0-2-3.avif/02.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_colors-animated-12bpc-keyframes-0-2-3.avif/02.png new file mode 100644 index 0000000000..154e606dc1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_colors-animated-12bpc-keyframes-0-2-3.avif/02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce9ba8d8f4bf019948ca353e0d8b62ec4fb69069cf1092fe2da56dbdf1d76aa9 +size 588 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_colors-animated-12bpc-keyframes-0-2-3.avif/03.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_colors-animated-12bpc-keyframes-0-2-3.avif/03.png new file mode 100644 index 0000000000..6daffd891f --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_colors-animated-12bpc-keyframes-0-2-3.avif/03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9ca95b4bf1b455065d15a8136c657ccd9673bf76ae8782cfe36644f5601dab7b +size 19572 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_colors-animated-12bpc-keyframes-0-2-3.avif/04.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_colors-animated-12bpc-keyframes-0-2-3.avif/04.png new file mode 100644 index 0000000000..5321d8a4bd --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_colors-animated-12bpc-keyframes-0-2-3.avif/04.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f694b7403ae7cda2d4efee5c5c97831fa0e1af5ff0473c76c4e0bb9a88c1bbc +size 18861 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-cdef-cosmos-10b.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-cdef-cosmos-10b.png new file mode 100644 index 0000000000..dd7e118dfa --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-cdef-cosmos-10b.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a58ff7040de9c9091da1cf537d0f9fe4c9920bb26378ef0469f12eb59aa5d73 +size 1575289 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-cdef-cosmos-12b.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-cdef-cosmos-12b.png new file mode 100644 index 0000000000..78f0466519 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-cdef-cosmos-12b.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:db1cdd8da85fac875f3c6e038de2d9e5136e900aee77720d56dd11409eddbc97 +size 1757127 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-colors-12b.avif/00.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-colors-12b.avif/00.png new file mode 100644 index 0000000000..e88912a5c9 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-colors-12b.avif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:49da02aaf5ccbcf0feef269580dd58cc80a53e00c6dffa6add695b2eb67a32ab +size 476 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-colors-12b.avif/01.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-colors-12b.avif/01.png new file mode 100644 index 0000000000..b2f7f6dc54 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-colors-12b.avif/01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:63b0fb34914c5995520ca7644138c31cc6ee5d960d5bd4e19b1ec35581fbea9c +size 655 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-colors-12b.avif/02.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-colors-12b.avif/02.png new file mode 100644 index 0000000000..efa7986dd4 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-colors-12b.avif/02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb0706a887d526ca5bf1fcb52e36845bfffa787a9445af6f8e250f741168d5d6 +size 588 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-colors-12b.avif/03.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-colors-12b.avif/03.png new file mode 100644 index 0000000000..0de71d2ffd --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-colors-12b.avif/03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47b16c51e03685ddb6f017c9d8e32f0d111cb7c00c78d3208f766e47f1c00151 +size 19572 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-colors-12b.avif/04.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-colors-12b.avif/04.png new file mode 100644 index 0000000000..eb14dd6f0e --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-colors-12b.avif/04.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4abaacb4afbcc621919bba9159cea11e8c965ad02095f41eeab77ab6507955e6 +size 18861 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-cosmos1650-10b.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-cosmos1650-10b.png new file mode 100644 index 0000000000..9652cfd0c8 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-cosmos1650-10b.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59d9f67b1f394e1e17e119ec6da20dcd21221aa226ff12bb1feda92b288ae60e +size 1581108 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-intrabc-abc-10b-444.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-intrabc-abc-10b-444.png new file mode 100644 index 0000000000..d6ace7cfce --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-intrabc-abc-10b-444.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9209b8d713cf35d6968de1bf5854cbc962f4b0e88287787641823ce7d62603c5 +size 9656 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-intrabc-abc-12b-444.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-intrabc-abc-12b-444.png new file mode 100644 index 0000000000..ef4241bd91 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-intrabc-abc-12b-444.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:708c1d45549c26e8bd00fe4a41dd8cf236f364212f5a6d066f2c076fafa70643 +size 9060 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-lossless-circle-10b-444.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-lossless-circle-10b-444.png new file mode 100644 index 0000000000..9635d012d9 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-lossless-circle-10b-444.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1245243fae3a61efec382ac0a7320a04076ffcc884a918cfe509540e6e75d986 +size 1651 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-lossless-circle-12b-444.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-lossless-circle-12b-444.png new file mode 100644 index 0000000000..13d396fb6b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-lossless-circle-12b-444.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:242daaedba9f4e58b41be45d8e7cac3354733739d26506e1b51884ad0956e50a +size 1648 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-10b-400.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-10b-400.png new file mode 100644 index 0000000000..1312894a25 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-10b-400.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0801c9bac271826fc2c9e11ad2b281f7b1cfc8de78dd2aa758ae581be98e00a +size 10862 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-10b-420.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-10b-420.png new file mode 100644 index 0000000000..783764b60b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-10b-420.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:690ca7a4d20d3586558df7f4efe015a8e76fb83ba75bd6e1189722f8bae10cd8 +size 21329 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-10b-422.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-10b-422.png new file mode 100644 index 0000000000..98571b0de0 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-10b-422.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:67c084cfe156c5f929c631f637fe14164c848e86046a88a13ecb79fd252a0b53 +size 21144 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-10b-444.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-10b-444.png new file mode 100644 index 0000000000..783764b60b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-10b-444.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:690ca7a4d20d3586558df7f4efe015a8e76fb83ba75bd6e1189722f8bae10cd8 +size 21329 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-12b-400.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-12b-400.png new file mode 100644 index 0000000000..ad271c9093 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-12b-400.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b568b2baaaad68925f02377ac3439c7f7211ad039f8f4ab81718915699c8a7e +size 14368 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-12b-420.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-12b-420.png new file mode 100644 index 0000000000..e1a28ff6a8 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-12b-420.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c65d680340f4725dc748ee9221aa852b8240c7965bc4968da6b57882cb3586d +size 27603 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-12b-422.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-12b-422.png new file mode 100644 index 0000000000..3611a12381 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-12b-422.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:75c677d73b49c721c4d799b2f4c9d69ac7e38379f4de53f856274f1542a7291a +size 28167 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-12b-444.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-12b-444.png new file mode 100644 index 0000000000..7870fc6462 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-profile-12b-444.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b80fa2ea85eea4f1e348ab6f527a40fd44abead3fe5d396b2e22afe0adfa427 +size 27730 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-restoration-cosmos-10b.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-restoration-cosmos-10b.png new file mode 100644 index 0000000000..d3796721f6 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-restoration-cosmos-10b.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3ae97814293e1b0e19be5dc8ddf4b344b9187d719ecfdc19976e91afd5d25d6c +size 1557477 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-restoration-cosmos-12b.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-restoration-cosmos-12b.png new file mode 100644 index 0000000000..05cf136c62 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-restoration-cosmos-12b.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d2841124ec7083d5e352f488db3d28d1e85980726d99c42a38021c82b8f7392 +size 1769807 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-superres-cosmos-10b.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-superres-cosmos-10b.png new file mode 100644 index 0000000000..5d9d7ecf7b --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-superres-cosmos-10b.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:65debec077b8ffe1f509f76daa5ca9d341fab3283d7f6bcc94f3162f6f3dcf42 +size 1497504 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-superres-cosmos-12b.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-superres-cosmos-12b.png new file mode 100644 index 0000000000..724e524b95 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/Decode_Rgba64_libavif-superres-cosmos-12b.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6e3ec3a748ae3bfd6c5b7cebc220b29bdd336ea4aa353e3913edcef86463383 +size 1750615 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccCompactNonSrgb_Rgba32_ducky_romm_icc_alpha.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccCompactNonSrgb_Rgba32_ducky_romm_icc_alpha.png new file mode 100644 index 0000000000..2d9cf61bf7 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccCompactNonSrgb_Rgba32_ducky_romm_icc_alpha.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:176d81c3d9b8fb8760d69f11d0804673e4a55704e4110b5eae163a64c9aee62a +size 34758 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccCompactNonSrgb_Rgba32_perceptual_icc.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccCompactNonSrgb_Rgba32_perceptual_icc.png new file mode 100644 index 0000000000..0617b606f1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccCompactNonSrgb_Rgba32_perceptual_icc.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:03112c131bbe53c210594e03eed387511150aa45325f6765dcd4b926c0059163 +size 237768 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccCompactNonSrgb_Rgba32_perceptual_icc_grid.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccCompactNonSrgb_Rgba32_perceptual_icc_grid.png new file mode 100644 index 0000000000..ca396521b3 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccCompactNonSrgb_Rgba32_perceptual_icc_grid.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af91fcd50dd0c2ce2197e1df29d1e0f694a40ee796364ccb6a9795109a3185d4 +size 237832 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccCompactNonSrgb_Rgba32_perceptual_icc_sequence.avif/00.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccCompactNonSrgb_Rgba32_perceptual_icc_sequence.avif/00.png new file mode 100644 index 0000000000..9de84854a1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccCompactNonSrgb_Rgba32_perceptual_icc_sequence.avif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8027baf25dbd10aa08ea6f7f1c5daba8ae4d36f12f799a9a40d525232a045028 +size 64591 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccCompactNonSrgb_Rgba32_perceptual_icc_sequence.avif/01.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccCompactNonSrgb_Rgba32_perceptual_icc_sequence.avif/01.png new file mode 100644 index 0000000000..9de84854a1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccCompactNonSrgb_Rgba32_perceptual_icc_sequence.avif/01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8027baf25dbd10aa08ea6f7f1c5daba8ae4d36f12f799a9a40d525232a045028 +size 64591 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccCompactSrgb_Rgba32_paris_icc_exif_xmp.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccCompactSrgb_Rgba32_paris_icc_exif_xmp.png new file mode 100644 index 0000000000..b1083c0601 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccCompactSrgb_Rgba32_paris_icc_exif_xmp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56582a8fa2426a6f975d336dab9a6b1dceee54af1ae23e7ba13c978f07868531 +size 196748 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccConvertAlpha_Rgba32_ducky_romm_icc_alpha.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccConvertAlpha_Rgba32_ducky_romm_icc_alpha.png new file mode 100644 index 0000000000..5cdd83af36 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccConvertAlpha_Rgba32_ducky_romm_icc_alpha.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c6e347151195deb23b703aad149bfd41bfae8c655900b8314daf8d316fb364c +size 24008 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccConvertGrid_Rgba32_perceptual_icc_grid.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccConvertGrid_Rgba32_perceptual_icc_grid.png new file mode 100644 index 0000000000..6b6e92534f --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccConvertGrid_Rgba32_perceptual_icc_grid.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:47b2f50dd6107d018b942161a043bef454a700fc547b7f7baf0369e5bb3b0107 +size 113013 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccConvertSequence_Rgba32_perceptual_icc_sequence.avif/00.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccConvertSequence_Rgba32_perceptual_icc_sequence.avif/00.png new file mode 100644 index 0000000000..229dbabff2 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccConvertSequence_Rgba32_perceptual_icc_sequence.avif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:838a549bfac91523793738a217492a2b8d68ea64aa19cbcd502472975eadff8a +size 61660 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccConvertSequence_Rgba32_perceptual_icc_sequence.avif/01.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccConvertSequence_Rgba32_perceptual_icc_sequence.avif/01.png new file mode 100644 index 0000000000..229dbabff2 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccConvertSequence_Rgba32_perceptual_icc_sequence.avif/01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:838a549bfac91523793738a217492a2b8d68ea64aa19cbcd502472975eadff8a +size 61660 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccConvert_Rgba32_perceptual_icc.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccConvert_Rgba32_perceptual_icc.png new file mode 100644 index 0000000000..b350d16dbe --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccConvert_Rgba32_perceptual_icc.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e89c80328e05430a8c32359629c1e70839635839f5a967e9f86a731c8330b064 +size 113016 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccPreserve_Rgba32_paris_icc_exif_xmp.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccPreserve_Rgba32_paris_icc_exif_xmp.png new file mode 100644 index 0000000000..b1083c0601 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccPreserve_Rgba32_paris_icc_exif_xmp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56582a8fa2426a6f975d336dab9a6b1dceee54af1ae23e7ba13c978f07868531 +size 196748 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccSkipMetadata_Rgba32_ducky_romm_icc_alpha.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccSkipMetadata_Rgba32_ducky_romm_icc_alpha.png new file mode 100644 index 0000000000..2d9cf61bf7 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccSkipMetadata_Rgba32_ducky_romm_icc_alpha.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:176d81c3d9b8fb8760d69f11d0804673e4a55704e4110b5eae163a64c9aee62a +size 34758 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccSkipMetadata_Rgba32_paris_icc_exif_xmp.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccSkipMetadata_Rgba32_paris_icc_exif_xmp.png new file mode 100644 index 0000000000..b1083c0601 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccSkipMetadata_Rgba32_paris_icc_exif_xmp.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:56582a8fa2426a6f975d336dab9a6b1dceee54af1ae23e7ba13c978f07868531 +size 196748 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccSkipMetadata_Rgba32_perceptual_icc_grid.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccSkipMetadata_Rgba32_perceptual_icc_grid.png new file mode 100644 index 0000000000..ca396521b3 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccSkipMetadata_Rgba32_perceptual_icc_grid.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af91fcd50dd0c2ce2197e1df29d1e0f694a40ee796364ccb6a9795109a3185d4 +size 237832 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccSkipMetadata_Rgba32_perceptual_icc_sequence.avif/00.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccSkipMetadata_Rgba32_perceptual_icc_sequence.avif/00.png new file mode 100644 index 0000000000..9de84854a1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccSkipMetadata_Rgba32_perceptual_icc_sequence.avif/00.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8027baf25dbd10aa08ea6f7f1c5daba8ae4d36f12f799a9a40d525232a045028 +size 64591 diff --git a/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccSkipMetadata_Rgba32_perceptual_icc_sequence.avif/01.png b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccSkipMetadata_Rgba32_perceptual_icc_sequence.avif/01.png new file mode 100644 index 0000000000..9de84854a1 --- /dev/null +++ b/tests/Images/External/ReferenceOutput/HeifDecoderTests/IccSkipMetadata_Rgba32_perceptual_icc_sequence.avif/01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8027baf25dbd10aa08ea6f7f1c5daba8ae4d36f12f799a9a40d525232a045028 +size 64591