diff --git a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs index f87dd7f4d1..1c65d6e00c 100644 --- a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs @@ -538,8 +538,10 @@ private void ParseItemLocation(BufferedReadStream stream, long boxLength) int bytesRead = 0; byte version = boxBuffer[bytesRead]; bytesRead += 4; - byte b1 = boxBuffer[bytesRead++]; - byte b2 = boxBuffer[bytesRead++]; + byte b1 = boxBuffer[bytesRead]; + bytesRead++; + byte b2 = boxBuffer[bytesRead]; + bytesRead++; int offsetSize = (b1 >> 4) & 0x0f; int lengthSize = b1 & 0x0f; int baseOffsetSize = (b2 >> 4) & 0x0f; @@ -554,28 +556,31 @@ private void ParseItemLocation(BufferedReadStream stream, long boxLength) { uint itemId = ReadUInt16Or32(boxBuffer, version == 2, ref bytesRead); HeifItem? item = this.FindItemById(itemId); + HeifLocationOffsetOrigin constructionMethod = HeifLocationOffsetOrigin.FileOffset; if (version is 1 or 2) { bytesRead++; - byte b3 = boxBuffer[bytesRead++]; - int constructionMethod = b3 & 0x0f; + byte b3 = boxBuffer[bytesRead]; + bytesRead++; + constructionMethod = (HeifLocationOffsetOrigin)(b3 & 0x0f); } uint dataReferenceIndex = BinaryPrimitives.ReadUInt16BigEndian(boxBuffer[bytesRead..]); bytesRead += 2; - ulong baseOffset = ReadUIntVariable(boxBuffer, baseOffsetSize, ref bytesRead); + long baseOffset = ReadUIntVariable(boxBuffer, baseOffsetSize, ref bytesRead); uint extentCount = BinaryPrimitives.ReadUInt16BigEndian(boxBuffer[bytesRead..]); bytesRead += 2; for (uint j = 0; j < extentCount; j++) { + uint extentIndex = 0; if (version is 1 or 2 && indexSize > 0) { - _ = ReadUIntVariable(boxBuffer, indexSize, ref bytesRead); + extentIndex = (uint)ReadUIntVariable(boxBuffer, indexSize, ref bytesRead); } - ulong extentOffset = ReadUIntVariable(boxBuffer, offsetSize, ref bytesRead); - ulong extentLength = ReadUIntVariable(boxBuffer, lengthSize, ref bytesRead); - HeifLocation loc = new HeifLocation((long)extentOffset, (long)extentLength); + long extentOffset = ReadUIntVariable(boxBuffer, offsetSize, ref bytesRead); + long extentLength = ReadUIntVariable(boxBuffer, lengthSize, ref bytesRead); + HeifLocation loc = new(constructionMethod, baseOffset, dataReferenceIndex, extentOffset, extentLength, extentIndex); item?.DataLocations.Add(loc); } } @@ -598,29 +603,36 @@ private static uint ReadUInt16Or32(Span buffer, bool isLarge, ref int byte return result; } - private static ulong ReadUIntVariable(Span buffer, int numBytes, ref int bytesRead) + private static long ReadUIntVariable(Span buffer, int numBytes, ref int bytesRead) { - ulong result = 0UL; - if (numBytes == 8) + long result = 0L; + int shift = 0; + if (numBytes > 8) { - result = BinaryPrimitives.ReadUInt64BigEndian(buffer[bytesRead..]); - bytesRead += 8; + throw new InvalidImageContentException($"Can't store large integer of {numBytes * 8} bits."); } - else if (numBytes == 4) + else + if (numBytes > 4) + { + result = (long)BinaryPrimitives.ReadUInt64BigEndian(buffer[bytesRead..]); + shift = 8 - numBytes; + } + else if (numBytes > 2) { result = BinaryPrimitives.ReadUInt32BigEndian(buffer[bytesRead..]); - bytesRead += 4; + shift = 4 - numBytes; } - else if (numBytes == 2) + else if (numBytes > 1) { result = BinaryPrimitives.ReadUInt16BigEndian(buffer[bytesRead..]); - bytesRead += 2; } else if (numBytes == 1) { - result = buffer[bytesRead++]; + result = buffer[bytesRead]; } + bytesRead += numBytes; + result >>= shift << 3; return result; } @@ -642,11 +654,11 @@ private Image ParseMediaData(BufferedReadStream stream, long box throw new NotImplementedException("No HVC decoding implemented yet"); } - int thumbFileOffset = (int)thumbItem.DataLocations[0].Offset; - int thumbFileLength = (int)thumbItem.DataLocations[0].Length; - stream.Skip((int)(thumbFileOffset - stream.Position)); - EnsureBoxBoundary(thumbFileLength, stream); - using IMemoryOwner thumbMemory = this.ReadIntoBuffer(stream, thumbFileLength); + long thumbPosition = thumbItem.DataLocations[0].GetStreamPosition(stream.Position, stream.Position); + long thumbLength = thumbItem.DataLocations[0].Length; + stream.Skip((int)(thumbPosition - stream.Position)); + EnsureBoxBoundary(thumbLength, stream); + using IMemoryOwner thumbMemory = this.ReadIntoBuffer(stream, thumbLength); Span thumbSpan = thumbMemory.GetSpan(); HeifMetadata meta = this.metadata.GetHeifMetadata(); diff --git a/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs b/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs index 4842cfcf8d..8985768112 100644 --- a/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs @@ -66,7 +66,7 @@ private static void GenerateItems(Image image, byte[] pixels, Li where TPixel : unmanaged, IPixel { HeifItem primaryItem = new(Heif4CharCode.Jpeg, 1u); - primaryItem.DataLocations.Add(new HeifLocation(0L, pixels.LongLength)); + primaryItem.DataLocations.Add(new HeifLocation(HeifLocationOffsetOrigin.ItemDataOffset, 0L, 0U, 0L, pixels.LongLength, 0U)); primaryItem.BitsPerPixel = 24; primaryItem.ChannelCount = 3; primaryItem.SetExtent(image.Size); diff --git a/src/ImageSharp/Formats/Heif/HeifItem.cs b/src/ImageSharp/Formats/Heif/HeifItem.cs index 25e6bb2988..d9f355317e 100644 --- a/src/ImageSharp/Formats/Heif/HeifItem.cs +++ b/src/ImageSharp/Formats/Heif/HeifItem.cs @@ -71,7 +71,7 @@ internal class HeifItem(Heif4CharCode type, uint id) /// /// Gets the list of data locations for this item. /// - public List DataLocations { get; } = new List(); + public List DataLocations { get; } = []; /// /// Set the image extent. diff --git a/src/ImageSharp/Formats/Heif/HeifLocation.cs b/src/ImageSharp/Formats/Heif/HeifLocation.cs index d739cb633c..afbc3ad035 100644 --- a/src/ImageSharp/Formats/Heif/HeifLocation.cs +++ b/src/ImageSharp/Formats/Heif/HeifLocation.cs @@ -6,10 +6,25 @@ namespace SixLabors.ImageSharp.Formats.Heif; /// /// Location within the file of an . /// -internal class HeifLocation(long offset, long length) +internal class HeifLocation(HeifLocationOffsetOrigin method, long baseOffset, uint dataReferenceIndex, long offset, long length, uint extentIndex) { /// - /// Gets the file offset of this location. + /// Gets the origin of the offsets in this location. + /// + public HeifLocationOffsetOrigin Origin { get; } = method; + + /// + /// Gets the base offset of this location. + /// + public long BaseOffset { get; } = baseOffset; + + /// + /// Gets the data reference index of this location. + /// + public uint DataReferenceInxdex { get; } = dataReferenceIndex; + + /// + /// Gets the offset of this location. /// public long Offset { get; } = offset; @@ -17,4 +32,21 @@ internal class HeifLocation(long offset, long length) /// Gets the length of this location. /// public long Length { get; } = length; + + /// + /// Gets the extent index of this location. + /// + public uint ExtentInxdex { get; } = extentIndex; + + /// + /// Gets the stream position of this location. + /// + /// Stream position of the MediaData box. + /// Stream position of the previous box. + public long GetStreamPosition(long positionOfMediaData, long positionOfItem) => this.Origin switch + { + HeifLocationOffsetOrigin.FileOffset => this.BaseOffset + this.Offset, + HeifLocationOffsetOrigin.ItemDataOffset => positionOfMediaData + this.BaseOffset + this.Offset, + _ => positionOfItem + this.BaseOffset + this.Offset + }; } diff --git a/src/ImageSharp/Formats/Heif/HeifLocationOffsetOrigin.cs b/src/ImageSharp/Formats/Heif/HeifLocationOffsetOrigin.cs new file mode 100644 index 0000000000..accceb380b --- /dev/null +++ b/src/ImageSharp/Formats/Heif/HeifLocationOffsetOrigin.cs @@ -0,0 +1,11 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif; + +internal enum HeifLocationOffsetOrigin +{ + FileOffset = 0, + ItemDataOffset = 1, + ItemOffset = 2 +}