Browse Source

Merge branch 'heic-support' into avif-in-heif

pull/2633/head
Ynse Hoornenborg 3 years ago
parent
commit
b27765f770
  1. 98
      src/ImageSharp/Formats/Heif/Heif4CharCode.cs
  2. 13
      src/ImageSharp/Formats/Heif/Heif4CharCode.tt
  3. 2
      src/ImageSharp/Formats/Heif/HeifConstants.cs
  4. 246
      src/ImageSharp/Formats/Heif/HeifDecoderCore.cs
  5. 42
      src/ImageSharp/Formats/Heif/HeifEncoderCore.cs
  6. 4
      src/ImageSharp/Formats/Heif/HeifImageFormatDetector.cs
  7. 2
      src/ImageSharp/Formats/Heif/HeifItem.cs
  8. 2
      src/ImageSharp/Formats/Heif/HeifItemLink.cs
  9. 2
      src/ImageSharp/Formats/Heif/HeifLocation.cs

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

@ -10,38 +10,38 @@ namespace SixLabors.ImageSharp.Formats.Heif;
/// <summary> /// <summary>
/// Supported 4 character codes for use in HEIF images. /// Supported 4 character codes for use in HEIF images.
/// </summary> /// </summary>
[GeneratedCode("T4", "")] [GeneratedCode("TextTemplateFileGenerator", "")]
public enum Heif4CharCode : uint public enum Heif4CharCode : uint
{ {
/// <summary> /// <summary>
/// File Type. /// File Type.
/// </summary> /// </summary>
ftyp = 0x66747970U, Ftyp = 0x66747970U,
/// <summary> /// <summary>
/// Metadata. /// Metadata.
/// </summary> /// </summary>
meta = 0x6D657461U, Meta = 0x6D657461U,
/// <summary> /// <summary>
/// Media Data. /// Media Data.
/// </summary> /// </summary>
mdat = 0x6D646174U, Mdat = 0x6D646174U,
/// <summary> /// <summary>
/// Item Information Entry. /// Item Information Entry.
/// </summary> /// </summary>
infe = 0x696E6665U, Infe = 0x696E6665U,
/// <summary> /// <summary>
/// Item Data. /// Item Data.
/// </summary> /// </summary>
idat = 0x69646174U, Idat = 0x69646174U,
/// <summary> /// <summary>
/// Item Location. /// Item Location.
/// </summary> /// </summary>
iloc = 0x696C6F63U, Iloc = 0x696C6F63U,
/// <summary> /// <summary>
/// EXIF metadata. /// EXIF metadata.
@ -51,202 +51,202 @@ public enum Heif4CharCode : uint
/// <summary> /// <summary>
/// Data Reference. /// Data Reference.
/// </summary> /// </summary>
dref = 0x64726566U, Dref = 0x64726566U,
/// <summary> /// <summary>
/// Primary Item. /// Primary Item.
/// </summary> /// </summary>
pitm = 0x7069746DU, Pitm = 0x7069746DU,
/// <summary> /// <summary>
/// Item Spatial Extent. /// Item Spatial Extent.
/// </summary> /// </summary>
ispe = 0x69737065U, Ispe = 0x69737065U,
/// <summary> /// <summary>
/// Alternative text. /// Alternative text.
/// </summary> /// </summary>
altt = 0x616C7474U, Altt = 0x616C7474U,
/// <summary> /// <summary>
/// Colour information. /// Colour information.
/// </summary> /// </summary>
colr = 0x636F6C72U, Colr = 0x636F6C72U,
/// <summary> /// <summary>
/// HVC configuration. /// HVC configuration.
/// </summary> /// </summary>
hvcC = 0x68766343U, HvcC = 0x68766343U,
/// <summary> /// <summary>
/// AV1 configuration. /// AV1 configuration.
/// </summary> /// </summary>
av1C = 0x61763143U, Av1C = 0x61763143U,
/// <summary> /// <summary>
/// Image Mirror. /// Image Mirror.
/// </summary> /// </summary>
imir = 0x696D6972U, Imir = 0x696D6972U,
/// <summary> /// <summary>
/// Image Rotation. /// Image Rotation.
/// </summary> /// </summary>
irot = 0x69726F74U, Irot = 0x69726F74U,
/// <summary> /// <summary>
/// Image Scaling. /// Image Scaling.
/// </summary> /// </summary>
iscl = 0x6973636CU, Iscl = 0x6973636CU,
/// <summary> /// <summary>
/// Pixel Aspect Ratio. /// Pixel Aspect Ratio.
/// </summary> /// </summary>
pasp = 0x70617370U, Pasp = 0x70617370U,
/// <summary> /// <summary>
/// Pixel Information. /// Pixel Information.
/// </summary> /// </summary>
pixi = 0x70697869U, Pixi = 0x70697869U,
/// <summary> /// <summary>
/// Reference Location. /// Reference Location.
/// </summary> /// </summary>
rloc = 0x726C6F63U, Rloc = 0x726C6F63U,
/// <summary> /// <summary>
/// User Description. /// User Description.
/// </summary> /// </summary>
udes = 0x75646573U, Udes = 0x75646573U,
/// <summary> /// <summary>
/// IPMP Control Box. /// IPMP Control Box.
/// </summary> /// </summary>
ipmc = 0x69706D63U, Ipmc = 0x69706D63U,
/// <summary> /// <summary>
/// Item Property Container. /// Item Property Container.
/// </summary> /// </summary>
ipco = 0x6970636FU, Ipco = 0x6970636FU,
/// <summary> /// <summary>
/// Item Property Association. /// Item Property Association.
/// </summary> /// </summary>
ipma = 0x69706D61U, Ipma = 0x69706D61U,
/// <summary> /// <summary>
/// High Efficient Image Coding brand. /// High Efficient Image Coding brand.
/// </summary> /// </summary>
heic = 0x68656963U, Heic = 0x68656963U,
/// <summary> /// <summary>
/// High Efficient Image Coding brand (legacy name). /// High Efficient Image Coding brand (legacy name).
/// </summary> /// </summary>
heix = 0x68656978U, Heix = 0x68656978U,
/// <summary> /// <summary>
/// High Efficient File brand. /// High Efficient File brand.
/// </summary> /// </summary>
mif1 = 0x6D696631U, Mif1 = 0x6D696631U,
/// <summary> /// <summary>
/// AVIF brand. /// AVIF brand.
/// </summary> /// </summary>
avif = 0x61766966U, Avif = 0x61766966U,
/// <summary> /// <summary>
/// High Efficiency Coding tile. /// High Efficiency Coding tile.
/// </summary> /// </summary>
hvc1 = 0x68766331U, Hvc1 = 0x68766331U,
/// <summary> /// <summary>
/// Legacy JPEG coded tile. /// Legacy JPEG coded tile.
/// </summary> /// </summary>
jpeg = 0x6A706567U, Jpeg = 0x6A706567U,
/// <summary> /// <summary>
/// AOMedia Video Coding tile. /// AOMedia Video Coding tile.
/// </summary> /// </summary>
av01 = 0x61763031U, Av01 = 0x61763031U,
/// <summary> /// <summary>
/// Data Information. /// Data Information.
/// </summary> /// </summary>
dinf = 0x64696E66U, Dinf = 0x64696E66U,
/// <summary> /// <summary>
/// Group list. /// Group list.
/// </summary> /// </summary>
grpl = 0x6772706CU, Grpl = 0x6772706CU,
/// <summary> /// <summary>
/// Handler. /// Handler.
/// </summary> /// </summary>
hdlr = 0x68646C72U, Hdlr = 0x68646C72U,
/// <summary> /// <summary>
/// Item Information. /// Item Information.
/// </summary> /// </summary>
iinf = 0x69696E66U, Iinf = 0x69696E66U,
/// <summary> /// <summary>
/// Item Property. /// Item Property.
/// </summary> /// </summary>
iprp = 0x69707270U, Iprp = 0x69707270U,
/// <summary> /// <summary>
/// Item Protection. /// Item Protection.
/// </summary> /// </summary>
ipro = 0x6970726FU, Ipro = 0x6970726FU,
/// <summary> /// <summary>
/// Item Reference. /// Item Reference.
/// </summary> /// </summary>
iref = 0x69726566U, Iref = 0x69726566U,
/// <summary> /// <summary>
/// Grid. /// Grid.
/// </summary> /// </summary>
grid = 0x67726964U, Grid = 0x67726964U,
/// <summary> /// <summary>
/// Derived Image. /// Derived Image.
/// </summary> /// </summary>
dimg = 0x64696D67U, Dimg = 0x64696D67U,
/// <summary> /// <summary>
/// Thumbnail. /// Thumbnail.
/// </summary> /// </summary>
thmb = 0x74686D62U, Thmb = 0x74686D62U,
/// <summary> /// <summary>
/// Content Description. /// Content Description.
/// </summary> /// </summary>
cdsc = 0x63647363U, Cdsc = 0x63647363U,
/// <summary> /// <summary>
/// MIME type. /// MIME type.
/// </summary> /// </summary>
mime = 0x6D696D65U, Mime = 0x6D696D65U,
/// <summary> /// <summary>
/// URI. /// URI.
/// </summary> /// </summary>
uri = 0x75726920U, Uri = 0x75726920U,
/// <summary> /// <summary>
/// Picture handler type. /// Picture handler type.
/// </summary> /// </summary>
pict = 0x70696374U, Pict = 0x70696374U,
/// <summary> /// <summary>
/// Unique Identifier. /// Unique Identifier.
/// </summary> /// </summary>
uuid = 0x75756964U, Uuid = 0x75756964U,
/// <summary> /// <summary>
/// Free space. /// Free space.
/// </summary> /// </summary>
free = 0x66726565U, Free = 0x66726565U,
/// <summary> /// <summary>
/// Color profile. /// Color profile.
@ -256,11 +256,11 @@ public enum Heif4CharCode : uint
/// <summary> /// <summary>
/// ICC Color profile. /// ICC Color profile.
/// </summary> /// </summary>
rICC = 0x72494343U, RICC = 0x72494343U,
/// <summary> /// <summary>
/// ICC Color profile. /// ICC Color profile.
/// </summary> /// </summary>
prof = 0x70726F66U, Prof = 0x70726F66U,
} }

13
src/ImageSharp/Formats/Heif/Heif4CharCode.tt

@ -1,6 +1,6 @@
<#@ template language="C#" #> <#@ template language="C#" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Text" #>
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp.Formats.Heif;
/// <summary> /// <summary>
/// Supported 4 character codes for use in HEIF images. /// Supported 4 character codes for use in HEIF images.
/// </summary> /// </summary>
[GeneratedCode("T4", "")] [GeneratedCode("TextTemplateFileGenerator", "")]
public enum Heif4CharCode : uint public enum Heif4CharCode : uint
{ {
<# <#
@ -75,12 +75,13 @@ public enum Heif4CharCode : uint
{ {
string shortName = codes[i]; string shortName = codes[i];
string longName = codes[i + 1]; string longName = codes[i + 1];
string pascal = PascalCasing(shortName);
string hex = Code2Hex(shortName); string hex = Code2Hex(shortName);
#> #>
/// <summary> /// <summary>
/// <#= longName #>. /// <#= longName #>.
/// </summary> /// </summary>
<#= shortName #> = <#= hex #>, <#= pascal #> = <#= hex #>,
<# <#
} }
@ -88,6 +89,12 @@ public enum Heif4CharCode : uint
} }
<#+ <#+
private string PascalCasing(string code)
{
char firstChar = char.ToUpper(code[0], System.Globalization.CultureInfo.InvariantCulture);
return string.Concat(firstChar, code.Substring(1));
}
private string Code2Hex(string code) private string Code2Hex(string code)
{ {
byte[] b = Encoding.ASCII.GetBytes(code); byte[] b = Encoding.ASCII.GetBytes(code);

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

@ -8,7 +8,7 @@ namespace SixLabors.ImageSharp.Formats.Heif;
/// </summary> /// </summary>
internal static class HeifConstants internal static class HeifConstants
{ {
public const Heif4CharCode HeicBrand = Heif4CharCode.heic; public const Heif4CharCode HeicBrand = Heif4CharCode.Heic;
/// <summary> /// <summary>
/// The list of mimetypes that equate to a HEIC. /// The list of mimetypes that equate to a HEIC.

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

@ -6,6 +6,7 @@ using System.Buffers.Binary;
using System.Text; using System.Text;
using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.Formats.Heif.Av1;
using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
@ -35,8 +36,6 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals
private readonly List<HeifItemLink> itemLinks; private readonly List<HeifItemLink> itemLinks;
private readonly byte[] buffer;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="HeifDecoderCore" /> class. /// Initializes a new instance of the <see cref="HeifDecoderCore" /> class.
/// </summary> /// </summary>
@ -48,7 +47,6 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals
this.metadata = new ImageMetadata(); this.metadata = new ImageMetadata();
this.items = new List<HeifItem>(); this.items = new List<HeifItem>();
this.itemLinks = new List<HeifItemLink>(); this.itemLinks = new List<HeifItemLink>();
this.buffer = new byte[80];
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -73,13 +71,13 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals
EnsureBoxBoundary(boxLength, stream); EnsureBoxBoundary(boxLength, stream);
switch (boxType) switch (boxType)
{ {
case Heif4CharCode.meta: case Heif4CharCode.Meta:
this.ParseMetadata(stream, boxLength); this.ParseMetadata(stream, boxLength);
break; break;
case Heif4CharCode.mdat: case Heif4CharCode.Mdat:
image = this.ParseMediaData<TPixel>(stream, boxLength); image = this.ParseMediaData<TPixel>(stream, boxLength);
break; break;
case Heif4CharCode.free: case Heif4CharCode.Free:
SkipBox(stream, boxLength); SkipBox(stream, boxLength);
break; break;
case 0U: case 0U:
@ -117,7 +115,7 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals
EnsureBoxBoundary(boxLength, stream); EnsureBoxBoundary(boxLength, stream);
switch (boxType) switch (boxType)
{ {
case Heif4CharCode.meta: case Heif4CharCode.Meta:
this.ParseMetadata(stream, boxLength); this.ParseMetadata(stream, boxLength);
break; break;
default: default:
@ -141,23 +139,24 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals
private bool CheckFileTypeBox(BufferedReadStream stream) private bool CheckFileTypeBox(BufferedReadStream stream)
{ {
long boxLength = this.ReadBoxHeader(stream, out Heif4CharCode boxType); long boxLength = this.ReadBoxHeader(stream, out Heif4CharCode boxType);
Span<byte> buffer = this.ReadIntoBuffer(stream, boxLength); using IMemoryOwner<byte> boxMemory = this.ReadIntoBuffer(stream, boxLength);
uint majorBrand = BinaryPrimitives.ReadUInt32BigEndian(buffer); Span<byte> boxBuffer = boxMemory.GetSpan();
bool correctBrand = majorBrand is (uint)Heif4CharCode.heic or (uint)Heif4CharCode.heix or (uint)Heif4CharCode.avif; uint majorBrand = BinaryPrimitives.ReadUInt32BigEndian(boxBuffer);
bool correctBrand = majorBrand is (uint)Heif4CharCode.Heic or (uint)Heif4CharCode.Heix or (uint)Heif4CharCode.Avif;
// TODO: Interpret minorVersion and compatible brands. // TODO: Interpret minorVersion and compatible brands.
return boxType == Heif4CharCode.ftyp && correctBrand; return boxType == Heif4CharCode.Ftyp && correctBrand;
} }
private void UpdateMetadata(ImageMetadata metadata, HeifItem item) private void UpdateMetadata(ImageMetadata metadata, HeifItem item)
{ {
HeifMetadata meta = metadata.GetHeifMetadata(); HeifMetadata meta = metadata.GetHeifMetadata();
HeifCompressionMethod compressionMethod = HeifCompressionMethod.Hevc; HeifCompressionMethod compressionMethod = HeifCompressionMethod.Hevc;
if (item.Type == Heif4CharCode.av01) if (item.Type == Heif4CharCode.Av01)
{ {
compressionMethod = HeifCompressionMethod.Av1; compressionMethod = HeifCompressionMethod.Av1;
} }
else if (item.Type == Heif4CharCode.jpeg || this.itemLinks.Any(link => link.Type == Heif4CharCode.thmb)) else if (item.Type == Heif4CharCode.Jpeg || this.itemLinks.Any(link => link.Type == Heif4CharCode.Thmb))
{ {
compressionMethod = HeifCompressionMethod.LegacyJpeg; compressionMethod = HeifCompressionMethod.LegacyJpeg;
} }
@ -168,7 +167,13 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals
private long ReadBoxHeader(BufferedReadStream stream, out Heif4CharCode boxType) private long ReadBoxHeader(BufferedReadStream stream, out Heif4CharCode boxType)
{ {
// Read 4 bytes of length of box // Read 4 bytes of length of box
Span<byte> buf = this.ReadIntoBuffer(stream, 8); Span<byte> buf = stackalloc byte[8];
int bytesRead = stream.Read(buf);
if (bytesRead != 8)
{
throw new InvalidImageContentException("Not enough data to read the Box header");
}
long boxSize = BinaryPrimitives.ReadUInt32BigEndian(buf); long boxSize = BinaryPrimitives.ReadUInt32BigEndian(buf);
long headerSize = 8; long headerSize = 8;
@ -177,7 +182,12 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals
if (boxSize == 1) if (boxSize == 1)
{ {
buf = this.ReadIntoBuffer(stream, 8); bytesRead = stream.Read(buf);
if (bytesRead != 8)
{
throw new InvalidImageContentException("Not enough data to read the Large Box header");
}
boxSize = (long)BinaryPrimitives.ReadUInt64BigEndian(buf); boxSize = (long)BinaryPrimitives.ReadUInt64BigEndian(buf);
headerSize += 8; headerSize += 8;
} }
@ -208,33 +218,33 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals
while (stream.Position < endPosition) while (stream.Position < endPosition)
{ {
long length = this.ReadBoxHeader(stream, out Heif4CharCode boxType); long length = this.ReadBoxHeader(stream, out Heif4CharCode boxType);
EnsureBoxBoundary(length, boxLength); EnsureBoxInsideParent(length, boxLength);
switch (boxType) switch (boxType)
{ {
case Heif4CharCode.iprp: case Heif4CharCode.Iprp:
this.ParseItemProperties(stream, length); this.ParseItemProperties(stream, length);
break; break;
case Heif4CharCode.iinf: case Heif4CharCode.Iinf:
this.ParseItemInfo(stream, length); this.ParseItemInfo(stream, length);
break; break;
case Heif4CharCode.iref: case Heif4CharCode.Iref:
this.ParseItemReference(stream, length); this.ParseItemReference(stream, length);
break; break;
case Heif4CharCode.pitm: case Heif4CharCode.Pitm:
this.ParsePrimaryItem(stream, length); this.ParsePrimaryItem(stream, length);
break; break;
case Heif4CharCode.hdlr: case Heif4CharCode.Hdlr:
this.ParseHandler(stream, length); this.ParseHandler(stream, length);
break; break;
case Heif4CharCode.iloc: case Heif4CharCode.Iloc:
this.ParseItemLocation(stream, length); this.ParseItemLocation(stream, length);
break; break;
case Heif4CharCode.dinf: case Heif4CharCode.Dinf:
case Heif4CharCode.idat: case Heif4CharCode.Idat:
case Heif4CharCode.grpl: case Heif4CharCode.Grpl:
case Heif4CharCode.ipro: case Heif4CharCode.Ipro:
case Heif4CharCode.uuid: case Heif4CharCode.Uuid:
case Heif4CharCode.ipmc: case Heif4CharCode.Ipmc:
// Silently skip these boxes. // Silently skip these boxes.
SkipBox(stream, length); SkipBox(stream, length);
break; break;
@ -246,12 +256,13 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals
private void ParseHandler(BufferedReadStream stream, long boxLength) private void ParseHandler(BufferedReadStream stream, long boxLength)
{ {
Span<byte> buffer = this.ReadIntoBuffer(stream, boxLength); using IMemoryOwner<byte> boxMemory = this.ReadIntoBuffer(stream, boxLength);
Span<byte> boxBuffer = boxMemory.GetSpan();
// Only read the handler type, to check if this is not a movie file. // Only read the handler type, to check if this is not a movie file.
int bytesRead = 8; int bytesRead = 8;
Heif4CharCode handlerType = (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); Heif4CharCode handlerType = (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(boxBuffer[bytesRead..]);
if (handlerType != Heif4CharCode.pict) if (handlerType != Heif4CharCode.Pict)
{ {
throw new ImageFormatException("Not a picture file."); throw new ImageFormatException("Not a picture file.");
} }
@ -259,16 +270,17 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals
private void ParseItemInfo(BufferedReadStream stream, long boxLength) private void ParseItemInfo(BufferedReadStream stream, long boxLength)
{ {
Span<byte> buffer = this.ReadIntoBuffer(stream, boxLength); using IMemoryOwner<byte> boxMemory = this.ReadIntoBuffer(stream, boxLength);
Span<byte> boxBuffer = boxMemory.GetSpan();
uint entryCount; uint entryCount;
int bytesRead = 0; int bytesRead = 0;
byte version = buffer[bytesRead]; byte version = boxBuffer[bytesRead];
bytesRead += 4; bytesRead += 4;
entryCount = ReadUInt16Or32(buffer, version != 0, ref bytesRead); entryCount = ReadUInt16Or32(boxBuffer, version != 0, ref bytesRead);
for (uint i = 0; i < entryCount; i++) for (uint i = 0; i < entryCount; i++)
{ {
bytesRead += this.ParseItemInfoEntry(buffer[bytesRead..]); bytesRead += this.ParseItemInfoEntry(boxBuffer[bytesRead..]);
} }
} }
@ -325,7 +337,7 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals
item = new HeifItem(itemType, itemId); item = new HeifItem(itemType, itemId);
item.Name = ReadNullTerminatedString(buffer[bytesRead..]); item.Name = ReadNullTerminatedString(buffer[bytesRead..]);
bytesRead += item.Name.Length + 1; bytesRead += item.Name.Length + 1;
if (item.Type == Heif4CharCode.mime) if (item.Type == Heif4CharCode.Mime)
{ {
item.ContentType = ReadNullTerminatedString(buffer[bytesRead..]); item.ContentType = ReadNullTerminatedString(buffer[bytesRead..]);
bytesRead += item.ContentType.Length + 1; bytesRead += item.ContentType.Length + 1;
@ -337,7 +349,7 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals
bytesRead += item.ContentEncoding.Length + 1; bytesRead += item.ContentEncoding.Length + 1;
} }
} }
else if (item.Type == Heif4CharCode.uri) else if (item.Type == Heif4CharCode.Uri)
{ {
item.UriType = ReadNullTerminatedString(buffer[bytesRead..]); item.UriType = ReadNullTerminatedString(buffer[bytesRead..]);
bytesRead += item.UriType.Length + 1; bytesRead += item.UriType.Length + 1;
@ -354,21 +366,22 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals
private void ParseItemReference(BufferedReadStream stream, long boxLength) private void ParseItemReference(BufferedReadStream stream, long boxLength)
{ {
Span<byte> buffer = this.ReadIntoBuffer(stream, boxLength); using IMemoryOwner<byte> boxMemory = this.ReadIntoBuffer(stream, boxLength);
Span<byte> boxBuffer = boxMemory.GetSpan();
int bytesRead = 0; int bytesRead = 0;
bool largeIds = buffer[bytesRead] != 0; bool largeIds = boxBuffer[bytesRead] != 0;
bytesRead += 4; bytesRead += 4;
while (bytesRead < boxLength) while (bytesRead < boxLength)
{ {
bytesRead += ParseBoxHeader(buffer[bytesRead..], out long subBoxLength, out Heif4CharCode linkType); bytesRead += ParseBoxHeader(boxBuffer[bytesRead..], out long subBoxLength, out Heif4CharCode linkType);
uint sourceId = ReadUInt16Or32(buffer, largeIds, ref bytesRead); uint sourceId = ReadUInt16Or32(boxBuffer, largeIds, ref bytesRead);
HeifItemLink link = new(linkType, sourceId); HeifItemLink link = new(linkType, sourceId);
int count = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); int count = BinaryPrimitives.ReadUInt16BigEndian(boxBuffer[bytesRead..]);
bytesRead += 2; bytesRead += 2;
for (uint i = 0; i < count; i++) for (uint i = 0; i < count; i++)
{ {
uint destId = ReadUInt16Or32(buffer, largeIds, ref bytesRead); uint destId = ReadUInt16Or32(boxBuffer, largeIds, ref bytesRead);
link.DestinationIds.Add(destId); link.DestinationIds.Add(destId);
} }
@ -379,10 +392,11 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals
private void ParsePrimaryItem(BufferedReadStream stream, long boxLength) private void ParsePrimaryItem(BufferedReadStream stream, long boxLength)
{ {
// BoxLength should be 6 or 8. // BoxLength should be 6 or 8.
Span<byte> buffer = this.ReadIntoBuffer(stream, boxLength); using IMemoryOwner<byte> boxMemory = this.ReadIntoBuffer(stream, boxLength);
byte version = buffer[0]; Span<byte> boxBuffer = boxMemory.GetSpan();
byte version = boxBuffer[0];
int bytesRead = 4; int bytesRead = 4;
this.primaryItem = ReadUInt16Or32(buffer, version != 0, ref bytesRead); this.primaryItem = ReadUInt16Or32(boxBuffer, version != 0, ref bytesRead);
} }
private void ParseItemProperties(BufferedReadStream stream, long boxLength) private void ParseItemProperties(BufferedReadStream stream, long boxLength)
@ -393,13 +407,13 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals
while (stream.Position < endBoxPosition) while (stream.Position < endBoxPosition)
{ {
long containerLength = this.ReadBoxHeader(stream, out Heif4CharCode containerType); long containerLength = this.ReadBoxHeader(stream, out Heif4CharCode containerType);
EnsureBoxBoundary(containerLength, boxLength); EnsureBoxInsideParent(containerLength, boxLength);
if (containerType == Heif4CharCode.ipco) if (containerType == Heif4CharCode.Ipco)
{ {
// Parse Item Property Container, which is just an array of property boxes. // Parse Item Property Container, which is just an array of property boxes.
this.ParsePropertyContainer(stream, containerLength, properties); this.ParsePropertyContainer(stream, containerLength, properties);
} }
else if (containerType == Heif4CharCode.ipma) else if (containerType == Heif4CharCode.Ipma)
{ {
// Parse Item Property Association // Parse Item Property Association
this.ParsePropertyAssociation(stream, containerLength, properties); this.ParsePropertyAssociation(stream, containerLength, properties);
@ -417,52 +431,53 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals
while (stream.Position < endPosition) while (stream.Position < endPosition)
{ {
int itemLength = (int)this.ReadBoxHeader(stream, out Heif4CharCode itemType); int itemLength = (int)this.ReadBoxHeader(stream, out Heif4CharCode itemType);
EnsureBoxBoundary(itemLength, boxLength); EnsureBoxInsideParent(itemLength, boxLength);
Span<byte> buffer = this.ReadIntoBuffer(stream, itemLength); using IMemoryOwner<byte> boxMemory = this.ReadIntoBuffer(stream, itemLength);
Span<byte> boxBuffer = boxMemory.GetSpan();
switch (itemType) switch (itemType)
{ {
case Heif4CharCode.ispe: case Heif4CharCode.Ispe:
// Skip over version (8 bits) and flags (24 bits). // Skip over version (8 bits) and flags (24 bits).
int width = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); int width = (int)BinaryPrimitives.ReadUInt32BigEndian(boxBuffer[4..]);
int height = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[8..]); int height = (int)BinaryPrimitives.ReadUInt32BigEndian(boxBuffer[8..]);
properties.Add(new KeyValuePair<Heif4CharCode, object>(Heif4CharCode.ispe, new Size(width, height))); properties.Add(new KeyValuePair<Heif4CharCode, object>(Heif4CharCode.Ispe, new Size(width, height)));
break; break;
case Heif4CharCode.pasp: case Heif4CharCode.Pasp:
int horizontalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer); int horizontalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(boxBuffer);
int verticalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); int verticalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(boxBuffer[4..]);
properties.Add(new KeyValuePair<Heif4CharCode, object>(Heif4CharCode.pasp, new Size(horizontalSpacing, verticalSpacing))); properties.Add(new KeyValuePair<Heif4CharCode, object>(Heif4CharCode.Pasp, new Size(horizontalSpacing, verticalSpacing)));
break; break;
case Heif4CharCode.pixi: case Heif4CharCode.Pixi:
// Skip over version (8 bits) and flags (24 bits). // Skip over version (8 bits) and flags (24 bits).
int channelCount = buffer[4]; int channelCount = boxBuffer[4];
int offset = 5; int offset = 5;
int bitsPerPixel = 0; int bitsPerPixel = 0;
for (int i = 0; i < channelCount; i++) for (int i = 0; i < channelCount; i++)
{ {
bitsPerPixel += buffer[offset + i]; bitsPerPixel += boxBuffer[offset + i];
} }
properties.Add(new KeyValuePair<Heif4CharCode, object>(Heif4CharCode.pixi, new int[] { channelCount, bitsPerPixel })); properties.Add(new KeyValuePair<Heif4CharCode, object>(Heif4CharCode.Pixi, new int[] { channelCount, bitsPerPixel }));
break; break;
case Heif4CharCode.colr: case Heif4CharCode.Colr:
Heif4CharCode profileType = (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(buffer); Heif4CharCode profileType = (Heif4CharCode)BinaryPrimitives.ReadUInt32BigEndian(boxBuffer);
if (profileType is Heif4CharCode.rICC or Heif4CharCode.prof) if (profileType is Heif4CharCode.RICC or Heif4CharCode.Prof)
{ {
byte[] iccData = new byte[itemLength - 4]; byte[] iccData = new byte[itemLength - 4];
buffer[4..].CopyTo(iccData); boxBuffer[4..].CopyTo(iccData);
this.metadata.IccProfile = new IccProfile(iccData); this.metadata.IccProfile = new IccProfile(iccData);
} }
break; break;
case Heif4CharCode.altt: case Heif4CharCode.Altt:
case Heif4CharCode.imir: case Heif4CharCode.Imir:
case Heif4CharCode.irot: case Heif4CharCode.Irot:
case Heif4CharCode.iscl: case Heif4CharCode.Iscl:
case Heif4CharCode.hvcC: case Heif4CharCode.HvcC:
case Heif4CharCode.av1C: case Heif4CharCode.Av1C:
case Heif4CharCode.rloc: case Heif4CharCode.Rloc:
case Heif4CharCode.udes: case Heif4CharCode.Udes:
// TODO: Implement // TODO: Implement
properties.Add(new KeyValuePair<Heif4CharCode, object>(itemType, new object())); properties.Add(new KeyValuePair<Heif4CharCode, object>(itemType, new object()));
break; break;
@ -474,36 +489,37 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals
private void ParsePropertyAssociation(BufferedReadStream stream, long boxLength, List<KeyValuePair<Heif4CharCode, object>> properties) private void ParsePropertyAssociation(BufferedReadStream stream, long boxLength, List<KeyValuePair<Heif4CharCode, object>> properties)
{ {
Span<byte> buffer = this.ReadIntoBuffer(stream, boxLength); using IMemoryOwner<byte> boxMemory = this.ReadIntoBuffer(stream, boxLength);
byte version = buffer[0]; Span<byte> boxBuffer = boxMemory.GetSpan();
byte flags = buffer[3]; byte version = boxBuffer[0];
byte flags = boxBuffer[3];
int bytesRead = 4; int bytesRead = 4;
int itemId = (int)ReadUInt16Or32(buffer, version >= 1, ref bytesRead); int itemId = (int)ReadUInt16Or32(boxBuffer, version >= 1, ref bytesRead);
int associationCount = buffer[bytesRead++]; int associationCount = boxBuffer[bytesRead++];
for (int i = 0; i < associationCount; i++) for (int i = 0; i < associationCount; i++)
{ {
uint propId; uint propId;
if (flags == 1) if (flags == 1)
{ {
propId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]) & 0x4FFFU; propId = BinaryPrimitives.ReadUInt16BigEndian(boxBuffer[bytesRead..]) & 0x4FFFU;
bytesRead += 2; bytesRead += 2;
} }
else else
{ {
propId = buffer[bytesRead++] & 0x4FU; propId = boxBuffer[bytesRead++] & 0x4FU;
} }
KeyValuePair<Heif4CharCode, object> prop = properties[(int)propId]; KeyValuePair<Heif4CharCode, object> prop = properties[(int)propId];
switch (prop.Key) switch (prop.Key)
{ {
case Heif4CharCode.ispe: case Heif4CharCode.Ispe:
this.items[itemId].SetExtent((Size)prop.Value); this.items[itemId].SetExtent((Size)prop.Value);
break; break;
case Heif4CharCode.pasp: case Heif4CharCode.Pasp:
this.items[itemId].PixelAspectRatio = (Size)prop.Value; this.items[itemId].PixelAspectRatio = (Size)prop.Value;
break; break;
case Heif4CharCode.pixi: case Heif4CharCode.Pixi:
int[] values = (int[])prop.Value; int[] values = (int[])prop.Value;
this.items[itemId].ChannelCount = values[0]; this.items[itemId].ChannelCount = values[0];
this.items[itemId].BitsPerPixel = values[1]; this.items[itemId].BitsPerPixel = values[1];
@ -514,12 +530,13 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals
private void ParseItemLocation(BufferedReadStream stream, long boxLength) private void ParseItemLocation(BufferedReadStream stream, long boxLength)
{ {
Span<byte> buffer = this.ReadIntoBuffer(stream, boxLength); using IMemoryOwner<byte> boxMemory = this.ReadIntoBuffer(stream, boxLength);
Span<byte> boxBuffer = boxMemory.GetSpan();
int bytesRead = 0; int bytesRead = 0;
byte version = buffer[bytesRead]; byte version = boxBuffer[bytesRead];
bytesRead += 4; bytesRead += 4;
byte b1 = buffer[bytesRead++]; byte b1 = boxBuffer[bytesRead++];
byte b2 = buffer[bytesRead++]; byte b2 = boxBuffer[bytesRead++];
int offsetSize = (b1 >> 4) & 0x0f; int offsetSize = (b1 >> 4) & 0x0f;
int lengthSize = b1 & 0x0f; int lengthSize = b1 & 0x0f;
int baseOffsetSize = (b2 >> 4) & 0x0f; int baseOffsetSize = (b2 >> 4) & 0x0f;
@ -529,32 +546,32 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals
indexSize = b2 & 0x0f; indexSize = b2 & 0x0f;
} }
uint itemCount = ReadUInt16Or32(buffer, version == 2, ref bytesRead); uint itemCount = ReadUInt16Or32(boxBuffer, version == 2, ref bytesRead);
for (uint i = 0; i < itemCount; i++) for (uint i = 0; i < itemCount; i++)
{ {
uint itemId = ReadUInt16Or32(buffer, version == 2, ref bytesRead); uint itemId = ReadUInt16Or32(boxBuffer, version == 2, ref bytesRead);
HeifItem? item = this.FindItemById(itemId); HeifItem? item = this.FindItemById(itemId);
if (version is 1 or 2) if (version is 1 or 2)
{ {
bytesRead++; bytesRead++;
byte b3 = buffer[bytesRead++]; byte b3 = boxBuffer[bytesRead++];
int constructionMethod = b3 & 0x0f; int constructionMethod = b3 & 0x0f;
} }
uint dataReferenceIndex = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); uint dataReferenceIndex = BinaryPrimitives.ReadUInt16BigEndian(boxBuffer[bytesRead..]);
bytesRead += 2; bytesRead += 2;
ulong baseOffset = ReadUIntVariable(buffer, baseOffsetSize, ref bytesRead); ulong baseOffset = ReadUIntVariable(boxBuffer, baseOffsetSize, ref bytesRead);
uint extentCount = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); uint extentCount = BinaryPrimitives.ReadUInt16BigEndian(boxBuffer[bytesRead..]);
bytesRead += 2; bytesRead += 2;
for (uint j = 0; j < extentCount; j++) for (uint j = 0; j < extentCount; j++)
{ {
if (version is 1 or 2 && indexSize > 0) if (version is 1 or 2 && indexSize > 0)
{ {
_ = ReadUIntVariable(buffer, indexSize, ref bytesRead); _ = ReadUIntVariable(boxBuffer, indexSize, ref bytesRead);
} }
ulong extentOffset = ReadUIntVariable(buffer, offsetSize, ref bytesRead); ulong extentOffset = ReadUIntVariable(boxBuffer, offsetSize, ref bytesRead);
ulong extentLength = ReadUIntVariable(buffer, lengthSize, ref bytesRead); ulong extentLength = ReadUIntVariable(boxBuffer, lengthSize, ref bytesRead);
HeifLocation loc = new HeifLocation((long)extentOffset, (long)extentLength); HeifLocation loc = new HeifLocation((long)extentOffset, (long)extentLength);
item?.DataLocations.Add(loc); item?.DataLocations.Add(loc);
} }
@ -607,15 +624,17 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals
private Image<TPixel> ParseMediaData<TPixel>(BufferedReadStream stream, long boxLength) private Image<TPixel> ParseMediaData<TPixel>(BufferedReadStream stream, long boxLength)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// FIXME: No HVC decoding yet, so parse only a JPEG thumbnail. EnsureBoxBoundary(boxLength, stream);
HeifItemLink? thumbLink = this.itemLinks.FirstOrDefault(link => link.Type == Heif4CharCode.thmb);
// FIXME: No specific decoding yet, so parse only a JPEG thumbnail.
HeifItemLink? thumbLink = this.itemLinks.FirstOrDefault(link => link.Type == Heif4CharCode.Thmb);
if (thumbLink == null) if (thumbLink == null)
{ {
throw new NotImplementedException("No thumbnail found"); throw new NotImplementedException("No thumbnail found");
} }
HeifItem? thumbItem = this.FindItemById(thumbLink.SourceId); HeifItem? thumbItem = this.FindItemById(thumbLink.SourceId);
if (thumbItem == null || thumbItem.Type != Heif4CharCode.jpeg) if (thumbItem == null || thumbItem.Type != Heif4CharCode.Jpeg)
{ {
throw new NotImplementedException("No HVC decoding implemented yet"); throw new NotImplementedException("No HVC decoding implemented yet");
} }
@ -623,9 +642,9 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals
int thumbFileOffset = (int)thumbItem.DataLocations[0].Offset; int thumbFileOffset = (int)thumbItem.DataLocations[0].Offset;
int thumbFileLength = (int)thumbItem.DataLocations[0].Length; int thumbFileLength = (int)thumbItem.DataLocations[0].Length;
stream.Skip((int)(thumbFileOffset - stream.Position)); stream.Skip((int)(thumbFileOffset - stream.Position));
using IMemoryOwner<byte> thumbMemory = this.configuration.MemoryAllocator.Allocate<byte>(thumbFileLength); EnsureBoxBoundary(thumbFileLength, stream);
using IMemoryOwner<byte> thumbMemory = this.ReadIntoBuffer(stream, thumbFileLength);
Span<byte> thumbSpan = thumbMemory.GetSpan(); Span<byte> thumbSpan = thumbMemory.GetSpan();
stream.Read(thumbSpan);
HeifMetadata meta = this.metadata.GetHeifMetadata(); HeifMetadata meta = this.metadata.GetHeifMetadata();
meta.CompressionMethod = HeifCompressionMethod.LegacyJpeg; meta.CompressionMethod = HeifCompressionMethod.LegacyJpeg;
@ -636,25 +655,22 @@ internal sealed class HeifDecoderCore : IImageDecoderInternals
private static void SkipBox(BufferedReadStream stream, long boxLength) private static void SkipBox(BufferedReadStream stream, long boxLength)
=> stream.Skip((int)boxLength); => stream.Skip((int)boxLength);
private Span<byte> ReadIntoBuffer(BufferedReadStream stream, long length) private IMemoryOwner<byte> ReadIntoBuffer(BufferedReadStream stream, long length)
{ {
if (length <= this.buffer.Length) IMemoryOwner<byte> buffer = this.configuration.MemoryAllocator.Allocate<byte>((int)length);
int bytesRead = stream.Read(buffer.GetSpan());
if (bytesRead != length)
{ {
stream.Read(this.buffer, 0, (int)length); throw new InvalidImageContentException("Stream length not sufficient for box content");
return this.buffer;
}
else
{
Span<byte> temp = new byte[length];
stream.Read(temp);
return temp;
} }
return buffer;
} }
private static void EnsureBoxBoundary(long boxLength, Stream stream) private static void EnsureBoxBoundary(long boxLength, Stream stream)
=> EnsureBoxBoundary(boxLength, stream.Length - stream.Position); => EnsureBoxInsideParent(boxLength, stream.Length - stream.Position);
private static void EnsureBoxBoundary(long boxLength, long parentLength) private static void EnsureBoxInsideParent(long boxLength, long parentLength)
{ {
if (boxLength > parentLength) if (boxLength > parentLength)
{ {

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

@ -16,7 +16,7 @@ internal sealed class HeifEncoderCore : IImageEncoderInternals
/// <summary> /// <summary>
/// The global configuration. /// The global configuration.
/// </summary> /// </summary>
private Configuration configuration; private readonly Configuration configuration;
/// <summary> /// <summary>
/// The encoder with options. /// The encoder with options.
@ -65,7 +65,7 @@ internal sealed class HeifEncoderCore : IImageEncoderInternals
private static void GenerateItems<TPixel>(Image<TPixel> image, byte[] pixels, List<HeifItem> items, List<HeifItemLink> links) private static void GenerateItems<TPixel>(Image<TPixel> image, byte[] pixels, List<HeifItem> items, List<HeifItemLink> links)
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(0L, pixels.LongLength)); primaryItem.DataLocations.Add(new HeifLocation(0L, pixels.LongLength));
primaryItem.BitsPerPixel = 24; primaryItem.BitsPerPixel = 24;
primaryItem.ChannelCount = 3; primaryItem.ChannelCount = 3;
@ -73,7 +73,7 @@ internal sealed class HeifEncoderCore : IImageEncoderInternals
items.Add(primaryItem); items.Add(primaryItem);
// Create a fake thumbnail, to make our own Decoder happy. // Create a fake thumbnail, to make our own Decoder happy.
HeifItemLink thumbnail = new(Heif4CharCode.thmb, 1u); HeifItemLink thumbnail = new(Heif4CharCode.Thmb, 1u);
thumbnail.DestinationIds.Add(1u); thumbnail.DestinationIds.Add(1u);
links.Add(thumbnail); links.Add(thumbnail);
} }
@ -108,14 +108,14 @@ internal sealed class HeifEncoderCore : IImageEncoderInternals
private void WriteFileTypeBox(Stream stream) private void WriteFileTypeBox(Stream stream)
{ {
Span<byte> buffer = stackalloc byte[24]; Span<byte> buffer = stackalloc byte[24];
int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.ftyp); int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.Ftyp);
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.heic); BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.Heic);
bytesWritten += 4; bytesWritten += 4;
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], 0); BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], 0);
bytesWritten += 4; bytesWritten += 4;
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.mif1); BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.Mif1);
bytesWritten += 4; bytesWritten += 4;
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.heic); BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.Heic);
bytesWritten += 4; bytesWritten += 4;
BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten); BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten);
@ -126,7 +126,7 @@ internal sealed class HeifEncoderCore : IImageEncoderInternals
{ {
using AutoExpandingMemory<byte> memory = new(this.configuration, 0x1000); using AutoExpandingMemory<byte> memory = new(this.configuration, 0x1000);
Span<byte> buffer = memory.GetSpan(12); Span<byte> buffer = memory.GetSpan(12);
int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.meta, 0, 0); int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.Meta, 0, 0);
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);
@ -143,10 +143,10 @@ internal sealed class HeifEncoderCore : IImageEncoderInternals
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);
int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.hdlr, 0, 0); int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.Hdlr, 0, 0);
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], 0); BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], 0);
bytesWritten += 4; bytesWritten += 4;
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.pict); BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)Heif4CharCode.Pict);
bytesWritten += 4; bytesWritten += 4;
for (int i = 0; i < 13; i++) for (int i = 0; i < 13; i++)
{ {
@ -160,7 +160,7 @@ internal sealed class HeifEncoderCore : IImageEncoderInternals
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);
int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.pitm, 0, 0); int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.Pitm, 0, 0);
BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], 1); BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], 1);
bytesWritten += 2; bytesWritten += 2;
@ -171,13 +171,13 @@ internal sealed class HeifEncoderCore : IImageEncoderInternals
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));
int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.iinf, 0, 0); int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.Iinf, 0, 0);
BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)items.Count); BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)items.Count);
bytesWritten += 2; bytesWritten += 2;
foreach (HeifItem item in items) foreach (HeifItem item in items)
{ {
int itemLengthOffset = bytesWritten; int itemLengthOffset = bytesWritten;
bytesWritten += WriteBoxHeader(buffer[bytesWritten..], Heif4CharCode.infe, 2, 0); bytesWritten += WriteBoxHeader(buffer[bytesWritten..], Heif4CharCode.Infe, 2, 0);
BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)item.Id); BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], (ushort)item.Id);
bytesWritten += 2; bytesWritten += 2;
BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], 0); BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], 0);
@ -196,7 +196,7 @@ internal sealed class HeifEncoderCore : IImageEncoderInternals
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))));
int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.iref, 0, 0); int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.Iref, 0, 0);
foreach (HeifItemLink link in links) foreach (HeifItemLink link in links)
{ {
int itemLengthOffset = bytesWritten; int itemLengthOffset = bytesWritten;
@ -222,11 +222,11 @@ internal sealed class HeifEncoderCore : IImageEncoderInternals
{ {
const ushort numPropPerItem = 1; const ushort numPropPerItem = 1;
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 // Write 'ipco' box
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)
{ {
bytesWritten += WriteSpatialExtentPropertyBox(memory, memoryOffset + bytesWritten, item); bytesWritten += WriteSpatialExtentPropertyBox(memory, memoryOffset + bytesWritten, item);
@ -237,7 +237,7 @@ internal sealed class HeifEncoderCore : IImageEncoderInternals
// Write 'ipma' box // Write 'ipma' box
int ipmaLengthOffset = bytesWritten; int ipmaLengthOffset = bytesWritten;
bytesWritten += WriteBoxHeader(buffer[bytesWritten..], Heif4CharCode.ipma, 0, 0); bytesWritten += WriteBoxHeader(buffer[bytesWritten..], Heif4CharCode.Ipma, 0, 0);
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)(items.Count * numPropPerItem)); BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)(items.Count * numPropPerItem));
bytesWritten += 4; bytesWritten += 4;
ushort propIndex = 0; ushort propIndex = 0;
@ -261,7 +261,7 @@ internal sealed class HeifEncoderCore : IImageEncoderInternals
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);
int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.ispe, 0, 0); int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.Ispe, 0, 0);
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)item.Extent.Width); BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)item.Extent.Width);
bytesWritten += 4; bytesWritten += 4;
BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)item.Extent.Height); BinaryPrimitives.WriteUInt32BigEndian(buffer[bytesWritten..], (uint)item.Extent.Height);
@ -274,7 +274,7 @@ internal sealed class HeifEncoderCore : IImageEncoderInternals
private static int WriteItemDataBox(AutoExpandingMemory<byte> memory, int memoryOffset) private static int WriteItemDataBox(AutoExpandingMemory<byte> memory, int memoryOffset)
{ {
Span<byte> buffer = memory.GetSpan(memoryOffset, 10); Span<byte> buffer = memory.GetSpan(memoryOffset, 10);
int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.idat); int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.Idat);
BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten); BinaryPrimitives.WriteUInt32BigEndian(buffer, (uint)bytesWritten);
return bytesWritten; return bytesWritten;
@ -283,7 +283,7 @@ internal sealed class HeifEncoderCore : IImageEncoderInternals
private static int WriteItemLocationBox(AutoExpandingMemory<byte> memory, int memoryOffset, List<HeifItem> items) private static int WriteItemLocationBox(AutoExpandingMemory<byte> memory, int memoryOffset, List<HeifItem> items)
{ {
Span<byte> buffer = memory.GetSpan(memoryOffset, 30 + (items.Count * 8)); Span<byte> buffer = memory.GetSpan(memoryOffset, 30 + (items.Count * 8));
int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.iloc, 1, 0); int bytesWritten = WriteBoxHeader(buffer, Heif4CharCode.Iloc, 1, 0);
buffer[bytesWritten++] = 0x44; buffer[bytesWritten++] = 0x44;
buffer[bytesWritten++] = 0; buffer[bytesWritten++] = 0;
BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], 1); BinaryPrimitives.WriteUInt16BigEndian(buffer[bytesWritten..], 1);
@ -313,7 +313,7 @@ internal sealed class HeifEncoderCore : IImageEncoderInternals
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];
int bytesWritten = WriteBoxHeader(buf, Heif4CharCode.mdat); int bytesWritten = WriteBoxHeader(buf, Heif4CharCode.Mdat);
BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)(data.Length + bytesWritten)); BinaryPrimitives.WriteUInt32BigEndian(buf, (uint)(data.Length + bytesWritten));
stream.Write(buf[..bytesWritten]); stream.Write(buf[..bytesWritten]);

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

@ -23,8 +23,8 @@ public sealed class HeifImageFormatDetector : IImageFormatDetector
private static bool IsSupportedFileFormat(ReadOnlySpan<byte> header) private static bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
{ {
bool hasFtyp = BinaryPrimitives.ReadUInt32BigEndian(header.Slice(4)) == (uint)Heif4CharCode.ftyp; bool hasFtyp = BinaryPrimitives.ReadUInt32BigEndian(header.Slice(4)) == (uint)Heif4CharCode.Ftyp;
uint brand = BinaryPrimitives.ReadUInt32BigEndian(header.Slice(8)); uint brand = BinaryPrimitives.ReadUInt32BigEndian(header.Slice(8));
return hasFtyp && (brand == (uint)Heif4CharCode.heic || brand == (uint)Heif4CharCode.heix || brand == (uint)Heif4CharCode.avif); return hasFtyp && (brand == (uint)Heif4CharCode.Heic || brand == (uint)Heif4CharCode.Heix || brand == (uint)Heif4CharCode.Avif);
} }
} }

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

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Heif;
/// <summary> /// <summary>
/// Provides definition for a HEIF Item. /// Provides definition for a HEIF Item.
/// </summary> /// </summary>
public class HeifItem(Heif4CharCode type, uint id) internal class HeifItem(Heif4CharCode type, uint id)
{ {
/// <summary> /// <summary>
/// Gets the ID of this Item. /// Gets the ID of this Item.

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

@ -6,7 +6,7 @@ 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>
public class HeifItemLink(Heif4CharCode type, uint sourceId) internal class HeifItemLink(Heif4CharCode type, uint sourceId)
{ {
/// <summary> /// <summary>
/// Gets the type of link. /// Gets the type of link.

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

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Heif;
/// <summary> /// <summary>
/// Location within the file of an <see cref="HeifItem"/>. /// Location within the file of an <see cref="HeifItem"/>.
/// </summary> /// </summary>
public class HeifLocation(long offset, long length) internal class HeifLocation(long offset, long length)
{ {
/// <summary> /// <summary>
/// Gets the file offset of this location. /// Gets the file offset of this location.

Loading…
Cancel
Save