|
|
|
@ -27,9 +27,11 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals |
|
|
|
|
|
|
|
private uint primaryItem; |
|
|
|
|
|
|
|
private List<HeicItem> items; |
|
|
|
private readonly List<HeicItem> items; |
|
|
|
|
|
|
|
private List<HeicItemLink> itemLinks; |
|
|
|
private readonly List<HeicItemLink> itemLinks; |
|
|
|
|
|
|
|
private readonly byte[] buffer; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Initializes a new instance of the <see cref="HeicDecoderCore" /> class.
|
|
|
|
@ -42,6 +44,7 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals |
|
|
|
this.metadata = new ImageMetadata(); |
|
|
|
this.items = new List<HeicItem>(); |
|
|
|
this.itemLinks = new List<HeicItemLink>(); |
|
|
|
this.buffer = new byte[80]; |
|
|
|
} |
|
|
|
|
|
|
|
/// <inheritdoc/>
|
|
|
|
@ -83,6 +86,7 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals |
|
|
|
|
|
|
|
var image = new Image<TPixel>(this.configuration, item.Extent.Width, item.Extent.Height, this.metadata); |
|
|
|
|
|
|
|
// TODO: Parse pixel data
|
|
|
|
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer(); |
|
|
|
|
|
|
|
return image; |
|
|
|
@ -103,6 +107,7 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals |
|
|
|
break; |
|
|
|
default: |
|
|
|
// Silently skip all other box types.
|
|
|
|
SkipBox(stream, length); |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
@ -118,10 +123,9 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals |
|
|
|
|
|
|
|
private bool CheckFileTypeBox(BufferedReadStream stream) |
|
|
|
{ |
|
|
|
var boxLength = this.ReadBoxHeader(stream, out Heic4CharCode boxType); |
|
|
|
Span<byte> buffer = stackalloc byte[(int)boxLength]; |
|
|
|
stream.Read(buffer); |
|
|
|
var majorBrand = BinaryPrimitives.ReadUInt32BigEndian(buffer); |
|
|
|
long boxLength = this.ReadBoxHeader(stream, out Heic4CharCode boxType); |
|
|
|
Span<byte> buffer = this.ReadIntoBuffer(stream, boxLength); |
|
|
|
uint majorBrand = BinaryPrimitives.ReadUInt32BigEndian(buffer); |
|
|
|
|
|
|
|
// TODO: Interpret minorVersion and compatible brands.
|
|
|
|
return boxType == Heic4CharCode.ftyp && majorBrand == (uint)Heic4CharCode.heic; |
|
|
|
@ -130,8 +134,7 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals |
|
|
|
private long ReadBoxHeader(BufferedReadStream stream, out Heic4CharCode boxType) |
|
|
|
{ |
|
|
|
// Read 4 bytes of length of box
|
|
|
|
Span<byte> buf = stackalloc byte[8]; |
|
|
|
stream.Read(buf); |
|
|
|
Span<byte> buf = this.ReadIntoBuffer(stream, 8); |
|
|
|
long boxSize = BinaryPrimitives.ReadUInt32BigEndian(buf); |
|
|
|
long headerSize = 8; |
|
|
|
|
|
|
|
@ -140,7 +143,7 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals |
|
|
|
|
|
|
|
if (boxSize == 1) |
|
|
|
{ |
|
|
|
stream.Read(buf); |
|
|
|
buf = this.ReadIntoBuffer(stream, 8); |
|
|
|
boxSize = (long)BinaryPrimitives.ReadUInt64BigEndian(buf); |
|
|
|
headerSize += 8; |
|
|
|
} |
|
|
|
@ -185,12 +188,21 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals |
|
|
|
this.ParsePrimaryItem(stream, length); |
|
|
|
break; |
|
|
|
case Heic4CharCode.dinf: |
|
|
|
case Heic4CharCode.grpl: |
|
|
|
SkipBox(stream, length); |
|
|
|
break; |
|
|
|
case Heic4CharCode.hdlr: |
|
|
|
SkipBox(stream, length); |
|
|
|
break; |
|
|
|
case Heic4CharCode.idat: |
|
|
|
SkipBox(stream, length); |
|
|
|
break; |
|
|
|
case Heic4CharCode.iloc: |
|
|
|
this.ParseItemLocation(stream, length); |
|
|
|
break; |
|
|
|
case Heic4CharCode.grpl: |
|
|
|
case Heic4CharCode.ipro: |
|
|
|
// TODO: Implement
|
|
|
|
SkipBox(stream, length); |
|
|
|
break; |
|
|
|
default: |
|
|
|
throw new ImageFormatException($"Unknown metadata box type of '{Enum.GetName(boxType)}'"); |
|
|
|
@ -200,22 +212,12 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals |
|
|
|
|
|
|
|
private void ParseItemInfo(BufferedReadStream stream, long boxLength) |
|
|
|
{ |
|
|
|
Span<byte> buffer = stackalloc byte[(int)boxLength]; |
|
|
|
stream.Read(buffer); |
|
|
|
Span<byte> buffer = this.ReadIntoBuffer(stream, boxLength); |
|
|
|
uint entryCount; |
|
|
|
int bytesRead = 0; |
|
|
|
if (buffer[bytesRead] == 0) |
|
|
|
{ |
|
|
|
bytesRead += 4; |
|
|
|
entryCount = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); |
|
|
|
bytesRead += 2; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
bytesRead += 4; |
|
|
|
entryCount = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); |
|
|
|
bytesRead += 4; |
|
|
|
} |
|
|
|
byte version = buffer[bytesRead]; |
|
|
|
bytesRead += 4; |
|
|
|
entryCount = ReadUInt16Or32(buffer, version != 0, ref bytesRead); |
|
|
|
|
|
|
|
for (uint i = 0; i < entryCount; i++) |
|
|
|
{ |
|
|
|
@ -267,17 +269,7 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals |
|
|
|
|
|
|
|
if (version >= 2) |
|
|
|
{ |
|
|
|
uint itemId = 0U; |
|
|
|
if (version == 2) |
|
|
|
{ |
|
|
|
itemId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); |
|
|
|
bytesRead += 2; |
|
|
|
} |
|
|
|
else if (version == 3) |
|
|
|
{ |
|
|
|
itemId = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); |
|
|
|
bytesRead += 4; |
|
|
|
} |
|
|
|
uint itemId = ReadUInt16Or32(buffer, version == 3, ref bytesRead); |
|
|
|
|
|
|
|
// Skip Protection Index, not sure what that means...
|
|
|
|
bytesRead += 2; |
|
|
|
@ -310,44 +302,21 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals |
|
|
|
|
|
|
|
private void ParseItemReference(BufferedReadStream stream, long boxLength) |
|
|
|
{ |
|
|
|
Span<byte> buffer = new byte[boxLength]; |
|
|
|
stream.Read(buffer); |
|
|
|
Span<byte> buffer = this.ReadIntoBuffer(stream, boxLength); |
|
|
|
int bytesRead = 0; |
|
|
|
bool largeIds = buffer[bytesRead] != 0; |
|
|
|
bytesRead += 4; |
|
|
|
while (bytesRead < boxLength) |
|
|
|
{ |
|
|
|
ParseBoxHeader(buffer[bytesRead..], out long subBoxLength, out Heic4CharCode linkType); |
|
|
|
uint sourceId; |
|
|
|
if (largeIds) |
|
|
|
{ |
|
|
|
sourceId = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); |
|
|
|
bytesRead += 4; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
sourceId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); |
|
|
|
bytesRead += 2; |
|
|
|
} |
|
|
|
|
|
|
|
uint sourceId = ReadUInt16Or32(buffer, largeIds, ref bytesRead); |
|
|
|
HeicItemLink link = new(linkType, sourceId); |
|
|
|
|
|
|
|
int count = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); |
|
|
|
bytesRead += 2; |
|
|
|
for (uint i = 0; i < count; i++) |
|
|
|
{ |
|
|
|
uint destId; |
|
|
|
if (largeIds) |
|
|
|
{ |
|
|
|
destId = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); |
|
|
|
bytesRead += 4; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
destId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); |
|
|
|
bytesRead += 2; |
|
|
|
} |
|
|
|
|
|
|
|
uint destId = ReadUInt16Or32(buffer, largeIds, ref bytesRead); |
|
|
|
link.DestinationIds.Add(destId); |
|
|
|
} |
|
|
|
|
|
|
|
@ -358,17 +327,10 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals |
|
|
|
private void ParsePrimaryItem(BufferedReadStream stream, long boxLength) |
|
|
|
{ |
|
|
|
// BoxLength should be 6 or 8.
|
|
|
|
Span<byte> buffer = stackalloc byte[(int)boxLength]; |
|
|
|
stream.Read(buffer); |
|
|
|
Span<byte> buffer = this.ReadIntoBuffer(stream, boxLength); |
|
|
|
byte version = buffer[0]; |
|
|
|
if (version == 0) |
|
|
|
{ |
|
|
|
this.primaryItem = BinaryPrimitives.ReadUInt16BigEndian(buffer[4..]); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
this.primaryItem = BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); |
|
|
|
} |
|
|
|
int bytesRead = 0; |
|
|
|
this.primaryItem = ReadUInt16Or32(buffer, version != 0, ref bytesRead); |
|
|
|
} |
|
|
|
|
|
|
|
private void ParseItemPropertyContainer(BufferedReadStream stream, long boxLength) |
|
|
|
@ -383,28 +345,21 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals |
|
|
|
while (stream.Position < endPosition) |
|
|
|
{ |
|
|
|
int length = (int)this.ReadBoxHeader(stream, out Heic4CharCode boxType); |
|
|
|
Span<byte> buffer = stackalloc byte[length]; |
|
|
|
Span<byte> buffer = this.ReadIntoBuffer(stream, boxLength); |
|
|
|
switch (boxType) |
|
|
|
{ |
|
|
|
case Heic4CharCode.ispe: |
|
|
|
// Length should be 12.
|
|
|
|
stream.Read(buffer); |
|
|
|
|
|
|
|
// Skip over version (8 bits) and flags (24 bits).
|
|
|
|
int width = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); |
|
|
|
int height = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[8..]); |
|
|
|
properties.Add(new KeyValuePair<Heic4CharCode, object>(Heic4CharCode.ispe, new Size(width, height))); |
|
|
|
break; |
|
|
|
case Heic4CharCode.pasp: |
|
|
|
// Length should be 8.
|
|
|
|
stream.Read(buffer); |
|
|
|
int horizontalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer); |
|
|
|
int verticalSpacing = (int)BinaryPrimitives.ReadUInt32BigEndian(buffer[4..]); |
|
|
|
properties.Add(new KeyValuePair<Heic4CharCode, object>(Heic4CharCode.pasp, new Size(horizontalSpacing, verticalSpacing))); |
|
|
|
break; |
|
|
|
case Heic4CharCode.pixi: |
|
|
|
stream.Read(buffer); |
|
|
|
|
|
|
|
// Skip over version (8 bits) and flags (24 bits).
|
|
|
|
int channelCount = buffer[4]; |
|
|
|
int offset = 5; |
|
|
|
@ -424,6 +379,7 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals |
|
|
|
case Heic4CharCode.rloc: |
|
|
|
case Heic4CharCode.udes: |
|
|
|
// TODO: Implement
|
|
|
|
SkipBox(stream, length); |
|
|
|
break; |
|
|
|
default: |
|
|
|
throw new ImageFormatException($"Unknown item property box type of '{Enum.GetName(boxType)}'"); |
|
|
|
@ -433,21 +389,11 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals |
|
|
|
else if (containerType == Heic4CharCode.ipma) |
|
|
|
{ |
|
|
|
// Parse Item Property Association
|
|
|
|
Span<byte> buffer = stackalloc byte[(int)boxLength]; |
|
|
|
Span<byte> buffer = this.ReadIntoBuffer(stream, 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 itemId = (int)ReadUInt16Or32(buffer, version >= 1, ref bytesRead); |
|
|
|
|
|
|
|
int associationCount = buffer[bytesRead++]; |
|
|
|
for (int i = 0; i < associationCount; i++) |
|
|
|
@ -482,9 +428,115 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private void ParseItemLocation(BufferedReadStream stream, long boxLength) |
|
|
|
{ |
|
|
|
Span<byte> buffer = this.ReadIntoBuffer(stream, boxLength); |
|
|
|
int bytesRead = 0; |
|
|
|
byte version = buffer[bytesRead++]; |
|
|
|
byte b1 = buffer[bytesRead++]; |
|
|
|
byte b2 = buffer[bytesRead++]; |
|
|
|
int offsetSize = (b1 >> 4) & 0x0f; |
|
|
|
int lengthSize = b1 & 0x0f; |
|
|
|
int baseOffsetSize = (b2 >> 4) & 0x0f; |
|
|
|
int indexSize = 0; |
|
|
|
if (version is 1 or 2) |
|
|
|
{ |
|
|
|
indexSize = b2 & 0x0f; |
|
|
|
} |
|
|
|
|
|
|
|
uint itemCount = ReadUInt16Or32(buffer, version >= 2, ref bytesRead); |
|
|
|
for (uint i = 0; i < itemCount; i++) |
|
|
|
{ |
|
|
|
uint itemId = ReadUInt16Or32(buffer, version >= 2, ref bytesRead); |
|
|
|
if (version is 1 or 2) |
|
|
|
{ |
|
|
|
bytesRead += 2; |
|
|
|
byte b3 = buffer[bytesRead++]; |
|
|
|
int constructionMethod = b3 & 0x0f; |
|
|
|
} |
|
|
|
|
|
|
|
uint dataReferenceIndex = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); |
|
|
|
bytesRead += 2; |
|
|
|
ulong baseOffset = ReadUIntVariable(buffer, baseOffsetSize, ref bytesRead); |
|
|
|
uint extentCount = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); |
|
|
|
bytesRead += 2; |
|
|
|
for (uint j = 0; j < extentCount; j++) |
|
|
|
{ |
|
|
|
if (version is 1 or 2 && indexSize > 0) |
|
|
|
{ |
|
|
|
ulong extentIndex = ReadUIntVariable(buffer, indexSize, ref bytesRead); |
|
|
|
} |
|
|
|
|
|
|
|
ulong extentOffset = ReadUIntVariable(buffer, offsetSize, ref bytesRead); |
|
|
|
ulong extentLength = ReadUIntVariable(buffer, lengthSize, ref bytesRead); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private static uint ReadUInt16Or32(Span<byte> buffer, bool isLarge, ref int bytesRead) |
|
|
|
{ |
|
|
|
uint result; |
|
|
|
if (isLarge) |
|
|
|
{ |
|
|
|
result = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); |
|
|
|
bytesRead += 4; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
result = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); |
|
|
|
bytesRead += 2; |
|
|
|
} |
|
|
|
|
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
private static ulong ReadUIntVariable(Span<byte> buffer, int numBytes, ref int bytesRead) |
|
|
|
{ |
|
|
|
ulong result = 0UL; |
|
|
|
if (numBytes == 8) |
|
|
|
{ |
|
|
|
result = BinaryPrimitives.ReadUInt64BigEndian(buffer[bytesRead..]); |
|
|
|
bytesRead += 8; |
|
|
|
} |
|
|
|
else if (numBytes == 4) |
|
|
|
{ |
|
|
|
result = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); |
|
|
|
bytesRead += 4; |
|
|
|
} |
|
|
|
else if (numBytes == 2) |
|
|
|
{ |
|
|
|
result = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); |
|
|
|
bytesRead += 2; |
|
|
|
} |
|
|
|
else if (numBytes == 1) |
|
|
|
{ |
|
|
|
result = buffer[bytesRead++]; |
|
|
|
} |
|
|
|
|
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
private void ParseMediaData(BufferedReadStream stream, long boxLength) |
|
|
|
{ |
|
|
|
// TODO: Implement
|
|
|
|
SkipBox(stream, boxLength); |
|
|
|
} |
|
|
|
|
|
|
|
private static void SkipBox(BufferedReadStream stream, long boxLength) |
|
|
|
=> stream.Skip((int)boxLength); |
|
|
|
|
|
|
|
private Span<byte> ReadIntoBuffer(BufferedReadStream stream, long length) |
|
|
|
{ |
|
|
|
if (length <= this.buffer.Length) |
|
|
|
{ |
|
|
|
stream.Read(this.buffer, 0, (int)length); |
|
|
|
return this.buffer; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
Span<byte> temp = new byte[length]; |
|
|
|
stream.Read(temp); |
|
|
|
return temp; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private HeicItem? FindItemById(uint itemId) |
|
|
|
|