From 0ee06fd8577c9ab2e87eb9c83f837200066533c0 Mon Sep 17 00:00:00 2001 From: Ynse Hoornenborg Date: Fri, 21 Feb 2025 20:38:33 +0100 Subject: [PATCH] Separate grid item decoder --- .../Common/Helpers/DisposableDictionary.cs | 12 +++ .../Common/Helpers/DisposableList.cs | 12 +++ .../Formats/Heif/GridHeifItemDecoder.cs | 70 +++++++++++++++++ .../Formats/Heif/HeifDecoderCore.cs | 78 ++++++------------- src/ImageSharp/Formats/Heif/HeifItem.cs | 2 + 5 files changed, 118 insertions(+), 56 deletions(-) create mode 100644 src/ImageSharp/Formats/Heif/GridHeifItemDecoder.cs diff --git a/src/ImageSharp/Common/Helpers/DisposableDictionary.cs b/src/ImageSharp/Common/Helpers/DisposableDictionary.cs index 3ba0ac62c0..d96d4387fd 100644 --- a/src/ImageSharp/Common/Helpers/DisposableDictionary.cs +++ b/src/ImageSharp/Common/Helpers/DisposableDictionary.cs @@ -14,6 +14,18 @@ public sealed class DisposableDictionary : Dictionary + public DisposableDictionary() + : base() + { + } + + /// + public DisposableDictionary(int capacity) + : base(capacity) + { + } + /// public void Dispose() { diff --git a/src/ImageSharp/Common/Helpers/DisposableList.cs b/src/ImageSharp/Common/Helpers/DisposableList.cs index 13ec11f88d..da71670c8d 100644 --- a/src/ImageSharp/Common/Helpers/DisposableList.cs +++ b/src/ImageSharp/Common/Helpers/DisposableList.cs @@ -12,6 +12,18 @@ public sealed class DisposableList : List, IDisposable { private bool disposedValue; + /// + public DisposableList() + : base() + { + } + + /// + public DisposableList(int capacity) + : base(capacity) + { + } + /// public void Dispose() { diff --git a/src/ImageSharp/Formats/Heif/GridHeifItemDecoder.cs b/src/ImageSharp/Formats/Heif/GridHeifItemDecoder.cs new file mode 100644 index 0000000000..4a447e7636 --- /dev/null +++ b/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; + +/// +/// Decoder for a grid of several into a single image. +/// +internal class GridHeifItemDecoder : IHeifItemDecoder + where TPixel : unmanaged, IPixel +{ + private readonly Configuration configuration; + private readonly IList items; + private readonly IList itemLinks; + private readonly IDictionary> buffers; + + public GridHeifItemDecoder(Configuration configuration, IList items, IList itemLinks, IDictionary> buffers) + { + this.configuration = configuration; + this.items = items; + this.itemLinks = itemLinks; + this.buffers = buffers; + } + + /// + /// Gets the item type this decoder decodes, which is . + /// + public Heif4CharCode Type => Heif4CharCode.Grid; + + /// + /// Gets the compression method this doceder uses. + /// + public HeifCompressionMethod CompressionMethod { get; private set; } + + /// + /// Decode the specified item as single image. + /// + public Image DecodeItemData(Configuration configuration, HeifItem gridItem, Span data) + { + List linked = this.itemLinks.First(l => l.SourceId == gridItem.Id).DestinationIds; + using DisposableList> gridTiles = new(linked.Count); + foreach (uint id in linked) + { + HeifItem? item = this.items.FirstOrDefault(item => item.Id == id); + if (item != null) + { + IHeifItemDecoder? decoder = HeifCompressionFactory.GetDecoder(item.Type); + if (decoder != null) + { + this.CompressionMethod = decoder.CompressionMethod; + IMemoryOwner itemMemory = this.buffers[item.Id]; + gridTiles.Add(decoder.DecodeItemData(this.configuration, item, itemMemory.GetSpan())); + } + } + } + + if (gridTiles.Count == 0) + { + return new Image(1, 1); + } + + // TODO: Combine grid tiles into a single image. + return new Image(1, 1); + } +} diff --git a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs index c98b8c659c..8ef92ae031 100644 --- a/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs +++ b/src/ImageSharp/Formats/Heif/HeifDecoderCore.cs @@ -58,6 +58,8 @@ internal sealed class HeifDecoderCore : ImageDecoderCore throw new ImageFormatException("Not an HEIF image."); } + this.items.Clear(); + this.itemLinks.Clear(); Image? image = null; while (stream.Position < stream.Length) { @@ -645,7 +647,7 @@ internal sealed class HeifDecoderCore : ImageDecoderCore } } - using DisposableDictionary> buffers = []; + using DisposableDictionary> buffers = new(locations.Count); foreach (HeifLocation loc in locations.Keys) { HeifItem item = locations[loc]; @@ -659,44 +661,38 @@ internal sealed class HeifDecoderCore : ImageDecoderCore HeifItem? rootItem = this.FindItemById(this.primaryItem); if (rootItem == null) { - throw new ImageFormatException("No primary HEIF item found."); + throw new ImageFormatException("No primary HEIF item defined."); } + IHeifItemDecoder? itemDecoder; if (rootItem.Type == Heif4CharCode.Grid) { - Image gridImage = this.DecodeGrid(rootItem, buffers); - if (gridImage.Width > 1 && gridImage.Height > 1) - { - return gridImage; - } - - gridImage.Dispose(); + itemDecoder = new GridHeifItemDecoder(this.configuration, this.items, this.itemLinks, buffers); } - IHeifItemDecoder? itemDecoder = HeifCompressionFactory.GetDecoder(rootItem.Type); + itemDecoder = HeifCompressionFactory.GetDecoder(rootItem.Type); HeifItem itemToDecode = rootItem; 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); - 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) + if (thumbLink != 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(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(); @@ -706,36 +702,6 @@ internal sealed class HeifDecoderCore : ImageDecoderCore return itemDecoder.DecodeItemData(this.configuration, itemToDecode, itemMemory.GetSpan()); } - private Image DecodeGrid(HeifItem gridItem, DisposableDictionary> buffers) - where TPixel : unmanaged, IPixel - { - List linked = this.itemLinks.First(l => l.SourceId == gridItem.Id).DestinationIds; - HeifMetadata meta = this.metadata.GetHeifMetadata(); - 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 CombineImageTiles(gridTiles); - return new Image(1, 1); - } - private static void SkipBox(Stream stream, long boxLength) => stream.Skip((int)boxLength); diff --git a/src/ImageSharp/Formats/Heif/HeifItem.cs b/src/ImageSharp/Formats/Heif/HeifItem.cs index d9f355317e..3c4cafe3d0 100644 --- a/src/ImageSharp/Formats/Heif/HeifItem.cs +++ b/src/ImageSharp/Formats/Heif/HeifItem.cs @@ -91,4 +91,6 @@ internal class HeifItem(Heif4CharCode type, uint id) this.GridCellExtent = extent; } } + + public override string ToString() => $"{this.Type}:{this.Id}"; }