From c21bbbd53124c27aa2c7849e1a7bef479e70d862 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 26 Feb 2023 21:00:18 +1000 Subject: [PATCH] Allow returning individual image frame metadata via Identify. --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 2 +- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 72 ++++++-- src/ImageSharp/Formats/Gif/LzwDecoder.cs | 155 +++++++++++++++++- .../Formats/Jpeg/JpegDecoderCore.cs | 2 +- src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs | 2 +- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 2 +- src/ImageSharp/Formats/Tga/TgaDecoderCore.cs | 3 +- .../Formats/Tiff/TiffDecoderCore.cs | 21 ++- .../Tiff/TiffDecoderMetadataCreator.cs | 10 +- .../Formats/Tiff/TiffFrameMetadata.cs | 10 +- src/ImageSharp/Formats/Tiff/TiffMetadata.cs | 17 -- .../Formats/Webp/WebpDecoderCore.cs | 5 +- src/ImageSharp/Image.cs | 52 ++++-- .../ImageFrameCollectionExtensions.cs | 21 +++ src/ImageSharp/ImageInfo.cs | 27 ++- .../Formats/Gif/GifMetadataTests.cs | 69 +++++--- .../Formats/Jpg/JpegDecoderTests.Metadata.cs | 136 +++++++++++---- .../Formats/Tiff/TiffMetadataTests.cs | 11 +- tests/ImageSharp.Tests/ImageInfoTests.cs | 24 ++- tests/ImageSharp.Tests/TestFormat.cs | 5 +- .../ReferenceCodecs/MagickReferenceDecoder.cs | 2 +- .../SystemDrawingReferenceDecoder.cs | 2 +- .../Tests/TestImageProviderTests.cs | 5 +- 23 files changed, 509 insertions(+), 146 deletions(-) create mode 100644 src/ImageSharp/ImageFrameCollectionExtensions.cs diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index cb1c7a251..dfc6bb961 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/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); } /// diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 3ebd65a67..17b5d8ec9 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -172,6 +172,9 @@ internal sealed class GifDecoderCore : IImageDecoderInternals /// public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { + uint frameCount = 0; + ImageFrameMetadata? previousFrame = null; + List 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); } /// @@ -486,7 +498,7 @@ internal sealed class GifDecoderCore : IImageDecoderInternals image = new Image(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 } } + /// + /// Reads the frames metadata. + /// + /// The containing image data. + /// The collection of frame metadata. + /// The previous frame metadata. + private void ReadFrameMetadata(BufferedReadStream stream, List 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); + } + /// /// Restores the current frame area to the background. /// @@ -627,18 +670,17 @@ internal sealed class GifDecoderCore : IImageDecoderInternals } /// - /// Sets the frames metadata. + /// Sets the metadata for the image frame. /// - /// The metadata. - /// Whether the metadata represents the root frame. + /// The metadata. [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; } diff --git a/src/ImageSharp/Formats/Gif/LzwDecoder.cs b/src/ImageSharp/Formats/Gif/LzwDecoder.cs index 1d63611fa..64b9cf386 100644 --- a/src/ImageSharp/Formats/Gif/LzwDecoder.cs +++ b/src/ImageSharp/Formats/Gif/LzwDecoder.cs @@ -61,7 +61,7 @@ internal sealed class LzwDecoder : IDisposable } /// - /// 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. /// /// Minimum code size of the data. /// The pixel array to decode to. @@ -232,6 +232,159 @@ internal sealed class LzwDecoder : IDisposable } } + /// + /// Decodes and decompresses all pixel indices from the stream allowing skipping of the data. + /// + /// Minimum code size of the data. + /// The resulting index table length. + 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 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++; + } + } + /// /// 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. diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs index ab0521712..45029f945 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/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); } /// diff --git a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs index 80db009a4..e1bc5be6e 100644 --- a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs +++ b/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); } /// diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 3b2f100e0..5c7423211 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/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 { diff --git a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs index 242876343..ce4f566b8 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/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); } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 456518e11..45bbed12d 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -163,12 +163,12 @@ internal class TiffDecoderCore : IImageDecoderInternals public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - var frames = new List>(); - var framesMetadata = new List(); + List> frames = new(); + List framesMetadata = new(); try { this.inputStream = stream; - var reader = new DirectoryReader(stream, this.configuration.MemoryAllocator); + DirectoryReader reader = new(stream, this.configuration.MemoryAllocator); IList directories = reader.Read(); this.byteOrder = reader.ByteOrder; @@ -221,21 +221,20 @@ internal class TiffDecoderCore : IImageDecoderInternals DirectoryReader reader = new(stream, this.configuration.MemoryAllocator); IList directories = reader.Read(); - var frames = new List(); + List 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); } /// @@ -248,7 +247,7 @@ internal class TiffDecoderCore : IImageDecoderInternals private ImageFrame DecodeFrame(ExifProfile tags, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - 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"); diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs index fcb48e28b..1ef2478e3 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs @@ -27,8 +27,6 @@ internal static class TiffDecoderMetadataCreator if (!ignoreMetadata) { - var tiffMetadata = imageMetaData.GetTiffMetadata(); - var framesMetadata = new List(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; diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs index 4fa0ba013..e30983098 100644 --- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs @@ -69,7 +69,7 @@ public class TiffFrameMetadata : IDeepCloneable /// The . 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? bitsPerSampleValue)) + if (profile.TryGetValue(ExifTag.BitsPerSample, out IExifValue? 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(); diff --git a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs index 2a31642fe..2759d0130 100644 --- a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs +++ b/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(other.Frames.Count); - foreach (var otherFrame in other.Frames) - { - var frame = (TiffFrameMetadata)otherFrame.DeepClone(); - frames.Add(frame); - } - - this.Frames = frames; } /// @@ -44,14 +35,6 @@ public class TiffMetadata : IDeepCloneable /// public TiffFormatType FormatType { get; set; } - /// - /// Gets or sets the frames. - /// - /// - /// The frames. - /// - public IReadOnlyList Frames { get; set; } = Array.Empty(); - /// public IDeepCloneable DeepClone() => new TiffMetadata(this); } diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs index 29be86e22..c4e2e0d55 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs +++ b/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); } } diff --git a/src/ImageSharp/Image.cs b/src/ImageSharp/Image.cs index 02fa01478..cba32cb78 100644 --- a/src/ImageSharp/Image.cs +++ b/src/ImageSharp/Image.cs @@ -14,7 +14,7 @@ namespace SixLabors.ImageSharp; /// For the non-generic type, the pixel type is only known at runtime. /// is always implemented by a pixel-specific instance. /// -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 /// /// Initializes a new instance of the class. /// - /// - /// The configuration which allows altering default behaviour or extending the library. - /// + /// The global configuration.. /// The pixel type information. /// The image metadata. /// The size in px units. 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(); + } /// /// Initializes a new instance of the class. /// - /// The configuration. + /// The global configuration. /// The . /// The . /// The width in px units. @@ -50,6 +52,39 @@ public abstract partial class Image : ImageInfo, IDisposable, IConfigurationProv { } + /// + Configuration IConfigurationProvider.Configuration => this.configuration; + + /// + /// Gets information about the image pixels. + /// + public PixelTypeInfo PixelType { get; } + + /// + /// Gets the image width in px units. + /// + public int Width => this.Size.Width; + + /// + /// Gets the image height in px units. + /// + public int Height => this.Size.Height; + + /// + /// Gets any metadata associated with the image. + /// + public ImageMetadata Metadata { get; } + + /// + /// Gets the size of the image in px units. + /// + public Size Size { get; internal set; } + + /// + /// Gets the bounds of the image. + /// + public Rectangle Bounds => new(0, 0, this.Width, this.Height); + /// /// Gets the implementing the public property. /// @@ -60,9 +95,6 @@ public abstract partial class Image : ImageInfo, IDisposable, IConfigurationProv /// public ImageFrameCollection Frames => this.NonGenericFrameCollection; - /// - Configuration IConfigurationProvider.Configuration => this.configuration; - /// public void Dispose() { diff --git a/src/ImageSharp/ImageFrameCollectionExtensions.cs b/src/ImageSharp/ImageFrameCollectionExtensions.cs new file mode 100644 index 000000000..660352c15 --- /dev/null +++ b/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; + +/// +/// Extension methods for . +/// +public static class ImageFrameCollectionExtensions +{ + /// + public static IEnumerable> AsEnumerable(this ImageFrameCollection source) + where TPixel : unmanaged, IPixel + => source; + + /// + public static IEnumerable Select(this ImageFrameCollection source, Func, TResult> selector) + where TPixel : unmanaged, IPixel => source.AsEnumerable().Select(selector); +} diff --git a/src/ImageSharp/ImageInfo.cs b/src/ImageSharp/ImageInfo.cs index fdc15a812..00319e9b5 100644 --- a/src/ImageSharp/ImageInfo.cs +++ b/src/ImageSharp/ImageInfo.cs @@ -15,11 +15,13 @@ public class ImageInfo /// Initializes a new instance of the class. /// /// The pixel type information. - /// The width of the image in px units. - /// The height of the image in px units. + /// The size of the image in px units. /// The image metadata. - 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 /// The pixel type information. /// The size of the image in px units. /// The image metadata. - public ImageInfo(PixelTypeInfo pixelType, Size size, ImageMetadata? metadata) + /// The collection of image frame metadata. + public ImageInfo( + PixelTypeInfo pixelType, + Size size, + ImageMetadata? metadata, + IReadOnlyList? frameMetadataCollection) { this.PixelType = pixelType; - this.Metadata = metadata ?? new ImageMetadata(); this.Size = size; + this.Metadata = metadata ?? new ImageMetadata(); + this.FrameMetadataCollection = frameMetadataCollection ?? Array.Empty(); } /// @@ -52,10 +60,15 @@ public class ImageInfo public int Height => this.Size.Height; /// - /// Gets any metadata associated wit The image. + /// Gets any metadata associated with the image. /// public ImageMetadata Metadata { get; } + /// + /// Gets the collection of metadata associated with individual image frames. + /// + public IReadOnlyList FrameMetadataCollection { get; } + /// /// Gets the size of the image in px units. /// diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs index 7def2003a..40ac94eea 100644 --- a/tests/ImageSharp.Tests/Formats/Gif/GifMetadataTests.cs +++ b/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 { "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 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 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 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 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 image = GifDecoder.Instance.Decode(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 image = await GifDecoder.Instance.DecodeAsync(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 image = GifDecoder.Instance.Decode(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); + } } diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs index 9bff30b6f..1c203e734 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs +++ b/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 test) + private static void TestImageInfo(string imagePath, IImageDecoder decoder, Action 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 img = decoder.Decode(DecoderOptions.Default, stream); - test(img); - } + ImageInfo imageInfo = decoder.Identify(DecoderOptions.Default, stream); + test(imageInfo); + } + + private static void TestImageDecode(string imagePath, IImageDecoder decoder, Action test) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + using Image img = decoder.Decode(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 decoding is performed, BitsPerPixel will match TPixel - int bpp32 = Unsafe.SizeOf() * 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 decoding is performed, BitsPerPixel will match TPixel + int bpp32 = Unsafe.SizeOf() * 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)] diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index bca7af9f8..0c6cdbc20 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/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] diff --git a/tests/ImageSharp.Tests/ImageInfoTests.cs b/tests/ImageSharp.Tests/ImageInfoTests.cs index 39091281d..73324eccd 100644 --- a/tests/ImageSharp.Tests/ImageInfoTests.cs +++ b/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 frameMetadata = new List() { 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); + } } diff --git a/tests/ImageSharp.Tests/TestFormat.cs b/tests/ImageSharp.Tests/TestFormat.cs index 92dd79132..f597b708d 100644 --- a/tests/ImageSharp.Tests/TestFormat.cs +++ b/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 image = this.Decode(this.CreateDefaultSpecializedOptions(options), stream, cancellationToken); - return new(image.PixelType, image.Width, image.Height, image.Metadata); + ImageFrameCollection m = image.Frames; + + return new(image.PixelType, image.Size, image.Metadata, new List(image.Frames.Select(x => x.Metadata))); } protected override TestDecoderOptions CreateDefaultSpecializedOptions(DecoderOptions options) diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs index a0fdb1331..ae09c4f3f 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/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 image = this.Decode(options, stream, cancellationToken); - return new(image.PixelType, image.Width, image.Height, image.Metadata); + return new(image.PixelType, image.Size, image.Metadata, new List(image.Frames.Select(x => x.Metadata))); } private static void FromRgba32Bytes(Configuration configuration, Span rgbaBytes, IMemoryGroup destinationGroup) diff --git a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs index 2deed6d48..a3408bedb 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs +++ b/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 Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs index 0a3e45b3c..cbce96110 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/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 image = this.Decode(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(image.Frames.Select(x => x.Metadata))); } protected override Image Decode(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 image = this.Decode(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(image.Frames.Select(x => x.Metadata))); } protected override Image Decode(TestDecoderWithParametersOptions options, Stream stream, CancellationToken cancellationToken)