diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md index fe12ce7ee4..fe99ed305f 100644 --- a/HEIF_IMPLEMENTATION_PLAN.md +++ b/HEIF_IMPLEMENTATION_PLAN.md @@ -869,6 +869,7 @@ Encoder verification contract: ### 7. Write complete AVIF output +- [~] The encoder-side AV1 codec configuration is now derived directly from the encoded sequence header and writes the fixed four-byte `av1C` record with empty `configOBUs`. The image payload retains the required sequence header, so the property introduces no sequence-header allocation, retention, or copy. Four production-header cases cover main, high, and professional profiles; 8-, 10-, and 12-bit precision; monochrome, 4:2:0, 4:2:2, and 4:4:4 sampling; exact fixed bytes; decoder reparsing; and header/property equivalence through direct net11 Release VSTest. Property-container emission and public AVIF activation remain open. - [ ] Write the correct AVIF file type, item information, locations, references, properties, AV1 configuration, dimensions, color, alpha, metadata, and media data. - [ ] Support single images, alpha auxiliary images, grids, multiple extents, and bounded image sequences in the final public scope. - [ ] Preserve ICC, Exif, and XMP according to encoder options. diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs b/src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs index c1fa28592b..5e39a3f5d7 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs @@ -12,6 +12,11 @@ namespace SixLabors.ImageSharp.Formats.Heif.Av1; /// internal sealed class Av1CodecConfiguration { + /// + /// The number of bytes in the fixed AV1 codec-configuration record. + /// + public const int FixedHeaderSize = 4; + /// /// The optional sequence-header payload retained from the configuration open bitstream units. /// @@ -40,7 +45,7 @@ internal sealed class Av1CodecConfiguration /// The general options governing metadata validation. public Av1CodecConfiguration(Span boxBuffer, DecoderOptions options) { - if (boxBuffer.Length < 4) + if (boxBuffer.Length < FixedHeaderSize) { throw new InvalidImageContentException("The AV1 codec configuration is truncated."); } @@ -83,7 +88,7 @@ internal sealed class Av1CodecConfiguration // The delay syntax is consumed to validate the fixed record, but it describes sample presentation and has // no meaning for the independently presented image item supported by this bounded container implementation. - ReadOnlySpan configObus = boxBuffer[4..]; + ReadOnlySpan configObus = boxBuffer[FixedHeaderSize..]; int sequenceHeaderCount = ScanObus( configObus, true, @@ -114,6 +119,30 @@ internal sealed class Av1CodecConfiguration } } + /// + /// Initializes a new instance of the class from an encoded sequence header. + /// + /// The sequence header describing the encoded image item. + public Av1CodecConfiguration(ObuSequenceHeader sequenceHeader) + { + ObuOperatingPoint operatingPoint = sequenceHeader.OperatingPoint[0]; + ObuColorConfig colorConfig = sequenceHeader.ColorConfig; + + this.SequenceProfile = (byte)sequenceHeader.SequenceProfile; + this.SequenceLevelIndex = (byte)operatingPoint.SequenceLevelIndex; + this.SequenceTier = operatingPoint.SequenceTier != 0; + this.HighBitDepth = colorConfig.BitDepth is Av1BitDepth.TenBit or Av1BitDepth.TwelveBit; + this.TwelveBit = colorConfig.BitDepth == Av1BitDepth.TwelveBit; + this.IsMonochrome = colorConfig.IsMonochrome; + this.ChromaSubsamplingX = colorConfig.SubSamplingX; + this.ChromaSubsamplingY = colorConfig.SubSamplingY; + this.ChromaSamplePosition = (byte)colorConfig.ChromaSamplePosition; + this.configSequenceHeader = []; + this.configSequenceHeaderExtension = -1; + this.configContentLightLevel = null; + this.configMasteringDisplayColorVolume = null; + } + /// /// Gets the sequence profile declared for the coded image. /// @@ -164,6 +193,27 @@ internal sealed class Av1CodecConfiguration /// public byte ChromaSamplePosition { get; } + /// + /// Writes the fixed AV1 codec-configuration record without optional configuration OBUs. + /// + /// The destination receiving the four-byte record. + public void WriteFixedHeader(Span destination) + { + // The image item payload already begins with its required sequence header. Keeping configOBUs empty avoids + // retaining or copying the same OBU into the configuration property. + destination[0] = 0x81; + destination[1] = (byte)((this.SequenceProfile << 5) | this.SequenceLevelIndex); + destination[2] = (byte)( + (this.SequenceTier ? 1 << 7 : 0) + | (this.HighBitDepth ? 1 << 6 : 0) + | (this.TwelveBit ? 1 << 5 : 0) + | (this.IsMonochrome ? 1 << 4 : 0) + | (this.ChromaSubsamplingX ? 1 << 3 : 0) + | (this.ChromaSubsamplingY ? 1 << 2 : 0) + | this.ChromaSamplePosition); + destination[3] = 0; + } + /// /// Validates the AV1 image item OBU layout and metadata against its item properties and configuration record. /// diff --git a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EncoderFrameTests.cs b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EncoderFrameTests.cs index 15d29eb4bd..b44808faef 100644 --- a/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EncoderFrameTests.cs +++ b/tests/ImageSharp.Tests/Formats/Heif/Av1/Av1EncoderFrameTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; @@ -251,6 +252,38 @@ public class Av1EncoderFrameTests Assert.Equal(allocation.AllocationId, returned.AllocationId); } + [Theory] + [InlineData(EightBit, Yuv400, 0x1F, 0x1C)] + [InlineData(TenBit, Yuv420, 0x1F, 0x4C)] + [InlineData(TenBit, Yuv444, 0x3F, 0x40)] + [InlineData(TwelveBit, Yuv422, 0x5F, 0x68)] + public void CodecConfigurationWritesFixedHeaderFromEncodedSequenceHeader( + int bitDepthValue, + int colorFormatValue, + byte expectedProfileAndLevel, + byte expectedColorFlags) + { + Av1BitDepth bitDepth = (Av1BitDepth)bitDepthValue; + Av1ColorFormat colorFormat = (Av1ColorFormat)colorFormatValue; + using Image source = new(8, 8); + using MemoryStream stream = new(); + ObuSequenceHeader sequenceHeader = Av1FrameEncoder.Encode( + Configuration.Default, + source.Frames.RootFrame, + stream, + CreateColorConfig(bitDepth, colorFormat), + qIndex: 37); + + Av1CodecConfiguration configuration = new(sequenceHeader); + byte[] fixedHeader = new byte[Av1CodecConfiguration.FixedHeaderSize]; + configuration.WriteFixedHeader(fixedHeader); + + Assert.Equal([0x81, expectedProfileAndLevel, expectedColorFlags, 0x00], fixedHeader); + Av1CodecConfiguration parsed = new(fixedHeader, new DecoderOptions()); + Assert.True(configuration.HasMatchingImageConfiguration(parsed)); + parsed.Validate(sequenceHeader); + } + [Fact] public void ScreenContentDetectorMatchesLibaomFeatureThresholds() {