Browse Source

Separate grid item decoder

pull/2633/head
Ynse Hoornenborg 1 year ago
parent
commit
0ee06fd857
  1. 12
      src/ImageSharp/Common/Helpers/DisposableDictionary.cs
  2. 12
      src/ImageSharp/Common/Helpers/DisposableList.cs
  3. 70
      src/ImageSharp/Formats/Heif/GridHeifItemDecoder.cs
  4. 78
      src/ImageSharp/Formats/Heif/HeifDecoderCore.cs
  5. 2
      src/ImageSharp/Formats/Heif/HeifItem.cs

12
src/ImageSharp/Common/Helpers/DisposableDictionary.cs

@ -14,6 +14,18 @@ public sealed class DisposableDictionary<TKey, TValue> : Dictionary<TKey, TValue
{ {
private bool disposedValue; private bool disposedValue;
/// <inheritdoc />
public DisposableDictionary()
: base()
{
}
/// <inheritdoc />
public DisposableDictionary(int capacity)
: base(capacity)
{
}
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
{ {

12
src/ImageSharp/Common/Helpers/DisposableList.cs

@ -12,6 +12,18 @@ public sealed class DisposableList<TValue> : List<TValue>, IDisposable
{ {
private bool disposedValue; private bool disposedValue;
/// <inheritdoc />
public DisposableList()
: base()
{
}
/// <inheritdoc />
public DisposableList(int capacity)
: base(capacity)
{
}
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose()
{ {

70
src/ImageSharp/Formats/Heif/GridHeifItemDecoder.cs

@ -0,0 +1,70 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Heif;
/// <summary>
/// Decoder for a grid of several <see cref="HeifItem"/> into a single image.
/// </summary>
internal class GridHeifItemDecoder<TPixel> : IHeifItemDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly Configuration configuration;
private readonly IList<HeifItem> items;
private readonly IList<HeifItemLink> itemLinks;
private readonly IDictionary<uint, IMemoryOwner<byte>> buffers;
public GridHeifItemDecoder(Configuration configuration, IList<HeifItem> items, IList<HeifItemLink> itemLinks, IDictionary<uint, IMemoryOwner<byte>> buffers)
{
this.configuration = configuration;
this.items = items;
this.itemLinks = itemLinks;
this.buffers = buffers;
}
/// <summary>
/// Gets the item type this decoder decodes, which is <see cref="Heif4CharCode.Grid"/>.
/// </summary>
public Heif4CharCode Type => Heif4CharCode.Grid;
/// <summary>
/// Gets the compression method this doceder uses.
/// </summary>
public HeifCompressionMethod CompressionMethod { get; private set; }
/// <summary>
/// Decode the specified item as single image.
/// </summary>
public Image<TPixel> DecodeItemData(Configuration configuration, HeifItem gridItem, Span<byte> data)
{
List<uint> linked = this.itemLinks.First(l => l.SourceId == gridItem.Id).DestinationIds;
using DisposableList<Image<TPixel>> gridTiles = new(linked.Count);
foreach (uint id in linked)
{
HeifItem? item = this.items.FirstOrDefault(item => item.Id == id);
if (item != null)
{
IHeifItemDecoder<TPixel>? decoder = HeifCompressionFactory.GetDecoder<TPixel>(item.Type);
if (decoder != null)
{
this.CompressionMethod = decoder.CompressionMethod;
IMemoryOwner<byte> itemMemory = this.buffers[item.Id];
gridTiles.Add(decoder.DecodeItemData(this.configuration, item, itemMemory.GetSpan()));
}
}
}
if (gridTiles.Count == 0)
{
return new Image<TPixel>(1, 1);
}
// TODO: Combine grid tiles into a single image.
return new Image<TPixel>(1, 1);
}
}

78
src/ImageSharp/Formats/Heif/HeifDecoderCore.cs

@ -58,6 +58,8 @@ internal sealed class HeifDecoderCore : ImageDecoderCore
throw new ImageFormatException("Not an HEIF image."); throw new ImageFormatException("Not an HEIF image.");
} }
this.items.Clear();
this.itemLinks.Clear();
Image<TPixel>? image = null; Image<TPixel>? image = null;
while (stream.Position < stream.Length) while (stream.Position < stream.Length)
{ {
@ -645,7 +647,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore
} }
} }
using DisposableDictionary<uint, IMemoryOwner<byte>> buffers = []; using DisposableDictionary<uint, IMemoryOwner<byte>> buffers = new(locations.Count);
foreach (HeifLocation loc in locations.Keys) foreach (HeifLocation loc in locations.Keys)
{ {
HeifItem item = locations[loc]; HeifItem item = locations[loc];
@ -659,44 +661,38 @@ internal sealed class HeifDecoderCore : ImageDecoderCore
HeifItem? rootItem = this.FindItemById(this.primaryItem); HeifItem? rootItem = this.FindItemById(this.primaryItem);
if (rootItem == null) if (rootItem == null)
{ {
throw new ImageFormatException("No primary HEIF item found."); throw new ImageFormatException("No primary HEIF item defined.");
} }
IHeifItemDecoder<TPixel>? itemDecoder;
if (rootItem.Type == Heif4CharCode.Grid) if (rootItem.Type == Heif4CharCode.Grid)
{ {
Image<TPixel> gridImage = this.DecodeGrid<TPixel>(rootItem, buffers); itemDecoder = new GridHeifItemDecoder<TPixel>(this.configuration, this.items, this.itemLinks, buffers);
if (gridImage.Width > 1 && gridImage.Height > 1)
{
return gridImage;
}
gridImage.Dispose();
} }
IHeifItemDecoder<TPixel>? itemDecoder = HeifCompressionFactory.GetDecoder<TPixel>(rootItem.Type); itemDecoder = HeifCompressionFactory.GetDecoder<TPixel>(rootItem.Type);
HeifItem itemToDecode = rootItem; HeifItem itemToDecode = rootItem;
if (itemDecoder == null) if (itemDecoder == null)
{ {
// FIXME: No specific decoding yet, so parse only a JPEG thumbnail. // Unable to decode the primary image, decode the thumbnail instead.
HeifItemLink? thumbLink = this.itemLinks.FirstOrDefault(link => link.Type == Heif4CharCode.Thmb); HeifItemLink? thumbLink = this.itemLinks.FirstOrDefault(link => link.Type == Heif4CharCode.Thmb);
if (thumbLink == null) 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}."); HeifItem? thumbItem = this.FindItemById(thumbLink.SourceId);
if (thumbItem != null)
{
itemDecoder = HeifCompressionFactory.GetDecoder<TPixel>(thumbItem.Type);
if (itemDecoder != null)
{
itemToDecode = thumbItem;
}
}
} }
}
itemToDecode = thumbItem; if (itemDecoder == null)
{
throw new ImageFormatException("No decodable item found inside this HEIF container.");
} }
HeifMetadata meta = this.metadata.GetHeifMetadata(); HeifMetadata meta = this.metadata.GetHeifMetadata();
@ -706,36 +702,6 @@ internal sealed class HeifDecoderCore : ImageDecoderCore
return itemDecoder.DecodeItemData(this.configuration, itemToDecode, itemMemory.GetSpan()); return itemDecoder.DecodeItemData(this.configuration, itemToDecode, itemMemory.GetSpan());
} }
private Image<TPixel> DecodeGrid<TPixel>(HeifItem gridItem, DisposableDictionary<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();
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 CombineImageTiles(gridTiles);
return new Image<TPixel>(1, 1);
}
private static void SkipBox(Stream stream, long boxLength) private static void SkipBox(Stream stream, long boxLength)
=> stream.Skip((int)boxLength); => stream.Skip((int)boxLength);

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

@ -91,4 +91,6 @@ internal class HeifItem(Heif4CharCode type, uint id)
this.GridCellExtent = extent; this.GridCellExtent = extent;
} }
} }
public override string ToString() => $"{this.Type}:{this.Id}";
} }

Loading…
Cancel
Save