Browse Source

Adapt code to .NET 8

pull/2633/head
Ynse Hoornenborg 3 years ago
parent
commit
c0297a37c6
  1. 2
      src/ImageSharp/Configuration.cs
  2. 98
      src/ImageSharp/Formats/Heic/FourCharacterCode.cs
  3. 26
      src/ImageSharp/Formats/Heic/HeicBoxType.cs
  4. 335
      src/ImageSharp/Formats/Heic/HeicDecoderCore.cs
  5. 1
      src/ImageSharp/Formats/Heic/HeicEncoderCore.cs
  6. 13
      src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs
  7. 76
      src/ImageSharp/Formats/Heic/HeicItem.cs
  8. 25
      src/ImageSharp/Formats/Heic/HeicItemLink.cs
  9. 23
      src/ImageSharp/Formats/Heic/HeicItemPropertyType.cs
  10. 22
      src/ImageSharp/Formats/Heic/HeicMetaSubBoxType.cs

2
src/ImageSharp/Configuration.cs

@ -5,8 +5,8 @@ using System.Collections.Concurrent;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Heic; using SixLabors.ImageSharp.Formats.Heic;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Pbm; using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Qoi; using SixLabors.ImageSharp.Formats.Qoi;

98
src/ImageSharp/Formats/Heic/FourCharacterCode.cs

@ -1,11 +1,16 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System.Buffers.Binary;
using System.CodeDom.Compiler;
using System.Text;
namespace SixLabors.ImageSharp.Formats.Heic; namespace SixLabors.ImageSharp.Formats.Heic;
/// <summary> /// <summary>
/// Provides constants for 4 Character codes used in HEIC images. /// Provides constants for 4 Character codes used in HEIC images.
/// </summary> /// </summary>
[GeneratedCode("T4", null)]
public static class FourCharacterCode public static class FourCharacterCode
{ {
// TODO: Create T4 template for this file // TODO: Create T4 template for this file
@ -13,197 +18,202 @@ public static class FourCharacterCode
/// <summary> /// <summary>
/// File Type /// File Type
/// </summary> /// </summary>
public const uint ftyp = 0x66747970U, public const uint ftyp = 0x66747970U;
/// <summary> /// <summary>
/// Metadata container /// Metadata container
/// </summary> /// </summary>
public const uint meta = 0x6D657461U, public const uint meta = 0x6D657461U;
/// <summary> /// <summary>
/// Media Data /// Media Data
/// </summary> /// </summary>
public const uint mdat = 0x6D646174U, public const uint mdat = 0x6D646174U;
/// <summary> /// <summary>
/// Item Information Entry /// Item Information Entry
/// </summary> /// </summary>
public const uint infe = 0x696E6665U, public const uint infe = 0x696E6665U;
/// <summary> /// <summary>
/// Item Data /// Item Data
/// </summary> /// </summary>
public const uint idat = 0x69646174U, public const uint idat = 0x69646174U;
/// <summary> /// <summary>
/// Item Location /// Item Location
/// </summary> /// </summary>
public const uint iloc = 0x696C6F63U, public const uint iloc = 0x696C6F63U;
/// <summary> /// <summary>
/// EXIF metadata /// EXIF metadata
/// </summary> /// </summary>
public const uint Exif = 0x45786966U, public const uint Exif = 0x45786966U;
/// <summary> /// <summary>
/// Data Reference /// Data Reference
/// </summary> /// </summary>
public const uint dref = 0x64726566U, public const uint dref = 0x64726566U;
/// <summary> /// <summary>
/// Primary Item /// Primary Item
/// </summary> /// </summary>
public const uint pitm = 0x7069746DU, public const uint pitm = 0x7069746DU;
/// <summary> /// <summary>
/// Item Spatial Extent /// Item Spatial Extent
/// </summary> /// </summary>
public const uint ispe = 0x69737064U, public const uint ispe = 0x69737064U;
/// <summary> /// <summary>
/// Alternative text /// Alternative text
/// </summary> /// </summary>
public const uint altt = 0, // 'altt' public const uint altt = 0; // 'altt'
/// <summary> /// <summary>
/// Colour information /// Colour information
/// </summary> /// </summary>
public const uint colr = 0, // 'colr' public const uint colr = 0; // 'colr'
/// <summary> /// <summary>
/// HVC configuration /// HVC configuration
/// </summary> /// </summary>
public const uint hvcC = 0, // 'hvcC' public const uint hvcC = 0; // 'hvcC'
/// <summary> /// <summary>
/// Image Mirror /// Image Mirror
/// </summary> /// </summary>
public const uint imir = 0, // 'imir' public const uint imir = 0; // 'imir'
/// <summary> /// <summary>
/// Image Rotation /// Image Rotation
/// </summary> /// </summary>
public const uint irot = 0, // 'irot' public const uint irot = 0; // 'irot'
/// <summary> /// <summary>
/// Image Scaling /// Image Scaling
/// </summary> /// </summary>
public const uint iscl = 0, // 'iscl' public const uint iscl = 0; // 'iscl'
/// <summary> /// <summary>
/// Pixel Aspect Ration /// Pixel Aspect Ration
/// </summary> /// </summary>
public const uint pasp = 0, // 'pasp' public const uint pasp = 0; // 'pasp'
/// <summary> /// <summary>
/// Pixel Information /// Pixel Information
/// </summary> /// </summary>
public const uint pixi = 0x70697869U, public const uint pixi = 0x70697869U;
/// <summary> /// <summary>
/// Reference Location /// Reference Location
/// </summary> /// </summary>
public const uint rloc = 0, // 'rloc public const uint rloc = 0; // 'rloc
/// <summary> /// <summary>
/// User Description /// User Description
/// </summary> /// </summary>
public const uint udes = 0, // 'udes' public const uint udes = 0; // 'udes'
/// <summary> /// <summary>
/// Item Property Container /// Item Property Container
/// </summary> /// </summary>
public const uint ipco = 0, public const uint ipco = 0;
/// <summary> /// <summary>
/// Item Property Association /// Item Property Association
/// </summary> /// </summary>
public const uint ipma = 0, public const uint ipma = 0;
/// <summary> /// <summary>
/// High Efficient Image Coding /// High Efficient Image Coding
/// </summary> /// </summary>
public const uint heic = 0, public const uint heic = 0;
/// <summary> /// <summary>
/// High Efficiency Coding tile /// High Efficiency Coding tile
/// </summary> /// </summary>
public const uint hvc1 = 0, public const uint hvc1 = 0;
/// <summary> /// <summary>
/// Data Information /// Data Information
/// </summary> /// </summary>
public const uint dinf = 0, public const uint dinf = 0;
/// <summary> /// <summary>
/// Group list /// Group list
/// </summary> /// </summary>
public const uint grpl = 0, public const uint grpl = 0;
/// <summary> /// <summary>
/// Handler /// Handler
/// </summary> /// </summary>
public const uint hdlr = 0, public const uint hdlr = 0;
/// <summary>
/// Item Data
/// </summary>
public const uint idat = 0, // 'idat'
/// <summary> /// <summary>
/// Item Information /// Item Information
/// </summary> /// </summary>
public const uint iinf = 0, // 'iinf' public const uint iinf = 0; // 'iinf'
/// <summary> /// <summary>
/// Item Property /// Item Property
/// </summary> /// </summary>
public const uint iprp = 0, // 'iprp' public const uint iprp = 0; // 'iprp'
/// <summary> /// <summary>
/// Item Protection /// Item Protection
/// </summary> /// </summary>
public const uint ipro = 0, // 'ipro' public const uint ipro = 0; // 'ipro'
/// <summary> /// <summary>
/// Item Reference /// Item Reference
/// </summary> /// </summary>
public const uint iref = 0, // 'iref' public const uint iref = 0; // 'iref'
/// <summary> /// <summary>
/// Grid /// Grid
/// </summary> /// </summary>
public const uint grid = 0, // 'grid' public const uint grid = 0; // 'grid'
/// <summary> /// <summary>
/// Derived Image /// Derived Image
/// </summary> /// </summary>
public const uint dimg = 0, // 'dimg' public const uint dimg = 0; // 'dimg'
/// <summary> /// <summary>
/// Thumbnail /// Thumbnail
/// </summary> /// </summary>
public const uint thmb = 0, // 'thmb' public const uint thmb = 0; // 'thmb'
/// <summary> /// <summary>
/// Content Description /// Content Description
/// </summary> /// </summary>
public const uint cdsc = 0, // 'cdsc' public const uint cdsc = 0; // 'cdsc'
/// <summary>
/// MIME type
/// </summary>
public const uint mime = 0; // 'mime'
/// <summary>
/// URI
/// </summary>
public const uint uri = 0; // 'uri '
public static uint Parse(string code) public static uint Parse(string code)
{ {
if (code.Length != 4) if (code.Length != 4)
{ {
throw new ImageFormatException(); throw new ImageFormatException("Unbale to parse FourCC code of more than 4 characters.");
} }
Span<byte> span = Encoding.UTF8.GetBytes(code); Span<byte> span = Encoding.UTF8.GetBytes(code);
return BinaryPrimitives.ReadUInt32BigEndian(buffer); return BinaryPrimitives.ReadUInt32BigEndian(span);
} }
public static string ToString(uint fourcc) public static string ToString(uint fourcc)
{ {
Span<byte> span = stackalloc new byte[4]; Span<byte> span = stackalloc byte[4];
BinaryPrimitives.WriteUInt32BigEndian(buffer, fourcc); BinaryPrimitives.WriteUInt32BigEndian(span, fourcc);
return Encoding.UTF8.GetString(span); return Encoding.UTF8.GetString(span);
} }
} }

26
src/ImageSharp/Formats/Heic/HeicBoxType.cs

@ -1,26 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heic;
/// <summary>
/// Provides enumeration of supported ISO Base Format Box Types for HEIC.
/// </summary>
public enum HeicBoxType : uint
{
FileType = FourCharacterCode.ftyp,
Meta = FourCharacterCode.meta,
MediaData = FourCharacterCode.mdat,
ItemInfo = FourCharacterCode.infe
ItemData = FourCharacterCode.idat,
ItemLocation = FourCharacterCode.iloc,
Exif = FourCharacterCode.Exif,
ItemPropertyAssociation = FourCharacterCode.ipma,
DataReference = FourCharacterCode.dref,
PrimaryItemReference = FourCharacterCode.pitm,
ImageSpatialExtentsProperty = FourCharacterCode.ispe,
// Possible box types outside of HEIC images:
Movie = FourCharacterCode.moov,
Track = FourCharacterCode.trak,
}

335
src/ImageSharp/Formats/Heic/HeicDecoderCore.cs

@ -1,12 +1,12 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System.Diagnostics.CodeAnalysis; using System.Buffers.Binary;
using System.Text;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Formats.Heic; namespace SixLabors.ImageSharp.Formats.Heic;
@ -25,6 +25,8 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals
/// </summary> /// </summary>
private ImageMetadata? metadata; private ImageMetadata? metadata;
private uint primaryItem;
private List<HeicItem> items; private List<HeicItem> items;
private List<HeicItemLink> itemLinks; private List<HeicItemLink> itemLinks;
@ -37,6 +39,9 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals
{ {
this.Options = options; this.Options = options;
this.configuration = options.Configuration; this.configuration = options.Configuration;
this.metadata = new ImageMetadata();
this.items = new List<HeicItem>();
this.itemLinks = new List<HeicItemLink>();
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -51,19 +56,19 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals
{ {
if (!this.CheckFileTypeBox(stream)) if (!this.CheckFileTypeBox(stream))
{ {
throw new InvalidImageFormatException(); throw new ImageFormatException("Not an HEIC image.");
} }
while (!stream.Eof) while (stream.EofHitCount == 0)
{ {
var length = ReadBoxHeader(stream, out var boxType); long length = this.ReadBoxHeader(stream, out var boxType);
switch (boxType) switch (boxType)
{ {
case HeicBoxType.Meta: case FourCharacterCode.meta:
ParseMetadata(stream, length); this.ParseMetadata(stream, length);
break; break;
case HeicBoxType.MediaData: case FourCharacterCode.mdat:
ParseMediaData(stream, length); this.ParseMediaData(stream, length);
break; break;
default: default:
throw new ImageFormatException($"Unknown box type of '{FourCharacterCode.ToString(boxType)}'"); throw new ImageFormatException($"Unknown box type of '{FourCharacterCode.ToString(boxType)}'");
@ -82,63 +87,64 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals
{ {
this.CheckFileTypeBox(stream); this.CheckFileTypeBox(stream);
while (!stream.Eof) while (stream.EofHitCount == 0)
{ {
var length = ReadBoxHeader(stream, out var boxType); long length = this.ReadBoxHeader(stream, out uint boxType);
var buffer = new byte[length];
stream.Read(buffer);
switch (boxType) switch (boxType)
{ {
case HeicBoxType.Metadata: case FourCharacterCode.meta:
ParseMetadata(buffer); this.ParseMetadata(stream, length);
break; break;
default: default:
// Silently skip all other box types. // Silently skip all other box types.
break; break;
} }
} }
return new ImageInfo(new PixelTypeInfo(bitsPerPixel), new(this.pixelSize.Width, this.pixelSize.Height), this.metadata); return new ImageInfo(new PixelTypeInfo(bitsPerPixel), new(this.pixelSize.Width, this.pixelSize.Height), this.metadata);
} }
private bool CheckFileTypeBox(BufferedReadStream stream) private bool CheckFileTypeBox(BufferedReadStream stream)
{ {
var boxLength = ReadBoxHeader(stream, out var boxType); var boxLength = this.ReadBoxHeader(stream, out var boxType);
Span<byte> buffer = stackalloc new byte[boxLength]; Span<byte> buffer = stackalloc byte[(int)boxLength];
stream.Read(buffer); stream.Read(buffer);
var majorBrand = BinaryPrimitives.ReadUInt32BigEndian(buf); var majorBrand = BinaryPrimitives.ReadUInt32BigEndian(buffer);
// TODO: Interpret minorVersion and compatible brands. // TODO: Interpret minorVersion and compatible brands.
return boxTypepe == HeicBoxType.FileType && majorBrand == HeicConstants.HeicBrand; return boxType == FourCharacterCode.ftyp && majorBrand == FourCharacterCode.heic;
} }
private ulong ReadBoxHeader(BufferedReadStream stream, out uint boxType) private long ReadBoxHeader(BufferedReadStream stream, out uint boxType)
{ {
// Read 4 bytes of length of box // Read 4 bytes of length of box
Span<byte> buf = stackalloc new byte[8]; Span<byte> buf = stackalloc byte[8];
stream.Read(buf); stream.Read(buf);
ulong boxSize = BinaryPrimitives.ReadUInt32BigEndian(buf); long boxSize = BinaryPrimitives.ReadUInt32BigEndian(buf);
ulong headerSize = 8; long headerSize = 8;
// Read 4 bytes of box type // Read 4 bytes of box type
boxType = BinaryPrimitives.ReadUInt32BigEndian(buf.Slice(4)); boxType = BinaryPrimitives.ReadUInt32BigEndian(buf[4..]);
if (boxSize == 1) if (boxSize == 1)
{ {
stream.Read(buf); stream.Read(buf);
boxSize = BinaryPrimitives.ReadUInt64BigEndian(buf); boxSize = (long)BinaryPrimitives.ReadUInt64BigEndian(buf);
headerSize += 8UL; headerSize += 8;
} }
return boxSize - headerSize; return boxSize - headerSize;
} }
private uint ParseBoxHeader(Span<byte> buffer, out ulong length, out uint boxType) private static int ParseBoxHeader(Span<byte> buffer, out long length, out uint boxType)
{ {
ulong boxSize = BinaryPrimitives.ReadUInt32BigEndian(buffer); long boxSize = BinaryPrimitives.ReadUInt32BigEndian(buffer);
ulong bytesRead = 4; int bytesRead = 4;
boxType = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); boxType = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]);
bytesRead += 4; bytesRead += 4;
if (boxSize == 1) if (boxSize == 1)
{ {
boxSize = BinaryPrimitives.ReadUInt64BigEndian(buffer.Slice(bytesRead)); boxSize = (long)BinaryPrimitives.ReadUInt64BigEndian(buffer[bytesRead..]);
bytesRead += 8; bytesRead += 8;
} }
@ -146,30 +152,32 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals
return bytesRead; return bytesRead;
} }
private void ParseMetadata(BufferedReadStream stream, uint boxLength) private void ParseMetadata(BufferedReadStream stream, long boxLength)
{ {
var endPosition = stream.Position + boxLength; long endPosition = stream.Position + boxLength;
while (stream.Position < endPosition) while (stream.Position < endPosition)
{ {
var length = ReadBoxHeader(stream, out var boxType); long length = this.ReadBoxHeader(stream, out uint boxType);
switch (boxType) switch (boxType)
{ {
case HeicMetaSubBoxType.ItemProperty: case FourCharacterCode.iprp:
ParseItemPropertyContainer(stream, length); this.ParseItemPropertyContainer(stream, length);
break; break;
case HeicMetaSubBoxType.ItemInfo: case FourCharacterCode.iinf:
ParseItemInfo(stream, length); this.ParseItemInfo(stream, length);
break; break;
case HeicMetaSubBoxType.ItemReference: case FourCharacterCode.iref:
ParseItemReference(stream, length); this.ParseItemReference(stream, length);
break; break;
case HeicMetaSubBoxType.DataInformation: case FourCharacterCode.pitm:
case HeicMetaSubBoxType.GroupsList: this.ParsePrimaryItem(stream, length);
case HeicMetaSubBoxType.Handler: break;
case HeicMetaSubBoxType.ItemData: case FourCharacterCode.dinf:
case HeicMetaSubBoxType.ItemLocation: case FourCharacterCode.grpl:
case HeicMetaSubBoxType.ItemProtection: case FourCharacterCode.hdlr:
case HeicMetaSubBoxType.PrimaryItem: case FourCharacterCode.idat:
case FourCharacterCode.iloc:
case FourCharacterCode.ipro:
// TODO: Implement // TODO: Implement
break; break;
default: default:
@ -178,63 +186,65 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals
} }
} }
private void ParseItemInfo(BufferedReadStream stream, uint boxLength) private void ParseItemInfo(BufferedReadStream stream, long boxLength)
{ {
Span<byte> buffer = new byte[length]; Span<byte> buffer = stackalloc byte[(int)boxLength];
stream.Read(buffer); stream.Read(buffer);
uint entryCount; uint entryCount;
int bytesRead = 0; int bytesRead = 0;
if (buffer[bytesRead] == 0) if (buffer[bytesRead] == 0)
{ {
bytesRead += 4; bytesRead += 4;
entryCount = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); entryCount = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]);
bytesRead += 2; bytesRead += 2;
} }
else else
{ {
bytesRead += 4; bytesRead += 4;
entryCount = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); entryCount = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]);
bytesRead += 4; bytesRead += 4;
} }
for(uint i = 0; i < entryCount; i++) for (uint i = 0; i < entryCount; i++)
{ {
bytesRead += ParseBoxHeader(out var subBoxLength, out var boxType); bytesRead += this.ParseItemInfoEntry(buffer[bytesRead..]);
ParseItemInfoEntry(buffer.Slice(bytesRead, subBoxLength));
bytesRead += subBoxLength;
} }
} }
private void ParseItemInfoEntry(Span<byte> buffer, uint boxLength) private int ParseItemInfoEntry(Span<byte> buffer)
{ {
int bytesRead = 0; int bytesRead = ParseBoxHeader(buffer, out long boxLength, out uint boxType);
var version = buffer[bytesRead]; byte version = buffer[bytesRead];
bytesRead += 4; bytesRead += 4;
var item = new HeicItem(); HeicItem? item = null;
if (version == 0 || version == 1) if (version is 0 or 1)
{ {
item.Id = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); uint itemId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]);
bytesRead += 2; bytesRead += 2;
item.ProtectionIndex = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); item = new HeicItem(boxType, itemId);
// Skip Protection Index, not sure what that means...
bytesRead += 2; bytesRead += 2;
item.Name = ReadNullTerminatedString(buffer.Slice(bytesRead)); item.Name = ReadNullTerminatedString(buffer[bytesRead..]);
bytesRead += item.Name.Length + 1; bytesRead += item.Name.Length + 1;
item.ContentType = ReadNullTerminatedString(buffer.Slice(bytesRead)); item.ContentType = ReadNullTerminatedString(buffer[bytesRead..]);
bytesRead += item.ContentType.Length + 1; bytesRead += item.ContentType.Length + 1;
// Optional field. // Optional field.
if (bytesRead < boxLength) if (bytesRead < boxLength)
{ {
item.ContentEncoding = ReadNullTerminatedString(buffer.Slice(bytesRead)); item.ContentEncoding = ReadNullTerminatedString(buffer[bytesRead..]);
bytesRead += item.ContentEncoding.Length + 1; bytesRead += item.ContentEncoding.Length + 1;
} }
} }
if (version == 1) if (version == 1)
{ {
// Optional fields. // Optional fields.
if (bytesRead < boxLength) if (bytesRead < boxLength)
{ {
item.ExtensionType = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); item!.ExtensionType = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]);
bytesRead += 4; bytesRead += 4;
} }
@ -246,132 +256,162 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals
if (version >= 2) if (version >= 2)
{ {
if (getVersion() == 2) uint itemId = 0U;
if (version == 2)
{ {
item.Id = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); itemId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]);
bytesRead += 2; bytesRead += 2;
} }
else if (getVersion() == 3) else if (version == 3)
{ {
item.Id = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); itemId = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]);
bytesRead += 4; bytesRead += 4;
} }
item.ProtectionIndex = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); // Skip Protection Index, not sure what that means...
bytesRead += 2; bytesRead += 2;
item.Type = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); uint itemType = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]);
bytesRead += 4; bytesRead += 4;
item.Name = ReadNullTerminatedString(buffer.Slice(bytesRead)); item = new HeicItem(itemId, itemType);
item.Name = ReadNullTerminatedString(buffer[bytesRead..]);
bytesRead += item.Name.Length + 1; bytesRead += item.Name.Length + 1;
if (item.Type == "mime") if (item.Type == FourCharacterCode.mime)
{ {
item.ContentType = ReadNullTerminatedString(buffer.Slice(bytesRead)); item.ContentType = ReadNullTerminatedString(buffer[bytesRead..]);
bytesRead += item.ContentType.Length + 1; bytesRead += item.ContentType.Length + 1;
// Optional field. // Optional field.
if (bytesRead < boxLength) if (bytesRead < boxLength)
{ {
item.ContentEncoding = ReadNullTerminatedString(buffer.Slice(bytesRead)); item.ContentEncoding = ReadNullTerminatedString(buffer[bytesRead..]);
bytesRead += item.ContentEncoding.Length + 1; bytesRead += item.ContentEncoding.Length + 1;
} }
} }
else if (item.Type == "uri ") else if (item.Type == FourCharacterCode.uri)
{ {
item.UriType = ReadNullTerminatedString(buffer.Slice(bytesRead)); item.UriType = ReadNullTerminatedString(buffer[bytesRead..]);
bytesRead += item.ContentEncoding.Length + 1; bytesRead += item.UriType.Length + 1;
} }
} }
return bytesRead;
} }
private void ParseItemReference(BufferedReadStream stream, uint boxLength) private void ParseItemReference(BufferedReadStream stream, long boxLength)
{ {
Span<byte> buffer = new byte[length]; Span<byte> buffer = new byte[boxLength];
stream.Read(buffer); stream.Read(buffer);
int bytesRead = 0; int bytesRead = 0;
bool largeIds = buffer[bytesRead] != 0; bool largeIds = buffer[bytesRead] != 0;
bytesRead += 4; bytesRead += 4;
while(bytesRead < boxLength) while (bytesRead < boxLength)
{ {
ParseBoxHeader(buffer.Slice(bytesRead), out var subBoxLength, out var linkType); ParseBoxHeader(buffer[bytesRead..], out long subBoxLength, out uint linkType);
var link = new HeicItemLink(); uint sourceId;
link.Type = linkType;
if (largeIds) if (largeIds)
{ {
link.Source = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); sourceId = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]);
bytesRead += 4; bytesRead += 4;
} }
else else
{ {
link.Source = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); sourceId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]);
bytesRead += 2; bytesRead += 2;
} }
var count = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); HeicItemLink link = new(linkType, sourceId);
int count = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]);
bytesRead += 2; bytesRead += 2;
for(uint i = 0; i < count; i++) for (uint i = 0; i < count; i++)
{ {
uint destId; uint destId;
if (largeIds) if (largeIds)
{ {
destId = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead)); destId = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]);
bytesRead += 4; bytesRead += 4;
} }
else else
{ {
destId = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead)); destId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]);
bytesRead += 2; bytesRead += 2;
} }
link.Destinations.Add(destId);
link.DestinationIds.Add(destId);
} }
itemLinks.Add(link); this.itemLinks!.Add(link);
} }
} }
private void ParseItemPropertyContainer(BufferedReadStream stream, uint boxLength) private void ParsePrimaryItem(BufferedReadStream stream, long boxLength)
{ {
var containerLength = ReadBoxHeader(stream, out var containerType); // BoxLength should be 6 or 8.
Span<byte> buffer = stackalloc byte[(int)boxLength];
stream.Read(buffer);
byte version = buffer[0];
if (version == 0)
{
this.primaryItem = BinaryPrimitives.ReadUInt16BigEndian(buffer[4..]);
}
else
{
this.primaryItem = BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]);
}
}
private void ParseItemPropertyContainer(BufferedReadStream stream, long boxLength)
{
// Cannot use Dictionary here, Properties can have multiple instances with the same key.
List<KeyValuePair<uint, object>> properties = new();
long containerLength = this.ReadBoxHeader(stream, out uint containerType);
if (containerType == FourCharacterCode.ipco) if (containerType == FourCharacterCode.ipco)
{ {
// Parse Item Property Container, which is just an array of preperty boxes. // Parse Item Property Container, which is just an array of preperty boxes.
var endPosition = stream.Position + containerLength; long endPosition = stream.Position + containerLength;
while (stream.Position < endPosition) while (stream.Position < endPosition)
{ {
var length = ReadBoxHeader(stream, out var boxType); int length = (int)this.ReadBoxHeader(stream, out uint boxType);
Span<byte> buffer = stackalloc byte[length];
switch (boxType) switch (boxType)
{ {
case HeicItemPropertyType.ImageSpatialExtents: case FourCharacterCode.ispe:
// Length should be 12. // Length should be 12.
Span<byte> buffer = stackalloc new byte[length];
stream.Read(buffer); stream.Read(buffer);
// Skip over version (8 bits) and flags (24 bits). // Skip over version (8 bits) and flags (24 bits).
var width = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(4)); uint width = BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]);
var height = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(8)); uint height = BinaryPrimitives.ReadUInt32BigEndian(buffer[8..]);
properties.Add(new KeyValuePair<uint, object>(FourCharacterCode.ispe, new uint[] { width, height }));
break; break;
case HeicItemPropertyType.PixelAspectRatio: case FourCharacterCode.pasp:
// Length should be 8. // Length should be 8.
Span<byte> buffer = stackalloc new byte[length];
stream.Read(buffer); stream.Read(buffer);
var horizontalSpacing = BinaryPrimitives.ReadUInt32BigEndian(buffer); uint horizontalSpacing = BinaryPrimitives.ReadUInt32BigEndian(buffer);
var verticalSpacing = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(4)); uint verticalSpacing = BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]);
properties.Add(new KeyValuePair<uint, object>(FourCharacterCode.pasp, new uint[] { horizontalSpacing, verticalSpacing }));
break; break;
case HeicItemPropertyType.PixelInformation: case FourCharacterCode.pixi:
Span<byte> buffer = stackalloc new byte[length];
stream.Read(buffer); stream.Read(buffer);
// Skip over version (8 bits) and flags (24 bits). // Skip over version (8 bits) and flags (24 bits).
var channelCount = buffer[4]; int channelCount = buffer[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[i]; bitsPerPixel += buffer[offset + i];
} }
properties.Add(new KeyValuePair<uint, object>(FourCharacterCode.pixi, new int[] { channelCount, bitsPerPixel }));
break; break;
case HeicItemPropertyType.AcessibilityText: case FourCharacterCode.altt:
case HeicItemPropertyType.ImageMirror: case FourCharacterCode.imir:
case HeicItemPropertyType.ImageRotation: case FourCharacterCode.irot:
case HeicItemPropertyType.ImageScaling: case FourCharacterCode.iscl:
case HeicItemPropertyType.RelativeLocation: case FourCharacterCode.rloc:
case HeicItemPropertyType.UserDescription; case FourCharacterCode.udes:
// TODO: Implement // TODO: Implement
break; break;
default: default:
@ -382,42 +422,49 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals
else if (containerType == FourCharacterCode.ipma) else if (containerType == FourCharacterCode.ipma)
{ {
// Parse Item Property Association // Parse Item Property Association
Span<byte> buffer = stackalloc byte[(int)boxLength];
byte version = buffer[0];
byte flags = buffer[3];
int itemId;
int bytesRead = 4;
if (version < 1)
{
itemId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]);
bytesRead += 2;
}
else
{
itemId = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]);
bytesRead += 4;
}
int associationCount = buffer[bytesRead++];
for (int i = 0; i < associationCount; i++)
{
uint propId;
if (flags == 1)
{
propId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]) & 0x4FFFU;
bytesRead += 2;
}
else
{
propId = buffer[bytesRead++] & 0x4FU;
}
this.items![itemId].SetProperty(properties[(int)propId]);
}
} }
} }
private void ParseMediaData(BufferedReadStream stream, uint boxLength) private void ParseMediaData(BufferedReadStream stream, long boxLength)
{ {
// TODO: Implement // TODO: Implement
} }
/// <summary> private static string ReadNullTerminatedString(Span<byte> span)
/// Forwards the stream to just past the Start of NAL marker.
/// </summary>
private void FindStartOfNal(BufferedReadStream stream)
{ {
uint i = stream.Position; Span<byte> bytes = span[..span.IndexOf((byte)0)];
uint length = 0; return Encoding.UTF8.GetString(bytes);
var dataLength = stream.Length;
while (i < streamLength)
{
var current = stream.ReadByte();
if (current == 0)
{
length++;
}
else if (length > 1 && current == 1)
{
// Found the marker !
//length++;
break;
}
else
{
// False alarm, resetting...
length = 0;
}
i++;
}
} }
} }

1
src/ImageSharp/Formats/Heic/HeicEncoderCore.cs

@ -47,7 +47,6 @@ internal sealed class HeicEncoderCore : IImageEncoderInternals
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
// TODO: Implement // TODO: Implement
stream.Flush(); stream.Flush();
} }
} }

13
src/ImageSharp/Formats/Heic/HeicImageFormatDetector.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Six Labors Split License. // Licensed under the Six Labors Split License.
using System.Buffers.Binary;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
namespace SixLabors.ImageSharp.Formats.Heic; namespace SixLabors.ImageSharp.Formats.Heic;
@ -11,8 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Heic;
public sealed class HeicImageFormatDetector : IImageFormatDetector public sealed class HeicImageFormatDetector : IImageFormatDetector
{ {
/// <inheritdoc/> /// <inheritdoc/>
int HeaderSize => 12; public int HeaderSize => 12;
/// <inheritdoc/> /// <inheritdoc/>
public bool TryDetectFormat(ReadOnlySpan<byte> header, [NotNullWhen(true)] out IImageFormat? format) public bool TryDetectFormat(ReadOnlySpan<byte> header, [NotNullWhen(true)] out IImageFormat? format)
@ -21,10 +21,7 @@ public sealed class HeicImageFormatDetector : IImageFormatDetector
return format != null; return format != null;
} }
private static bool IsSupportedFileFormat(ReadOnlySpan<byte> header) private static bool IsSupportedFileFormat(ReadOnlySpan<byte> header) =>
{ BinaryPrimitives.ReadUInt32BigEndian(header.Slice(4)) == FourCharacterCode.ftyp &&
return BinaryPrimitives.ReadUInt32BigEndian(header.Slice(8)) == FourCharacterCode.heic;
BinaryPrimitives.ReadUInt32BigEndian(header.Slice(4)) == FourCharacterCode.ftyp &&
BinaryPrimitives.ReadUInt32BigEndian(header.Slice(8)) == FourCharacterCode.heic
}
} }

76
src/ImageSharp/Formats/Heic/HeicItem.cs

@ -3,30 +3,62 @@
namespace SixLabors.ImageSharp.Formats.Heic; namespace SixLabors.ImageSharp.Formats.Heic;
public enum HeicItemType
{
Hvc1,
Grid,
Exif
}
public class HeicItemLink
{
public uint Type;
public HeicItem Source;
public List<HeicItem> Destinations = new List<HeicItem>();
}
/// <summary> /// <summary>
/// Provides definition for a HEIC Item. /// Provides definition for a HEIC Item.
/// </summary> /// </summary>
public class HeicItem public class HeicItem(uint type, uint id)
{ {
public uint Id; /// <summary>
public HeicItemType type; /// Gets the ID of this Item.
public string Name; /// </summary>
public string ContentType; public uint Id { get; } = id;
public string ContentEncoding;
public uint ExtensionType; /// <summary>
public string UriType; /// Gets the type of this Item.
/// </summary>
public uint Type { get; } = type;
/// <summary>
/// Gets or sets the name of this item.
/// </summary>
public string? Name { get; set; }
/// <summary>
/// Gets or sets the Content Type of this item.
/// </summary>
public string? ContentType { get; set; }
/// <summary>
/// Gets or sets the Content Encoding of this item.
/// </summary>
public string? ContentEncoding { get; set; }
/// <summary>
/// Gets or sets the type of extension of this item.
/// </summary>
public uint ExtensionType { get; set; }
/// <summary>
/// Gets or sets the URI of this item.
/// </summary>
public string? UriType { get; set; }
/// <summary>
/// Sets a property on this item.
/// </summary>
public void SetProperty(KeyValuePair<uint, object> pair)
{
switch (pair.Key)
{
case FourCharacterCode.ispe:
// Set image extents
break;
case FourCharacterCode.pasp:
// Set pixel aspact ratio
break;
case FourCharacterCode.pixi:
// Set pixel information
break;
}
}
} }

25
src/ImageSharp/Formats/Heic/HeicItemLink.cs

@ -0,0 +1,25 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heic;
/// <summary>
/// Link between <see cref="HeicItem"/> instances within the same HEIC file.
/// </summary>
public class HeicItemLink(uint type, uint sourceId)
{
/// <summary>
/// Gets the type of link.
/// </summary>
public uint Type { get; } = type;
/// <summary>
/// Gets the ID of the source item of this link.
/// </summary>
public uint SourceId { get; } = sourceId;
/// <summary>
/// Gets the destination item IDs of this link.
/// </summary>
public List<uint> DestinationIds { get; } = new List<uint>();
}

23
src/ImageSharp/Formats/Heic/HeicItemPropertyType.cs

@ -1,23 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heic;
/// <summary>
/// Provides enumeration of supported Item Property Types for HEIC.
/// </summary>
public enum HeicItemPropertyType : uint
{
Invalid = 0,
AcessibilityText = FourCharacterCode.altt,
Colour = FourCharacterCode.colr,
HvcConfiguration = FourCharacterCode.hvcC,
ImageMirror = FourCharacterCode.imir,
ImageRotation = FourCharacterCode.irot,
ImageScaling = FourCharacterCode.iscl,
ImageSpatialExtents = FourCharacterCode.ispe,
PixelAspectRatio = FourCharacterCode.pasp,
PixelInformation = FourCharacterCode.pixi,
RelativeLocation = FourCharacterCode.rloc,
UserDescription = FourCharacterCode.udes,
}

22
src/ImageSharp/Formats/Heic/HeicMetaSubBoxType.cs

@ -1,22 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
namespace SixLabors.ImageSharp.Formats.Heic;
/// <summary>
/// Provides enumeration of supported sub type boxes within the 'meta' box for HEIC.
/// </summary>
public enum HeicMetaSubBoxType : uint
{
Invalid = 0,
DataInformation = 0, // 'dinf'
GroupsList = 0, // 'grpl'
Handler = 0, // 'hdlr'
ItemData = 0, // 'idat'
ItemInfo = 0, // 'iinf'
ItemLocation = 0, // 'iloc'
ItemProperty = 0, // 'iprp'
ItemProtection = 0, // 'ipro'
ItemReference = 0, // 'iref'
PrimaryItem = 0, // 'pitm'
}
Loading…
Cancel
Save