Browse Source

Harden and document HEIF still-image container

pull/2633/head
James Jackson-South 2 weeks ago
parent
commit
1b4ec92730
  1. 13
      src/ImageSharp/Formats/Heif/Av1HeifItemDecoder.cs
  2. 45
      src/ImageSharp/Formats/Heif/GridHeifItemDecoder.cs
  3. 9
      src/ImageSharp/Formats/Heif/HeifCompressionFactory.cs
  4. 16
      src/ImageSharp/Formats/Heif/HeifCompressionMethod.cs
  5. 26
      src/ImageSharp/Formats/Heif/HeifConstants.cs
  6. 3
      src/ImageSharp/Formats/Heif/HeifDecoder.cs
  7. 754
      src/ImageSharp/Formats/Heif/HeifDecoderCore.cs
  8. 194
      src/ImageSharp/Formats/Heif/HeifEncoderCore.cs
  9. 3
      src/ImageSharp/Formats/Heif/HeifFormat.cs
  10. 11
      src/ImageSharp/Formats/Heif/HeifImageFormatDetector.cs
  11. 8
      src/ImageSharp/Formats/Heif/HeifItem.cs
  12. 2
      src/ImageSharp/Formats/Heif/HeifItemLink.cs
  13. 21
      src/ImageSharp/Formats/Heif/HeifLocation.cs
  14. 30
      src/ImageSharp/Formats/Heif/HeifLocationComparer.cs
  15. 14
      src/ImageSharp/Formats/Heif/HeifLocationOffsetOrigin.cs
  16. 16
      src/ImageSharp/Formats/Heif/IHeifItemDecoder.cs
  17. 13
      src/ImageSharp/Formats/Heif/JpegHeifItemDecoder.cs

13
src/ImageSharp/Formats/Heif/Av1HeifItemDecoder.cs

@ -7,24 +7,29 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Heif; namespace SixLabors.ImageSharp.Formats.Heif;
/// <summary> /// <summary>
/// Decoder for a single <see cref="HeifItem"/> into a AVIF image. /// Decodes a single AV1-coded HEIF image item.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The destination pixel type.</typeparam>
internal class Av1HeifItemDecoder<TPixel> : IHeifItemDecoder<TPixel> internal class Av1HeifItemDecoder<TPixel> : IHeifItemDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
/// <summary> /// <summary>
/// Gets the item type this decoder decodes, which is <see cref="Heif4CharCode.Av01"/>. /// Gets the AV1-coded image item type.
/// </summary> /// </summary>
public Heif4CharCode Type => Heif4CharCode.Av01; public Heif4CharCode Type => Heif4CharCode.Av01;
/// <summary> /// <summary>
/// Gets the compression method this doceder uses, which is <see cref="HeifCompressionMethod.Av1"/>. /// Gets the AV1 compression method.
/// </summary> /// </summary>
public HeifCompressionMethod CompressionMethod => HeifCompressionMethod.Av1; public HeifCompressionMethod CompressionMethod => HeifCompressionMethod.Av1;
/// <summary> /// <summary>
/// Decode the specified item as AVIF. /// Decodes the encoded AV1 payload of an image item.
/// </summary> /// </summary>
/// <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>
/// <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)
{ {
Av1Decoder decoder = new(configuration); Av1Decoder decoder = new(configuration);

45
src/ImageSharp/Formats/Heif/GridHeifItemDecoder.cs

@ -9,16 +9,39 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Heif; namespace SixLabors.ImageSharp.Formats.Heif;
/// <summary> /// <summary>
/// Decoder for a grid of several <see cref="HeifItem"/> into a single image. /// Decodes the image items referenced by a HEIF grid derived-image item.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The destination pixel type.</typeparam>
internal class GridHeifItemDecoder<TPixel> : IHeifItemDecoder<TPixel> internal class GridHeifItemDecoder<TPixel> : IHeifItemDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
/// <summary>
/// The configuration used to decode each compressed grid tile.
/// </summary>
private readonly Configuration configuration; private readonly Configuration configuration;
/// <summary>
/// The item definitions available to the grid.
/// </summary>
private readonly IList<HeifItem> items; private readonly IList<HeifItem> items;
/// <summary>
/// The item-reference relationships used to locate the grid's tiles.
/// </summary>
private readonly IList<HeifItemLink> itemLinks; private readonly IList<HeifItemLink> itemLinks;
/// <summary>
/// The assembled encoded payload for each referenced image item.
/// </summary>
private readonly IDictionary<uint, IMemoryOwner<byte>> buffers; private readonly IDictionary<uint, IMemoryOwner<byte>> buffers;
/// <summary>
/// Initializes a new instance of the <see cref="GridHeifItemDecoder{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration used to decode compressed grid tiles.</param>
/// <param name="items">The item definitions in the containing HEIF file.</param>
/// <param name="itemLinks">The item-reference relationships in the containing HEIF file.</param>
/// <param name="buffers">The assembled encoded payload for each image item.</param>
public GridHeifItemDecoder(Configuration configuration, IList<HeifItem> items, IList<HeifItemLink> itemLinks, IDictionary<uint, IMemoryOwner<byte>> buffers) public GridHeifItemDecoder(Configuration configuration, IList<HeifItem> items, IList<HeifItemLink> itemLinks, IDictionary<uint, IMemoryOwner<byte>> buffers)
{ {
this.configuration = configuration; this.configuration = configuration;
@ -28,29 +51,37 @@ internal class GridHeifItemDecoder<TPixel> : IHeifItemDecoder<TPixel>
} }
/// <summary> /// <summary>
/// Gets the item type this decoder decodes, which is <see cref="Heif4CharCode.Grid"/>. /// Gets the grid derived-image item type.
/// </summary> /// </summary>
public Heif4CharCode Type => Heif4CharCode.Grid; public Heif4CharCode Type => Heif4CharCode.Grid;
/// <summary> /// <summary>
/// Gets the compression method this doceder uses. /// Gets the compression method used by the decoded grid tiles.
/// </summary> /// </summary>
public HeifCompressionMethod CompressionMethod { get; private set; } public HeifCompressionMethod CompressionMethod { get; private set; }
/// <summary> /// <summary>
/// Decode the specified item as single image. /// Decodes the tiles referenced by a grid derived-image item.
/// </summary> /// </summary>
/// <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>
/// <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)
{ {
List<uint> linked = this.itemLinks.First(l => l.SourceId == gridItem.Id).DestinationIds; List<uint> linked = this.itemLinks.First(
link => link.Type == Heif4CharCode.Dimg && link.SourceId == gridItem.Id).DestinationIds;
// Each compressed tile decoder returns an owned Image. Keep every tile alive until
// the final grid has copied its pixels, then dispose all intermediates together.
using DisposableList<Image<TPixel>> gridTiles = new(linked.Count); using DisposableList<Image<TPixel>> gridTiles = new(linked.Count);
foreach (uint id in linked) foreach (uint id in linked)
{ {
HeifItem? item = this.items.FirstOrDefault(item => item.Id == id); HeifItem? item = this.items.FirstOrDefault(item => item.Id == id);
if (item != null) if (item is not null)
{ {
IHeifItemDecoder<TPixel>? decoder = HeifCompressionFactory.GetDecoder<TPixel>(item.Type); IHeifItemDecoder<TPixel>? decoder = HeifCompressionFactory.GetDecoder<TPixel>(item.Type);
if (decoder != null) if (decoder is not null)
{ {
this.CompressionMethod = decoder.CompressionMethod; this.CompressionMethod = decoder.CompressionMethod;
IMemoryOwner<byte> itemMemory = this.buffers[item.Id]; IMemoryOwner<byte> itemMemory = this.buffers[item.Id];

9
src/ImageSharp/Formats/Heif/HeifCompressionFactory.cs

@ -6,13 +6,16 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Heif; namespace SixLabors.ImageSharp.Formats.Heif;
/// <summary> /// <summary>
/// Factory for item decoders inside the HEIF container format. /// Selects the still-image decoder for a compressed HEIF image item.
/// </summary> /// </summary>
internal class HeifCompressionFactory internal static class HeifCompressionFactory
{ {
/// <summary> /// <summary>
/// Get a decoder implementation. /// Gets a decoder for the specified compressed image item type.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The destination pixel type.</typeparam>
/// <param name="type">The image item type.</param>
/// <returns>A matching item decoder, or <see langword="null"/> when the item type is not supported.</returns>
public static IHeifItemDecoder<TPixel>? GetDecoder<TPixel>(Heif4CharCode type) public static IHeifItemDecoder<TPixel>? GetDecoder<TPixel>(Heif4CharCode type)
where TPixel : unmanaged, IPixel<TPixel> => type switch where TPixel : unmanaged, IPixel<TPixel> => type switch
{ {

16
src/ImageSharp/Formats/Heif/HeifCompressionMethod.cs

@ -4,42 +4,42 @@
namespace SixLabors.ImageSharp.Formats.Heif; namespace SixLabors.ImageSharp.Formats.Heif;
/// <summary> /// <summary>
/// Compression algorithms possible inside an HEIF (High Efficiency Image Format) based file. /// Identifies the compression method used by a coded image item in a HEIF file.
/// </summary> /// </summary>
public enum HeifCompressionMethod public enum HeifCompressionMethod
{ {
/// <summary> /// <summary>
/// High Efficiency Video Coding /// High Efficiency Video Coding (HEVC).
/// </summary> /// </summary>
Hevc, Hevc,
/// <summary> /// <summary>
/// Legact JPEG /// Legacy JPEG coding.
/// </summary> /// </summary>
LegacyJpeg, LegacyJpeg,
/// <summary> /// <summary>
/// JPEG 2000 /// JPEG 2000 coding.
/// </summary> /// </summary>
Jpeg2000, Jpeg2000,
/// <summary> /// <summary>
/// JPEG-XR /// JPEG XR coding.
/// </summary> /// </summary>
JpegXR, JpegXR,
/// <summary> /// <summary>
/// JPEG-XS /// JPEG XS coding.
/// </summary> /// </summary>
JpegXS, JpegXS,
/// <summary> /// <summary>
/// AOMedia's Video 1 coding /// AOMedia Video 1 (AV1) coding.
/// </summary> /// </summary>
Av1, Av1,
/// <summary> /// <summary>
/// Advanced Video Coding /// Advanced Video Coding (AVC).
/// </summary> /// </summary>
Avc, Avc,
} }

26
src/ImageSharp/Formats/Heif/HeifConstants.cs

@ -10,6 +10,9 @@ namespace SixLabors.ImageSharp.Formats.Heif;
/// </summary> /// </summary>
internal static class HeifConstants internal static class HeifConstants
{ {
/// <summary>
/// The HEIC still-image brand written by the encoder.
/// </summary>
public const Heif4CharCode HeicBrand = Heif4CharCode.Heic; public const Heif4CharCode HeicBrand = Heif4CharCode.Heic;
/// <summary> /// <summary>
@ -22,8 +25,20 @@ internal static class HeifConstants
/// </summary> /// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "heic", "heif", "hif", "avif" }; public static readonly IEnumerable<string> FileExtensions = new[] { "heic", "heif", "hif", "avif" };
/// <summary>
/// Determines whether a file-type box describes a supported still-image container.
/// </summary>
/// <param name="boxContent">
/// The file-type box payload, beginning with the major brand and minor version and followed by compatible brands.
/// </param>
/// <returns>
/// <see langword="true"/> when the major brand is a supported still-image brand, or when an otherwise unknown
/// major brand declares a supported compatible still-image brand; otherwise, <see langword="false"/>.
/// </returns>
public static bool IsSupportedFileType(ReadOnlySpan<byte> boxContent) public static bool IsSupportedFileType(ReadOnlySpan<byte> boxContent)
{ {
// Every brand is a four-character code. The payload must contain the major brand and minor version before
// any compatible brands, otherwise accepting a partial trailing code could produce a false detection.
if (boxContent.Length < 8 || (boxContent.Length & 3) != 0) if (boxContent.Length < 8 || (boxContent.Length & 3) != 0)
{ {
return false; return false;
@ -32,6 +47,7 @@ internal static class HeifConstants
Heif4CharCode majorBrand = (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(boxContent); Heif4CharCode majorBrand = (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(boxContent);
if (IsSequenceBrand(majorBrand)) if (IsSequenceBrand(majorBrand))
{ {
// Sequence major brands describe timed image sequences, which the still-image decoder cannot expose.
return false; return false;
} }
@ -53,6 +69,11 @@ internal static class HeifConstants
return false; return false;
} }
/// <summary>
/// Determines whether <paramref name="brand"/> identifies a still-image container supported by this codec.
/// </summary>
/// <param name="brand">The registered file-type brand.</param>
/// <returns><see langword="true"/> when the brand identifies a supported still-image container.</returns>
private static bool IsSupportedStillImageBrand(Heif4CharCode brand) private static bool IsSupportedStillImageBrand(Heif4CharCode brand)
=> brand is Heif4CharCode.Heic => brand is Heif4CharCode.Heic
or Heif4CharCode.Heix or Heif4CharCode.Heix
@ -60,6 +81,11 @@ internal static class HeifConstants
or Heif4CharCode.Avif or Heif4CharCode.Avif
or Heif4CharCode.Jpeg; or Heif4CharCode.Jpeg;
/// <summary>
/// Determines whether <paramref name="brand"/> identifies a timed image sequence.
/// </summary>
/// <param name="brand">The registered file-type brand.</param>
/// <returns><see langword="true"/> when the brand identifies a timed image sequence.</returns>
private static bool IsSequenceBrand(Heif4CharCode brand) private static bool IsSequenceBrand(Heif4CharCode brand)
=> brand is Heif4CharCode.Hevc => brand is Heif4CharCode.Hevc
or Heif4CharCode.Hevx or Heif4CharCode.Hevx

3
src/ImageSharp/Formats/Heif/HeifDecoder.cs

@ -10,6 +10,9 @@ namespace SixLabors.ImageSharp.Formats.Heif;
/// </summary> /// </summary>
public sealed class HeifDecoder : ImageDecoder public sealed class HeifDecoder : ImageDecoder
{ {
/// <summary>
/// Initializes a new instance of the <see cref="HeifDecoder"/> class.
/// </summary>
private HeifDecoder() private HeifDecoder()
{ {
} }

754
src/ImageSharp/Formats/Heif/HeifDecoderCore.cs

File diff suppressed because it is too large

194
src/ImageSharp/Formats/Heif/HeifEncoderCore.cs

@ -41,16 +41,16 @@ internal sealed class HeifEncoderCore
/// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param> /// <param name="image">The <see cref="ImageFrame{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param> /// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
/// <param name="cancellationToken">The token to request cancellation.</param> /// <param name="cancellationToken">The token to request cancellation.</param>
public async void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken) public void Encode<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Guard.NotNull(image, nameof(image)); Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
byte[] pixels = await CompressPixels(image, cancellationToken); byte[] pixels = CompressPixels(image, cancellationToken);
List<HeifItem> items = new(); List<HeifItem> items = new();
List<HeifItemLink> links = new(); List<HeifItemLink> links = new();
GenerateItems(image, pixels, items, links); GenerateItems(image, pixels, items);
// Write out the generated header and pixels. // Write out the generated header and pixels.
this.WriteFileTypeBox(stream); this.WriteFileTypeBox(stream);
@ -62,22 +62,33 @@ internal sealed class HeifEncoderCore
meta.CompressionMethod = HeifCompressionMethod.LegacyJpeg; meta.CompressionMethod = HeifCompressionMethod.LegacyJpeg;
} }
private static void GenerateItems<TPixel>(Image<TPixel> image, byte[] pixels, List<HeifItem> items, List<HeifItemLink> links) /// <summary>
/// Builds the item declarations and relationships for the encoded image payload.
/// </summary>
/// <typeparam name="TPixel">The source pixel format.</typeparam>
/// <param name="image">The source image.</param>
/// <param name="pixels">The encoded primary-item payload.</param>
/// <param name="items">The destination item collection.</param>
private static void GenerateItems<TPixel>(Image<TPixel> image, byte[] pixels, List<HeifItem> items)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
HeifItem primaryItem = new(Heif4CharCode.Jpeg, 1u); HeifItem primaryItem = new(Heif4CharCode.Jpeg, 1u);
primaryItem.DataLocations.Add(new HeifLocation(HeifLocationOffsetOrigin.ItemDataOffset, 0L, 0L, pixels.LongLength)); primaryItem.DataLocations.Add(new HeifLocation(HeifLocationOffsetOrigin.FileOffset, 0L, 0L, pixels.LongLength));
primaryItem.BitsPerPixel = 24; primaryItem.BitsPerPixel = 24;
primaryItem.ChannelCount = 3; primaryItem.ChannelCount = 3;
primaryItem.SetExtent(image.Size); primaryItem.SetExtent(image.Size);
items.Add(primaryItem); items.Add(primaryItem);
// Create a fake thumbnail, to make our own Decoder happy. // No item relationship is emitted until the writer has a distinct derived image,
HeifItemLink thumbnail = new(Heif4CharCode.Thmb, 1u); // thumbnail, auxiliary image, or metadata item to reference.
thumbnail.DestinationIds.Add(1u);
links.Add(thumbnail);
} }
/// <summary>
/// Writes an eight-byte ISO BMFF basic box header with a placeholder size.
/// </summary>
/// <param name="buffer">The destination beginning at the box size field.</param>
/// <param name="type">The box four-character code.</param>
/// <returns>The number of header bytes written.</returns>
private static int WriteBoxHeader(Span<byte> buffer, Heif4CharCode type) private static int WriteBoxHeader(Span<byte> buffer, Heif4CharCode type)
{ {
int bytesWritten = 0; int bytesWritten = 0;
@ -89,6 +100,14 @@ internal sealed class HeifEncoderCore
return bytesWritten; return bytesWritten;
} }
/// <summary>
/// Writes a 12-byte ISO BMFF full-box header with a placeholder size.
/// </summary>
/// <param name="buffer">The destination beginning at the box size field.</param>
/// <param name="type">The box four-character code.</param>
/// <param name="version">The full-box syntax version.</param>
/// <param name="flags">The 24-bit full-box flags value.</param>
/// <returns>The number of header bytes written.</returns>
private static int WriteBoxHeader(Span<byte> buffer, Heif4CharCode type, byte version, uint flags) private static int WriteBoxHeader(Span<byte> buffer, Heif4CharCode type, byte version, uint flags)
{ {
int bytesWritten = 0; int bytesWritten = 0;
@ -97,7 +116,8 @@ internal sealed class HeifEncoderCore
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)type); BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)type);
bytesWritten += 4; bytesWritten += 4;
// Layout in memory is 4 bytes, 1 version byte followed by 3 flag bytes. // Writing the 24-bit flags as a big-endian 32-bit value establishes the three flag bytes, after which the
// version overwrites the leading byte to form the full-box version-and-flags word.
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], flags); BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], flags);
buffer[bytesWritten] = version; buffer[bytesWritten] = version;
bytesWritten += 4; bytesWritten += 4;
@ -105,6 +125,10 @@ internal sealed class HeifEncoderCore
return bytesWritten; return bytesWritten;
} }
/// <summary>
/// Writes the major brand, minor version, and compatible brands for the current HEIF output.
/// </summary>
/// <param name="stream">The destination stream.</param>
private void WriteFileTypeBox(Stream stream) private void WriteFileTypeBox(Stream stream)
{ {
Span<byte> buffer = stackalloc byte[24]; Span<byte> buffer = stackalloc byte[24];
@ -122,6 +146,12 @@ internal sealed class HeifEncoderCore
stream.Write(buffer); stream.Write(buffer);
} }
/// <summary>
/// Writes the metadata box containing item declarations, relationships, properties, and file locations.
/// </summary>
/// <param name="items">The declared image and metadata items.</param>
/// <param name="links">The typed relationships between items.</param>
/// <param name="stream">The destination stream positioned after the file-type box.</param>
private void WriteMetadataBox(List<HeifItem> items, List<HeifItemLink> links, Stream stream) private void WriteMetadataBox(List<HeifItem> items, List<HeifItemLink> links, Stream stream)
{ {
using AutoExpandingMemory<byte> memory = new(this.configuration, 0x1000); using AutoExpandingMemory<byte> memory = new(this.configuration, 0x1000);
@ -130,16 +160,34 @@ internal sealed class HeifEncoderCore
bytesWritten += WriteHandlerBox(memory, bytesWritten); bytesWritten += WriteHandlerBox(memory, bytesWritten);
bytesWritten += WritePrimaryItemBox(memory, bytesWritten); bytesWritten += WritePrimaryItemBox(memory, bytesWritten);
bytesWritten += WriteItemInfoBox(memory, bytesWritten, items); bytesWritten += WriteItemInfoBox(memory, bytesWritten, items);
bytesWritten += WriteItemReferenceBox(memory, bytesWritten, items, links); if (links.Count > 0)
{
// iref is optional and has no meaning without at least one typed item relationship.
bytesWritten += WriteItemReferenceBox(memory, bytesWritten, items, links);
}
bytesWritten += WriteItemPropertiesBox(memory, bytesWritten, items); bytesWritten += WriteItemPropertiesBox(memory, bytesWritten, items);
bytesWritten += WriteItemDataBox(memory, bytesWritten);
bytesWritten += WriteItemLocationBox(memory, bytesWritten, items); // iloc needs the absolute mdat payload position, but that position depends on the final meta length. Emit it
// once to establish the stable box size, calculate the following mdat position, then patch the same bytes.
int itemLocationOffset = bytesWritten;
bytesWritten += WriteItemLocationBox(memory, bytesWritten, items, 0);
// The mdat payload immediately follows the completed meta box and its own eight-byte header.
long mediaDataOffset = checked(stream.Position + bytesWritten + 8);
WriteItemLocationBox(memory, itemLocationOffset, items, mediaDataOffset);
buffer = memory.GetSpan(bytesWritten); buffer = memory.GetSpan(bytesWritten);
BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten); BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten);
stream.Write(buffer); stream.Write(buffer);
} }
/// <summary>
/// Writes the picture metadata handler box.
/// </summary>
/// <param name="memory">The expanding metadata buffer.</param>
/// <param name="memoryOffset">The destination offset within the metadata box.</param>
/// <returns>The complete handler-box length.</returns>
private static int WriteHandlerBox(AutoExpandingMemory<byte> memory, int memoryOffset) private static int WriteHandlerBox(AutoExpandingMemory<byte> memory, int memoryOffset)
{ {
Span<byte> buffer = memory.GetSpan(memoryOffset, 33); Span<byte> buffer = memory.GetSpan(memoryOffset, 33);
@ -157,6 +205,12 @@ internal sealed class HeifEncoderCore
return bytesWritten; return bytesWritten;
} }
/// <summary>
/// Writes the identifier of the primary presentation item.
/// </summary>
/// <param name="memory">The expanding metadata buffer.</param>
/// <param name="memoryOffset">The destination offset within the metadata box.</param>
/// <returns>The complete primary-item-box length.</returns>
private static int WritePrimaryItemBox(AutoExpandingMemory<byte> memory, int memoryOffset) private static int WritePrimaryItemBox(AutoExpandingMemory<byte> memory, int memoryOffset)
{ {
Span<byte> buffer = memory.GetSpan(memoryOffset, 14); Span<byte> buffer = memory.GetSpan(memoryOffset, 14);
@ -168,6 +222,13 @@ internal sealed class HeifEncoderCore
return bytesWritten; return bytesWritten;
} }
/// <summary>
/// Writes the item-information box and one version-two entry for each item.
/// </summary>
/// <param name="memory">The expanding metadata buffer.</param>
/// <param name="memoryOffset">The destination offset within the metadata box.</param>
/// <param name="items">The items to declare.</param>
/// <returns>The complete item-information-box length.</returns>
private static int WriteItemInfoBox(AutoExpandingMemory<byte> memory, int memoryOffset, List<HeifItem> items) private static int WriteItemInfoBox(AutoExpandingMemory<byte> memory, int memoryOffset, List<HeifItem> items)
{ {
Span<byte> buffer = memory.GetSpan(memoryOffset, 14 + (items.Count * 21)); Span<byte> buffer = memory.GetSpan(memoryOffset, 14 + (items.Count * 21));
@ -193,6 +254,14 @@ internal sealed class HeifEncoderCore
return bytesWritten; return bytesWritten;
} }
/// <summary>
/// Writes typed item-reference child boxes using 16-bit item identifiers.
/// </summary>
/// <param name="memory">The expanding metadata buffer.</param>
/// <param name="memoryOffset">The destination offset within the metadata box.</param>
/// <param name="items">The declared items used to size the destination.</param>
/// <param name="links">The relationships to write.</param>
/// <returns>The complete item-reference-box length.</returns>
private static int WriteItemReferenceBox(AutoExpandingMemory<byte> memory, int memoryOffset, List<HeifItem> items, List<HeifItemLink> links) private static int WriteItemReferenceBox(AutoExpandingMemory<byte> memory, int memoryOffset, List<HeifItem> items, List<HeifItemLink> links)
{ {
Span<byte> buffer = memory.GetSpan(memoryOffset, 12 + (links.Count * (12 + (items.Count * 2)))); Span<byte> buffer = memory.GetSpan(memoryOffset, 12 + (links.Count * (12 + (items.Count * 2))));
@ -218,12 +287,19 @@ internal sealed class HeifEncoderCore
return bytesWritten; return bytesWritten;
} }
/// <summary>
/// Writes spatial-extent properties and their one-based item associations.
/// </summary>
/// <param name="memory">The expanding metadata buffer.</param>
/// <param name="memoryOffset">The destination offset within the metadata box.</param>
/// <param name="items">The items whose dimensions are written and associated.</param>
/// <returns>The complete item-properties-box length.</returns>
private static int WriteItemPropertiesBox(AutoExpandingMemory<byte> memory, int memoryOffset, List<HeifItem> items) private static int WriteItemPropertiesBox(AutoExpandingMemory<byte> memory, int memoryOffset, List<HeifItem> items)
{ {
Span<byte> buffer = memory.GetSpan(memoryOffset, 20); Span<byte> buffer = memory.GetSpan(memoryOffset, 20);
int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.Iprp); int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.Iprp);
// Write 'ipco' box // ipco order defines the one-based property indices written later in ipma.
int ipcoLengthOffset = bytesWritten; int ipcoLengthOffset = bytesWritten;
bytesWritten += WriteBoxHeader(buffer[bytesWritten..], Heif4CharCode.Ipco); bytesWritten += WriteBoxHeader(buffer[bytesWritten..], Heif4CharCode.Ipco);
foreach (HeifItem item in items) foreach (HeifItem item in items)
@ -236,7 +312,7 @@ internal sealed class HeifEncoderCore
int propertyIndexSize = largePropertyIndex ? 2 : 1; int propertyIndexSize = largePropertyIndex ? 2 : 1;
buffer = memory.GetSpan(memoryOffset, bytesWritten + 16 + ((3 + propertyIndexSize) * items.Count)); buffer = memory.GetSpan(memoryOffset, bytesWritten + 16 + ((3 + propertyIndexSize) * items.Count));
// Write 'ipma' box // ipma uses a 15-bit index only when the property table cannot fit in the compact seven-bit form.
int ipmaLengthOffset = bytesWritten; int ipmaLengthOffset = bytesWritten;
bytesWritten += WriteBoxHeader(buffer[bytesWritten..], Heif4CharCode.Ipma, 0, largePropertyIndex ? 1U : 0U); bytesWritten += WriteBoxHeader(buffer[bytesWritten..], Heif4CharCode.Ipma, 0, largePropertyIndex ? 1U : 0U);
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)items.Count); BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)items.Count);
@ -267,6 +343,13 @@ internal sealed class HeifEncoderCore
return bytesWritten; return bytesWritten;
} }
/// <summary>
/// Writes an item's display width and height as an image-spatial-extents property.
/// </summary>
/// <param name="memory">The expanding metadata buffer.</param>
/// <param name="memoryOffset">The destination offset within the property container.</param>
/// <param name="item">The item whose extent is written.</param>
/// <returns>The complete image-spatial-extents-box length.</returns>
private static int WriteSpatialExtentPropertyBox(AutoExpandingMemory<byte> memory, int memoryOffset, HeifItem item) private static int WriteSpatialExtentPropertyBox(AutoExpandingMemory<byte> memory, int memoryOffset, HeifItem item)
{ {
Span<byte> buffer = memory.GetSpan(memoryOffset, 20); Span<byte> buffer = memory.GetSpan(memoryOffset, 20);
@ -280,45 +363,58 @@ internal sealed class HeifEncoderCore
return bytesWritten; return bytesWritten;
} }
private static int WriteItemDataBox(AutoExpandingMemory<byte> memory, int memoryOffset) /// <summary>
{ /// Writes version-one file-relative locations for every ordered item extent.
Span<byte> buffer = memory.GetSpan(memoryOffset, 10); /// </summary>
int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.Idat); /// <param name="memory">The expanding metadata buffer.</param>
/// <param name="memoryOffset">The destination offset within the metadata box.</param>
BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten); /// <param name="items">The items and relative payload extents to locate.</param>
return bytesWritten; /// <param name="mediaDataOffset">The absolute stream offset of the media-data payload.</param>
} /// <returns>The complete item-location-box length.</returns>
private static int WriteItemLocationBox(AutoExpandingMemory<byte> memory, int memoryOffset, List<HeifItem> items, long mediaDataOffset)
private static int WriteItemLocationBox(AutoExpandingMemory<byte> memory, int memoryOffset, List<HeifItem> items)
{ {
Span<byte> buffer = memory.GetSpan(memoryOffset, 30 + (items.Count * 8)); int extentCount = items.Sum(item => item.DataLocations.Count);
Span<byte> buffer = memory.GetSpan(memoryOffset, 16 + (items.Count * 8) + (extentCount * 12));
int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.Iloc, 1, 0); int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.Iloc, 1, 0);
buffer[bytesWritten++] = 0x44;
// The high and low nibbles select eight-byte offsets and four-byte lengths. Base offsets and extent indices
// are omitted, because every generated extent is written as one absolute file offset into mdat.
buffer[bytesWritten++] = 0x84;
buffer[bytesWritten++] = 0; buffer[bytesWritten++] = 0;
BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], 1); BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)items.Count);
bytesWritten += 2;
BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)items[0].Id);
bytesWritten += 2; bytesWritten += 2;
for (int i = 0; i < 4; i++) foreach (HeifItem item in items)
{ {
buffer[bytesWritten++] = 0; BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)item.Id);
} bytesWritten += 2;
IEnumerable<HeifLocation> itemLocs = items.SelectMany(item => item.DataLocations).Where(loc => loc != null); // Version 1 stores twelve reserved bits followed by the four-bit construction method.
BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)itemLocs.Count()); BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)HeifLocationOffsetOrigin.FileOffset);
bytesWritten += 2; bytesWritten += 2;
foreach (HeifLocation loc in itemLocs) BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], 0);
{ bytesWritten += 2;
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)loc.Offset); BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)item.DataLocations.Count);
bytesWritten += 4; bytesWritten += 2;
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)loc.Length); foreach (HeifLocation loc in item.DataLocations)
bytesWritten += 4; {
// Generated locations are relative to the mdat payload until the enclosing meta size is known.
long absoluteOffset = checked(mediaDataOffset + loc.BaseOffset + loc.Offset);
BinaryPrimitives.WriteUInt64BigEndian(buffer[bytesWritten..], (ulong)absoluteOffset);
bytesWritten += 8;
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)loc.Length);
bytesWritten += 4;
}
} }
BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten); BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten);
return bytesWritten; return bytesWritten;
} }
/// <summary>
/// Writes the encoded primary-item bytes in a media-data box.
/// </summary>
/// <param name="data">The encoded item payload.</param>
/// <param name="stream">The destination stream.</param>
private void WriteMediaDataBox(Span<byte> data, Stream stream) private void WriteMediaDataBox(Span<byte> data, Stream stream)
{ {
Span<byte> buf = stackalloc byte[12]; Span<byte> buf = stackalloc byte[12];
@ -329,7 +425,14 @@ internal sealed class HeifEncoderCore
stream.Write(data); stream.Write(data);
} }
private static async Task<byte[]> CompressPixels<TPixel>(Image<TPixel> image, CancellationToken cancellationToken) /// <summary>
/// Encodes the source pixels as the current legacy JPEG item payload.
/// </summary>
/// <typeparam name="TPixel">The source pixel format.</typeparam>
/// <param name="image">The source image.</param>
/// <param name="cancellationToken">The token used to cancel payload encoding.</param>
/// <returns>The encoded JPEG item bytes.</returns>
private static byte[] CompressPixels<TPixel>(Image<TPixel> image, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using MemoryStream stream = new(); using MemoryStream stream = new();
@ -337,7 +440,10 @@ internal sealed class HeifEncoderCore
{ {
ColorType = JpegColorType.YCbCrRatio420 ColorType = JpegColorType.YCbCrRatio420
}; };
await image.SaveAsJpegAsync(stream, encoder, cancellationToken);
// ImageEncoder is a synchronous contract. Wait for the cancellable JPEG operation
// so HEIF encoding cannot return while its temporary item payload is still being produced.
image.SaveAsJpegAsync(stream, encoder, cancellationToken).GetAwaiter().GetResult();
return stream.ToArray(); return stream.ToArray();
} }
} }

3
src/ImageSharp/Formats/Heif/HeifFormat.cs

@ -8,6 +8,9 @@ namespace SixLabors.ImageSharp.Formats.Heif;
/// </summary> /// </summary>
public sealed class HeifFormat : IImageFormat<HeifMetadata> public sealed class HeifFormat : IImageFormat<HeifMetadata>
{ {
/// <summary>
/// Initializes a new instance of the <see cref="HeifFormat"/> class.
/// </summary>
private HeifFormat() private HeifFormat()
{ {
} }

11
src/ImageSharp/Formats/Heif/HeifImageFormatDetector.cs

@ -18,11 +18,18 @@ public sealed class HeifImageFormatDetector : IImageFormatDetector
public bool TryDetectFormat(ReadOnlySpan<byte> header, [NotNullWhen(true)] out IImageFormat? format) public bool TryDetectFormat(ReadOnlySpan<byte> header, [NotNullWhen(true)] out IImageFormat? format)
{ {
format = IsSupportedFileFormat(header) ? HeifFormat.Instance : null; format = IsSupportedFileFormat(header) ? HeifFormat.Instance : null;
return format != null; return format is not null;
} }
/// <summary>
/// Determines whether the available header begins with a supported still-image HEIF file-type box.
/// </summary>
/// <param name="header">The fixed-size header prefix supplied by format detection.</param>
/// <returns><see langword="true"/> when the prefix declares a supported still-image brand; otherwise, <see langword="false"/>.</returns>
private static bool IsSupportedFileFormat(ReadOnlySpan<byte> header) private static bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
{ {
// Detection is intentionally limited to files beginning with ftyp. Other valid top-level boxes can precede
// ftyp in ISO BMFF, but scanning arbitrary input is outside the fixed-header detector contract.
if (header.Length < 16 || BinaryPrimitives.ReadUInt32BigEndian(header[4..]) != (uint)Heif4CharCode.Ftyp) if (header.Length < 16 || BinaryPrimitives.ReadUInt32BigEndian(header[4..]) != (uint)Heif4CharCode.Ftyp)
{ {
return false; return false;
@ -34,6 +41,8 @@ public sealed class HeifImageFormatDetector : IImageFormatDetector
return false; return false;
} }
// HeaderSize may expose only a prefix of a longer ftyp box. Whole compatible-brand codes in that prefix are
// sufficient for detection; the decoder validates the complete box before reading the rest of the container.
int availableContentLength = (int)Math.Min(boxSize - 8, (uint)header.Length - 8); int availableContentLength = (int)Math.Min(boxSize - 8, (uint)header.Length - 8);
availableContentLength &= ~3; availableContentLength &= ~3;
return HeifConstants.IsSupportedFileType(header.Slice(8, availableContentLength)); return HeifConstants.IsSupportedFileType(header.Slice(8, availableContentLength));

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

@ -4,8 +4,10 @@
namespace SixLabors.ImageSharp.Formats.Heif; namespace SixLabors.ImageSharp.Formats.Heif;
/// <summary> /// <summary>
/// Provides definition for a HEIF Item. /// Describes a metadata or image item in a HEIF still-image container.
/// </summary> /// </summary>
/// <param name="type">The four-character item type.</param>
/// <param name="id">The item identifier used by locations, properties, and references.</param>
internal class HeifItem(Heif4CharCode type, uint id) internal class HeifItem(Heif4CharCode type, uint id)
{ {
/// <summary> /// <summary>
@ -92,5 +94,9 @@ internal class HeifItem(Heif4CharCode type, uint id)
} }
} }
/// <summary>
/// Returns the item type and identifier.
/// </summary>
/// <returns>The item type and identifier separated by a colon.</returns>
public override string ToString() => $"{this.Type}:{this.Id}"; public override string ToString() => $"{this.Type}:{this.Id}";
} }

2
src/ImageSharp/Formats/Heif/HeifItemLink.cs

@ -6,6 +6,8 @@ namespace SixLabors.ImageSharp.Formats.Heif;
/// <summary> /// <summary>
/// Link between <see cref="HeifItem"/> instances within the same HEIF file. /// Link between <see cref="HeifItem"/> instances within the same HEIF file.
/// </summary> /// </summary>
/// <param name="type">The four-character reference type.</param>
/// <param name="sourceId">The identifier of the item that owns the references.</param>
internal class HeifItemLink(Heif4CharCode type, uint sourceId) internal class HeifItemLink(Heif4CharCode type, uint sourceId)
{ {
/// <summary> /// <summary>

21
src/ImageSharp/Formats/Heif/HeifLocation.cs

@ -4,8 +4,12 @@
namespace SixLabors.ImageSharp.Formats.Heif; namespace SixLabors.ImageSharp.Formats.Heif;
/// <summary> /// <summary>
/// Location within the file of an <see cref="HeifItem"/>. /// Describes one contiguous extent of an item's encoded data.
/// </summary> /// </summary>
/// <param name="origin">The origin from which the base and extent offsets are measured.</param>
/// <param name="baseOffset">The item-location base offset.</param>
/// <param name="offset">The extent offset relative to the base offset.</param>
/// <param name="length">The length of the extent in bytes.</param>
internal class HeifLocation(HeifLocationOffsetOrigin origin, long baseOffset, long offset, long length) internal class HeifLocation(HeifLocationOffsetOrigin origin, long baseOffset, long offset, long length)
{ {
/// <summary> /// <summary>
@ -14,25 +18,26 @@ internal class HeifLocation(HeifLocationOffsetOrigin origin, long baseOffset, lo
public HeifLocationOffsetOrigin Origin { get; } = origin; public HeifLocationOffsetOrigin Origin { get; } = origin;
/// <summary> /// <summary>
/// Gets the base offset of this location. /// Gets the item-location base offset in bytes.
/// </summary> /// </summary>
public long BaseOffset { get; } = baseOffset; public long BaseOffset { get; } = baseOffset;
/// <summary> /// <summary>
/// Gets the offset of this location. /// Gets the extent offset relative to <see cref="BaseOffset"/> in bytes.
/// </summary> /// </summary>
public long Offset { get; } = offset; public long Offset { get; } = offset;
/// <summary> /// <summary>
/// Gets the length of this location. /// Gets the extent length in bytes.
/// </summary> /// </summary>
public long Length { get; } = length; public long Length { get; } = length;
/// <summary> /// <summary>
/// Gets the stream position of this location. /// Resolves the absolute stream position of this extent.
/// </summary> /// </summary>
/// <param name="positionOfMediaData">Stream position of the MediaData box.</param> /// <param name="positionOfMediaData">The absolute origin of the item-data payload.</param>
/// <param name="positionOfItem">Stream position of the previous box.</param> /// <param name="positionOfItem">The absolute origin of the referenced item payload.</param>
/// <returns>The absolute byte position of the extent in the input stream.</returns>
public long GetStreamPosition(long positionOfMediaData, long positionOfItem) => this.Origin switch public long GetStreamPosition(long positionOfMediaData, long positionOfItem) => this.Origin switch
{ {
HeifLocationOffsetOrigin.FileOffset => this.BaseOffset + this.Offset, HeifLocationOffsetOrigin.FileOffset => this.BaseOffset + this.Offset,
@ -40,8 +45,10 @@ internal class HeifLocation(HeifLocationOffsetOrigin origin, long baseOffset, lo
_ => positionOfItem + this.BaseOffset + this.Offset _ => positionOfItem + this.BaseOffset + this.Offset
}; };
/// <inheritdoc/>
public override int GetHashCode() => HashCode.Combine(this.Origin, this.Offset, this.Length, this.BaseOffset); public override int GetHashCode() => HashCode.Combine(this.Origin, this.Offset, this.Length, this.BaseOffset);
/// <inheritdoc/>
public override bool Equals(object? obj) public override bool Equals(object? obj)
{ {
if (obj is not HeifLocation other) if (obj is not HeifLocation other)

30
src/ImageSharp/Formats/Heif/HeifLocationComparer.cs

@ -3,22 +3,43 @@
namespace SixLabors.ImageSharp.Formats.Heif; namespace SixLabors.ImageSharp.Formats.Heif;
/// <summary>
/// Orders item extents by their resolved absolute stream position.
/// </summary>
internal class HeifLocationComparer : IComparer<HeifLocation> internal class HeifLocationComparer : IComparer<HeifLocation>
{ {
/// <summary>
/// The absolute origin of item-data-relative extents.
/// </summary>
private readonly long positionOfMediaData; private readonly long positionOfMediaData;
/// <summary>
/// The absolute origin of item-relative extents.
/// </summary>
private readonly long positionOfItem; private readonly long positionOfItem;
/// <summary>
/// Initializes a new instance of the <see cref="HeifLocationComparer"/> class.
/// </summary>
/// <param name="positionOfMediaData">The absolute origin of the item-data payload.</param>
/// <param name="positionOfItem">The absolute origin of the referenced item payload.</param>
public HeifLocationComparer(long positionOfMediaData, long positionOfItem) public HeifLocationComparer(long positionOfMediaData, long positionOfItem)
{ {
this.positionOfMediaData = positionOfMediaData; this.positionOfMediaData = positionOfMediaData;
this.positionOfItem = positionOfItem; this.positionOfItem = positionOfItem;
} }
/// <summary>
/// Compares two extents by their resolved absolute stream positions.
/// </summary>
/// <param name="x">The first extent.</param>
/// <param name="y">The second extent.</param>
/// <returns>A negative value when <paramref name="x"/> precedes <paramref name="y"/>, zero when their positions match, or a positive value otherwise.</returns>
public int Compare(HeifLocation? x, HeifLocation? y) public int Compare(HeifLocation? x, HeifLocation? y)
{ {
if (x == null) if (x is null)
{ {
if (y == null) if (y is null)
{ {
return 0; return 0;
} }
@ -26,7 +47,7 @@ internal class HeifLocationComparer : IComparer<HeifLocation>
return 1; return 1;
} }
if (y == null) if (y is null)
{ {
return -1; return -1;
} }
@ -34,6 +55,7 @@ internal class HeifLocationComparer : IComparer<HeifLocation>
long xPos = x.GetStreamPosition(this.positionOfMediaData, this.positionOfItem); long xPos = x.GetStreamPosition(this.positionOfMediaData, this.positionOfItem);
long yPos = y.GetStreamPosition(this.positionOfMediaData, this.positionOfItem); long yPos = y.GetStreamPosition(this.positionOfMediaData, this.positionOfItem);
return Math.Sign(xPos - yPos); // CompareTo avoids overflowing when valid 64-bit offsets lie near opposite numeric limits.
return xPos.CompareTo(yPos);
} }
} }

14
src/ImageSharp/Formats/Heif/HeifLocationOffsetOrigin.cs

@ -3,9 +3,23 @@
namespace SixLabors.ImageSharp.Formats.Heif; namespace SixLabors.ImageSharp.Formats.Heif;
/// <summary>
/// Identifies the origin used to resolve an item-location extent offset.
/// </summary>
internal enum HeifLocationOffsetOrigin internal enum HeifLocationOffsetOrigin
{ {
/// <summary>
/// The base and extent offsets are absolute file offsets.
/// </summary>
FileOffset = 0, FileOffset = 0,
/// <summary>
/// The base and extent offsets are relative to the item-data box payload.
/// </summary>
ItemDataOffset = 1, ItemDataOffset = 1,
/// <summary>
/// The base and extent offsets are relative to another item payload.
/// </summary>
ItemOffset = 2 ItemOffset = 2
} }

16
src/ImageSharp/Formats/Heif/IHeifItemDecoder.cs

@ -6,28 +6,28 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Heif; namespace SixLabors.ImageSharp.Formats.Heif;
/// <summary> /// <summary>
/// Decoder for a single <see cref="HeifItem"/>. /// Decodes the compressed payload of a single HEIF image item.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel type to use.</typeparam> /// <typeparam name="TPixel">The destination pixel type.</typeparam>
internal interface IHeifItemDecoder<TPixel> internal interface IHeifItemDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
/// <summary> /// <summary>
/// Gets the type of item this decoder can decode. /// Gets the image item type decoded by this implementation.
/// </summary> /// </summary>
public Heif4CharCode Type { get; } public Heif4CharCode Type { get; }
/// <summary> /// <summary>
/// Gets the <see cref="HeifCompressionMethod"/> tis decoder uses. /// Gets the compression method used by the image item.
/// </summary> /// </summary>
public HeifCompressionMethod CompressionMethod { get; } public HeifCompressionMethod CompressionMethod { get; }
/// <summary> /// <summary>
/// Decode the specified item, given encoded data. /// Decodes the compressed payload of an image item.
/// </summary> /// </summary>
/// <param name="configuration">The configuration to used.</param> /// <param name="configuration">The configuration that supplies memory allocation and codec services.</param>
/// <param name="item">The item to decode.</param> /// <param name="item">The HEIF item whose encoded payload is being decoded.</param>
/// <param name="data">The encoded data.</param> /// <param name="data">The encoded image payload.</param>
/// <returns>The decoded image.</returns> /// <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);
} }

13
src/ImageSharp/Formats/Heif/JpegHeifItemDecoder.cs

@ -6,24 +6,29 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Heif; namespace SixLabors.ImageSharp.Formats.Heif;
/// <summary> /// <summary>
/// Decoder for a single <see cref="HeifItem"/> into a JPEG image. /// Decodes a single JPEG-coded HEIF image item.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The destination pixel type.</typeparam>
internal class JpegHeifItemDecoder<TPixel> : IHeifItemDecoder<TPixel> internal class JpegHeifItemDecoder<TPixel> : IHeifItemDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
/// <summary> /// <summary>
/// Gets the item type this decoder decodes, which is <see cref="Heif4CharCode.Jpeg"/>. /// Gets the JPEG-coded image item type.
/// </summary> /// </summary>
public Heif4CharCode Type => Heif4CharCode.Jpeg; public Heif4CharCode Type => Heif4CharCode.Jpeg;
/// <summary> /// <summary>
/// Gets the compression method this doceder uses, which is <see cref="HeifCompressionMethod.LegacyJpeg"/>. /// Gets the legacy JPEG compression method.
/// </summary> /// </summary>
public HeifCompressionMethod CompressionMethod => HeifCompressionMethod.LegacyJpeg; public HeifCompressionMethod CompressionMethod => HeifCompressionMethod.LegacyJpeg;
/// <summary> /// <summary>
/// Decode the specified item as JPEG. /// Decodes the encoded JPEG payload of an image item.
/// </summary> /// </summary>
/// <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>
/// <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)
{ {
Image<TPixel> image = Image.Load<TPixel>(data); Image<TPixel> image = Image.Load<TPixel>(data);

Loading…
Cancel
Save