Browse Source

Allow returning individual image frame metadata via Identify.

pull/2363/head
James Jackson-South 3 years ago
parent
commit
c21bbbd531
  1. 2
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  2. 72
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  3. 155
      src/ImageSharp/Formats/Gif/LzwDecoder.cs
  4. 2
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  5. 2
      src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs
  6. 2
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  7. 3
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  8. 21
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  9. 10
      src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs
  10. 10
      src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
  11. 17
      src/ImageSharp/Formats/Tiff/TiffMetadata.cs
  12. 5
      src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
  13. 52
      src/ImageSharp/Image.cs
  14. 21
      src/ImageSharp/ImageFrameCollectionExtensions.cs
  15. 27
      src/ImageSharp/ImageInfo.cs
  16. 69
      tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs
  17. 136
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs
  18. 11
      tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs
  19. 24
      tests/ImageSharp.Tests/ImageInfoTests.cs
  20. 5
      tests/ImageSharp.Tests/TestFormat.cs
  21. 2
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs
  22. 2
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs
  23. 5
      tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs

2
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -208,7 +208,7 @@ internal sealed class BmpDecoderCore : IImageDecoderInternals
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.ReadImageHeaders(stream, out _, out _);
return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), this.infoHeader.Width, this.infoHeader.Height, this.metadata);
return new ImageInfo(new PixelTypeInfo(this.infoHeader.BitsPerPixel), new(this.infoHeader.Width, this.infoHeader.Height), this.metadata);
}
/// <summary>

72
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -172,6 +172,9 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
/// <inheritdoc />
public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
uint frameCount = 0;
ImageFrameMetadata? previousFrame = null;
List<ImageFrameMetadata> framesMetadata = new();
try
{
this.ReadLogicalScreenDescriptorAndGlobalColorTable(stream);
@ -182,14 +185,23 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
{
if (nextFlag == GifConstants.ImageLabel)
{
this.ReadImageDescriptor(stream);
if (previousFrame != null && ++frameCount == this.maxFrames)
{
break;
}
this.ReadFrameMetadata(stream, framesMetadata, ref previousFrame);
// Reset per-frame state.
this.imageDescriptor = default;
this.graphicsControlExtension = default;
}
else if (nextFlag == GifConstants.ExtensionIntroducer)
{
switch (stream.ReadByte())
{
case GifConstants.GraphicControlLabel:
SkipBlock(stream); // Skip graphic control extension block
this.ReadGraphicalControlExtension(stream);
break;
case GifConstants.CommentLabel:
this.ReadComments(stream);
@ -226,9 +238,9 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
return new ImageInfo(
new PixelTypeInfo(this.logicalScreenDescriptor.BitsPerPixel),
this.logicalScreenDescriptor.Width,
this.logicalScreenDescriptor.Height,
this.metadata);
new(this.logicalScreenDescriptor.Width, this.logicalScreenDescriptor.Height),
this.metadata,
framesMetadata);
}
/// <summary>
@ -486,7 +498,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
image = new Image<TPixel>(this.configuration, imageWidth, imageHeight, this.metadata);
}
this.SetFrameMetadata(image.Frames.RootFrame.Metadata, true);
this.SetFrameMetadata(image.Frames.RootFrame.Metadata);
imageFrame = image.Frames.RootFrame;
}
@ -499,7 +511,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
currentFrame = image!.Frames.CreateFrame();
this.SetFrameMetadata(currentFrame.Metadata, false);
this.SetFrameMetadata(currentFrame.Metadata);
imageFrame = currentFrame;
@ -606,6 +618,37 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
}
}
/// <summary>
/// Reads the frames metadata.
/// </summary>
/// <param name="stream">The <see cref="BufferedReadStream"/> containing image data.</param>
/// <param name="frameMetadata">The collection of frame metadata.</param>
/// <param name="previousFrame">The previous frame metadata.</param>
private void ReadFrameMetadata(BufferedReadStream stream, List<ImageFrameMetadata> frameMetadata, ref ImageFrameMetadata? previousFrame)
{
this.ReadImageDescriptor(stream);
// Skip the color table for this frame if local.
if (this.imageDescriptor.LocalColorTableFlag)
{
stream.Skip(this.imageDescriptor.LocalColorTableSize * 3);
}
// Skip the frame indices. Pixels length + mincode size.
// The gif format does not tell us the length of the compressed data beforehand.
int minCodeSize = stream.ReadByte();
using LzwDecoder lzwDecoder = new(this.configuration.MemoryAllocator, stream);
lzwDecoder.SkipIndices(minCodeSize, this.imageDescriptor.Width * this.imageDescriptor.Height);
ImageFrameMetadata currentFrame = new();
frameMetadata.Add(currentFrame);
this.SetFrameMetadata(currentFrame);
previousFrame = currentFrame;
// Skip any remaining blocks
SkipBlock(stream);
}
/// <summary>
/// Restores the current frame area to the background.
/// </summary>
@ -627,18 +670,17 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
}
/// <summary>
/// Sets the frames metadata.
/// Sets the metadata for the image frame.
/// </summary>
/// <param name="meta">The metadata.</param>
/// <param name="isRoot">Whether the metadata represents the root frame.</param>
/// <param name="metadata">The metadata.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SetFrameMetadata(ImageFrameMetadata meta, bool isRoot)
private void SetFrameMetadata(ImageFrameMetadata metadata)
{
// Frames can either use the global table or their own local table.
if (isRoot && this.logicalScreenDescriptor.GlobalColorTableFlag
if (this.logicalScreenDescriptor.GlobalColorTableFlag
&& this.logicalScreenDescriptor.GlobalColorTableSize > 0)
{
GifFrameMetadata gifMeta = meta.GetGifMetadata();
GifFrameMetadata gifMeta = metadata.GetGifMetadata();
gifMeta.ColorTableMode = GifColorTableMode.Global;
gifMeta.ColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize;
}
@ -646,7 +688,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
if (this.imageDescriptor.LocalColorTableFlag
&& this.imageDescriptor.LocalColorTableSize > 0)
{
GifFrameMetadata gifMeta = meta.GetGifMetadata();
GifFrameMetadata gifMeta = metadata.GetGifMetadata();
gifMeta.ColorTableMode = GifColorTableMode.Local;
gifMeta.ColorTableLength = this.imageDescriptor.LocalColorTableSize;
}
@ -654,7 +696,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals
// Graphics control extensions is optional.
if (this.graphicsControlExtension != default)
{
GifFrameMetadata gifMeta = meta.GetGifMetadata();
GifFrameMetadata gifMeta = metadata.GetGifMetadata();
gifMeta.FrameDelay = this.graphicsControlExtension.DelayTime;
gifMeta.DisposalMethod = this.graphicsControlExtension.DisposalMethod;
}

155
src/ImageSharp/Formats/Gif/LzwDecoder.cs

@ -61,7 +61,7 @@ internal sealed class LzwDecoder : IDisposable
}
/// <summary>
/// Decodes and decompresses all pixel indices from the stream.
/// Decodes and decompresses all pixel indices from the stream, assigning the pixel values to the buffer.
/// </summary>
/// <param name="minCodeSize">Minimum code size of the data.</param>
/// <param name="pixels">The pixel array to decode to.</param>
@ -232,6 +232,159 @@ internal sealed class LzwDecoder : IDisposable
}
}
/// <summary>
/// Decodes and decompresses all pixel indices from the stream allowing skipping of the data.
/// </summary>
/// <param name="minCodeSize">Minimum code size of the data.</param>
/// <param name="length">The resulting index table length.</param>
public void SkipIndices(int minCodeSize, int length)
{
// Calculate the clear code. The value of the clear code is 2 ^ minCodeSize
int clearCode = 1 << minCodeSize;
// It is possible to specify a larger LZW minimum code size than the palette length in bits
// which may leave a gap in the codes where no colors are assigned.
// http://www.matthewflickinger.com/lab/whatsinagif/lzw_image_data.asp#lzw_compression
if (minCodeSize < 2 || clearCode > MaxStackSize)
{
// Don't attempt to decode the frame indices.
// Theoretically we could determine a min code size from the length of the provided
// color palette but we won't bother since the image is most likely corrupted.
GifThrowHelper.ThrowInvalidImageContentException("Gif Image does not contain a valid LZW minimum code.");
}
int codeSize = minCodeSize + 1;
// Calculate the end code
int endCode = clearCode + 1;
// Calculate the available code.
int availableCode = clearCode + 2;
// Jillzhangs Code see: http://giflib.codeplex.com/
// Adapted from John Cristy's ImageMagick.
int code;
int oldCode = NullCode;
int codeMask = (1 << codeSize) - 1;
int bits = 0;
int top = 0;
int count = 0;
int bi = 0;
int xyz = 0;
int data = 0;
int first = 0;
ref int prefixRef = ref MemoryMarshal.GetReference(this.prefix.GetSpan());
ref int suffixRef = ref MemoryMarshal.GetReference(this.suffix.GetSpan());
ref int pixelStackRef = ref MemoryMarshal.GetReference(this.pixelStack.GetSpan());
for (code = 0; code < clearCode; code++)
{
Unsafe.Add(ref suffixRef, code) = (byte)code;
}
Span<byte> buffer = stackalloc byte[byte.MaxValue];
while (xyz < length)
{
if (top == 0)
{
if (bits < codeSize)
{
// Load bytes until there are enough bits for a code.
if (count == 0)
{
// Read a new data block.
count = this.ReadBlock(buffer);
if (count == 0)
{
break;
}
bi = 0;
}
data += buffer[bi] << bits;
bits += 8;
bi++;
count--;
continue;
}
// Get the next code
code = data & codeMask;
data >>= codeSize;
bits -= codeSize;
// Interpret the code
if (code > availableCode || code == endCode)
{
break;
}
if (code == clearCode)
{
// Reset the decoder
codeSize = minCodeSize + 1;
codeMask = (1 << codeSize) - 1;
availableCode = clearCode + 2;
oldCode = NullCode;
continue;
}
if (oldCode == NullCode)
{
Unsafe.Add(ref pixelStackRef, top++) = Unsafe.Add(ref suffixRef, code);
oldCode = code;
first = code;
continue;
}
int inCode = code;
if (code == availableCode)
{
Unsafe.Add(ref pixelStackRef, top++) = (byte)first;
code = oldCode;
}
while (code > clearCode)
{
Unsafe.Add(ref pixelStackRef, top++) = Unsafe.Add(ref suffixRef, code);
code = Unsafe.Add(ref prefixRef, code);
}
int suffixCode = Unsafe.Add(ref suffixRef, code);
first = suffixCode;
Unsafe.Add(ref pixelStackRef, top++) = suffixCode;
// Fix for Gifs that have "deferred clear code" as per here :
// https://bugzilla.mozilla.org/show_bug.cgi?id=55918
if (availableCode < MaxStackSize)
{
Unsafe.Add(ref prefixRef, availableCode) = oldCode;
Unsafe.Add(ref suffixRef, availableCode) = first;
availableCode++;
if (availableCode == codeMask + 1 && availableCode < MaxStackSize)
{
codeSize++;
codeMask = (1 << codeSize) - 1;
}
}
oldCode = inCode;
}
// Pop a pixel off the pixel stack.
top--;
// Clear missing pixels
xyz++;
}
}
/// <summary>
/// Reads the next data block from the stream. A data block begins with a byte,
/// which defines the size of the block, followed by the block itself.

2
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -235,7 +235,7 @@ internal sealed class JpegDecoderCore : IRawJpegData, IImageDecoderInternals
this.InitDerivedMetadataProperties();
Size pixelSize = this.Frame.PixelSize;
return new ImageInfo(new PixelTypeInfo(this.Frame.BitsPerPixel), pixelSize.Width, pixelSize.Height, this.Metadata);
return new ImageInfo(new PixelTypeInfo(this.Frame.BitsPerPixel), new(pixelSize.Width, pixelSize.Height), this.Metadata);
}
/// <summary>

2
src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs

@ -88,7 +88,7 @@ internal sealed class PbmDecoderCore : IImageDecoderInternals
// BlackAndWhite pixels are encoded into a byte.
int bitsPerPixel = this.componentType == PbmComponentType.Short ? 16 : 8;
return new ImageInfo(new PixelTypeInfo(bitsPerPixel), this.pixelSize.Width, this.pixelSize.Height, this.metadata);
return new ImageInfo(new PixelTypeInfo(bitsPerPixel), new(this.pixelSize.Width, this.pixelSize.Height), this.metadata);
}
/// <summary>

2
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -370,7 +370,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
PngThrowHelper.ThrowNoHeader();
}
return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metadata);
return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), new(this.header.Width, this.header.Height), metadata);
}
finally
{

3
src/ImageSharp/Formats/Tga/TgaDecoderCore.cs

@ -658,8 +658,7 @@ internal sealed class TgaDecoderCore : IImageDecoderInternals
this.ReadFileHeader(stream);
return new ImageInfo(
new PixelTypeInfo(this.fileHeader.PixelDepth),
this.fileHeader.Width,
this.fileHeader.Height,
new(this.fileHeader.Width, this.fileHeader.Height),
this.metadata);
}

21
src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

@ -163,12 +163,12 @@ internal class TiffDecoderCore : IImageDecoderInternals
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
var frames = new List<ImageFrame<TPixel>>();
var framesMetadata = new List<ImageFrameMetadata>();
List<ImageFrame<TPixel>> frames = new();
List<ImageFrameMetadata> framesMetadata = new();
try
{
this.inputStream = stream;
var reader = new DirectoryReader(stream, this.configuration.MemoryAllocator);
DirectoryReader reader = new(stream, this.configuration.MemoryAllocator);
IList<ExifProfile> directories = reader.Read();
this.byteOrder = reader.ByteOrder;
@ -221,21 +221,20 @@ internal class TiffDecoderCore : IImageDecoderInternals
DirectoryReader reader = new(stream, this.configuration.MemoryAllocator);
IList<ExifProfile> directories = reader.Read();
var frames = new List<ImageFrameMetadata>();
List<ImageFrameMetadata> framesMetadata = new();
foreach (ExifProfile dir in directories)
{
var frame = this.CreateFrameMetadata(dir);
frames.Add(frame);
framesMetadata.Add(this.CreateFrameMetadata(dir));
}
ExifProfile rootFrameExifProfile = directories.First();
ExifProfile rootFrameExifProfile = directories[0];
ImageMetadata metadata = TiffDecoderMetadataCreator.Create(frames, this.skipMetadata, reader.ByteOrder, reader.IsBigTiff);
ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.skipMetadata, reader.ByteOrder, reader.IsBigTiff);
int width = GetImageWidth(rootFrameExifProfile);
int height = GetImageHeight(rootFrameExifProfile);
return new ImageInfo(new PixelTypeInfo((int)frames.First().GetTiffMetadata().BitsPerPixel), width, height, metadata);
return new ImageInfo(new PixelTypeInfo((int)framesMetadata[0].GetTiffMetadata().BitsPerPixel), new(width, height), metadata, framesMetadata);
}
/// <summary>
@ -248,7 +247,7 @@ internal class TiffDecoderCore : IImageDecoderInternals
private ImageFrame<TPixel> DecodeFrame<TPixel>(ExifProfile tags, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
var imageFrameMetaData = this.CreateFrameMetadata(tags);
ImageFrameMetadata imageFrameMetaData = this.CreateFrameMetadata(tags);
bool isTiled = this.VerifyAndParse(tags, imageFrameMetaData.GetTiffMetadata());
int width = GetImageWidth(tags);
@ -786,7 +785,7 @@ internal class TiffDecoderCore : IImageDecoderInternals
bitsPerPixel = this.BitsPerSample.Channel2;
break;
case 3:
bitsPerPixel = this.BitsPerSample.Channel2;
bitsPerPixel = this.BitsPerSample.Channel3;
break;
default:
TiffThrowHelper.ThrowNotSupported("More then 4 color channels are not supported");

10
src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs

@ -27,8 +27,6 @@ internal static class TiffDecoderMetadataCreator
if (!ignoreMetadata)
{
var tiffMetadata = imageMetaData.GetTiffMetadata();
var framesMetadata = new List<TiffFrameMetadata>(frames.Count);
for (int i = 0; i < frames.Count; i++)
{
ImageFrameMetadata frameMetaData = frames[i];
@ -46,11 +44,7 @@ internal static class TiffDecoderMetadataCreator
{
frameMetaData.IccProfile = new IccProfile(iccProfileBytes.Value);
}
framesMetadata.Add(frameMetaData.GetTiffMetadata());
}
tiffMetadata.Frames = framesMetadata;
}
return imageMetaData;
@ -58,7 +52,7 @@ internal static class TiffDecoderMetadataCreator
private static ImageMetadata Create(ByteOrder byteOrder, bool isBigTiff, ExifProfile exifProfile)
{
var imageMetaData = new ImageMetadata();
ImageMetadata imageMetaData = new();
SetResolution(imageMetaData, exifProfile);
TiffMetadata tiffMetadata = imageMetaData.GetTiffMetadata();
@ -94,7 +88,7 @@ internal static class TiffDecoderMetadataCreator
if (iptc != null)
{
if (iptc.DataType == ExifDataType.Byte || iptc.DataType == ExifDataType.Undefined)
if (iptc.DataType is ExifDataType.Byte or ExifDataType.Undefined)
{
iptcBytes = (byte[])iptc.GetValue();
return true;

10
src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs

@ -69,7 +69,7 @@ public class TiffFrameMetadata : IDeepCloneable
/// <returns>The <see cref="TiffFrameMetadata"/>.</returns>
internal static TiffFrameMetadata Parse(ExifProfile profile)
{
var meta = new TiffFrameMetadata();
TiffFrameMetadata meta = new();
Parse(meta, profile);
return meta;
}
@ -83,12 +83,10 @@ public class TiffFrameMetadata : IDeepCloneable
{
if (profile != null)
{
if (profile.TryGetValue(ExifTag.BitsPerSample, out IExifValue<ushort[]>? bitsPerSampleValue))
if (profile.TryGetValue(ExifTag.BitsPerSample, out IExifValue<ushort[]>? bitsPerSampleValue)
&& TiffBitsPerSample.TryParse(bitsPerSampleValue.Value, out TiffBitsPerSample bitsPerSample))
{
if (TiffBitsPerSample.TryParse(bitsPerSampleValue.Value, out TiffBitsPerSample bitsPerSample))
{
meta.BitsPerSample = bitsPerSample;
}
meta.BitsPerSample = bitsPerSample;
}
meta.BitsPerPixel = meta.BitsPerSample?.BitsPerPixel();

17
src/ImageSharp/Formats/Tiff/TiffMetadata.cs

@ -23,15 +23,6 @@ public class TiffMetadata : IDeepCloneable
{
this.ByteOrder = other.ByteOrder;
this.FormatType = other.FormatType;
var frames = new List<TiffFrameMetadata>(other.Frames.Count);
foreach (var otherFrame in other.Frames)
{
var frame = (TiffFrameMetadata)otherFrame.DeepClone();
frames.Add(frame);
}
this.Frames = frames;
}
/// <summary>
@ -44,14 +35,6 @@ public class TiffMetadata : IDeepCloneable
/// </summary>
public TiffFormatType FormatType { get; set; }
/// <summary>
/// Gets or sets the frames.
/// </summary>
/// <value>
/// The frames.
/// </value>
public IReadOnlyList<TiffFrameMetadata> Frames { get; set; } = Array.Empty<TiffFrameMetadata>();
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new TiffMetadata(this);
}

5
src/ImageSharp/Formats/Webp/WebpDecoderCore.cs

@ -152,7 +152,10 @@ internal sealed class WebpDecoderCore : IImageDecoderInternals, IDisposable
this.ReadImageHeader();
using (this.webImageInfo = this.ReadVp8Info(true))
{
return new ImageInfo(new PixelTypeInfo((int)this.webImageInfo.BitsPerPixel), (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.metadata);
return new ImageInfo(
new PixelTypeInfo((int)this.webImageInfo.BitsPerPixel),
new((int)this.webImageInfo.Width, (int)this.webImageInfo.Height),
this.metadata);
}
}

52
src/ImageSharp/Image.cs

@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp;
/// For the non-generic <see cref="Image"/> type, the pixel type is only known at runtime.
/// <see cref="Image"/> is always implemented by a pixel-specific <see cref="Image{TPixel}"/> instance.
/// </summary>
public abstract partial class Image : ImageInfo, IDisposable, IConfigurationProvider
public abstract partial class Image : IDisposable, IConfigurationProvider
{
private bool isDisposed;
private readonly Configuration configuration;
@ -22,20 +22,22 @@ public abstract partial class Image : ImageInfo, IDisposable, IConfigurationProv
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class.
/// </summary>
/// <param name="configuration">
/// The configuration which allows altering default behaviour or extending the library.
/// </param>
/// <param name="configuration">The global configuration..</param>
/// <param name="pixelType">The pixel type information.</param>
/// <param name="metadata">The image metadata.</param>
/// <param name="size">The size in px units.</param>
protected Image(Configuration configuration, PixelTypeInfo pixelType, ImageMetadata? metadata, Size size)
: base(pixelType, size, metadata)
=> this.configuration = configuration;
{
this.configuration = configuration;
this.PixelType = pixelType;
this.Size = size;
this.Metadata = metadata ?? new ImageMetadata();
}
/// <summary>
/// Initializes a new instance of the <see cref="Image"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="configuration">The global configuration.</param>
/// <param name="pixelType">The <see cref="PixelTypeInfo"/>.</param>
/// <param name="metadata">The <see cref="ImageMetadata"/>.</param>
/// <param name="width">The width in px units.</param>
@ -50,6 +52,39 @@ public abstract partial class Image : ImageInfo, IDisposable, IConfigurationProv
{
}
/// <inheritdoc/>
Configuration IConfigurationProvider.Configuration => this.configuration;
/// <summary>
/// Gets information about the image pixels.
/// </summary>
public PixelTypeInfo PixelType { get; }
/// <summary>
/// Gets the image width in px units.
/// </summary>
public int Width => this.Size.Width;
/// <summary>
/// Gets the image height in px units.
/// </summary>
public int Height => this.Size.Height;
/// <summary>
/// Gets any metadata associated with the image.
/// </summary>
public ImageMetadata Metadata { get; }
/// <summary>
/// Gets the size of the image in px units.
/// </summary>
public Size Size { get; internal set; }
/// <summary>
/// Gets the bounds of the image.
/// </summary>
public Rectangle Bounds => new(0, 0, this.Width, this.Height);
/// <summary>
/// Gets the <see cref="ImageFrameCollection"/> implementing the public <see cref="Frames"/> property.
/// </summary>
@ -60,9 +95,6 @@ public abstract partial class Image : ImageInfo, IDisposable, IConfigurationProv
/// </summary>
public ImageFrameCollection Frames => this.NonGenericFrameCollection;
/// <inheritdoc/>
Configuration IConfigurationProvider.Configuration => this.configuration;
/// <inheritdoc />
public void Dispose()
{

21
src/ImageSharp/ImageFrameCollectionExtensions.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp;
/// <summary>
/// Extension methods for <see cref="ImageFrameCollection{TPixel}"/>.
/// </summary>
public static class ImageFrameCollectionExtensions
{
/// <inheritdoc cref="Enumerable.AsEnumerable{TPixel}(IEnumerable{TPixel})"/>
public static IEnumerable<ImageFrame<TPixel>> AsEnumerable<TPixel>(this ImageFrameCollection<TPixel> source)
where TPixel : unmanaged, IPixel<TPixel>
=> source;
/// <inheritdoc cref="Enumerable.Select{TPixel, TResult}(IEnumerable{TPixel}, Func{TPixel, int, TResult})"/>
public static IEnumerable<TResult> Select<TPixel, TResult>(this ImageFrameCollection<TPixel> source, Func<ImageFrame<TPixel>, TResult> selector)
where TPixel : unmanaged, IPixel<TPixel> => source.AsEnumerable().Select(selector);
}

27
src/ImageSharp/ImageInfo.cs

@ -15,11 +15,13 @@ public class ImageInfo
/// Initializes a new instance of the <see cref="ImageInfo"/> class.
/// </summary>
/// <param name="pixelType">The pixel type information.</param>
/// <param name="width">The width of the image in px units.</param>
/// <param name="height">The height of the image in px units.</param>
/// <param name="size">The size of the image in px units.</param>
/// <param name="metadata">The image metadata.</param>
public ImageInfo(PixelTypeInfo pixelType, int width, int height, ImageMetadata? metadata)
: this(pixelType, new(width, height), metadata)
public ImageInfo(
PixelTypeInfo pixelType,
Size size,
ImageMetadata? metadata)
: this(pixelType, size, metadata, null)
{
}
@ -29,11 +31,17 @@ public class ImageInfo
/// <param name="pixelType">The pixel type information.</param>
/// <param name="size">The size of the image in px units.</param>
/// <param name="metadata">The image metadata.</param>
public ImageInfo(PixelTypeInfo pixelType, Size size, ImageMetadata? metadata)
/// <param name="frameMetadataCollection">The collection of image frame metadata.</param>
public ImageInfo(
PixelTypeInfo pixelType,
Size size,
ImageMetadata? metadata,
IReadOnlyList<ImageFrameMetadata>? frameMetadataCollection)
{
this.PixelType = pixelType;
this.Metadata = metadata ?? new ImageMetadata();
this.Size = size;
this.Metadata = metadata ?? new ImageMetadata();
this.FrameMetadataCollection = frameMetadataCollection ?? Array.Empty<ImageFrameMetadata>();
}
/// <summary>
@ -52,10 +60,15 @@ public class ImageInfo
public int Height => this.Size.Height;
/// <summary>
/// Gets any metadata associated wit The image.
/// Gets any metadata associated with the image.
/// </summary>
public ImageMetadata Metadata { get; }
/// <summary>
/// Gets the collection of metadata associated with individual image frames.
/// </summary>
public IReadOnlyList<ImageFrameMetadata> FrameMetadataCollection { get; }
/// <summary>
/// Gets the size of the image in px units.
/// </summary>

69
tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using Microsoft.CodeAnalysis;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Metadata;
@ -30,7 +31,7 @@ public class GifMetadataTests
[Fact]
public void CloneIsDeep()
{
var meta = new GifMetadata
GifMetadata meta = new()
{
RepeatCount = 1,
ColorTableMode = GifColorTableMode.Global,
@ -38,7 +39,7 @@ public class GifMetadataTests
Comments = new List<string> { "Foo" }
};
var clone = (GifMetadata)meta.DeepClone();
GifMetadata clone = (GifMetadata)meta.DeepClone();
clone.RepeatCount = 2;
clone.ColorTableMode = GifColorTableMode.Local;
@ -54,7 +55,7 @@ public class GifMetadataTests
[Fact]
public void Decode_IgnoreMetadataIsFalse_CommentsAreRead()
{
var testFile = TestFile.Create(TestImages.Gif.Rings);
TestFile testFile = TestFile.Create(TestImages.Gif.Rings);
using Image<Rgba32> image = testFile.CreateRgba32Image(GifDecoder.Instance);
GifMetadata metadata = image.Metadata.GetGifMetadata();
@ -70,7 +71,7 @@ public class GifMetadataTests
SkipMetadata = true
};
var testFile = TestFile.Create(TestImages.Gif.Rings);
TestFile testFile = TestFile.Create(TestImages.Gif.Rings);
using Image<Rgba32> image = testFile.CreateRgba32Image(GifDecoder.Instance, options);
GifMetadata metadata = image.Metadata.GetGifMetadata();
@ -80,7 +81,7 @@ public class GifMetadataTests
[Fact]
public void Decode_CanDecodeLargeTextComment()
{
var testFile = TestFile.Create(TestImages.Gif.LargeComment);
TestFile testFile = TestFile.Create(TestImages.Gif.LargeComment);
using Image<Rgba32> image = testFile.CreateRgba32Image(GifDecoder.Instance);
GifMetadata metadata = image.Metadata.GetGifMetadata();
@ -92,11 +93,11 @@ public class GifMetadataTests
[Fact]
public void Encode_PreservesTextData()
{
var decoder = GifDecoder.Instance;
var testFile = TestFile.Create(TestImages.Gif.LargeComment);
GifDecoder decoder = GifDecoder.Instance;
TestFile testFile = TestFile.Create(TestImages.Gif.LargeComment);
using Image<Rgba32> input = testFile.CreateRgba32Image(decoder);
using var memoryStream = new MemoryStream();
using MemoryStream memoryStream = new();
input.Save(memoryStream, new GifEncoder());
memoryStream.Position = 0;
@ -111,8 +112,8 @@ public class GifMetadataTests
[MemberData(nameof(RatioFiles))]
public void Identify_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
TestFile testFile = TestFile.Create(imagePath);
using MemoryStream stream = new(testFile.Bytes, false);
ImageInfo image = GifDecoder.Instance.Identify(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
@ -124,8 +125,8 @@ public class GifMetadataTests
[MemberData(nameof(RatioFiles))]
public async Task Identify_VerifyRatioAsync(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
TestFile testFile = TestFile.Create(imagePath);
using MemoryStream stream = new(testFile.Bytes, false);
ImageInfo image = await GifDecoder.Instance.IdentifyAsync(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
@ -137,8 +138,8 @@ public class GifMetadataTests
[MemberData(nameof(RatioFiles))]
public void Decode_VerifyRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
TestFile testFile = TestFile.Create(imagePath);
using MemoryStream stream = new(testFile.Bytes, false);
using Image<Rgba32> image = GifDecoder.Instance.Decode<Rgba32>(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
@ -150,8 +151,8 @@ public class GifMetadataTests
[MemberData(nameof(RatioFiles))]
public async Task Decode_VerifyRatioAsync(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
TestFile testFile = TestFile.Create(imagePath);
using MemoryStream stream = new(testFile.Bytes, false);
using Image<Rgba32> image = await GifDecoder.Instance.DecodeAsync<Rgba32>(DecoderOptions.Default, stream);
ImageMetadata meta = image.Metadata;
Assert.Equal(xResolution, meta.HorizontalResolution);
@ -163,8 +164,8 @@ public class GifMetadataTests
[MemberData(nameof(RepeatFiles))]
public void Identify_VerifyRepeatCount(string imagePath, uint repeatCount)
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
TestFile testFile = TestFile.Create(imagePath);
using MemoryStream stream = new(testFile.Bytes, false);
ImageInfo image = GifDecoder.Instance.Identify(DecoderOptions.Default, stream);
GifMetadata meta = image.Metadata.GetGifMetadata();
Assert.Equal(repeatCount, meta.RepeatCount);
@ -174,10 +175,38 @@ public class GifMetadataTests
[MemberData(nameof(RepeatFiles))]
public void Decode_VerifyRepeatCount(string imagePath, uint repeatCount)
{
var testFile = TestFile.Create(imagePath);
using var stream = new MemoryStream(testFile.Bytes, false);
TestFile testFile = TestFile.Create(imagePath);
using MemoryStream stream = new(testFile.Bytes, false);
using Image<Rgba32> image = GifDecoder.Instance.Decode<Rgba32>(DecoderOptions.Default, stream);
GifMetadata meta = image.Metadata.GetGifMetadata();
Assert.Equal(repeatCount, meta.RepeatCount);
}
[Theory]
[InlineData(TestImages.Gif.Cheers, 93, GifColorTableMode.Global, 256, 4, GifDisposalMethod.NotDispose)]
public void Identify_Frames(
string imagePath,
int framesCount,
GifColorTableMode colorTableMode,
int globalColorTableLength,
int frameDelay,
GifDisposalMethod disposalMethod)
{
TestFile testFile = TestFile.Create(imagePath);
using MemoryStream stream = new(testFile.Bytes, false);
ImageInfo imageInfo = Image.Identify(stream);
Assert.NotNull(imageInfo);
GifMetadata gifMetadata = imageInfo.Metadata.GetGifMetadata();
Assert.NotNull(gifMetadata);
Assert.Equal(framesCount, imageInfo.FrameMetadataCollection.Count);
GifFrameMetadata gifFrameMetadata = imageInfo.FrameMetadataCollection[imageInfo.FrameMetadataCollection.Count - 1].GetGifMetadata();
Assert.Equal(colorTableMode, gifFrameMetadata.ColorTableMode);
Assert.Equal(globalColorTableLength, gifFrameMetadata.ColorTableLength);
Assert.Equal(frameDelay, gifFrameMetadata.FrameDelay);
Assert.Equal(disposalMethod, gifFrameMetadata.DisposalMethod);
}
}

136
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs

@ -175,20 +175,20 @@ public partial class JpegDecoderTests
Assert.Equal(expectedColorType, meta.ColorType);
}
private static void TestImageInfo(string imagePath, IImageDecoder decoder, bool useIdentify, Action<ImageInfo> test)
private static void TestImageInfo(string imagePath, IImageDecoder decoder, Action<ImageInfo> test)
{
TestFile testFile = TestFile.Create(imagePath);
using MemoryStream stream = new(testFile.Bytes, false);
if (useIdentify)
{
ImageInfo imageInfo = decoder.Identify(DecoderOptions.Default, stream);
test(imageInfo);
}
else
{
using Image<Rgba32> img = decoder.Decode<Rgba32>(DecoderOptions.Default, stream);
test(img);
}
ImageInfo imageInfo = decoder.Identify(DecoderOptions.Default, stream);
test(imageInfo);
}
private static void TestImageDecode(string imagePath, IImageDecoder decoder, Action<Image> test)
{
TestFile testFile = TestFile.Create(imagePath);
using MemoryStream stream = new(testFile.Bytes, false);
using Image<Rgba32> img = decoder.Decode<Rgba32>(DecoderOptions.Default, stream);
test(img);
}
private static void TestMetadataImpl(
@ -197,25 +197,57 @@ public partial class JpegDecoderTests
string imagePath,
int expectedPixelSize,
bool exifProfilePresent,
bool iccProfilePresent) => TestImageInfo(
imagePath,
decoder,
useIdentify,
imageInfo =>
bool iccProfilePresent)
{
if (useIdentify)
{
TestImageInfo(
imagePath,
decoder,
imageInfo =>
{
Assert.NotNull(imageInfo);
Assert.NotNull(imageInfo.PixelType);
Assert.Equal(expectedPixelSize, imageInfo.PixelType.BitsPerPixel);
ExifProfile exifProfile = imageInfo.Metadata.ExifProfile;
if (useIdentify)
if (exifProfilePresent)
{
Assert.NotNull(exifProfile);
Assert.NotEmpty(exifProfile.Values);
}
else
{
Assert.Null(exifProfile);
}
IccProfile iccProfile = imageInfo.Metadata.IccProfile;
if (iccProfilePresent)
{
Assert.Equal(expectedPixelSize, imageInfo.PixelType.BitsPerPixel);
Assert.NotNull(iccProfile);
Assert.NotEmpty(iccProfile.Entries);
}
else
{
// When full Image<TPixel> decoding is performed, BitsPerPixel will match TPixel
int bpp32 = Unsafe.SizeOf<Rgba32>() * 8;
Assert.Equal(bpp32, imageInfo.PixelType.BitsPerPixel);
Assert.Null(iccProfile);
}
});
}
else
{
TestImageDecode(
imagePath,
decoder,
imageInfo =>
{
Assert.NotNull(imageInfo);
Assert.NotNull(imageInfo.PixelType);
// When full Image<TPixel> decoding is performed, BitsPerPixel will match TPixel
int bpp32 = Unsafe.SizeOf<Rgba32>() * 8;
Assert.Equal(bpp32, imageInfo.PixelType.BitsPerPixel);
ExifProfile exifProfile = imageInfo.Metadata.ExifProfile;
@ -241,6 +273,8 @@ public partial class JpegDecoderTests
Assert.Null(iccProfile);
}
});
}
}
[Theory]
[InlineData(false)]
@ -268,28 +302,60 @@ public partial class JpegDecoderTests
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Decoder_Reads_Correct_Resolution_From_Jfif(bool useIdentify) => TestImageInfo(
TestImages.Jpeg.Baseline.Floorplan,
JpegDecoder.Instance,
useIdentify,
imageInfo =>
{
Assert.Equal(300, imageInfo.Metadata.HorizontalResolution);
Assert.Equal(300, imageInfo.Metadata.VerticalResolution);
});
public void Decoder_Reads_Correct_Resolution_From_Jfif(bool useIdentify)
{
if (useIdentify)
{
TestImageInfo(
TestImages.Jpeg.Baseline.Floorplan,
JpegDecoder.Instance,
imageInfo =>
{
Assert.Equal(300, imageInfo.Metadata.HorizontalResolution);
Assert.Equal(300, imageInfo.Metadata.VerticalResolution);
});
}
else
{
TestImageDecode(
TestImages.Jpeg.Baseline.Floorplan,
JpegDecoder.Instance,
image =>
{
Assert.Equal(300, image.Metadata.HorizontalResolution);
Assert.Equal(300, image.Metadata.VerticalResolution);
});
}
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Decoder_Reads_Correct_Resolution_From_Exif(bool useIdentify) => TestImageInfo(
TestImages.Jpeg.Baseline.Jpeg420Exif,
JpegDecoder.Instance,
useIdentify,
imageInfo =>
public void Decoder_Reads_Correct_Resolution_From_Exif(bool useIdentify)
{
if (useIdentify)
{
TestImageInfo(
TestImages.Jpeg.Baseline.Jpeg420Exif,
JpegDecoder.Instance,
imageInfo =>
{
Assert.Equal(72, imageInfo.Metadata.HorizontalResolution);
Assert.Equal(72, imageInfo.Metadata.VerticalResolution);
});
}
else
{
TestImageDecode(
TestImages.Jpeg.Baseline.Jpeg420Exif,
JpegDecoder.Instance,
imageInfo =>
{
Assert.Equal(72, imageInfo.Metadata.HorizontalResolution);
Assert.Equal(72, imageInfo.Metadata.VerticalResolution);
});
}
}
[Theory]
[WithFile(TestImages.Jpeg.Issues.InvalidIptcTag, PixelTypes.Rgba32)]

11
tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs

@ -122,9 +122,14 @@ public class TiffMetadataTests
TiffMetadata tiffMetadata = imageInfo.Metadata.GetTiffMetadata();
Assert.NotNull(tiffMetadata);
Assert.Equal(framesCount, tiffMetadata.Frames.Count);
Assert.Equal(photometric, tiffMetadata.Frames[0].PhotometricInterpretation);
Assert.Equal(inkSet, tiffMetadata.Frames[0].InkSet);
Assert.Equal(framesCount, imageInfo.FrameMetadataCollection.Count);
foreach (ImageFrameMetadata metadata in imageInfo.FrameMetadataCollection)
{
TiffFrameMetadata tiffFrameMetadata = metadata.GetTiffMetadata();
Assert.Equal(photometric, tiffFrameMetadata.PhotometricInterpretation);
Assert.Equal(inkSet, tiffFrameMetadata.InkSet);
}
}
[Theory]

24
tests/ImageSharp.Tests/ImageInfoTests.cs

@ -18,7 +18,7 @@ public class ImageInfoTests
PixelTypeInfo pixelType = new(8);
ImageMetadata meta = new();
ImageInfo info = new(pixelType, width, height, meta);
ImageInfo info = new(pixelType, size, meta);
Assert.Equal(pixelType, info.PixelType);
Assert.Equal(width, info.Width);
@ -27,4 +27,26 @@ public class ImageInfoTests
Assert.Equal(rectangle, info.Bounds);
Assert.Equal(meta, info.Metadata);
}
[Fact]
public void ImageInfoInitializesCorrectlyWithFrameMetadata()
{
const int width = 50;
const int height = 60;
Size size = new(width, height);
Rectangle rectangle = new(0, 0, width, height);
PixelTypeInfo pixelType = new(8);
ImageMetadata meta = new();
IReadOnlyList<ImageFrameMetadata> frameMetadata = new List<ImageFrameMetadata>() { new() };
ImageInfo info = new(pixelType, size, meta, frameMetadata);
Assert.Equal(pixelType, info.PixelType);
Assert.Equal(width, info.Width);
Assert.Equal(height, info.Height);
Assert.Equal(size, info.Size);
Assert.Equal(rectangle, info.Bounds);
Assert.Equal(meta, info.Metadata);
Assert.Equal(frameMetadata.Count, info.FrameMetadataCollection.Count);
}
}

5
tests/ImageSharp.Tests/TestFormat.cs

@ -4,6 +4,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities;
@ -203,7 +204,9 @@ public class TestFormat : IImageFormatConfigurationModule, IImageFormat
{
Image<TestPixelForAgnosticDecode> image =
this.Decode<TestPixelForAgnosticDecode>(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken);
return new(image.PixelType, image.Width, image.Height, image.Metadata);
ImageFrameCollection<TestPixelForAgnosticDecode> m = image.Frames;
return new(image.PixelType, image.Size, image.Metadata, new List<ImageFrameMetadata>(image.Frames.Select(x => x.Metadata)));
}
protected override TestDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options)

2
tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs

@ -75,7 +75,7 @@ public class MagickReferenceDecoder : ImageDecoder
protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
using Image<Rgba32> image = this.Decode<Rgba32>(options, stream, cancellationToken);
return new(image.PixelType, image.Width, image.Height, image.Metadata);
return new(image.PixelType, image.Size, image.Metadata, new List<ImageFrameMetadata>(image.Frames.Select(x => x.Metadata)));
}
private static void FromRgba32Bytes<TPixel>(Configuration configuration, Span<byte> rgbaBytes, IMemoryGroup<TPixel> destinationGroup)

2
tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs

@ -17,7 +17,7 @@ public class SystemDrawingReferenceDecoder : ImageDecoder
{
using SDBitmap sourceBitmap = new(stream);
PixelTypeInfo pixelType = new(SDImage.GetPixelFormatSize(sourceBitmap.PixelFormat));
return new ImageInfo(pixelType, sourceBitmap.Width, sourceBitmap.Height, new ImageMetadata());
return new ImageInfo(pixelType, new(sourceBitmap.Width, sourceBitmap.Height), new ImageMetadata());
}
protected override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken)

5
tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs

@ -5,6 +5,7 @@ using System.Collections.Concurrent;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using Xunit.Abstractions;
@ -366,7 +367,7 @@ public class TestImageProviderTests
protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
using Image<Rgba32> image = this.Decode<Rgba32>(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken);
return new(image.PixelType, image.Width, image.Height, image.Metadata);
return new(image.PixelType, image.Size, image.Metadata, new List<ImageFrameMetadata>(image.Frames.Select(x => x.Metadata)));
}
protected override Image<TPixel> Decode<TPixel>(TestDecoderOptions options, Stream stream, CancellationToken cancellationToken)
@ -409,7 +410,7 @@ public class TestImageProviderTests
protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken)
{
using Image<Rgba32> image = this.Decode<Rgba32>(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken);
return new(image.PixelType, image.Width, image.Height, image.Metadata);
return new(image.PixelType, image.Size, image.Metadata, new List<ImageFrameMetadata>(image.Frames.Select(x => x.Metadata)));
}
protected override Image<TPixel> Decode<TPixel>(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken)

Loading…
Cancel
Save