From e71012eafe500ba6abdfd42606042d08cb78813f Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Wed, 19 Feb 2025 20:28:38 +0100 Subject: [PATCH] Read all items from input stream --- .../Common/Helpers/DisposableDictionary.cs | 42 +++++++ .../Common/Helpers/DisposableList.cs | 40 ++++++ src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs | 7 +- .../Formats/Heif/Av1HeifItemDecoder.cs | 33 +++++ .../Formats/Heif/HeifCompressionFactory.cs | 23 ++++ .../Formats/Heif/HeifDecoderCore.cs | 115 ++++++++++++++--- .../Formats/Heif/HeifEncoderCore.cs | 2 +- src/ImageSharp/Formats/Heif/HeifLocation.cs | 31 +++-- .../Formats/Heif/HeifLocationComparer.cs | 39 ++++++ .../Formats/Heif/IHeifItemDecoder.cs | 33 +++++ .../Formats/Heif/JpegHeifItemDecoder.cs | 32 +++++ .../Formats/Heif/HeifLocationTests.cs | 116 ++++++++++++++++++ 12 files changed, 480 insertions(+), 33 deletions(-) create mode 100644 src/ImageSharp/Common/Helpers/DisposableDictionary.cs create mode 100644 src/ImageSharp/Common/Helpers/DisposableList.cs create mode 100644 src/ImageSharp/Formats/Heif/Av1HeifItemDecoder.cs create mode 100644 src/ImageSharp/Formats/Heif/HeifCompressionFactory.cs create mode 100644 src/ImageSharp/Formats/Heif/HeifLocationComparer.cs create mode 100644 src/ImageSharp/Formats/Heif/IHeifItemDecoder.cs create mode 100644 src/ImageSharp/Formats/Heif/JpegHeifItemDecoder.cs create mode 100644 tests/ImageSharp.Tests/Formats/Heif/HeifLocationTests.cs diff --git a/src/ImageSharp/Common/Helpers/DisposableDictionary.cs b/src/ImageSharp/Common/Helpers/DisposableDictionary.cs new file mode 100644 index 0000000000..3b078f25ca --- /dev/null +++ b/src/ImageSharp/Common/Helpers/DisposableDictionary.cs @@ -0,0 +1,42 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Common.Helpers; + +/// +/// Dictionary of objects, which is itself . +/// +/// The type of the key. +/// Tye type of value, needs to implement . +public sealed class DisposableDictionary : Dictionary, IDisposable + where TKey : notnull + where TValue : IDisposable +{ + private bool disposedValue; + + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + this.Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!this.disposedValue) + { + if (disposing) + { + foreach (KeyValuePair pair in this) + { + pair.Value?.Dispose(); + } + } + + this.Clear(); + this.disposedValue = true; + } + } + +} diff --git a/src/ImageSharp/Common/Helpers/DisposableList.cs b/src/ImageSharp/Common/Helpers/DisposableList.cs new file mode 100644 index 0000000000..8d0512b69c --- /dev/null +++ b/src/ImageSharp/Common/Helpers/DisposableList.cs @@ -0,0 +1,40 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Common.Helpers; + +/// +/// List of objects, which is itself . +/// +/// Tye type of value, needs to implement . +public sealed class DisposableList : List, IDisposable + where TValue : IDisposable +{ + private bool disposedValue; + + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + this.Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + private void Dispose(bool disposing) + { + if (!this.disposedValue) + { + if (disposing) + { + foreach (TValue item in this) + { + item?.Dispose(); + } + } + + this.Clear(); + this.disposedValue = true; + } + } + +} diff --git a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs index 3b3663522e..767743430a 100644 --- a/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs +++ b/src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs @@ -4,6 +4,7 @@ using SixLabors.ImageSharp.Formats.Heif.Av1.OpenBitstreamUnit; using SixLabors.ImageSharp.Formats.Heif.Av1.Pipeline; using SixLabors.ImageSharp.Formats.Heif.Av1.Tiling; +using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Heif.Av1; @@ -28,7 +29,8 @@ internal class Av1Decoder : IAv1TileReader public Av1FrameBuffer? FrameBuffer { get; private set; } - public void Decode(Span buffer) + public Image Decode(Span buffer) + where TPixel : unmanaged, IPixel { Av1BitStreamReader reader = new(buffer); this.obuReader.ReadAll(ref reader, buffer.Length, () => this, false); @@ -40,6 +42,9 @@ internal class Av1Decoder : IAv1TileReader this.FrameBuffer = new(this.configuration, this.SequenceHeader, this.SequenceHeader.ColorConfig.GetColorFormat(), false); this.frameDecoder = new(this.SequenceHeader, this.FrameHeader, this.FrameInfo, this.FrameBuffer); this.frameDecoder.DecodeFrame(); + + // TODO: Implement returning proper pixels. + return new Image(1, 1); } public void ReadTile(Span tileData, int tileNum) diff --git a/src/ImageSharp/Formats/Heif/Av1HeifItemDecoder.cs b/src/ImageSharp/Formats/Heif/Av1HeifItemDecoder.cs new file mode 100644 index 0000000000..3b96a3b46c --- /dev/null +++ b/src/ImageSharp/Formats/Heif/Av1HeifItemDecoder.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif.Av1; +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Heif; + +/// +/// Decoder for a single into a AVIF image. +/// +internal class Av1HeifItemDecoder : IHeifItemDecoder + where TPixel : unmanaged, IPixel +{ + /// + /// Gets the item type this decoder decodes, which is . + /// + public Heif4CharCode Type => Heif4CharCode.Av01; + + /// + /// Gets the compression method this doceder uses, which is . + /// + public HeifCompressionMethod CompressionMethod => HeifCompressionMethod.Av1; + + /// + /// Decode the specified item as AVIF. + /// + public Image DecodeItemData(Configuration configuration, HeifItem item, Span data) + { + Av1Decoder decoder = new(configuration); + return decoder.Decode(data); + } +} diff --git a/src/ImageSharp/Formats/Heif/HeifCompressionFactory.cs b/src/ImageSharp/Formats/Heif/HeifCompressionFactory.cs new file mode 100644 index 0000000000..cb874d5bc3 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/HeifCompressionFactory.cs @@ -0,0 +1,23 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Heif; + +/// +/// Factory for item decoders inside the HEIF container format. +/// +internal class HeifCompressionFactory +{ + /// + /// Get a decoder implementation. + /// + public static IHeifItemDecoder? GetDecoder(Heif4CharCode type) + where TPixel : unmanaged, IPixel => type switch + { + Heif4CharCode.Jpeg => new JpegHeifItemDecoder(), + Heif4CharCode.Av01 => new Av1HeifItemDecoder(), + _ => null + }; +} diff --git a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs index 4481aadf7b..6da3d100d9 100644 --- a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs @@ -5,6 +5,7 @@ using System; using System.Buffers; using System.Buffers.Binary; using System.Text; +using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Formats.Heif.Av1; using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.Memory; @@ -573,7 +574,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore long extentOffset = ReadUIntVariable(boxBuffer, offsetSize, ref bytesRead); long extentLength = ReadUIntVariable(boxBuffer, lengthSize, ref bytesRead); - HeifLocation loc = new(constructionMethod, baseOffset, dataReferenceIndex, extentOffset, extentLength, extentIndex); + HeifLocation loc = new(constructionMethod, baseOffset, extentOffset, extentLength); item?.DataLocations.Add(loc); } } @@ -629,41 +630,117 @@ internal sealed class HeifDecoderCore : ImageDecoderCore return result; } - private Image ParseMediaData(BufferedReadStream stream, long boxLength) + private Image ParseMediaData(Stream stream, long boxLength) where TPixel : unmanaged, IPixel { EnsureBoxBoundary(boxLength, stream); - // FIXME: No specific decoding yet, so parse only a JPEG thumbnail. - HeifItemLink? thumbLink = this.itemLinks.FirstOrDefault(link => link.Type == Heif4CharCode.Thmb); - if (thumbLink == null) + IComparer comparer = new HeifLocationComparer(stream.Position, stream.Position); + SortedList locations = new(comparer); + foreach (HeifItem item in this.items) { - throw new NotImplementedException("No thumbnail found"); + HeifLocation loc = item.DataLocations[0]; + if (loc.Length != 0) + { + locations[loc] = item; + } + } + + using DisposableDictionary> buffers = []; + foreach (HeifLocation loc in locations.Keys) + { + HeifItem item = locations[loc]; + long streamPosition = loc.GetStreamPosition(stream.Position, stream.Position); + long dataLength = loc.Length; + stream.Skip((int)(streamPosition - stream.Position)); + EnsureBoxBoundary(dataLength, stream); + buffers.Add(item.Id, this.ReadIntoBuffer(stream, dataLength)); } - HeifItem? thumbItem = this.FindItemById(thumbLink.SourceId); - if (thumbItem == null || thumbItem.Type != Heif4CharCode.Jpeg) + HeifItem? rootItem = this.FindItemById(this.primaryItem); + if (rootItem == null) { - throw new NotImplementedException("No HVC decoding implemented yet"); + throw new ImageFormatException("No primary HEIF item found."); } - 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(); + if (rootItem.Type == Heif4CharCode.Grid) + { + Image gridImage = this.DecodeGrid(rootItem, buffers); + if (gridImage.Width > 1 && gridImage.Height > 1) + { + return gridImage; + } + + gridImage.Dispose(); + } + IHeifItemDecoder? itemDecoder = HeifCompressionFactory.GetDecoder(rootItem.Type); + HeifItem itemToDecode = rootItem; + if (itemDecoder == null) + { + // FIXME: No specific decoding yet, so parse only a JPEG thumbnail. + HeifItemLink? thumbLink = this.itemLinks.FirstOrDefault(link => link.Type == Heif4CharCode.Thmb); + if (thumbLink == null) + { + throw new NotImplementedException("No thumbnail found"); + } + + HeifItem? thumbItem = this.FindItemById(thumbLink.SourceId); + if (thumbItem == null) + { + throw new NotImplementedException("No thumbnail defined"); + } + + itemDecoder = HeifCompressionFactory.GetDecoder(thumbItem.Type); + if (itemDecoder == null) + { + throw new NotImplementedException($"Don't know how to decode a thumbnail of type: {thumbItem.Type}."); + } + + itemToDecode = thumbItem; + } + + HeifMetadata meta = this.metadata.GetHeifMetadata(); + meta.CompressionMethod = itemDecoder.CompressionMethod; + + IMemoryOwner itemMemory = buffers[itemToDecode.Id]; + return itemDecoder.DecodeItemData(this.configuration, itemToDecode, itemMemory.GetSpan()); + } + + private Image DecodeGrid(HeifItem gridItem, IDictionary> buffers) + where TPixel : unmanaged, IPixel + { + List linked = this.itemLinks.First(l => l.SourceId == gridItem.Id).DestinationIds; HeifMetadata meta = this.metadata.GetHeifMetadata(); - meta.CompressionMethod = HeifCompressionMethod.LegacyJpeg; + using DisposableList> gridTiles = []; + foreach (uint id in linked) + { + HeifItem? item = this.FindItemById(id); + if (item != null) + { + IHeifItemDecoder? decoder = HeifCompressionFactory.GetDecoder(item.Type); + if (decoder != null) + { + meta.CompressionMethod = decoder.CompressionMethod; + IMemoryOwner itemMemory = buffers[item.Id]; + gridTiles.Add(decoder.DecodeItemData(this.configuration, item, itemMemory.GetSpan())); + } + } + } + + if (gridTiles.Count == 0) + { + return new Image(1, 1); + } - return Image.Load(thumbSpan); + // return CombineImageTiles(gridTiles); + return new Image(1, 1); } - private static void SkipBox(BufferedReadStream stream, long boxLength) + private static void SkipBox(Stream stream, long boxLength) => stream.Skip((int)boxLength); - private IMemoryOwner ReadIntoBuffer(BufferedReadStream stream, long length) + private IMemoryOwner ReadIntoBuffer(Stream stream, long length) { IMemoryOwner buffer = this.configuration.MemoryAllocator.Allocate((int)length); int bytesRead = stream.Read(buffer.GetSpan()); diff --git a/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs b/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs index 519aa3200b..8cee2f02c5 100644 --- a/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifEncoderCore.cs @@ -66,7 +66,7 @@ internal sealed class HeifEncoderCore where TPixel : unmanaged, IPixel { HeifItem primaryItem = new(Heif4CharCode.Jpeg, 1u); - primaryItem.DataLocations.Add(new HeifLocation(HeifLocationOffsetOrigin.ItemDataOffset, 0L, 0U, 0L, pixels.LongLength, 0U)); + primaryItem.DataLocations.Add(new HeifLocation(HeifLocationOffsetOrigin.ItemDataOffset, 0L, 0L, pixels.LongLength)); primaryItem.BitsPerPixel = 24; primaryItem.ChannelCount = 3; primaryItem.SetExtent(image.Size); diff --git a/src/ImageSharp/Formats/Heif/HeifLocation.cs b/src/ImageSharp/Formats/Heif/HeifLocation.cs index afbc3ad035..1f2f35787f 100644 --- a/src/ImageSharp/Formats/Heif/HeifLocation.cs +++ b/src/ImageSharp/Formats/Heif/HeifLocation.cs @@ -6,23 +6,18 @@ namespace SixLabors.ImageSharp.Formats.Heif; /// /// Location within the file of an . /// -internal class HeifLocation(HeifLocationOffsetOrigin method, long baseOffset, uint dataReferenceIndex, long offset, long length, uint extentIndex) +internal class HeifLocation(HeifLocationOffsetOrigin origin, long baseOffset, long offset, long length) { /// /// Gets the origin of the offsets in this location. /// - public HeifLocationOffsetOrigin Origin { get; } = method; + public HeifLocationOffsetOrigin Origin { get; } = origin; /// /// 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. /// @@ -33,11 +28,6 @@ internal class HeifLocation(HeifLocationOffsetOrigin method, long baseOffset, ui /// public long Length { get; } = length; - /// - /// Gets the extent index of this location. - /// - public uint ExtentInxdex { get; } = extentIndex; - /// /// Gets the stream position of this location. /// @@ -49,4 +39,21 @@ internal class HeifLocation(HeifLocationOffsetOrigin method, long baseOffset, ui HeifLocationOffsetOrigin.ItemDataOffset => positionOfMediaData + this.BaseOffset + this.Offset, _ => positionOfItem + this.BaseOffset + this.Offset }; + + public override int GetHashCode() => HashCode.Combine(this.Origin, this.Offset, this.Length, this.BaseOffset); + + public override bool Equals(object? obj) + { + if (obj is not HeifLocation other) + { + return false; + } + + if (this.Origin != other.Origin || this.Length != other.Length) + { + return false; + } + + return this.Offset == other.Offset && this.BaseOffset == other.BaseOffset; + } } diff --git a/src/ImageSharp/Formats/Heif/HeifLocationComparer.cs b/src/ImageSharp/Formats/Heif/HeifLocationComparer.cs new file mode 100644 index 0000000000..e249114ced --- /dev/null +++ b/src/ImageSharp/Formats/Heif/HeifLocationComparer.cs @@ -0,0 +1,39 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats.Heif; + +internal class HeifLocationComparer : IComparer +{ + private readonly long positionOfMediaData; + private readonly long positionOfItem; + + public HeifLocationComparer(long positionOfMediaData, long positionOfItem) + { + this.positionOfMediaData = positionOfMediaData; + this.positionOfItem = positionOfItem; + } + + public int Compare(HeifLocation? x, HeifLocation? y) + { + if (x == null) + { + if (y == null) + { + return 0; + } + + return 1; + } + + if (y == null) + { + return -1; + } + + long xPos = x.GetStreamPosition(this.positionOfMediaData, this.positionOfItem); + long yPos = y.GetStreamPosition(this.positionOfMediaData, this.positionOfItem); + + return Math.Sign(xPos - yPos); + } +} diff --git a/src/ImageSharp/Formats/Heif/IHeifItemDecoder.cs b/src/ImageSharp/Formats/Heif/IHeifItemDecoder.cs new file mode 100644 index 0000000000..b5f66afe4a --- /dev/null +++ b/src/ImageSharp/Formats/Heif/IHeifItemDecoder.cs @@ -0,0 +1,33 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Heif; + +/// +/// Decoder for a single . +/// +/// The pixel type to use. +internal interface IHeifItemDecoder + where TPixel : unmanaged, IPixel +{ + /// + /// Gets the type of item this decoder can decode. + /// + public Heif4CharCode Type { get; } + + /// + /// Gets the tis decoder uses. + /// + public HeifCompressionMethod CompressionMethod { get; } + + /// + /// Decode the specified item, given encoded data. + /// + /// The configuration to used. + /// The item to decode. + /// The encoded data. + /// The decoded image. + public Image DecodeItemData(Configuration configuration, HeifItem item, Span data); +} diff --git a/src/ImageSharp/Formats/Heif/JpegHeifItemDecoder.cs b/src/ImageSharp/Formats/Heif/JpegHeifItemDecoder.cs new file mode 100644 index 0000000000..b095f6eb14 --- /dev/null +++ b/src/ImageSharp/Formats/Heif/JpegHeifItemDecoder.cs @@ -0,0 +1,32 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.PixelFormats; + +namespace SixLabors.ImageSharp.Formats.Heif; + +/// +/// Decoder for a single into a JPEG image. +/// +internal class JpegHeifItemDecoder : IHeifItemDecoder + where TPixel : unmanaged, IPixel +{ + /// + /// Gets the item type this decoder decodes, which is . + /// + public Heif4CharCode Type => Heif4CharCode.Jpeg; + + /// + /// Gets the compression method this doceder uses, which is . + /// + public HeifCompressionMethod CompressionMethod => HeifCompressionMethod.LegacyJpeg; + + /// + /// Decode the specified item as JPEG. + /// + public Image DecodeItemData(Configuration configuration, HeifItem item, Span data) + { + Image image = Image.Load(data); + return image; + } +} diff --git a/tests/ImageSharp.Tests/Formats/Heif/HeifLocationTests.cs b/tests/ImageSharp.Tests/Formats/Heif/HeifLocationTests.cs new file mode 100644 index 0000000000..b1877c7b5a --- /dev/null +++ b/tests/ImageSharp.Tests/Formats/Heif/HeifLocationTests.cs @@ -0,0 +1,116 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Formats.Heif; + +namespace SixLabors.ImageSharp.Tests.Formats.Heif; + +[Trait("Format", "Heif")] +public class HeifLocationTests +{ + [Fact] + public void CheckSameLocationFromDifferentOrigin() + { + // Arrange + const int dataPosition = 50; + const int itemPosition = 100; + HeifLocation fromItem = new(HeifLocationOffsetOrigin.ItemOffset, 0, 10, 42); + HeifLocation fromFile = new(HeifLocationOffsetOrigin.FileOffset, 0, 110, 42); + HeifLocation fromData = new(HeifLocationOffsetOrigin.ItemDataOffset, 0, 60, 42); + + // Act + long itemActual = fromItem.GetStreamPosition(dataPosition, itemPosition); + long fileActual = fromFile.GetStreamPosition(dataPosition, itemPosition); + long dataActual = fromData.GetStreamPosition(dataPosition, itemPosition); + + // Assert + Assert.Equal(110, itemActual); + Assert.Equal(110, fileActual); + Assert.Equal(110, dataActual); + } + + [Fact] + public void CheckSameLocationFromDifferentOriginWithBaseOffset() + { + // Arrange + const int dataPosition = 50; + const int itemPosition = 100; + HeifLocation fromItem = new(HeifLocationOffsetOrigin.ItemOffset, 40, 10, 42); + HeifLocation fromFile = new(HeifLocationOffsetOrigin.FileOffset, 40, 110, 42); + HeifLocation fromData = new(HeifLocationOffsetOrigin.ItemDataOffset, 40, 60, 42); + + // Act + long itemActual = fromItem.GetStreamPosition(dataPosition, itemPosition); + long fileActual = fromFile.GetStreamPosition(dataPosition, itemPosition); + long dataActual = fromData.GetStreamPosition(dataPosition, itemPosition); + + // Assert + Assert.Equal(150, itemActual); + Assert.Equal(150, fileActual); + Assert.Equal(150, dataActual); + } + + [Fact] + public void CheckComparerOnSameLocation() + { + // Arrange + const int dataPosition = 50; + const int itemPosition = 100; + HeifLocation fromItem = new(HeifLocationOffsetOrigin.ItemOffset, 0, 50, 42); + HeifLocation fromFile = new(HeifLocationOffsetOrigin.FileOffset, 30, 120, 42); + HeifLocation fromData = new(HeifLocationOffsetOrigin.ItemDataOffset, 40, 60, 42); + HeifLocationComparer comparer = new(dataPosition, itemPosition); + + // Act + int item2Data = comparer.Compare(fromItem, fromData); + int item2File = comparer.Compare(fromItem, fromFile); + int file2Data = comparer.Compare(fromFile, fromData); + int data2File = comparer.Compare(fromData, fromFile); + + // Assert + Assert.Equal(0, item2Data); + Assert.Equal(0, item2File); + Assert.Equal(0, file2Data); + Assert.Equal(0, data2File); + } + + [Fact] + public void CheckComparerOnLowerLocation() + { + // Arrange + const int dataPosition = 50; + const int itemPosition = 100; + HeifLocation fromItem = new(HeifLocationOffsetOrigin.ItemOffset, 0, 50, 42); + HeifLocation fromFile = new(HeifLocationOffsetOrigin.FileOffset, 30, 150, 42); + HeifLocation fromData = new(HeifLocationOffsetOrigin.ItemDataOffset, 40, 80, 42); + HeifLocationComparer comparer = new(dataPosition, itemPosition); + + // Act + int item2Data = comparer.Compare(fromItem, fromData); + int item2File = comparer.Compare(fromItem, fromFile); + + // Assert + Assert.Equal(-1, item2Data); + Assert.Equal(-1, item2File); + } + + [Fact] + public void CheckComparerOnHigherLocation() + { + // Arrange + const int dataPosition = 50; + const int itemPosition = 100; + HeifLocation fromItem = new(HeifLocationOffsetOrigin.ItemOffset, 10, 50, 42); + HeifLocation fromFile = new(HeifLocationOffsetOrigin.FileOffset, 30, 120, 42); + HeifLocation fromData = new(HeifLocationOffsetOrigin.ItemDataOffset, 40, 60, 42); + HeifLocationComparer comparer = new(dataPosition, itemPosition); + + // Act + int item2Data = comparer.Compare(fromItem, fromData); + int item2File = comparer.Compare(fromItem, fromFile); + + // Assert + Assert.Equal(1, item2Data); + Assert.Equal(1, item2File); + } +}