Browse Source

Byte alignment fixes

pull/2633/head
Ynse Hoornenborg 2 years ago
parent
commit
f892c0475f
  1. 213
      src/ImageSharp/Formats/Heic/HeicDecoderCore.cs

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

@ -2,6 +2,7 @@
// Licensed under the Six Labors Split License.
using System.Buffers.Binary;
using System.Globalization;
using System.Text;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -62,7 +63,7 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals
throw new ImageFormatException("Not an HEIC image.");
}
while (stream.EofHitCount == 0)
while (stream.Position < stream.Length)
{
long length = this.ReadBoxHeader(stream, out Heic4CharCode boxType);
switch (boxType)
@ -97,7 +98,7 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals
{
this.CheckFileTypeBox(stream);
while (stream.EofHitCount == 0)
while (stream.Position < stream.Length)
{
long length = this.ReadBoxHeader(stream, out Heic4CharCode boxType);
switch (boxType)
@ -170,13 +171,14 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals
private void ParseMetadata(BufferedReadStream stream, long boxLength)
{
long endPosition = stream.Position + boxLength;
stream.Skip(4);
while (stream.Position < endPosition)
{
long length = this.ReadBoxHeader(stream, out Heic4CharCode boxType);
switch (boxType)
{
case Heic4CharCode.iprp:
this.ParseItemPropertyContainer(stream, length);
this.ParseItemProperties(stream, length);
break;
case Heic4CharCode.iinf:
this.ParseItemInfo(stream, length);
@ -273,9 +275,9 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals
// Skip Protection Index, not sure what that means...
bytesRead += 2;
uint itemType = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]);
Heic4CharCode itemType = (Heic4CharCode)BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]);
bytesRead += 4;
item = new HeicItem((Heic4CharCode)itemId, itemType);
item = new HeicItem(itemType, itemId);
item.Name = ReadNullTerminatedString(buffer[bytesRead..]);
bytesRead += item.Name.Length + 1;
if (item.Type == Heic4CharCode.mime)
@ -297,6 +299,11 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals
}
}
if (item != null)
{
this.items.Add(item);
}
return bytesRead;
}
@ -308,7 +315,7 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals
bytesRead += 4;
while (bytesRead < boxLength)
{
ParseBoxHeader(buffer[bytesRead..], out long subBoxLength, out Heic4CharCode linkType);
bytesRead += ParseBoxHeader(buffer[bytesRead..], out long subBoxLength, out Heic4CharCode linkType);
uint sourceId = ReadUInt16Or32(buffer, largeIds, ref bytesRead);
HeicItemLink link = new(linkType, sourceId);
@ -329,101 +336,122 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals
// BoxLength should be 6 or 8.
Span<byte> buffer = this.ReadIntoBuffer(stream, boxLength);
byte version = buffer[0];
int bytesRead = 0;
int bytesRead = 4;
this.primaryItem = ReadUInt16Or32(buffer, version != 0, ref bytesRead);
}
private void ParseItemPropertyContainer(BufferedReadStream stream, long boxLength)
private void ParseItemProperties(BufferedReadStream stream, long boxLength)
{
// Cannot use Dictionary here, Properties can have multiple instances with the same key.
List<KeyValuePair<Heic4CharCode, object>> properties = new();
long containerLength = this.ReadBoxHeader(stream, out Heic4CharCode containerType);
if (containerType == Heic4CharCode.ipco)
long endBoxPosition = stream.Position + boxLength;
while (stream.Position < endBoxPosition)
{
// Parse Item Property Container, which is just an array of preperty boxes.
long endPosition = stream.Position + containerLength;
while (stream.Position < endPosition)
long containerLength = this.ReadBoxHeader(stream, out Heic4CharCode containerType);
if (containerType == Heic4CharCode.ipco)
{
int length = (int)this.ReadBoxHeader(stream, out Heic4CharCode boxType);
Span<byte> buffer = this.ReadIntoBuffer(stream, boxLength);
switch (boxType)
{
case Heic4CharCode.ispe:
// 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:
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:
// Skip over version (8 bits) and flags (24 bits).
int channelCount = buffer[4];
int offset = 5;
int bitsPerPixel = 0;
for (int i = 0; i < channelCount; i++)
{
bitsPerPixel += buffer[offset + i];
}
properties.Add(new KeyValuePair<Heic4CharCode, object>(Heic4CharCode.pixi, new int[] { channelCount, bitsPerPixel }));
break;
case Heic4CharCode.altt:
case Heic4CharCode.imir:
case Heic4CharCode.irot:
case Heic4CharCode.iscl:
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)}'");
}
// Parse Item Property Container, which is just an array of property boxes.
this.ParsePropertyContainer(stream, containerLength, properties);
}
else if (containerType == Heic4CharCode.ipma)
{
// Parse Item Property Association
this.ParsePropertyAssociation(stream, containerLength, properties);
}
else
{
string enumName = Enum.GetName(containerType) ?? ((uint)containerType).ToString("X", CultureInfo.InvariantCulture);
throw new ImageFormatException($"Unknown container type in property box of '{enumName}'");
}
}
else if (containerType == Heic4CharCode.ipma)
}
private void ParsePropertyContainer(BufferedReadStream stream, long boxLength, List<KeyValuePair<Heic4CharCode, object>> properties)
{
long endPosition = stream.Position + boxLength;
while (stream.Position < endPosition)
{
// Parse Item Property Association
Span<byte> buffer = this.ReadIntoBuffer(stream, boxLength);
byte version = buffer[0];
byte flags = buffer[3];
int bytesRead = 4;
int itemId = (int)ReadUInt16Or32(buffer, version >= 1, ref bytesRead);
int associationCount = buffer[bytesRead++];
for (int i = 0; i < associationCount; i++)
int itemLength = (int)this.ReadBoxHeader(stream, out Heic4CharCode itemType);
Span<byte> buffer = this.ReadIntoBuffer(stream, itemLength);
switch (itemType)
{
uint propId;
if (flags == 1)
{
propId = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]) & 0x4FFFU;
bytesRead += 2;
}
else
{
propId = buffer[bytesRead++] & 0x4FU;
}
case Heic4CharCode.ispe:
// 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:
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:
// Skip over version (8 bits) and flags (24 bits).
int channelCount = buffer[4];
int offset = 5;
int bitsPerPixel = 0;
for (int i = 0; i < channelCount; i++)
{
bitsPerPixel += buffer[offset + i];
}
properties.Add(new KeyValuePair<Heic4CharCode, object>(Heic4CharCode.pixi, new int[] { channelCount, bitsPerPixel }));
KeyValuePair<Heic4CharCode, object> prop = properties[(int)propId];
switch (prop.Key)
{
case Heic4CharCode.ispe:
this.items[itemId].SetExtent((Size)prop.Value);
break;
case Heic4CharCode.pasp:
this.items[itemId].PixelAspectRatio = (Size)prop.Value;
break;
case Heic4CharCode.pixi:
int[] values = (int[])prop.Value;
this.items[itemId].ChannelCount = values[0];
this.items[itemId].BitsPerPixel = values[1];
break;
}
break;
case Heic4CharCode.altt:
case Heic4CharCode.colr:
case Heic4CharCode.imir:
case Heic4CharCode.irot:
case Heic4CharCode.iscl:
case Heic4CharCode.hvcC:
case Heic4CharCode.rloc:
case Heic4CharCode.udes:
// TODO: Implement
break;
default:
string enumName = Enum.GetName(itemType) ?? ((uint)itemType).ToString("X", CultureInfo.InvariantCulture);
throw new ImageFormatException($"Unknown item type in property box of '{enumName}'");
}
}
}
private void ParsePropertyAssociation(BufferedReadStream stream, long boxLength, List<KeyValuePair<Heic4CharCode, object>> properties)
{
Span<byte> buffer = this.ReadIntoBuffer(stream, boxLength);
byte version = buffer[0];
byte flags = buffer[3];
int bytesRead = 4;
int itemId = (int)ReadUInt16Or32(buffer, version >= 1, ref bytesRead);
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;
}
KeyValuePair<Heic4CharCode, object> prop = properties[(int)propId];
switch (prop.Key)
{
case Heic4CharCode.ispe:
this.items[itemId].SetExtent((Size)prop.Value);
break;
case Heic4CharCode.pasp:
this.items[itemId].PixelAspectRatio = (Size)prop.Value;
break;
case Heic4CharCode.pixi:
int[] values = (int[])prop.Value;
this.items[itemId].ChannelCount = values[0];
this.items[itemId].BitsPerPixel = values[1];
break;
}
}
}
@ -432,7 +460,8 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals
{
Span<byte> buffer = this.ReadIntoBuffer(stream, boxLength);
int bytesRead = 0;
byte version = buffer[bytesRead++];
byte version = buffer[bytesRead];
bytesRead += 4;
byte b1 = buffer[bytesRead++];
byte b2 = buffer[bytesRead++];
int offsetSize = (b1 >> 4) & 0x0f;
@ -444,13 +473,13 @@ internal sealed class HeicDecoderCore : IImageDecoderInternals
indexSize = b2 & 0x0f;
}
uint itemCount = ReadUInt16Or32(buffer, version >= 2, ref bytesRead);
uint itemCount = ReadUInt16Or32(buffer, version == 2, ref bytesRead);
for (uint i = 0; i < itemCount; i++)
{
uint itemId = ReadUInt16Or32(buffer, version >= 2, ref bytesRead);
uint itemId = ReadUInt16Or32(buffer, version == 2, ref bytesRead);
if (version is 1 or 2)
{
bytesRead += 2;
bytesRead++;
byte b3 = buffer[bytesRead++];
int constructionMethod = b3 & 0x0f;
}

Loading…
Cancel
Save