Browse Source

Apply HEIF image color properties

pull/2633/head
James Jackson-South 1 week ago
parent
commit
c6e7aedaba
  1. 4
      HEIF_IMPLEMENTATION_PLAN.md
  2. 17
      src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs
  3. 12
      src/ImageSharp/Formats/Heif/Av1HeifItemDecoder.cs
  4. 15
      src/ImageSharp/Formats/Heif/GridHeifItemDecoder.cs
  5. 8
      src/ImageSharp/Formats/Heif/Heif4CharCode.cs
  6. 6
      src/ImageSharp/Formats/Heif/Heif4CharCode.tt
  7. 120
      src/ImageSharp/Formats/Heif/HeifDecoderCore.cs
  8. 15
      src/ImageSharp/Formats/Heif/HeifItem.cs
  9. 10
      src/ImageSharp/Formats/Heif/IHeifItemDecoder.cs
  10. 10
      src/ImageSharp/Formats/Heif/JpegHeifItemDecoder.cs

4
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<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. |
@ -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

17
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
/// </summary>
/// <typeparam name="TPixel">The destination pixel type.</typeparam>
/// <param name="buffer">The complete AV1 elementary-stream payload.</param>
/// <param name="containerColorProfile">
/// The container color description that overrides matching sequence-header color information.
/// </param>
/// <returns>The decoded image.</returns>
public Image<TPixel> Decode<TPixel>(Span<byte> buffer)
public Image<TPixel> Decode<TPixel>(Span<byte> buffer, CicpProfile? containerColorProfile = null)
where TPixel : unmanaged, IPixel<TPixel>
{
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<byte> frameBuffer = new(
this.configuration,

12
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<TPixel> : IHeifItemDecoder<TPixel>
/// <param name="configuration">The configuration that supplies memory allocation and codec services.</param>
/// <param name="item">The HEIF item whose encoded payload is being decoded.</param>
/// <param name="data">The encoded AV1 payload.</param>
/// <param name="colorProfile">
/// The container color description that overrides matching color information in the AV1 sequence header.
/// </param>
/// <returns>The decoded image.</returns>
public Image<TPixel> DecodeItemData(Configuration configuration, HeifItem item, Span<byte> data)
public Image<TPixel> DecodeItemData(
Configuration configuration,
HeifItem item,
Span<byte> data,
CicpProfile? colorProfile)
{
Av1Decoder decoder = new(configuration);
return decoder.Decode<TPixel>(data);
return decoder.Decode<TPixel>(data, colorProfile);
}
}

15
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<TPixel> : IHeifItemDecoder<TPixel>
/// <param name="configuration">The configuration associated with the containing HEIF decode.</param>
/// <param name="gridItem">The grid derived-image item.</param>
/// <param name="data">The grid descriptor payload.</param>
/// <param name="colorProfile">The container color description inherited by tiles that do not declare one.</param>
/// <returns>The image reconstructed from the referenced grid tiles.</returns>
public Image<TPixel> DecodeItemData(Configuration configuration, HeifItem gridItem, Span<byte> data)
public Image<TPixel> DecodeItemData(
Configuration configuration,
HeifItem gridItem,
Span<byte> data,
CicpProfile? colorProfile)
{
if (data.Length < 8)
{
@ -171,7 +177,12 @@ internal class GridHeifItemDecoder<TPixel> : IHeifItemDecoder<TPixel>
}
this.CompressionMethod = decoder.CompressionMethod;
Image<TPixel> tile = decoder.DecodeItemData(this.configuration, item, itemMemory.GetSpan());
Image<TPixel> tile = decoder.DecodeItemData(
this.configuration,
item,
itemMemory.GetSpan(),
item.CicpProfile ?? colorProfile);
try
{
HeifItemDecoderUtilities.ScaleToItemExtent(tile, item);

8
src/ImageSharp/Formats/Heif/Heif4CharCode.cs

@ -299,17 +299,17 @@ public enum Heif4CharCode : uint
Free = 0x66726565U,
/// <summary>
/// Color profile.
/// CICP color information.
/// </summary>
nclx = 0x6E636C78U,
Nclx = 0x6E636C78U,
/// <summary>
/// ICC Color profile.
/// Restricted ICC color profile.
/// </summary>
RICC = 0x72494343U,
/// <summary>
/// ICC Color profile.
/// Unrestricted ICC color profile.
/// </summary>
Prof = 0x70726F66U,

6
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",
};
#>

120
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);
}
}
/// <summary>
@ -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, object>(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, object>(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
}
}
/// <summary>
/// Applies the color profiles associated with a presented still-image item.
/// </summary>
/// <param name="metadata">The image metadata receiving the profiles.</param>
/// <param name="colorItem">The color image item whose pixels are presented.</param>
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<Rgba32>(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();
}
}
/// <summary>
/// Applies Exif and XMP metadata items that describe a decoded color image item.
/// </summary>
@ -1450,7 +1551,12 @@ internal sealed class HeifDecoderCore : ImageDecoderCore
throw new InvalidImageContentException($"Item {item.Id} has no data extents.");
}
Image<TPixel> image = decoder.DecodeItemData(this.configuration, item, itemMemory.GetSpan());
Image<TPixel> 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);
}
/// <summary>

15
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;
/// <summary>
@ -50,6 +53,18 @@ internal class HeifItem(Heif4CharCode type, uint id)
/// </summary>
public string? AuxiliaryType { get; set; }
/// <summary>
/// Gets or sets the ICC profile associated with this color image item, or <see langword="null"/> when the item
/// has no restricted or unrestricted ICC color-information property.
/// </summary>
public IccProfile? IccProfile { get; set; }
/// <summary>
/// Gets or sets the CICP color description associated with this color image item, or <see langword="null"/>
/// when the item has no <c>nclx</c> color-information property.
/// </summary>
public CicpProfile? CicpProfile { get; set; }
/// <summary>
/// Gets or sets the aspect ratio of the pixels.
/// </summary>

10
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<TPixel>
/// <param name="configuration">The configuration that supplies memory allocation and codec services.</param>
/// <param name="item">The HEIF item whose encoded payload is being decoded.</param>
/// <param name="data">The encoded image payload.</param>
/// <param name="colorProfile">
/// The container color description that overrides matching color information in the encoded image payload.
/// </param>
/// <returns>The decoded image.</returns>
public Image<TPixel> DecodeItemData(Configuration configuration, HeifItem item, Span<byte> data);
public Image<TPixel> DecodeItemData(
Configuration configuration,
HeifItem item,
Span<byte> data,
CicpProfile? colorProfile);
}

10
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<TPixel> : IHeifItemDecoder<TPixel>
/// <param name="configuration">The configuration associated with the containing HEIF decode.</param>
/// <param name="item">The HEIF item whose encoded payload is being decoded.</param>
/// <param name="data">The encoded JPEG payload.</param>
/// <param name="colorProfile">The container color description associated with the image item.</param>
/// <returns>The decoded image.</returns>
public Image<TPixel> DecodeItemData(Configuration configuration, HeifItem item, Span<byte> data)
public Image<TPixel> DecodeItemData(
Configuration configuration,
HeifItem item,
Span<byte> 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<TPixel> image = Image.Load<TPixel>(data);
return image;
}

Loading…
Cancel
Save