Browse Source

Read all items from input stream

pull/2633/head
Ynse Hoornenborg 1 year ago
parent
commit
e71012eafe
  1. 42
      src/ImageSharp/Common/Helpers/DisposableDictionary.cs
  2. 40
      src/ImageSharp/Common/Helpers/DisposableList.cs
  3. 7
      src/ImageSharp/Formats/Heif/Av1/Av1Decoder.cs
  4. 33
      src/ImageSharp/Formats/Heif/Av1HeifItemDecoder.cs
  5. 23
      src/ImageSharp/Formats/Heif/HeifCompressionFactory.cs
  6. 115
      src/ImageSharp/Formats/Heif/HeifDecoderCore.cs
  7. 2
      src/ImageSharp/Formats/Heif/HeifEncoderCore.cs
  8. 31
      src/ImageSharp/Formats/Heif/HeifLocation.cs
  9. 39
      src/ImageSharp/Formats/Heif/HeifLocationComparer.cs
  10. 33
      src/ImageSharp/Formats/Heif/IHeifItemDecoder.cs
  11. 32
      src/ImageSharp/Formats/Heif/JpegHeifItemDecoder.cs
  12. 116
      tests/ImageSharp.Tests/Formats/Heif/HeifLocationTests.cs

42
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;
/// <summary>
/// Dictionary of <see cref="IDisposable"/> objects, which is itself <see cref="IDisposable"/>.
/// </summary>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <typeparam name="TValue">Tye type of value, needs to implement <see cref="IDisposable"/>.</typeparam>
public sealed class DisposableDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IDisposable
where TKey : notnull
where TValue : IDisposable
{
private bool disposedValue;
/// <inheritdoc />
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<TKey, TValue> pair in this)
{
pair.Value?.Dispose();
}
}
this.Clear();
this.disposedValue = true;
}
}
}

40
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;
/// <summary>
/// List of <see cref="IDisposable"/> objects, which is itself <see cref="IDisposable"/>.
/// </summary>
/// <typeparam name="TValue">Tye type of value, needs to implement <see cref="IDisposable"/>.</typeparam>
public sealed class DisposableList<TValue> : List<TValue>, IDisposable
where TValue : IDisposable
{
private bool disposedValue;
/// <inheritdoc />
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;
}
}
}

7
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<byte>? FrameBuffer { get; private set; }
public void Decode(Span<byte> buffer)
public Image<TPixel> Decode<TPixel>(Span<byte> buffer)
where TPixel : unmanaged, IPixel<TPixel>
{
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<TPixel>(1, 1);
}
public void ReadTile(Span<byte> tileData, int tileNum)

33
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;
/// <summary>
/// Decoder for a single <see cref="HeifItem"/> into a AVIF image.
/// </summary>
internal class Av1HeifItemDecoder<TPixel> : IHeifItemDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Gets the item type this decoder decodes, which is <see cref="Heif4CharCode.Av01"/>.
/// </summary>
public Heif4CharCode Type => Heif4CharCode.Av01;
/// <summary>
/// Gets the compression method this doceder uses, which is <see cref="HeifCompressionMethod.Av1"/>.
/// </summary>
public HeifCompressionMethod CompressionMethod => HeifCompressionMethod.Av1;
/// <summary>
/// Decode the specified item as AVIF.
/// </summary>
public Image<TPixel> DecodeItemData(Configuration configuration, HeifItem item, Span<byte> data)
{
Av1Decoder decoder = new(configuration);
return decoder.Decode<TPixel>(data);
}
}

23
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;
/// <summary>
/// Factory for item decoders inside the HEIF container format.
/// </summary>
internal class HeifCompressionFactory
{
/// <summary>
/// Get a decoder implementation.
/// </summary>
public static IHeifItemDecoder<TPixel>? GetDecoder<TPixel>(Heif4CharCode type)
where TPixel : unmanaged, IPixel<TPixel> => type switch
{
Heif4CharCode.Jpeg => new JpegHeifItemDecoder<TPixel>(),
Heif4CharCode.Av01 => new Av1HeifItemDecoder<TPixel>(),
_ => null
};
}

115
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<TPixel> ParseMediaData<TPixel>(BufferedReadStream stream, long boxLength)
private Image<TPixel> ParseMediaData<TPixel>(Stream stream, long boxLength)
where TPixel : unmanaged, IPixel<TPixel>
{
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<HeifLocation> comparer = new HeifLocationComparer(stream.Position, stream.Position);
SortedList<HeifLocation, HeifItem> 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<uint, IMemoryOwner<byte>> 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<byte> thumbMemory = this.ReadIntoBuffer(stream, thumbLength);
Span<byte> thumbSpan = thumbMemory.GetSpan();
if (rootItem.Type == Heif4CharCode.Grid)
{
Image<TPixel> gridImage = this.DecodeGrid<TPixel>(rootItem, buffers);
if (gridImage.Width > 1 && gridImage.Height > 1)
{
return gridImage;
}
gridImage.Dispose();
}
IHeifItemDecoder<TPixel>? itemDecoder = HeifCompressionFactory.GetDecoder<TPixel>(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<TPixel>(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<byte> itemMemory = buffers[itemToDecode.Id];
return itemDecoder.DecodeItemData(this.configuration, itemToDecode, itemMemory.GetSpan());
}
private Image<TPixel> DecodeGrid<TPixel>(HeifItem gridItem, IDictionary<uint, IMemoryOwner<byte>> buffers)
where TPixel : unmanaged, IPixel<TPixel>
{
List<uint> linked = this.itemLinks.First(l => l.SourceId == gridItem.Id).DestinationIds;
HeifMetadata meta = this.metadata.GetHeifMetadata();
meta.CompressionMethod = HeifCompressionMethod.LegacyJpeg;
using DisposableList<Image<TPixel>> gridTiles = [];
foreach (uint id in linked)
{
HeifItem? item = this.FindItemById(id);
if (item != null)
{
IHeifItemDecoder<TPixel>? decoder = HeifCompressionFactory.GetDecoder<TPixel>(item.Type);
if (decoder != null)
{
meta.CompressionMethod = decoder.CompressionMethod;
IMemoryOwner<byte> itemMemory = buffers[item.Id];
gridTiles.Add(decoder.DecodeItemData(this.configuration, item, itemMemory.GetSpan()));
}
}
}
if (gridTiles.Count == 0)
{
return new Image<TPixel>(1, 1);
}
return Image.Load<TPixel>(thumbSpan);
// return CombineImageTiles(gridTiles);
return new Image<TPixel>(1, 1);
}
private static void SkipBox(BufferedReadStream stream, long boxLength)
private static void SkipBox(Stream stream, long boxLength)
=> stream.Skip((int)boxLength);
private IMemoryOwner<byte> ReadIntoBuffer(BufferedReadStream stream, long length)
private IMemoryOwner<byte> ReadIntoBuffer(Stream stream, long length)
{
IMemoryOwner<byte> buffer = this.configuration.MemoryAllocator.Allocate<byte>((int)length);
int bytesRead = stream.Read(buffer.GetSpan());

2
src/ImageSharp/Formats/Heif/HeifEncoderCore.cs

@ -66,7 +66,7 @@ internal sealed class HeifEncoderCore
where TPixel : unmanaged, IPixel<TPixel>
{
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);

31
src/ImageSharp/Formats/Heif/HeifLocation.cs

@ -6,23 +6,18 @@ namespace SixLabors.ImageSharp.Formats.Heif;
/// <summary>
/// Location within the file of an <see cref="HeifItem"/>.
/// </summary>
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)
{
/// <summary>
/// Gets the origin of the offsets in this location.
/// </summary>
public HeifLocationOffsetOrigin Origin { get; } = method;
public HeifLocationOffsetOrigin Origin { get; } = origin;
/// <summary>
/// Gets the base offset of this location.
/// </summary>
public long BaseOffset { get; } = baseOffset;
/// <summary>
/// Gets the data reference index of this location.
/// </summary>
public uint DataReferenceInxdex { get; } = dataReferenceIndex;
/// <summary>
/// Gets the offset of this location.
/// </summary>
@ -33,11 +28,6 @@ internal class HeifLocation(HeifLocationOffsetOrigin method, long baseOffset, ui
/// </summary>
public long Length { get; } = length;
/// <summary>
/// Gets the extent index of this location.
/// </summary>
public uint ExtentInxdex { get; } = extentIndex;
/// <summary>
/// Gets the stream position of this location.
/// </summary>
@ -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;
}
}

39
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<HeifLocation>
{
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);
}
}

33
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;
/// <summary>
/// Decoder for a single <see cref="HeifItem"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel type to use.</typeparam>
internal interface IHeifItemDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Gets the type of item this decoder can decode.
/// </summary>
public Heif4CharCode Type { get; }
/// <summary>
/// Gets the <see cref="HeifCompressionMethod"/> tis decoder uses.
/// </summary>
public HeifCompressionMethod CompressionMethod { get; }
/// <summary>
/// Decode the specified item, given encoded data.
/// </summary>
/// <param name="configuration">The configuration to used.</param>
/// <param name="item">The item to decode.</param>
/// <param name="data">The encoded data.</param>
/// <returns>The decoded image.</returns>
public Image<TPixel> DecodeItemData(Configuration configuration, HeifItem item, Span<byte> data);
}

32
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;
/// <summary>
/// Decoder for a single <see cref="HeifItem"/> into a JPEG image.
/// </summary>
internal class JpegHeifItemDecoder<TPixel> : IHeifItemDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Gets the item type this decoder decodes, which is <see cref="Heif4CharCode.Jpeg"/>.
/// </summary>
public Heif4CharCode Type => Heif4CharCode.Jpeg;
/// <summary>
/// Gets the compression method this doceder uses, which is <see cref="HeifCompressionMethod.LegacyJpeg"/>.
/// </summary>
public HeifCompressionMethod CompressionMethod => HeifCompressionMethod.LegacyJpeg;
/// <summary>
/// Decode the specified item as JPEG.
/// </summary>
public Image<TPixel> DecodeItemData(Configuration configuration, HeifItem item, Span<byte> data)
{
Image<TPixel> image = Image.Load<TPixel>(data);
return image;
}
}

116
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);
}
}
Loading…
Cancel
Save