diff --git a/HEIF_IMPLEMENTATION_PLAN.md b/HEIF_IMPLEMENTATION_PLAN.md index 16ef9a19c..7cbe1088c 100644 --- a/HEIF_IMPLEMENTATION_PLAN.md +++ b/HEIF_IMPLEMENTATION_PLAN.md @@ -90,6 +90,7 @@ This snapshot pins or classifies the available references and failures; it does | `HeifDecoderCore.ReadBoxHeader` and `HeifDecoderCore.ParseBoxHeader` | ISO/IEC 14496-12 section 4.2.2 basic box syntax | libavif `src/stream.c` functions `avifROStreamReadBoxHeaderPartial` and `avifROStreamReadBoxHeader` at `092276ce89098ead06db80975173191e5fee1826` | Resolve 32-bit, 64-bit, UUID, and top-level size-zero boxes into content lengths only after validating the complete variable-sized header and the remaining parent boundary. Nested size-zero boxes are invalid; large skips retain 64-bit offsets. | | `HeifDecoderCore.ParseMetadata` | ISO/IEC 14496-12 `MetaBox` and HEIF item declarations, locations, properties, and associations | libavif `src/read.c` functions `avifParseMetaBox`, `avifMetaFindOrCreateItem`, `avifParseItemLocationBox`, and `avifParseItemPropertiesBox` at `092276ce89098ead06db80975173191e5fee1826` | Index unique recognized metadata children by type and payload location, then parse them in dependency order so physical placement does not control item lookup or property association. Duplicate unique children and truncated full-box headers are invalid. | | `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. | | `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`, 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. | @@ -119,7 +120,8 @@ This assessment is based on the current source after the upstream ImageSharp mer - Alpha auxiliary decoding now recognizes `auxC`, `auxl`, and `prem`, supports a direct auxiliary image/grid and the per-color-grid-tile form, preserves normalized alpha through `L16`/`Rgba64`, box-resamples differing plane sizes, and reports alpha presence. HEVC alpha remains blocked on the HEVC image-item decoder, while independent AVIF alpha fixtures are still required against the incomplete AV1 reconstruction pipeline. - Clean aperture, image rotation, and image mirror properties now validate their registered payloads, exact integer crop geometry, and essential associations; affect Identify dimensions; and reuse ImageSharp's optimized crop/rotate/flip processors after auxiliary alpha composition in the MIAF-defined order. Independent transform vectors must still verify every crop/rotation/mirror/alpha combination. - 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. -- HEVC and AV1 configuration, ICC/CICP color information, pixel aspect ratio, and several image-item properties/relationships are missing or parsed without fully affecting output. +- 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. +- HEVC and AV1 configuration, pixel aspect ratio, and several image-item properties/relationships are 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. ### HEVC decoder and encoder diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs index 17baf7d1c..cfe06249c 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs @@ -4,6 +4,7 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +using SixLabors.ImageSharp.Metadata.Profiles.Cicp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats.Utils; @@ -59,8 +60,11 @@ internal class Av1Decoder : IAv1TileReader /// /// The destination pixel type. /// The complete AV1 elementary-stream payload. + /// + /// The container color description that overrides matching sequence-header color information. + /// /// The decoded image. - public Image Decode(Span buffer) + public Image Decode(Span buffer, CicpProfile? containerColorProfile = null) where TPixel : unmanaged, IPixel { Av1BitStreamReader reader = new(buffer); @@ -69,6 +73,17 @@ internal class Av1Decoder : IAv1TileReader Guard.NotNull(this.SequenceHeader, nameof(this.SequenceHeader)); Guard.NotNull(this.FrameHeader, nameof(this.FrameHeader)); + if (containerColorProfile is not null) + { + // ISO BMFF color information takes precedence over the matching bitstream fields. Apply only the four + // CICP values; chroma layout and bit depth remain properties of the AV1 coded image itself. + ObuColorConfig colorConfig = this.SequenceHeader.ColorConfig; + colorConfig.ColorPrimaries = (ObuColorPrimaries)containerColorProfile.ColorPrimaries; + colorConfig.TransferCharacteristics = (ObuTransferCharacteristics)containerColorProfile.TransferCharacteristics; + colorConfig.MatrixCoefficients = (ObuMatrixCoefficients)containerColorProfile.MatrixCoefficients; + colorConfig.ColorRange = containerColorProfile.FullRange; + } + this.FrameInfo = this.tileReader.FrameInfo; using Av1FrameBuffer frameBuffer = new( this.configuration, diff --git a/src/ImageSharp/Formats/Heif/Av1HeifItemDecoder.cs b/src/ImageSharp/Formats/Heif/Av1HeifItemDecoder.cs index f5caf15ce..6a7d1e1a8 100644 --- a/src/ImageSharp/Formats/Heif/Av1HeifItemDecoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1HeifItemDecoder.cs @@ -2,6 +2,7 @@ // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.Metadata.Profiles.Cicp; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Heif; @@ -29,10 +30,17 @@ internal class Av1HeifItemDecoder : IHeifItemDecoder /// The configuration that supplies memory allocation and codec services. /// The HEIF item whose encoded payload is being decoded. /// The encoded AV1 payload. + /// + /// The container color description that overrides matching color information in the AV1 sequence header. + /// /// The decoded image. - public Image DecodeItemData(Configuration configuration, HeifItem item, Span data) + public Image DecodeItemData( + Configuration configuration, + HeifItem item, + Span data, + CicpProfile? colorProfile) { Av1Decoder decoder = new(configuration); - return decoder.Decode(data); + return decoder.Decode(data, colorProfile); } } diff --git a/src/ImageSharp/Formats/Heif/GridHeifItemDecoder.cs b/src/ImageSharp/Formats/Heif/GridHeifItemDecoder.cs index 612a57eef..28f917559 100644 --- a/src/ImageSharp/Formats/Heif/GridHeifItemDecoder.cs +++ b/src/ImageSharp/Formats/Heif/GridHeifItemDecoder.cs @@ -5,6 +5,7 @@ using System.Buffers; using System.Buffers.Binary; using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Memory; +using SixLabors.ImageSharp.Metadata.Profiles.Cicp; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Heif; @@ -81,8 +82,13 @@ internal class GridHeifItemDecoder : IHeifItemDecoder /// The configuration associated with 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 image reconstructed from the referenced grid tiles. - public Image DecodeItemData(Configuration configuration, HeifItem gridItem, Span data) + public Image DecodeItemData( + Configuration configuration, + HeifItem gridItem, + Span data, + CicpProfile? colorProfile) { if (data.Length < 8) { @@ -171,7 +177,12 @@ internal class GridHeifItemDecoder : IHeifItemDecoder } this.CompressionMethod = decoder.CompressionMethod; - Image tile = decoder.DecodeItemData(this.configuration, item, itemMemory.GetSpan()); + Image tile = decoder.DecodeItemData( + this.configuration, + item, + itemMemory.GetSpan(), + item.CicpProfile ?? colorProfile); + try { HeifItemDecoderUtilities.ScaleToItemExtent(tile, item); diff --git a/src/ImageSharp/Formats/Heif/Heif4CharCode.cs b/src/ImageSharp/Formats/Heif/Heif4CharCode.cs index 5289411f8..dc88fc0b0 100644 --- a/src/ImageSharp/Formats/Heif/Heif4CharCode.cs +++ b/src/ImageSharp/Formats/Heif/Heif4CharCode.cs @@ -299,17 +299,17 @@ public enum Heif4CharCode : uint Free = 0x66726565U, /// - /// Color profile. + /// CICP color information. /// - nclx = 0x6E636C78U, + Nclx = 0x6E636C78U, /// - /// ICC Color profile. + /// Restricted ICC color profile. /// RICC = 0x72494343U, /// - /// ICC Color profile. + /// Unrestricted ICC color profile. /// Prof = 0x70726F66U, diff --git a/src/ImageSharp/Formats/Heif/Heif4CharCode.tt b/src/ImageSharp/Formats/Heif/Heif4CharCode.tt index d16ec150f..2a891357f 100644 --- a/src/ImageSharp/Formats/Heif/Heif4CharCode.tt +++ b/src/ImageSharp/Formats/Heif/Heif4CharCode.tt @@ -64,9 +64,9 @@ "pict", "Picture handler type", "uuid", "Unique Identifier", "free", "Free space", - "nclx", "Color profile", - "rICC", "ICC Color profile", - "prof", "ICC Color profile", + "nclx", "CICP color information", + "rICC", "Restricted ICC color profile", + "prof", "Unrestricted ICC color profile", }; #> diff --git a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs index 4bb6b250c..3adbc100a 100644 --- a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs @@ -9,6 +9,7 @@ using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Metadata; +using SixLabors.ImageSharp.Metadata.Profiles.Cicp; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Xmp; @@ -238,6 +239,11 @@ internal sealed class HeifDecoderCore : ImageDecoderCore meta.CompressionMethod = compressionMethod; meta.HasAlpha = this.FindAlphaItem(presentationItem) is not null || (presentationItem.Type == Heif4CharCode.Grid && this.FindGridAlphaTiles(presentationItem) is not null); + + if (!this.Options.SkipMetadata) + { + this.ApplyItemColorMetadata(metadata, presentationItem); + } } /// @@ -830,15 +836,61 @@ internal sealed class HeifDecoderCore : ImageDecoderCore case Heif4CharCode.Colr: EnsureBufferRemaining(boxBuffer, 0, 4, "color information"); Heif4CharCode profileType = (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(boxBuffer); + object colorInformation = UnknownProperty; if (profileType is Heif4CharCode.RICC or Heif4CharCode.Prof) { - byte[] iccData = new byte[(int)itemLength - 4]; - boxBuffer[4..].CopyTo(iccData); - this.metadata.IccProfile = new IccProfile(iccData); + EnsureBufferRemaining(boxBuffer, 4, 1, "ICC color information"); + byte[] iccData = boxBuffer[4..].ToArray(); + IccProfile? iccProfile = null; + this.ExecuteAncillarySegmentAction(() => + { + IccProfile candidate = new(iccData); + if (!candidate.CheckIsValid()) + { + throw new InvalidIccProfileException("Invalid HEIF ICC profile."); + } + + iccProfile = candidate; + }); + + // A malformed ancillary profile can be ignored by policy while the physical property still + // occupies its ipco index and remains understood for essential-association handling. + colorInformation = iccProfile ?? new object(); } + else if (profileType == Heif4CharCode.Nclx) + { + EnsureBufferRemaining(boxBuffer, 4, 7, "CICP color information"); + ushort colorPrimaries = BinaryPrimitives.ReadUInt16BigEndian(boxBuffer[4..]); + ushort transferCharacteristics = BinaryPrimitives.ReadUInt16BigEndian(boxBuffer[6..]); + ushort matrixCoefficients = BinaryPrimitives.ReadUInt16BigEndian(boxBuffer[8..]); + byte rangeAndReserved = boxBuffer[10]; + if ((rangeAndReserved & 0x7F) != 0) + { + throw new InvalidImageContentException("The HEIF CICP color property has nonzero reserved bits."); + } - // Property indices refer to every box in ipco, including properties handled directly while parsing. - properties.Add(new KeyValuePair(Heif4CharCode.Colr, new object())); + // The box fields are 16-bit so future registrations remain representable. ImageSharp's CICP + // profile exposes the currently registered byte-sized H.273 values and maps others to unspecified. + byte colorPrimariesValue = colorPrimaries <= byte.MaxValue + ? (byte)colorPrimaries + : (byte)CicpColorPrimaries.Unspecified; + + byte transferCharacteristicsValue = transferCharacteristics <= byte.MaxValue + ? (byte)transferCharacteristics + : (byte)CicpTransferCharacteristics.Unspecified; + + byte matrixCoefficientsValue = matrixCoefficients <= byte.MaxValue + ? (byte)matrixCoefficients + : (byte)CicpMatrixCoefficients.Unspecified; + + colorInformation = new CicpProfile( + colorPrimariesValue, + transferCharacteristicsValue, + matrixCoefficientsValue, + (rangeAndReserved & 0x80) != 0); + } + + properties.Add(new KeyValuePair(Heif4CharCode.Colr, colorInformation)); break; case Heif4CharCode.Av1C: @@ -997,6 +1049,27 @@ internal sealed class HeifDecoderCore : ImageDecoderCore } item.AuxiliaryType = (string)prop.Value; + break; + case Heif4CharCode.Colr: + if (prop.Value is IccProfile iccProfile) + { + if (item.IccProfile is not null) + { + throw new InvalidImageContentException($"Item {itemId} associates more than one ICC color property."); + } + + item.IccProfile = iccProfile; + } + else if (prop.Value is CicpProfile cicpProfile) + { + if (item.CicpProfile is not null) + { + throw new InvalidImageContentException($"Item {itemId} associates more than one CICP color property."); + } + + item.CicpProfile = cicpProfile; + } + break; case Heif4CharCode.Clap: if (item.CleanAperture is not null) @@ -1321,7 +1394,9 @@ internal sealed class HeifDecoderCore : ImageDecoderCore if (!this.Options.SkipMetadata) { + this.ApplyItemColorMetadata(image.Metadata, itemToDecode); this.ApplyAssociatedMetadata(image.Metadata, rootItem, buffers); + _ = this.TryConvertIccProfile(image); } // MIAF defines crop, rotation, and mirror as presentation operations in that order. Applying the @@ -1343,6 +1418,32 @@ internal sealed class HeifDecoderCore : ImageDecoderCore } } + /// + /// Applies the color profiles associated with a presented still-image item. + /// + /// The image metadata receiving the profiles. + /// The color image item whose pixels are presented. + private void ApplyItemColorMetadata(ImageMetadata metadata, HeifItem colorItem) + { + // Color properties can be associated with the derived grid or its coded tile items. Prefer the presentation + // grid and use the first decodable tile only when the grid does not provide the corresponding profile. + HeifItem? gridTile = colorItem.Type == Heif4CharCode.Grid + ? this.FindDecodableGridTile(colorItem) + : null; + + IccProfile? iccProfile = colorItem.IccProfile ?? gridTile?.IccProfile; + if (iccProfile is not null) + { + metadata.IccProfile = iccProfile.DeepClone(); + } + + CicpProfile? cicpProfile = colorItem.CicpProfile ?? gridTile?.CicpProfile; + if (cicpProfile is not null) + { + metadata.CicpProfile = cicpProfile.DeepClone(); + } + } + /// /// Applies Exif and XMP metadata items that describe a decoded color image item. /// @@ -1450,7 +1551,12 @@ internal sealed class HeifDecoderCore : ImageDecoderCore throw new InvalidImageContentException($"Item {item.Id} has no data extents."); } - Image image = decoder.DecodeItemData(this.configuration, item, itemMemory.GetSpan()); + Image image = decoder.DecodeItemData( + this.configuration, + item, + itemMemory.GetSpan(), + item.CicpProfile); + try { HeifItemDecoderUtilities.ScaleToItemExtent(image, item); @@ -1590,7 +1696,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore buffers, alphaTileIds); - return gridDecoder.DecodeItemData(this.configuration, colorItem, gridMemory.GetSpan()); + return gridDecoder.DecodeItemData(this.configuration, colorItem, gridMemory.GetSpan(), null); } /// diff --git a/src/ImageSharp/Formats/Heif/HeifItem.cs b/src/ImageSharp/Formats/Heif/HeifItem.cs index cbd404075..75327c1de 100644 --- a/src/ImageSharp/Formats/Heif/HeifItem.cs +++ b/src/ImageSharp/Formats/Heif/HeifItem.cs @@ -1,6 +1,9 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Metadata.Profiles.Cicp; +using SixLabors.ImageSharp.Metadata.Profiles.Icc; + namespace SixLabors.ImageSharp.Formats.Heif; /// @@ -50,6 +53,18 @@ internal class HeifItem(Heif4CharCode type, uint id) /// public string? AuxiliaryType { get; set; } + /// + /// Gets or sets the ICC profile associated with this color image item, or when the item + /// has no restricted or unrestricted ICC color-information property. + /// + public IccProfile? IccProfile { get; set; } + + /// + /// Gets or sets the CICP color description associated with this color image item, or + /// when the item has no nclx color-information property. + /// + public CicpProfile? CicpProfile { get; set; } + /// /// Gets or sets the aspect ratio of the pixels. /// diff --git a/src/ImageSharp/Formats/Heif/IHeifItemDecoder.cs b/src/ImageSharp/Formats/Heif/IHeifItemDecoder.cs index f1f106de8..1c096c803 100644 --- a/src/ImageSharp/Formats/Heif/IHeifItemDecoder.cs +++ b/src/ImageSharp/Formats/Heif/IHeifItemDecoder.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Metadata.Profiles.Cicp; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Heif; @@ -28,6 +29,13 @@ internal interface IHeifItemDecoder /// The configuration that supplies memory allocation and codec services. /// 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 decoded image. - public Image DecodeItemData(Configuration configuration, HeifItem item, Span data); + public Image DecodeItemData( + Configuration configuration, + HeifItem item, + Span data, + CicpProfile? colorProfile); } diff --git a/src/ImageSharp/Formats/Heif/JpegHeifItemDecoder.cs b/src/ImageSharp/Formats/Heif/JpegHeifItemDecoder.cs index 823f5bfd9..c83b6bc81 100644 --- a/src/ImageSharp/Formats/Heif/JpegHeifItemDecoder.cs +++ b/src/ImageSharp/Formats/Heif/JpegHeifItemDecoder.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Metadata.Profiles.Cicp; using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Heif; @@ -28,9 +29,16 @@ internal class JpegHeifItemDecoder : IHeifItemDecoder /// The configuration associated with 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 decoded image. - public Image DecodeItemData(Configuration configuration, HeifItem item, Span data) + public Image DecodeItemData( + Configuration configuration, + HeifItem item, + Span data, + CicpProfile? colorProfile) { + // 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. Image image = Image.Load(data); return image; }