Browse Source

Validate AV1 configuration OBUs

pull/2633/head
James Jackson-South 1 week ago
parent
commit
bd9e9bf1d5
  1. 4
      HEIF_IMPLEMENTATION_PLAN.md
  2. 202
      src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs
  3. 2
      src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs

4
HEIF_IMPLEMENTATION_PLAN.md

@ -94,7 +94,7 @@ This snapshot pins or classifies the available references and failures; it does
| `HeifDecoderCore.ApplyAssociatedMetadata` | HEIF Annex A Exif item data, MIME metadata items, and `cdsc` item references | libavif `src/read.c` function `avifDecoderFindMetadata`, `src/exif.c` function `avifGetExifTiffHeaderOffset`, and the Exif/XMP item writing paths in `src/write.c` at `092276ce89098ead06db80975173191e5fee1826` | Resolve only metadata items whose `cdsc` reference identifies the decoded primary image, validate the Exif TIFF-header offset, and attach Exif or `application/rdf+xml` XMP through ImageSharp's existing profile types. This is a bounded still-image metadata path; it does not introduce a generic ISO BMFF metadata, media, or track model. | | `HeifDecoderCore.ApplyAssociatedMetadata` | HEIF Annex A Exif item data, MIME metadata items, and `cdsc` item references | libavif `src/read.c` function `avifDecoderFindMetadata`, `src/exif.c` function `avifGetExifTiffHeaderOffset`, and the Exif/XMP item writing paths in `src/write.c` at `092276ce89098ead06db80975173191e5fee1826` | Resolve only metadata items whose `cdsc` reference identifies the decoded primary image, validate the Exif TIFF-header offset, and attach Exif or `application/rdf+xml` XMP through ImageSharp's existing profile types. This is a bounded still-image metadata path; it does not introduce a generic ISO BMFF metadata, media, or track model. |
| `HeifDecoderCore` color-property parsing/association, `HeifItem` color profiles, and `Av1Decoder` container color override | ISO/IEC 14496-12 section 12.1.5 color information; HEIF section 6.5.5.1 color-information properties; AV1-ISOBMFF section 2.3.4 configuration semantics | libavif `src/read.c` functions `avifParseColourInformationBox`, `avifReadColorNclxProperty`, and `avifReadColorProperties`, plus `src/write.c` function `avifEncoderWriteColorProperties`, at `092276ce89098ead06db80975173191e5fee1826` | Associate at most one ICC and one `nclx` property with each presented color image item, validate ICC payloads and CICP reserved bits, expose them through ImageSharp's existing profile types, inherit a grid's CICP description only for tiles that do not declare one, and let container CICP values override the matching AV1 sequence-header fields before still-image reconstruction and YUV-to-RGB conversion. Retain only the two image color profiles; do not add a reusable color-box, sample-entry, track, or media model. | | `HeifDecoderCore` color-property parsing/association, `HeifItem` color profiles, and `Av1Decoder` container color override | ISO/IEC 14496-12 section 12.1.5 color information; HEIF section 6.5.5.1 color-information properties; AV1-ISOBMFF section 2.3.4 configuration semantics | libavif `src/read.c` functions `avifParseColourInformationBox`, `avifReadColorNclxProperty`, and `avifReadColorProperties`, plus `src/write.c` function `avifEncoderWriteColorProperties`, at `092276ce89098ead06db80975173191e5fee1826` | Associate at most one ICC and one `nclx` property with each presented color image item, validate ICC payloads and CICP reserved bits, expose them through ImageSharp's existing profile types, inherit a grid's CICP description only for tiles that do not declare one, and let container CICP values override the matching AV1 sequence-header fields before still-image reconstruction and YUV-to-RGB conversion. Retain only the two image color profiles; do not add a reusable color-box, sample-entry, track, or media model. |
| `HeifPixelAspectRatio`, `HeifItem.PixelAspectRatio`, and `HeifDecoderCore.ApplyItemPixelAspectRatioMetadata` | ISO/IEC 14496-12 section 12.1.4.3 pixel aspect ratio | libavif `src/read.c` function `avifParsePixelAspectRatioBox`, `src/write.c` function `avifEncoderWritePaspProperty`, and the presented-image property selection in `src/read.c` at `092276ce89098ead06db80975173191e5fee1826` | Preserve the two unsigned 32-bit relative spacings on the associated image item, reject zero or duplicate ratios, and map the displayed pixel width-to-height ratio into ImageSharp's existing unitless resolution metadata. Exchange the metadata axes after a quarter-turn presentation rotation and fall back from a derived grid to its first decodable tile only when the grid does not declare `pasp`. This remains one still-image presentation property and introduces no generic transform, sample-entry, or display model. | | `HeifPixelAspectRatio`, `HeifItem.PixelAspectRatio`, and `HeifDecoderCore.ApplyItemPixelAspectRatioMetadata` | ISO/IEC 14496-12 section 12.1.4.3 pixel aspect ratio | libavif `src/read.c` function `avifParsePixelAspectRatioBox`, `src/write.c` function `avifEncoderWritePaspProperty`, and the presented-image property selection in `src/read.c` at `092276ce89098ead06db80975173191e5fee1826` | Preserve the two unsigned 32-bit relative spacings on the associated image item, reject zero or duplicate ratios, and map the displayed pixel width-to-height ratio into ImageSharp's existing unitless resolution metadata. Exchange the metadata axes after a quarter-turn presentation rotation and fall back from a derived grid to its first decodable tile only when the grid does not declare `pasp`. This remains one still-image presentation property and introduces no generic transform, sample-entry, or display model. |
| `Av1CodecConfiguration`, `HeifItem.Av1CodecConfiguration`, `Av1HeifItemDecoder`, and AV1 grid configuration checks | AV1-ISOBMFF sections 2.3.3 and 2.3.4 codec-configuration record syntax and semantics; AVIF section 2.2.1 AV1 item-configuration property; MIAF section 7.3.11.4.1 grid input constraints | libavif `src/read.c` functions `avifParseCodecConfiguration`, `avifDecoderItemValidateProperties`, and `avifReadCodecConfigProperty` at `092276ce89098ead06db80975173191e5fee1826` | Associate exactly one `av1C` property with each decoded `av01` image item, validate the fixed record and its bit depth/chroma fields against the item's AV1 sequence header and optional `pixi` channel depths, require matching configurations across grid tiles, and report the encoded image precision and monochrome shape through `HeifMetadata`. Preserve optional configuration OBUs for the remaining item-level semantic validation. Consume but do not retain presentation-delay syntax, and introduce no sample entry, sample description, track, timing, or generic decoder-configuration model. | | `Av1CodecConfiguration`, `HeifItem.Av1CodecConfiguration`, `Av1HeifItemDecoder`, and AV1 grid configuration checks | AV1-ISOBMFF sections 2.3.3 and 2.3.4 codec-configuration record syntax and semantics; AVIF sections 2.1 and 2.2.1 AV1 image-item and item-configuration constraints; MIAF section 7.3.11.4.1 grid input constraints | libavif `src/read.c` functions `avifParseCodecConfiguration`, `avifDecoderItemValidateProperties`, and `avifReadCodecConfigProperty` at `092276ce89098ead06db80975173191e5fee1826` | Associate exactly one `av1C` property with each decoded `av01` image item, validate the fixed record and its bit depth/chroma fields against the item's AV1 sequence header and optional `pixi` channel depths, require matching configurations across grid tiles, and report the encoded image precision and monochrome shape through `HeifMetadata`. Validate low-overhead OBU framing, require exactly one sequence header in the image item, allow at most one first-position sequence header in `configOBUs`, and compare a repeated header's extension and payload exactly while ignoring only its legal size-field representation. Metadata OBU equivalence with `clli`, `mdcv`, and related image properties remains required. Consume but do not retain presentation-delay syntax, and introduce no sample entry, sample description, track, timing, or generic decoder-configuration model. |
| `GridHeifItemDecoder` and `HeifDecoderCore` grid/thumbnail selection | ISO/IEC 23008-12 section 6.6.2.3 image-grid syntax and MIAF grid-cell constraints | libavif `src/read.c` functions `avifParseImageGridBox`, `avifDecoderDataAllocateImagePlanes`, and `avifDecoderDataCopyTileToImage` at `092276ce89098ead06db80975173191e5fee1826` | Parse version-zero 16-bit and 32-bit grid descriptors, preserve row-major `dimg` order, require the declared tile count and one coding format, validate canvas coverage and edge overlap, and crop only the rightmost column and bottom row while copying through ImageSharp pixel buffers. A primary grid whose tile codec is unavailable may use only a decodable thumbnail that explicitly references that grid. | | `GridHeifItemDecoder` and `HeifDecoderCore` grid/thumbnail selection | ISO/IEC 23008-12 section 6.6.2.3 image-grid syntax and MIAF grid-cell constraints | libavif `src/read.c` functions `avifParseImageGridBox`, `avifDecoderDataAllocateImagePlanes`, and `avifDecoderDataCopyTileToImage` at `092276ce89098ead06db80975173191e5fee1826` | Parse version-zero 16-bit and 32-bit grid descriptors, preserve row-major `dimg` order, require the declared tile count and one coding format, validate canvas coverage and edge overlap, and crop only the rightmost column and bottom row while copying through ImageSharp pixel buffers. A primary grid whose tile codec is unavailable may use only a decodable thumbnail that explicitly references that grid. |
| `HeifDecoderCore` alpha auxiliary selection/composition and `GridHeifItemDecoder` auxiliary tile ordering | ISO/IEC 23008-12 alpha auxiliary image semantics, `auxC`, `auxl`, `prem`, and per-grid-tile alpha relationships | libavif `src/read.c` functions `avifParseAuxiliaryTypeProperty`, `avifDecoderItemIsAlphaAux`, `avifMetaFindAlphaItem`, and `avifDecoderCheckAlphaProperties`, plus `src/scale.c` box-filter scaling at `092276ce89098ead06db80975173191e5fee1826` | Recognize both registered alpha URNs, decode a direct alpha image/grid or the complete row-major set of per-color-tile alpha auxiliaries, normalize through `L16`, box-resample differing auxiliary dimensions, compose through `Rgba64` and `PixelOperations<TPixel>`, and unassociate `prem` color samples with transparent-black handling. This remains an image-item relationship only; no track or generic media-reference model is introduced. | | `HeifDecoderCore` alpha auxiliary selection/composition and `GridHeifItemDecoder` auxiliary tile ordering | ISO/IEC 23008-12 alpha auxiliary image semantics, `auxC`, `auxl`, `prem`, and per-grid-tile alpha relationships | libavif `src/read.c` functions `avifParseAuxiliaryTypeProperty`, `avifDecoderItemIsAlphaAux`, `avifMetaFindAlphaItem`, and `avifDecoderCheckAlphaProperties`, plus `src/scale.c` box-filter scaling at `092276ce89098ead06db80975173191e5fee1826` | Recognize both registered alpha URNs, decode a direct alpha image/grid or the complete row-major set of per-color-tile alpha auxiliaries, normalize through `L16`, box-resample differing auxiliary dimensions, compose through `Rgba64` and `PixelOperations<TPixel>`, and unassociate `prem` color samples with transparent-black handling. This remains an image-item relationship only; no track or generic media-reference model is introduced. |
@ -126,7 +126,7 @@ This assessment is based on the current source after the upstream ImageSharp mer
- Decode now resolves `cdsc`-associated Exif and `application/rdf+xml` XMP items for the primary still image, validates the declared Exif TIFF-header offset, and attaches the payloads through ImageSharp's existing profile types before presentation transforms. Independent AVIF, HEIC, and HIF metadata fixtures and Identify-time profile reporting remain required. - Decode now resolves `cdsc`-associated Exif and `application/rdf+xml` XMP items for the primary still image, validates the declared Exif TIFF-header offset, and attaches the payloads through ImageSharp's existing profile types before presentation transforms. Independent AVIF, HEIC, and HIF metadata fixtures and Identify-time profile reporting remain required.
- ICC and `nclx` CICP color properties are now associated with the presented color item instead of global parser state, validated, exposed on Decode and Identify through the existing ImageSharp profiles, and used to override matching AV1 bitstream color fields before still-image color conversion. Independent ICC/CICP fixtures, decoded AV1 bitstream-CICP fallback metadata, ICC conversion coverage, and HEVC integration remain required. - ICC and `nclx` CICP color properties are now associated with the presented color item instead of global parser state, validated, exposed on Decode and Identify through the existing ImageSharp profiles, and used to override matching AV1 bitstream color fields before still-image color conversion. Independent ICC/CICP fixtures, decoded AV1 bitstream-CICP fallback metadata, ICC conversion coverage, and HEVC integration remain required.
- Pixel aspect ratio now preserves the complete unsigned spacing pair, affects Decode and Identify through ImageSharp's existing unitless resolution metadata, and follows quarter-turn presentation rotation. Independent grid, rotation, and maximum-spacing fixtures remain required. - Pixel aspect ratio now preserves the complete unsigned spacing pair, affects Decode and Identify through ImageSharp's existing unitless resolution metadata, and follows quarter-turn presentation rotation. Independent grid, rotation, and maximum-spacing fixtures remain required.
- AV1 codec configuration is now retained per `av01` image item rather than in decoder-global state. Decode requires the property, validates its fixed record against the item's sequence header and any associated `pixi` channel depths, and requires matching configurations across grid tiles. Identify now reports the configuration's 8/10/12-bit precision and monochrome shape. Optional configuration OBUs are preserved but their duplicate sequence-header and metadata semantics still require validation; HEVC `hvcC` remains unimplemented. - AV1 codec configuration is now retained per `av01` image item rather than in decoder-global state. Decode requires the property, validates its fixed record against the item's sequence header and any associated `pixi` channel depths, and requires matching configurations across grid tiles. Identify now reports the configuration's 8/10/12-bit precision and monochrome shape. The optional `configOBUs` sequence is bounded and validated, including its mandatory size fields, first-position/at-most-one sequence-header rule, the image item's exactly-one sequence-header rule, and exact comparison of a repeated configuration header with the item header. Metadata OBU equivalence with `clli`, `mdcv`, and related still-image properties remains required; HEVC `hvcC` remains unimplemented.
- Several image-item properties and relationships remain missing or parsed without fully affecting output. - Several image-item properties and relationships remain missing or parsed without fully affecting output.
- Identify and decode now use the same bounded metadata parser and both validate the complete leading file type box. The parsed state is still mutable and Identify does not yet report the complete bit depth, color, profile, or transform model. - Identify and decode now use the same bounded metadata parser and both validate the complete leading file type box. The parsed state is still mutable and Identify does not yet report the complete bit depth, color, profile, or transform model.

202
src/ImageSharp/Formats/Heif/Av1/Av1CodecConfiguration.cs

@ -15,6 +15,22 @@ internal sealed class Av1CodecConfiguration
/// </summary> /// </summary>
private readonly byte[] configObus; private readonly byte[] configObus;
/// <summary>
/// The byte offset of the optional sequence-header payload within <see cref="configObus"/>, or <c>-1</c> when
/// the configuration contains no sequence header.
/// </summary>
private readonly int configSequenceHeaderOffset;
/// <summary>
/// The byte length of the optional sequence-header payload within <see cref="configObus"/>.
/// </summary>
private readonly int configSequenceHeaderLength;
/// <summary>
/// The sequence-header OBU extension byte, or <c>-1</c> when its header has no extension.
/// </summary>
private readonly int configSequenceHeaderExtension;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Av1CodecConfiguration"/> class from an AV1 codec-configuration /// Initializes a new instance of the <see cref="Av1CodecConfiguration"/> class from an AV1 codec-configuration
/// item-property payload. /// item-property payload.
@ -66,6 +82,19 @@ internal sealed class Av1CodecConfiguration
// The delay syntax is consumed to validate the fixed record, but it describes sample presentation and has // 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. // no meaning for the independently presented image item supported by this bounded container implementation.
this.configObus = boxBuffer[4..].ToArray(); this.configObus = boxBuffer[4..].ToArray();
int sequenceHeaderCount = ScanObus(
this.configObus,
true,
true,
"AV1 codec configuration",
out this.configSequenceHeaderOffset,
out this.configSequenceHeaderLength,
out this.configSequenceHeaderExtension);
if (sequenceHeaderCount > 1)
{
throw new InvalidImageContentException("The AV1 codec configuration contains more than one sequence header OBU.");
}
} }
/// <summary> /// <summary>
@ -123,6 +152,47 @@ internal sealed class Av1CodecConfiguration
/// </summary> /// </summary>
public ReadOnlyMemory<byte> ConfigObus => this.configObus; public ReadOnlyMemory<byte> ConfigObus => this.configObus;
/// <summary>
/// Validates the AV1 image item OBU layout and any sequence header repeated by the configuration record.
/// </summary>
/// <param name="itemData">The complete AV1 image item payload.</param>
public void ValidateItemData(ReadOnlySpan<byte> itemData)
{
int sequenceHeaderCount = ScanObus(
itemData,
false,
false,
"AV1 image item",
out int itemSequenceHeaderOffset,
out int itemSequenceHeaderLength,
out int itemSequenceHeaderExtension);
if (sequenceHeaderCount != 1)
{
throw new InvalidImageContentException($"The AV1 image item contains {sequenceHeaderCount} sequence header OBUs instead of exactly one.");
}
if (this.configSequenceHeaderOffset >= 0)
{
ReadOnlySpan<byte> configSequenceHeader = this.configObus.AsSpan(
this.configSequenceHeaderOffset,
this.configSequenceHeaderLength);
ReadOnlySpan<byte> itemSequenceHeader = itemData.Slice(
itemSequenceHeaderOffset,
itemSequenceHeaderLength);
// Compare the extension and payload rather than the encoded OBU size. Configuration OBUs must carry a
// size field while an image item's final OBU may omit one, and different legal LEB128 widths do not alter
// the Sequence Header OBU being repeated.
if (this.configSequenceHeaderExtension != itemSequenceHeaderExtension
|| !configSequenceHeader.SequenceEqual(itemSequenceHeader))
{
throw new InvalidImageContentException("The AV1 codec configuration sequence header does not match the image item sequence header.");
}
}
}
/// <summary> /// <summary>
/// Determines whether another item configuration describes the same coded-image sample layout. /// Determines whether another item configuration describes the same coded-image sample layout.
/// </summary> /// </summary>
@ -163,4 +233,136 @@ internal sealed class Av1CodecConfiguration
throw new InvalidImageContentException("The AV1 item configuration does not match its sequence header."); throw new InvalidImageContentException("The AV1 item configuration does not match its sequence header.");
} }
} }
/// <summary>
/// Scans a low-overhead AV1 OBU sequence and locates its first sequence-header payload.
/// </summary>
/// <param name="data">The complete bounded OBU sequence.</param>
/// <param name="requireSizeFields">Indicates that every OBU must carry its registered payload-size field.</param>
/// <param name="sequenceHeaderMustBeFirst">
/// Indicates that a sequence-header OBU, when present, must be the first OBU in the sequence.
/// </param>
/// <param name="sourceName">The source description used by invalid-content errors.</param>
/// <param name="sequenceHeaderOffset">Receives the first sequence-header payload offset, or <c>-1</c>.</param>
/// <param name="sequenceHeaderLength">Receives the first sequence-header payload length.</param>
/// <param name="sequenceHeaderExtension">Receives the first sequence-header extension byte, or <c>-1</c>.</param>
/// <returns>The number of sequence-header OBUs in the sequence.</returns>
private static int ScanObus(
ReadOnlySpan<byte> data,
bool requireSizeFields,
bool sequenceHeaderMustBeFirst,
string sourceName,
out int sequenceHeaderOffset,
out int sequenceHeaderLength,
out int sequenceHeaderExtension)
{
sequenceHeaderOffset = -1;
sequenceHeaderLength = 0;
sequenceHeaderExtension = -1;
int sequenceHeaderCount = 0;
int obuIndex = 0;
int offset = 0;
while (offset < data.Length)
{
byte header = data[offset++];
if ((header & 0x81) != 0)
{
throw new InvalidImageContentException($"The {sourceName} contains an OBU with a set forbidden or reserved header bit.");
}
ObuType type = (ObuType)((header >> 3) & 0x0F);
bool hasExtension = (header & 0x04) != 0;
bool hasSizeField = (header & 0x02) != 0;
int extension = -1;
if (hasExtension)
{
if (offset >= data.Length)
{
throw new InvalidImageContentException($"The {sourceName} contains a truncated OBU extension header.");
}
extension = data[offset++];
if ((extension & 0x07) != 0)
{
throw new InvalidImageContentException($"The {sourceName} contains an OBU extension with nonzero reserved bits.");
}
}
if (requireSizeFields && !hasSizeField)
{
throw new InvalidImageContentException($"The {sourceName} contains an OBU without its required payload-size field.");
}
int payloadLength;
if (hasSizeField)
{
payloadLength = ReadObuPayloadLength(data, ref offset, sourceName);
}
else
{
// Low-overhead image item syntax permits only the final OBU to omit its size, in which case the
// remaining item bytes are that OBU's payload and cannot contain another independently parsed OBU.
payloadLength = data.Length - offset;
}
if (payloadLength > data.Length - offset)
{
throw new InvalidImageContentException($"The {sourceName} contains an OBU payload that exceeds its data boundary.");
}
if (type == ObuType.SequenceHeader)
{
if (sequenceHeaderMustBeFirst && obuIndex != 0)
{
throw new InvalidImageContentException($"The {sourceName} contains a sequence header OBU after another OBU.");
}
sequenceHeaderCount++;
if (sequenceHeaderOffset < 0)
{
sequenceHeaderOffset = offset;
sequenceHeaderLength = payloadLength;
sequenceHeaderExtension = extension;
}
}
offset += payloadLength;
obuIndex++;
}
return sequenceHeaderCount;
}
/// <summary>
/// Reads a bounded AV1 little-endian base-128 OBU payload length.
/// </summary>
/// <param name="data">The complete bounded OBU sequence.</param>
/// <param name="offset">The current byte offset, advanced past the encoded length.</param>
/// <param name="sourceName">The source description used by invalid-content errors.</param>
/// <returns>The payload length representable by the current item buffer.</returns>
private static int ReadObuPayloadLength(ReadOnlySpan<byte> data, ref int offset, string sourceName)
{
ulong value = 0;
for (int byteIndex = 0; byteIndex < 8; byteIndex++)
{
if (offset >= data.Length)
{
throw new InvalidImageContentException($"The {sourceName} contains a truncated OBU payload length.");
}
byte current = data[offset++];
value |= (ulong)(current & 0x7F) << (byteIndex * 7);
if ((current & 0x80) == 0)
{
if (value > int.MaxValue)
{
throw new InvalidImageContentException($"The {sourceName} contains an OBU payload too large to buffer.");
}
return (int)value;
}
}
throw new InvalidImageContentException($"The {sourceName} contains an unterminated OBU payload length.");
}
} }

2
src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs

@ -73,6 +73,8 @@ internal class Av1Decoder : IAv1TileReader
Av1CodecConfiguration? codecConfiguration = null) Av1CodecConfiguration? codecConfiguration = null)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
codecConfiguration?.ValidateItemData(buffer);
Av1BitStreamReader reader = new(buffer); Av1BitStreamReader reader = new(buffer);
this.obuReader.ReadAll(ref reader, buffer.Length, () => this, false); this.obuReader.ReadAll(ref reader, buffer.Length, () => this, false);
Guard.NotNull(this.tileReader, nameof(this.tileReader)); Guard.NotNull(this.tileReader, nameof(this.tileReader));

Loading…
Cancel
Save