Browse Source

Adapt code to .NET 8

pull/2633/head
Ynse Hoornenborg 2 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.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Heic;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Pbm;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Qoi;

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

@ -1,11 +1,16 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers.Binary;
using System.CodeDom.Compiler;
using System.Text;
namespace SixLabors.ImageSharp.Formats.Heic;
/// <summary>
/// Provides constants for 4 Character codes used in HEIC images.
/// </summary>
[GeneratedCode("T4", null)]
public static class FourCharacterCode
{
// TODO: Create T4 template for this file
@ -13,197 +18,202 @@ public static class FourCharacterCode
/// <summary>
/// File Type
/// </summary>
public const uint ftyp = 0x66747970U,
public const uint ftyp = 0x66747970U;
/// <summary>
/// Metadata container
/// </summary>
public const uint meta = 0x6D657461U,
public const uint meta = 0x6D657461U;
/// <summary>
/// Media Data
/// </summary>
public const uint mdat = 0x6D646174U,
public const uint mdat = 0x6D646174U;
/// <summary>
/// Item Information Entry
/// </summary>
public const uint infe = 0x696E6665U,
public const uint infe = 0x696E6665U;
/// <summary>
/// Item Data
/// </summary>
public const uint idat = 0x69646174U,
public const uint idat = 0x69646174U;
/// <summary>
/// Item Location
/// </summary>
public const uint iloc = 0x696C6F63U,
public const uint iloc = 0x696C6F63U;
/// <summary>
/// EXIF metadata
/// </summary>
public const uint Exif = 0x45786966U,
public const uint Exif = 0x45786966U;
/// <summary>
/// Data Reference
/// </summary>
public const uint dref = 0x64726566U,
public const uint dref = 0x64726566U;
/// <summary>
/// Primary Item
/// </summary>
public const uint pitm = 0x7069746DU,
public const uint pitm = 0x7069746DU;
/// <summary>
/// Item Spatial Extent
/// </summary>
public const uint ispe = 0x69737064U,
public const uint ispe = 0x69737064U;
/// <summary>
/// Alternative text
/// </summary>
public const uint altt = 0, // 'altt'
public const uint altt = 0; // 'altt'
/// <summary>
/// Colour information
/// </summary>
public const uint colr = 0, // 'colr'
public const uint colr = 0; // 'colr'
/// <summary>
/// HVC configuration
/// </summary>
public const uint hvcC = 0, // 'hvcC'
public const uint hvcC = 0; // 'hvcC'
/// <summary>
/// Image Mirror
/// </summary>
public const uint imir = 0, // 'imir'
public const uint imir = 0; // 'imir'
/// <summary>
/// Image Rotation
/// </summary>
public const uint irot = 0, // 'irot'
public const uint irot = 0; // 'irot'
/// <summary>
/// Image Scaling
/// </summary>
public const uint iscl = 0, // 'iscl'
public const uint iscl = 0; // 'iscl'
/// <summary>
/// Pixel Aspect Ration
/// </summary>
public const uint pasp = 0, // 'pasp'
public const uint pasp = 0; // 'pasp'
/// <summary>
/// Pixel Information
/// </summary>
public const uint pixi = 0x70697869U,
public const uint pixi = 0x70697869U;
/// <summary>
/// Reference Location
/// </summary>
public const uint rloc = 0, // 'rloc
public const uint rloc = 0; // 'rloc
/// <summary>
/// User Description
/// </summary>
public const uint udes = 0, // 'udes'
public const uint udes = 0; // 'udes'
/// <summary>
/// Item Property Container
/// </summary>
public const uint ipco = 0,
public const uint ipco = 0;
/// <summary>
/// Item Property Association
/// </summary>
public const uint ipma = 0,
public const uint ipma = 0;
/// <summary>
/// High Efficient Image Coding
/// </summary>
public const uint heic = 0,
public const uint heic = 0;
/// <summary>
/// High Efficiency Coding tile
/// </summary>
public const uint hvc1 = 0,
public const uint hvc1 = 0;
/// <summary>
/// Data Information
/// </summary>
public const uint dinf = 0,
public const uint dinf = 0;
/// <summary>
/// Group list
/// </summary>
public const uint grpl = 0,
public const uint grpl = 0;
/// <summary>
/// Handler
/// </summary>
public const uint hdlr = 0,
/// <summary>
/// Item Data
/// </summary>
public const uint idat = 0, // 'idat'
public const uint hdlr = 0;
/// <summary>
/// Item Information
/// </summary>
public const uint iinf = 0, // 'iinf'
public const uint iinf = 0; // 'iinf'
/// <summary>
/// Item Property
/// </summary>
public const uint iprp = 0, // 'iprp'
public const uint iprp = 0; // 'iprp'
/// <summary>
/// Item Protection
/// </summary>
public const uint ipro = 0, // 'ipro'
public const uint ipro = 0; // 'ipro'
/// <summary>
/// Item Reference
/// </summary>
public const uint iref = 0, // 'iref'
public const uint iref = 0; // 'iref'
/// <summary>
/// Grid
/// </summary>
public const uint grid = 0, // 'grid'
public const uint grid = 0; // 'grid'
/// <summary>
/// Derived Image
/// </summary>
public const uint dimg = 0, // 'dimg'
public const uint dimg = 0; // 'dimg'
/// <summary>
/// Thumbnail
/// </summary>
public const uint thmb = 0, // 'thmb'
public const uint thmb = 0; // 'thmb'
/// <summary>
/// Content Description
/// </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)
{
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);
return BinaryPrimitives.ReadUInt32BigEndian(buffer);
return BinaryPrimitives.ReadUInt32BigEndian(span);
}
public static string ToString(uint fourcc)
{
Span<byte> span = stackalloc new byte[4];
BinaryPrimitives.WriteUInt32BigEndian(buffer, fourcc);
Span<byte> span = stackalloc byte[4];
BinaryPrimitives.WriteUInt32BigEndian(span, fourcc);
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.
// 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.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Formats.Heic;
@ -25,6 +25,8 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals
/// </summary>
private ImageMetadata? metadata;
private uint primaryItem;
private List<HeicItem> items;
private List<HeicItemLink> itemLinks;
@ -37,6 +39,9 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals
{
this.Options = options;
this.configuration = options.Configuration;
this.metadata = new ImageMetadata();
this.items = new List<HeicItem>();
this.itemLinks = new List<HeicItemLink>();
}
/// <inheritdoc/>
@ -51,19 +56,19 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals
{
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)
{
case HeicBoxType.Meta:
ParseMetadata(stream, length);
case FourCharacterCode.meta:
this.ParseMetadata(stream, length);
break;
case HeicBoxType.MediaData:
ParseMediaData(stream, length);
case FourCharacterCode.mdat:
this.ParseMediaData(stream, length);
break;
default:
throw new ImageFormatException($"Unknown box type of '{FourCharacterCode.ToString(boxType)}'");
@ -82,63 +87,64 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals
{
this.CheckFileTypeBox(stream);
while (!stream.Eof)
while (stream.EofHitCount == 0)
{
var length = ReadBoxHeader(stream, out var boxType);
var buffer = new byte[length];
stream.Read(buffer);
long length = this.ReadBoxHeader(stream, out uint boxType);
switch (boxType)
{
case HeicBoxType.Metadata:
ParseMetadata(buffer);
case FourCharacterCode.meta:
this.ParseMetadata(stream, length);
break;
default:
// Silently skip all other box types.
break;
}
}
return new ImageInfo(new PixelTypeInfo(bitsPerPixel), new(this.pixelSize.Width, this.pixelSize.Height), this.metadata);
}
private bool CheckFileTypeBox(BufferedReadStream stream)
{
var boxLength = ReadBoxHeader(stream, out var boxType);
Span<byte> buffer = stackalloc new byte[boxLength];
var boxLength = this.ReadBoxHeader(stream, out var boxType);
Span<byte> buffer = stackalloc byte[(int)boxLength];
stream.Read(buffer);
var majorBrand = BinaryPrimitives.ReadUInt32BigEndian(buf);
var majorBrand = BinaryPrimitives.ReadUInt32BigEndian(buffer);
// 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
Span<byte> buf = stackalloc new byte[8];
Span<byte> buf = stackalloc byte[8];
stream.Read(buf);
ulong boxSize = BinaryPrimitives.ReadUInt32BigEndian(buf);
ulong headerSize = 8;
long boxSize = BinaryPrimitives.ReadUInt32BigEndian(buf);
long headerSize = 8;
// Read 4 bytes of box type
boxType = BinaryPrimitives.ReadUInt32BigEndian(buf.Slice(4));
boxType = BinaryPrimitives.ReadUInt32BigEndian(buf[4..]);
if (boxSize == 1)
{
stream.Read(buf);
boxSize = BinaryPrimitives.ReadUInt64BigEndian(buf);
headerSize += 8UL;
boxSize = (long)BinaryPrimitives.ReadUInt64BigEndian(buf);
headerSize += 8;
}
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);
ulong bytesRead = 4;
boxType = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead));
long boxSize = BinaryPrimitives.ReadUInt32BigEndian(buffer);
int bytesRead = 4;
boxType = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]);
bytesRead += 4;
if (boxSize == 1)
{
boxSize = BinaryPrimitives.ReadUInt64BigEndian(buffer.Slice(bytesRead));
boxSize = (long)BinaryPrimitives.ReadUInt64BigEndian(buffer[bytesRead..]);
bytesRead += 8;
}
@ -146,30 +152,32 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals
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)
{
var length = ReadBoxHeader(stream, out var boxType);
long length = this.ReadBoxHeader(stream, out uint boxType);
switch (boxType)
{
case HeicMetaSubBoxType.ItemProperty:
ParseItemPropertyContainer(stream, length);
case FourCharacterCode.iprp:
this.ParseItemPropertyContainer(stream, length);
break;
case HeicMetaSubBoxType.ItemInfo:
ParseItemInfo(stream, length);
case FourCharacterCode.iinf:
this.ParseItemInfo(stream, length);
break;
case HeicMetaSubBoxType.ItemReference:
ParseItemReference(stream, length);
case FourCharacterCode.iref:
this.ParseItemReference(stream, length);
break;
case HeicMetaSubBoxType.DataInformation:
case HeicMetaSubBoxType.GroupsList:
case HeicMetaSubBoxType.Handler:
case HeicMetaSubBoxType.ItemData:
case HeicMetaSubBoxType.ItemLocation:
case HeicMetaSubBoxType.ItemProtection:
case HeicMetaSubBoxType.PrimaryItem:
case FourCharacterCode.pitm:
this.ParsePrimaryItem(stream, length);
break;
case FourCharacterCode.dinf:
case FourCharacterCode.grpl:
case FourCharacterCode.hdlr:
case FourCharacterCode.idat:
case FourCharacterCode.iloc:
case FourCharacterCode.ipro:
// TODO: Implement
break;
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);
uint entryCount;
int bytesRead = 0;
if (buffer[bytesRead] == 0)
{
bytesRead += 4;
entryCount = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead));
entryCount = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]);
bytesRead += 2;
}
else
{
bytesRead += 4;
entryCount = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead));
entryCount = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]);
bytesRead += 4;
}
for(uint i = 0; i < entryCount; i++)
for (uint i = 0; i < entryCount; i++)
{
bytesRead += ParseBoxHeader(out var subBoxLength, out var boxType);
ParseItemInfoEntry(buffer.Slice(bytesRead, subBoxLength));
bytesRead += subBoxLength;
bytesRead += this.ParseItemInfoEntry(buffer[bytesRead..]);
}
}
private void ParseItemInfoEntry(Span<byte> buffer, uint boxLength)
private int ParseItemInfoEntry(Span<byte> buffer)
{
int bytesRead = 0;
var version = buffer[bytesRead];
int bytesRead = ParseBoxHeader(buffer, out long boxLength, out uint boxType);
byte version = buffer[bytesRead];
bytesRead += 4;
var item = new HeicItem();
if (version == 0 || version == 1)
HeicItem? item = null;
if (version is 0 or 1)
{
item.Id = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead));
uint itemId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]);
bytesRead += 2;
item.ProtectionIndex = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead));
item = new HeicItem(boxType, itemId);
// Skip Protection Index, not sure what that means...
bytesRead += 2;
item.Name = ReadNullTerminatedString(buffer.Slice(bytesRead));
item.Name = ReadNullTerminatedString(buffer[bytesRead..]);
bytesRead += item.Name.Length + 1;
item.ContentType = ReadNullTerminatedString(buffer.Slice(bytesRead));
item.ContentType = ReadNullTerminatedString(buffer[bytesRead..]);
bytesRead += item.ContentType.Length + 1;
// Optional field.
if (bytesRead < boxLength)
{
item.ContentEncoding = ReadNullTerminatedString(buffer.Slice(bytesRead));
item.ContentEncoding = ReadNullTerminatedString(buffer[bytesRead..]);
bytesRead += item.ContentEncoding.Length + 1;
}
}
if (version == 1)
{
// Optional fields.
if (bytesRead < boxLength)
{
item.ExtensionType = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead));
item!.ExtensionType = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]);
bytesRead += 4;
}
@ -246,132 +256,162 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals
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;
}
else if (getVersion() == 3)
else if (version == 3)
{
item.Id = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead));
itemId = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]);
bytesRead += 4;
}
item.ProtectionIndex = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead));
// Skip Protection Index, not sure what that means...
bytesRead += 2;
item.Type = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead));
uint itemType = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]);
bytesRead += 4;
item.Name = ReadNullTerminatedString(buffer.Slice(bytesRead));
item = new HeicItem(itemId, itemType);
item.Name = ReadNullTerminatedString(buffer[bytesRead..]);
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;
// Optional field.
if (bytesRead < boxLength)
{
item.ContentEncoding = ReadNullTerminatedString(buffer.Slice(bytesRead));
item.ContentEncoding = ReadNullTerminatedString(buffer[bytesRead..]);
bytesRead += item.ContentEncoding.Length + 1;
}
}
else if (item.Type == "uri ")
else if (item.Type == FourCharacterCode.uri)
{
item.UriType = ReadNullTerminatedString(buffer.Slice(bytesRead));
bytesRead += item.ContentEncoding.Length + 1;
item.UriType = ReadNullTerminatedString(buffer[bytesRead..]);
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);
int bytesRead = 0;
bool largeIds = buffer[bytesRead] != 0;
bytesRead += 4;
while(bytesRead < boxLength)
while (bytesRead < boxLength)
{
ParseBoxHeader(buffer.Slice(bytesRead), out var subBoxLength, out var linkType);
var link = new HeicItemLink();
link.Type = linkType;
ParseBoxHeader(buffer[bytesRead..], out long subBoxLength, out uint linkType);
uint sourceId;
if (largeIds)
{
link.Source = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead));
sourceId = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]);
bytesRead += 4;
}
else
{
link.Source = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead));
sourceId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]);
bytesRead += 2;
}
var count = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead));
HeicItemLink link = new(linkType, sourceId);
int count = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]);
bytesRead += 2;
for(uint i = 0; i < count; i++)
for (uint i = 0; i < count; i++)
{
uint destId;
if (largeIds)
{
destId = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(bytesRead));
destId = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]);
bytesRead += 4;
}
else
{
destId = BinaryPrimitives.ReadUInt16BigEndian(buffer.Slice(bytesRead));
destId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]);
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)
{
// 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)
{
var length = ReadBoxHeader(stream, out var boxType);
int length = (int)this.ReadBoxHeader(stream, out uint boxType);
Span<byte> buffer = stackalloc byte[length];
switch (boxType)
{
case HeicItemPropertyType.ImageSpatialExtents:
case FourCharacterCode.ispe:
// Length should be 12.
Span<byte> buffer = stackalloc new byte[length];
stream.Read(buffer);
// Skip over version (8 bits) and flags (24 bits).
var width = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(4));
var height = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(8));
uint width = BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]);
uint height = BinaryPrimitives.ReadUInt32BigEndian(buffer[8..]);
properties.Add(new KeyValuePair<uint, object>(FourCharacterCode.ispe, new uint[] { width, height }));
break;
case HeicItemPropertyType.PixelAspectRatio:
case FourCharacterCode.pasp:
// Length should be 8.
Span<byte> buffer = stackalloc new byte[length];
stream.Read(buffer);
var horizontalSpacing = BinaryPrimitives.ReadUInt32BigEndian(buffer);
var verticalSpacing = BinaryPrimitives.ReadUInt32BigEndian(buffer.Slice(4));
uint horizontalSpacing = BinaryPrimitives.ReadUInt32BigEndian(buffer);
uint verticalSpacing = BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]);
properties.Add(new KeyValuePair<uint, object>(FourCharacterCode.pasp, new uint[] { horizontalSpacing, verticalSpacing }));
break;
case HeicItemPropertyType.PixelInformation:
Span<byte> buffer = stackalloc new byte[length];
case FourCharacterCode.pixi:
stream.Read(buffer);
// Skip over version (8 bits) and flags (24 bits).
var channelCount = buffer[4];
int channelCount = buffer[4];
int offset = 5;
int bitsPerPixel = 0;
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;
case HeicItemPropertyType.AcessibilityText:
case HeicItemPropertyType.ImageMirror:
case HeicItemPropertyType.ImageRotation:
case HeicItemPropertyType.ImageScaling:
case HeicItemPropertyType.RelativeLocation:
case HeicItemPropertyType.UserDescription;
case FourCharacterCode.altt:
case FourCharacterCode.imir:
case FourCharacterCode.irot:
case FourCharacterCode.iscl:
case FourCharacterCode.rloc:
case FourCharacterCode.udes:
// TODO: Implement
break;
default:
@ -382,42 +422,49 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals
else if (containerType == FourCharacterCode.ipma)
{
// 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
}
/// <summary>
/// Forwards the stream to just past the Start of NAL marker.
/// </summary>
private void FindStartOfNal(BufferedReadStream stream)
private static string ReadNullTerminatedString(Span<byte> span)
{
uint i = stream.Position;
uint length = 0;
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++;
}
Span<byte> bytes = span[..span.IndexOf((byte)0)];
return Encoding.UTF8.GetString(bytes);
}
}

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

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

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

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers.Binary;
using System.Diagnostics.CodeAnalysis;
namespace SixLabors.ImageSharp.Formats.Heic;
@ -11,8 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Heic;
public sealed class HeicImageFormatDetector : IImageFormatDetector
{
/// <inheritdoc/>
int HeaderSize => 12;
public int HeaderSize => 12;
/// <inheritdoc/>
public bool TryDetectFormat(ReadOnlySpan<byte> header, [NotNullWhen(true)] out IImageFormat? format)
@ -21,10 +21,7 @@ public sealed class HeicImageFormatDetector : IImageFormatDetector
return format != null;
}
private static bool IsSupportedFileFormat(ReadOnlySpan<byte> header)
{
return
BinaryPrimitives.ReadUInt32BigEndian(header.Slice(4)) == FourCharacterCode.ftyp &&
BinaryPrimitives.ReadUInt32BigEndian(header.Slice(8)) == FourCharacterCode.heic
}
private static bool IsSupportedFileFormat(ReadOnlySpan<byte> header) =>
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;
public enum HeicItemType
{
Hvc1,
Grid,
Exif
}
public class HeicItemLink
{
public uint Type;
public HeicItem Source;
public List<HeicItem> Destinations = new List<HeicItem>();
}
/// <summary>
/// Provides definition for a HEIC Item.
/// </summary>
public class HeicItem
public class HeicItem(uint type, uint id)
{
public uint Id;
public HeicItemType type;
public string Name;
public string ContentType;
public string ContentEncoding;
public uint ExtensionType;
public string UriType;
/// <summary>
/// Gets the ID of this Item.
/// </summary>
public uint Id { get; } = id;
/// <summary>
/// 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