Browse Source

Use single buffer per decoder instance

pull/2633/head
Ynse Hoornenborg 2 years ago
parent
commit
c3ce98000a
  1. 236
      src/ImageSharp/Formats/Heic/HeicDecoderCore.cs
  2. 8
      src/ImageSharp/Formats/Heic/HeicItem.cs

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

@ -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)

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

@ -68,6 +68,13 @@ public class HeicItem(Heic4CharCode type, uint id)
/// </summary>
public Size GridCellExtent { get; private set; }
/// <summary>
/// Set the image extent.
/// </summary>
/// <param name="extent">The size to set the extent to.</param>
/// <remarks>
/// Might be called twice for a grid, in which case the second call is the cell extent.
/// </remarks>
public void SetExtent(Size extent)
{
if (this.Extent == default)
@ -79,5 +86,4 @@ public class HeicItem(Heic4CharCode type, uint id)
this.GridCellExtent = extent;
}
}
}

Loading…
Cancel
Save