From 1c23ccb8397812d493f8f0bb46af296a45f609f8 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Sun, 29 Jan 2023 18:50:53 +0300 Subject: [PATCH 01/14] implement adding frames metadata to tiff image metadata --- src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs | 4 ++-- src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs | 9 +++++++-- .../Formats/Tiff/TiffDecoderMetadataCreator.cs | 11 ++++++++++- src/ImageSharp/Formats/Tiff/TiffMetadata.cs | 8 ++++++++ 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs index 54d5c4ce86..755e79e42e 100644 --- a/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs +++ b/src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs @@ -37,7 +37,7 @@ public DirectoryReader(Stream stream, MemoryAllocator allocator) /// Reads image file directories. /// /// Image file directories. - public IEnumerable Read() + public IList Read() { this.ByteOrder = ReadByteOrder(this.stream); var headerReader = new HeaderReader(this.stream, this.ByteOrder); @@ -66,7 +66,7 @@ private static ByteOrder ReadByteOrder(Stream stream) throw TiffThrowHelper.ThrowInvalidHeader(); } - private IEnumerable ReadIfds(bool isBigTiff) + private IList ReadIfds(bool isBigTiff) { var readers = new List(); while (this.nextIfdOffset != 0 && this.nextIfdOffset < (ulong)this.stream.Length) diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 8333dbf31c..0579a5dd10 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -169,7 +169,7 @@ public Image Decode(BufferedReadStream stream, CancellationToken this.inputStream = stream; var reader = new DirectoryReader(stream, this.configuration.MemoryAllocator); - IEnumerable directories = reader.Read(); + IList directories = reader.Read(); this.byteOrder = reader.ByteOrder; this.isBigTiff = reader.IsBigTiff; @@ -188,6 +188,8 @@ public Image Decode(BufferedReadStream stream, CancellationToken ImageMetadata metadata = TiffDecoderMetadataCreator.Create(frames, this.skipMetadata, reader.ByteOrder, reader.IsBigTiff); + TiffDecoderMetadataCreator.FillFrames(metadata.GetTiffMetadata(), directories); + // TODO: Tiff frames can have different sizes. ImageFrame root = frames[0]; this.Dimensions = root.Size(); @@ -217,12 +219,15 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat { this.inputStream = stream; DirectoryReader reader = new(stream, this.configuration.MemoryAllocator); - IEnumerable directories = reader.Read(); + IList directories = reader.Read(); ExifProfile rootFrameExifProfile = directories.First(); TiffFrameMetadata rootMetadata = TiffFrameMetadata.Parse(rootFrameExifProfile); ImageMetadata metadata = TiffDecoderMetadataCreator.Create(reader.ByteOrder, reader.IsBigTiff, rootFrameExifProfile); + + TiffDecoderMetadataCreator.FillFrames(metadata.GetTiffMetadata(), directories); + int width = GetImageWidth(rootFrameExifProfile); int height = GetImageHeight(rootFrameExifProfile); diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs index 1a6c9dd7de..c2a12c93cf 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs @@ -61,10 +61,19 @@ public static ImageMetadata Create(ByteOrder byteOrder, bool isBigTiff, ExifProf TiffMetadata tiffMetadata = imageMetaData.GetTiffMetadata(); tiffMetadata.ByteOrder = byteOrder; tiffMetadata.FormatType = isBigTiff ? TiffFormatType.BigTIFF : TiffFormatType.Default; - return imageMetaData; } + public static void FillFrames(TiffMetadata tiffMetadata, IList directories) + { + foreach (ExifProfile dir in directories) + { + TiffFrameMetadata meta = TiffFormat.Instance.CreateDefaultFormatFrameMetadata(); + TiffFrameMetadata.Parse(meta, dir); + tiffMetadata.Frames.Add(meta); + } + } + private static void SetResolution(ImageMetadata imageMetaData, ExifProfile exifProfile) { imageMetaData.ResolutionUnits = exifProfile != null ? UnitConverter.ExifProfileToResolutionUnit(exifProfile) : PixelResolutionUnit.PixelsPerInch; diff --git a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs index 29ffc82ce0..6f9af6c07b 100644 --- a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs @@ -31,6 +31,14 @@ public TiffMetadata() /// public TiffFormatType FormatType { get; set; } + /// + /// Gets or sets the frames. + /// + /// + /// The frames. + /// + public IList Frames { get; set; } = new List(); + /// public IDeepCloneable DeepClone() => new TiffMetadata(this); } From 681c921b0bf3da865788fdceea9198ec0d73cdc8 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Mon, 20 Feb 2023 11:19:59 +0300 Subject: [PATCH 02/14] add TiffInkSet property --- .../Formats/Tiff/Constants/TiffInkSet.cs | 26 +++++++++++++++++++ .../Formats/Tiff/TiffFrameMetadata.cs | 10 +++++++ 2 files changed, 36 insertions(+) create mode 100644 src/ImageSharp/Formats/Tiff/Constants/TiffInkSet.cs diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffInkSet.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffInkSet.cs new file mode 100644 index 0000000000..abdaca8900 --- /dev/null +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffInkSet.cs @@ -0,0 +1,26 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +using SixLabors.ImageSharp.Metadata.Profiles.Exif; + +namespace SixLabors.ImageSharp.Formats.Tiff.Constants; + +/// +/// Enumeration representing the set of inks used in a separated () image. +/// +public enum TiffInkSet : ushort +{ + /// + /// CMYK. + /// The order of the components is cyan, magenta, yellow, black. + /// Usually, a value of 0 represents 0% ink coverage and a value of 255 represents 100% ink coverage for that component, but see DotRange. + /// The field should not exist when InkSet=1. + /// + Cmyk = 1, + + /// + /// Not CMYK. + /// See the field for a description of the inks to be used. + /// + NotCmyk = 2 +} diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs index 5d0b85bf22..c0cbd7e872 100644 --- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs @@ -55,6 +55,11 @@ private TiffFrameMetadata(TiffFrameMetadata other) /// public TiffPredictor? Predictor { get; set; } + /// + /// Gets or sets the set of inks used in a separated () image. + /// + public TiffInkSet? InkSet { get; set; } + /// /// Returns a new instance parsed from the given Exif profile. /// @@ -102,6 +107,11 @@ internal static void Parse(TiffFrameMetadata meta, ExifProfile profile) meta.Predictor = (TiffPredictor)predictorValue.Value; } + if (profile.TryGetValue(ExifTag.InkSet, out IExifValue? inkSetValue)) + { + meta.InkSet = (TiffInkSet)inkSetValue.Value; + } + profile.RemoveValue(ExifTag.BitsPerSample); profile.RemoveValue(ExifTag.Compression); profile.RemoveValue(ExifTag.PhotometricInterpretation); From 561c5d82a77366758c13b4f1095cae9aa271c88e Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Mon, 20 Feb 2023 11:23:23 +0300 Subject: [PATCH 03/14] assert not supported ink names --- src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index a13fb58de7..26905965ee 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -509,6 +509,11 @@ private static void ParseColorType(this TiffDecoderCore options, ExifProfile exi TiffThrowHelper.ThrowNotSupported("Only 8 bits per channel is supported for CMYK images."); } + if (exifProfile.GetValueInternal(ExifTag.InkNames) is not null) + { + TiffThrowHelper.ThrowNotSupported("The custom ink name strings are not supported for CMYK images."); + } + options.ColorType = TiffColorType.Cmyk; break; } From de71b0a85b0bac37832347c4bb9462e587ca5711 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Mon, 20 Feb 2023 13:51:27 +0300 Subject: [PATCH 04/14] universal metadata parsing methods for loading and identify --- .../Formats/Tiff/TiffDecoderCore.cs | 45 +++++++++++-------- .../Tiff/TiffDecoderMetadataCreator.cs | 24 +++------- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 0579a5dd10..456518e11a 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -164,6 +164,7 @@ public Image Decode(BufferedReadStream stream, CancellationToken where TPixel : unmanaged, IPixel { var frames = new List>(); + var framesMetadata = new List(); try { this.inputStream = stream; @@ -179,6 +180,7 @@ public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken.ThrowIfCancellationRequested(); ImageFrame frame = this.DecodeFrame(ifd, cancellationToken); frames.Add(frame); + framesMetadata.Add(frame.Metadata); if (++frameCount == this.maxFrames) { @@ -186,9 +188,7 @@ public Image Decode(BufferedReadStream stream, CancellationToken } } - ImageMetadata metadata = TiffDecoderMetadataCreator.Create(frames, this.skipMetadata, reader.ByteOrder, reader.IsBigTiff); - - TiffDecoderMetadataCreator.FillFrames(metadata.GetTiffMetadata(), directories); + ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.skipMetadata, reader.ByteOrder, reader.IsBigTiff); // TODO: Tiff frames can have different sizes. ImageFrame root = frames[0]; @@ -221,17 +221,21 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat DirectoryReader reader = new(stream, this.configuration.MemoryAllocator); IList directories = reader.Read(); - ExifProfile rootFrameExifProfile = directories.First(); - TiffFrameMetadata rootMetadata = TiffFrameMetadata.Parse(rootFrameExifProfile); + var frames = new List(); + foreach (ExifProfile dir in directories) + { + var frame = this.CreateFrameMetadata(dir); + frames.Add(frame); + } - ImageMetadata metadata = TiffDecoderMetadataCreator.Create(reader.ByteOrder, reader.IsBigTiff, rootFrameExifProfile); + ExifProfile rootFrameExifProfile = directories.First(); - TiffDecoderMetadataCreator.FillFrames(metadata.GetTiffMetadata(), directories); + ImageMetadata metadata = TiffDecoderMetadataCreator.Create(frames, this.skipMetadata, reader.ByteOrder, reader.IsBigTiff); int width = GetImageWidth(rootFrameExifProfile); int height = GetImageHeight(rootFrameExifProfile); - return new ImageInfo(new PixelTypeInfo((int)rootMetadata.BitsPerPixel), width, height, metadata); + return new ImageInfo(new PixelTypeInfo((int)frames.First().GetTiffMetadata().BitsPerPixel), width, height, metadata); } /// @@ -244,16 +248,8 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat private ImageFrame DecodeFrame(ExifProfile tags, CancellationToken cancellationToken) where TPixel : unmanaged, IPixel { - ImageFrameMetadata imageFrameMetaData = new(); - if (!this.skipMetadata) - { - imageFrameMetaData.ExifProfile = tags; - } - - TiffFrameMetadata tiffFrameMetaData = imageFrameMetaData.GetTiffMetadata(); - TiffFrameMetadata.Parse(tiffFrameMetaData, tags); - - bool isTiled = this.VerifyAndParse(tags, tiffFrameMetaData); + var imageFrameMetaData = this.CreateFrameMetadata(tags); + bool isTiled = this.VerifyAndParse(tags, imageFrameMetaData.GetTiffMetadata()); int width = GetImageWidth(tags); int height = GetImageHeight(tags); @@ -271,6 +267,19 @@ private ImageFrame DecodeFrame(ExifProfile tags, CancellationTok return frame; } + private ImageFrameMetadata CreateFrameMetadata(ExifProfile tags) + { + ImageFrameMetadata imageFrameMetaData = new(); + if (!this.skipMetadata) + { + imageFrameMetaData.ExifProfile = tags; + } + + TiffFrameMetadata.Parse(imageFrameMetaData.GetTiffMetadata(), tags); + + return imageFrameMetaData; + } + /// /// Decodes the image data for Tiff's which arrange the pixel data in stripes. /// diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs index c2a12c93cf..f17b928829 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs @@ -8,7 +8,6 @@ using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Iptc; using SixLabors.ImageSharp.Metadata.Profiles.Xmp; -using SixLabors.ImageSharp.PixelFormats; namespace SixLabors.ImageSharp.Formats.Tiff; @@ -17,22 +16,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff; /// internal static class TiffDecoderMetadataCreator { - public static ImageMetadata Create(List> frames, bool ignoreMetadata, ByteOrder byteOrder, bool isBigTiff) - where TPixel : unmanaged, IPixel + public static ImageMetadata Create(List frames, bool ignoreMetadata, ByteOrder byteOrder, bool isBigTiff) { if (frames.Count < 1) { TiffThrowHelper.ThrowImageFormatException("Expected at least one frame."); } - ImageMetadata imageMetaData = Create(byteOrder, isBigTiff, frames[0].Metadata.ExifProfile); + ImageMetadata imageMetaData = Create(byteOrder, isBigTiff, frames[0].ExifProfile); if (!ignoreMetadata) { + var tiffMetadata = imageMetaData.GetTiffMetadata(); for (int i = 0; i < frames.Count; i++) { - ImageFrame frame = frames[i]; - ImageFrameMetadata frameMetaData = frame.Metadata; + ImageFrameMetadata frameMetaData = frames[i]; if (TryGetIptc(frameMetaData.ExifProfile.Values, out byte[] iptcBytes)) { frameMetaData.IptcProfile = new IptcProfile(iptcBytes); @@ -47,13 +45,15 @@ public static ImageMetadata Create(List> frames, bool { frameMetaData.IccProfile = new IccProfile(iccProfileBytes.Value); } + + tiffMetadata.Frames.Add(frameMetaData.GetTiffMetadata()); } } return imageMetaData; } - public static ImageMetadata Create(ByteOrder byteOrder, bool isBigTiff, ExifProfile exifProfile) + private static ImageMetadata Create(ByteOrder byteOrder, bool isBigTiff, ExifProfile exifProfile) { var imageMetaData = new ImageMetadata(); SetResolution(imageMetaData, exifProfile); @@ -64,16 +64,6 @@ public static ImageMetadata Create(ByteOrder byteOrder, bool isBigTiff, ExifProf return imageMetaData; } - public static void FillFrames(TiffMetadata tiffMetadata, IList directories) - { - foreach (ExifProfile dir in directories) - { - TiffFrameMetadata meta = TiffFormat.Instance.CreateDefaultFormatFrameMetadata(); - TiffFrameMetadata.Parse(meta, dir); - tiffMetadata.Frames.Add(meta); - } - } - private static void SetResolution(ImageMetadata imageMetaData, ExifProfile exifProfile) { imageMetaData.ResolutionUnits = exifProfile != null ? UnitConverter.ExifProfileToResolutionUnit(exifProfile) : PixelResolutionUnit.PixelsPerInch; From 308bfa448ff64e831ec8df3c0d71cd3e4c245916 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Mon, 20 Feb 2023 14:21:42 +0300 Subject: [PATCH 05/14] deep cloning --- .../Formats/Tiff/TiffDecoderMetadataCreator.cs | 5 ++++- .../Formats/Tiff/TiffFrameMetadata.cs | 1 + src/ImageSharp/Formats/Tiff/TiffMetadata.cs | 17 +++++++++++++++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs index f17b928829..fcb48e28be 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs @@ -28,6 +28,7 @@ public static ImageMetadata Create(List frames, bool ignoreM 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,8 +47,10 @@ public static ImageMetadata Create(List frames, bool ignoreM frameMetaData.IccProfile = new IccProfile(iccProfileBytes.Value); } - tiffMetadata.Frames.Add(frameMetaData.GetTiffMetadata()); + framesMetadata.Add(frameMetaData.GetTiffMetadata()); } + + tiffMetadata.Frames = framesMetadata; } return imageMetaData; diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs index c0cbd7e872..4fa0ba0135 100644 --- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs @@ -28,6 +28,7 @@ private TiffFrameMetadata(TiffFrameMetadata other) this.Compression = other.Compression; this.PhotometricInterpretation = other.PhotometricInterpretation; this.Predictor = other.Predictor; + this.InkSet = other.InkSet; } /// diff --git a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs index 6f9af6c07b..2a31642fec 100644 --- a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs @@ -19,7 +19,20 @@ public TiffMetadata() /// Initializes a new instance of the class. /// /// The metadata to create an instance from. - private TiffMetadata(TiffMetadata other) => this.ByteOrder = other.ByteOrder; + private TiffMetadata(TiffMetadata other) + { + 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; + } /// /// Gets or sets the byte order. @@ -37,7 +50,7 @@ public TiffMetadata() /// /// The frames. /// - public IList Frames { get; set; } = new List(); + public IReadOnlyList Frames { get; set; } = Array.Empty(); /// public IDeepCloneable DeepClone() => new TiffMetadata(this); From 220b8aa6defb2e7feb8107b037f310fcd43cf474 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Mon, 20 Feb 2023 14:36:23 +0300 Subject: [PATCH 06/14] add tests --- .../Formats/Tiff/TiffMetadataTests.cs | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index 5b09a244b5..bca7af9f81 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -31,13 +31,16 @@ public void TiffMetadata_CloneIsDeep() TiffMetadata meta = new() { ByteOrder = ByteOrder.BigEndian, + FormatType = TiffFormatType.BigTIFF, }; TiffMetadata clone = (TiffMetadata)meta.DeepClone(); clone.ByteOrder = ByteOrder.LittleEndian; + clone.FormatType = TiffFormatType.Default; - Assert.False(meta.ByteOrder == clone.ByteOrder); + Assert.Equal(ByteOrder.BigEndian, meta.ByteOrder); + Assert.Equal(TiffFormatType.BigTIFF, meta.FormatType); } [Theory] @@ -106,6 +109,24 @@ public void Identify_DetectsCorrectByteOrder(string imagePath, ByteOrder expecte Assert.Equal(expectedByteOrder, tiffMetadata.ByteOrder); } + [Theory] + [InlineData(Cmyk, 1, TiffPhotometricInterpretation.Separated, TiffInkSet.Cmyk)] + public void Identify_Frames(string imagePath, int framesCount, TiffPhotometricInterpretation photometric, TiffInkSet? inkSet) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + + ImageInfo imageInfo = Image.Identify(stream); + + Assert.NotNull(imageInfo); + 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); + } + [Theory] [WithFile(SampleMetadata, PixelTypes.Rgba32, false)] [WithFile(SampleMetadata, PixelTypes.Rgba32, true)] From c21bbbd53124c27aa2c7849e1a7bef479e70d862 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 26 Feb 2023 21:00:18 +1000 Subject: [PATCH 07/14] 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 cb1c7a2511..dfc6bb9611 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -208,7 +208,7 @@ public Image Decode(BufferedReadStream stream, CancellationToken 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 3ebd65a67f..17b5d8ec9b 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -172,6 +172,9 @@ public Image Decode(BufferedReadStream stream, CancellationToken /// public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) { + uint frameCount = 0; + ImageFrameMetadata? previousFrame = null; + List framesMetadata = new(); try { this.ReadLogicalScreenDescriptorAndGlobalColorTable(stream); @@ -182,14 +185,23 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat { 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 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat 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 @@ private void ReadFrameColors(ref Image? image, ref ImageFrame(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 @@ private void ReadFrameColors(ref Image? image, ref ImageFrame(ref Image? image, ref ImageFrame + /// 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 @@ private void RestoreToBackground(ImageFrame frame) } /// - /// 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 @@ private void SetFrameMetadata(ImageFrameMetadata meta, bool isRoot) 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 @@ private void SetFrameMetadata(ImageFrameMetadata meta, bool isRoot) // 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 1d63611fad..64b9cf386c 100644 --- a/src/ImageSharp/Formats/Gif/LzwDecoder.cs +++ b/src/ImageSharp/Formats/Gif/LzwDecoder.cs @@ -61,7 +61,7 @@ public LzwDecoder(MemoryAllocator memoryAllocator, BufferedReadStream stream) } /// - /// 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 @@ public void DecodePixels(int minCodeSize, Buffer2D pixels) } } + /// + /// 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 ab0521712a..45029f9459 100644 --- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs +++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs @@ -235,7 +235,7 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat 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 80db009a44..e1bc5be6e8 100644 --- a/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs +++ b/src/ImageSharp/Formats/Pbm/PbmDecoderCore.cs @@ -88,7 +88,7 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat // 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 3b2f100e0a..5c74232119 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -370,7 +370,7 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat 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 2428763432..ce4f566b87 100644 --- a/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs +++ b/src/ImageSharp/Formats/Tga/TgaDecoderCore.cs @@ -658,8 +658,7 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat 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 456518e11a..45bbed12d5 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -163,12 +163,12 @@ public TiffDecoderCore(DecoderOptions options) 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 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat 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 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat 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 @@ private int CalculateStripBufferSize(int width, int height, int plane = -1) 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 fcb48e28be..1ef2478e3d 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs @@ -27,8 +27,6 @@ public static ImageMetadata Create(List frames, bool ignoreM 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 @@ public static ImageMetadata Create(List frames, bool ignoreM { frameMetaData.IccProfile = new IccProfile(iccProfileBytes.Value); } - - framesMetadata.Add(frameMetaData.GetTiffMetadata()); } - - tiffMetadata.Frames = framesMetadata; } return imageMetaData; @@ -58,7 +52,7 @@ public static ImageMetadata Create(List frames, bool ignoreM 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 @@ private static bool TryGetIptc(IReadOnlyList exifValues, out byte[] 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 4fa0ba0135..e309830984 100644 --- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs @@ -69,7 +69,7 @@ private TiffFrameMetadata(TiffFrameMetadata other) /// The . internal static TiffFrameMetadata Parse(ExifProfile profile) { - var meta = new TiffFrameMetadata(); + TiffFrameMetadata meta = new(); Parse(meta, profile); return meta; } @@ -83,12 +83,10 @@ internal static void Parse(TiffFrameMetadata meta, ExifProfile profile) { 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 2a31642fec..2759d0130c 100644 --- a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs @@ -23,15 +23,6 @@ private TiffMetadata(TiffMetadata other) { 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 @@ private TiffMetadata(TiffMetadata other) /// 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 29be86e22f..c4e2e0d55a 100644 --- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs +++ b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs @@ -152,7 +152,10 @@ public ImageInfo Identify(BufferedReadStream stream, CancellationToken cancellat 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 02fa014781..cba32cb782 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 @@ internal Image( { } + /// + 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 @@ internal Image( /// 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 0000000000..660352c159 --- /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 fdc15a8127..00319e9b55 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 ImageInfo(PixelTypeInfo pixelType, int width, int height, ImageMetadata? /// 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 ImageInfo(PixelTypeInfo pixelType, Size size, ImageMetadata? metadata) 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 7def2003ae..40ac94eea6 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 void CloneIsDeep() 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 void CloneIsDeep() [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 void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored() 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 void Decode_IgnoreMetadataIsTrue_CommentsAreIgnored() [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 void Decode_CanDecodeLargeTextComment() [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 void Encode_PreservesTextData() [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 void Identify_VerifyRatio(string imagePath, int xResolution, int yResolut [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 async Task Identify_VerifyRatioAsync(string imagePath, int xResolution, i [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 void Decode_VerifyRatio(string imagePath, int xResolution, int yResolutio [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 async Task Decode_VerifyRatioAsync(string imagePath, int xResolution, int [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 void Identify_VerifyRepeatCount(string imagePath, uint repeatCount) [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 9bff30b6f4..1c203e7342 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs @@ -175,20 +175,20 @@ public void Decode_DetectsCorrectColorType(TestImageProvider pro 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 @@ private static void TestMetadataImpl( 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 @@ private static void TestMetadataImpl( Assert.Null(iccProfile); } }); + } + } [Theory] [InlineData(false)] @@ -268,28 +302,60 @@ public void IgnoreMetadata_ControlsWhetherMetadataIsParsed(bool ignoreMetadata) [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 bca7af9f81..0c6cdbc207 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -122,9 +122,14 @@ public void Identify_Frames(string imagePath, int framesCount, TiffPhotometricIn 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 39091281d3..73324eccd7 100644 --- a/tests/ImageSharp.Tests/ImageInfoTests.cs +++ b/tests/ImageSharp.Tests/ImageInfoTests.cs @@ -18,7 +18,7 @@ public void ImageInfoInitializesCorrectly() 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 void ImageInfoInitializesCorrectly() 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 92dd791328..f597b708d5 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 @@ protected override ImageInfo Identify(DecoderOptions options, Stream stream, Can { 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 a0fdb13319..ae09c4f3f2 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs @@ -75,7 +75,7 @@ protected override Image Decode(DecoderOptions options, Stream stream, Cancellat 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 2deed6d488..a3408bedb4 100644 --- a/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs +++ b/tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/SystemDrawingReferenceDecoder.cs @@ -17,7 +17,7 @@ protected override ImageInfo Identify(DecoderOptions options, Stream stream, Can { 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 0a3e45b3c8..cbce961103 100644 --- a/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs +++ b/tests/ImageSharp.Tests/TestUtilities/Tests/TestImageProviderTests.cs @@ -5,6 +5,7 @@ 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 static void DoTestThreadSafe(Action action) 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 static void DoTestThreadSafe(Action action) 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) From e9e0e644ce82c811f8326ca7c7393e53db3fdf28 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Mon, 27 Feb 2023 16:55:50 +0300 Subject: [PATCH 08/14] update README.md --- src/ImageSharp/Formats/Tiff/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/README.md b/src/ImageSharp/Formats/Tiff/README.md index 8f4cd83639..81db2f5d4f 100644 --- a/src/ImageSharp/Formats/Tiff/README.md +++ b/src/ImageSharp/Formats/Tiff/README.md @@ -57,7 +57,7 @@ |Separated (TIFF Extension) | | Y | | |YCbCr (TIFF Extension) | | Y | | |CieLab (TIFF Extension) | | Y | | -|IccLab (TechNote 1) | | | | +|IccLab (TechNote 1) | | Y | | |CMYK | | Y | | |Tiled Images | | Y | | @@ -127,9 +127,9 @@ |CleanFaxData | | | | |ConsecutiveBadFaxLines | | | | |SubIFDs | | - | | -|InkSet | | | | -|InkNames | | | | -|NumberOfInks | | | | +|InkSet | | Y | CMYK | +|InkNames | | - | | +|NumberOfInks | | - | | |DotRange | | | | |TargetPrinter | | | | |SampleFormat | | - | | From d19b128ed8bdec6b93e749227b17b6c4b19a19b5 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Mon, 27 Feb 2023 16:59:52 +0300 Subject: [PATCH 09/14] update README.md --- src/ImageSharp/Formats/Tiff/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Tiff/README.md b/src/ImageSharp/Formats/Tiff/README.md index 81db2f5d4f..48cbd54cad 100644 --- a/src/ImageSharp/Formats/Tiff/README.md +++ b/src/ImageSharp/Formats/Tiff/README.md @@ -57,7 +57,7 @@ |Separated (TIFF Extension) | | Y | | |YCbCr (TIFF Extension) | | Y | | |CieLab (TIFF Extension) | | Y | | -|IccLab (TechNote 1) | | Y | | +|IccLab (TechNote 1) | | | | |CMYK | | Y | | |Tiled Images | | Y | | From 07ca4055fd13d5c13a46d7a4f7dc56a1af37c55f Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Tue, 28 Feb 2023 13:28:36 +0300 Subject: [PATCH 10/14] add Bit32 value for TiffBitsPerPixel --- src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs index 38da4b5f8b..dc57c12f19 100644 --- a/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs +++ b/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs @@ -70,6 +70,11 @@ public enum TiffBitsPerPixel /// Bit30 = 30, + /// + /// 32 bits per pixel. One byte for each color channel. + /// + Bit32 = 32, + /// /// 36 bits per pixel. 12 bit for each color channel. /// From cfaf555c1fa0126d680733bd5c3466eccbc169d4 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Tue, 28 Feb 2023 13:30:43 +0300 Subject: [PATCH 11/14] add test for notSupported 64 bit cmyk, add more tests for Identify (incl 64 bit cmyk) --- tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs | 1 + tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs | 7 +++++-- tests/ImageSharp.Tests/TestImages.cs | 2 ++ tests/Images/Input/Tiff/cmyk_deflate_64bit.tiff | 3 +++ 4 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 tests/Images/Input/Tiff/cmyk_deflate_64bit.tiff diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs index 0c35008658..8ba76193c5 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs @@ -25,6 +25,7 @@ public class TiffDecoderTests : TiffDecoderBaseTester [Theory] [WithFile(MultiframeDifferentSize, PixelTypes.Rgba32)] [WithFile(MultiframeDifferentVariants, PixelTypes.Rgba32)] + [WithFile(Cmyk64BitDeflate, PixelTypes.Rgba32)] public void ThrowsNotSupported(TestImageProvider provider) where TPixel : unmanaged, IPixel => Assert.Throws(() => provider.GetImage(TiffDecoder.Instance)); diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index 0c6cdbc207..40346cdd89 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -110,8 +110,10 @@ public void Identify_DetectsCorrectByteOrder(string imagePath, ByteOrder expecte } [Theory] - [InlineData(Cmyk, 1, TiffPhotometricInterpretation.Separated, TiffInkSet.Cmyk)] - public void Identify_Frames(string imagePath, int framesCount, TiffPhotometricInterpretation photometric, TiffInkSet? inkSet) + [InlineData(Cmyk, 1, TiffBitsPerPixel.Bit32, TiffPhotometricInterpretation.Separated, TiffInkSet.Cmyk)] + [InlineData(Cmyk64BitDeflate, 1, 64, TiffPhotometricInterpretation.Separated, TiffInkSet.Cmyk)] + [InlineData(YCbCrJpegCompressed, 1, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.YCbCr, null)] + public void Identify_Frames(string imagePath, int framesCount, TiffBitsPerPixel bitsPerPixel, TiffPhotometricInterpretation photometric, TiffInkSet? inkSet) { TestFile testFile = TestFile.Create(imagePath); using MemoryStream stream = new(testFile.Bytes, false); @@ -127,6 +129,7 @@ public void Identify_Frames(string imagePath, int framesCount, TiffPhotometricIn foreach (ImageFrameMetadata metadata in imageInfo.FrameMetadataCollection) { TiffFrameMetadata tiffFrameMetadata = metadata.GetTiffMetadata(); + Assert.Equal(bitsPerPixel, tiffFrameMetadata.BitsPerPixel); Assert.Equal(photometric, tiffFrameMetadata.PhotometricInterpretation); Assert.Equal(inkSet, tiffFrameMetadata.InkSet); } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 7c383d389d..2dd1ca71c2 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -762,6 +762,7 @@ public static class Tiff public const string Benchmark_RgbLzw = "medium_rgb_lzw.tiff"; public const string Benchmark_RgbPackbits = "medium_rgb_packbits.tiff"; public const string Benchmark_RgbUncompressed = "medium_rgb_uncompressed.tiff"; + public const string Benchmark_CmykUncompressed = "medium_cmyk_uncompressed.tiff"; public const string Calliphora_GrayscaleUncompressed = "Tiff/Calliphora_grayscale_uncompressed.tiff"; public const string Calliphora_GrayscaleDeflate_Predictor = "Tiff/Calliphora_gray_deflate_predictor.tiff"; @@ -957,6 +958,7 @@ public static class Tiff public const string CieLabLzwPredictor = "Tiff/CieLab_lzwcompressed_predictor.tiff"; public const string Cmyk = "Tiff/Cmyk.tiff"; + public const string Cmyk64BitDeflate = "Tiff/cmyk_deflate_64bit.tiff"; public const string Issues1716Rgb161616BitLittleEndian = "Tiff/Issues/Issue1716.tiff"; public const string Issues1891 = "Tiff/Issues/Issue1891.tiff"; diff --git a/tests/Images/Input/Tiff/cmyk_deflate_64bit.tiff b/tests/Images/Input/Tiff/cmyk_deflate_64bit.tiff new file mode 100644 index 0000000000..f267c5e152 --- /dev/null +++ b/tests/Images/Input/Tiff/cmyk_deflate_64bit.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45793bfd1c9e92910b5b38805499859c38bb2fa1a2ae0c22888c16cc88b25d23 +size 53002 From 91c3d5ecf84300ee21476d14dee05e4f84cf9067 Mon Sep 17 00:00:00 2001 From: Ildar Khayrutdinov Date: Tue, 28 Feb 2023 13:38:09 +0300 Subject: [PATCH 12/14] cleanup --- tests/ImageSharp.Tests/TestImages.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 2dd1ca71c2..589168f009 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -762,7 +762,6 @@ public static class Tiff public const string Benchmark_RgbLzw = "medium_rgb_lzw.tiff"; public const string Benchmark_RgbPackbits = "medium_rgb_packbits.tiff"; public const string Benchmark_RgbUncompressed = "medium_rgb_uncompressed.tiff"; - public const string Benchmark_CmykUncompressed = "medium_cmyk_uncompressed.tiff"; public const string Calliphora_GrayscaleUncompressed = "Tiff/Calliphora_grayscale_uncompressed.tiff"; public const string Calliphora_GrayscaleDeflate_Predictor = "Tiff/Calliphora_gray_deflate_predictor.tiff"; From 747923d953666c72bcde99919115f55c09f1f248 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 1 Mar 2023 12:00:18 +1000 Subject: [PATCH 13/14] Add Bit64 and cleanup --- .../TiffPhotometricInterpretation.cs | 45 +++++++--------- .../Formats/Tiff/TiffBitsPerPixel.cs | 51 +++++++++---------- .../Formats/Tiff/TiffEncoderCore.cs | 4 ++ .../Formats/Tiff/TiffMetadataTests.cs | 2 +- 4 files changed, 47 insertions(+), 55 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs index cb537dda3c..6585be6f2f 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs @@ -9,9 +9,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants; public enum TiffPhotometricInterpretation : ushort { /// - /// Bilevel and grayscale: 0 is imaged as white. The maximum value is imaged as black. - /// - /// Not supported by the TiffEncoder. + /// Bilevel and grayscale: 0 is imaged as white. The maximum value is imaged as black. + /// Not supported by the TiffEncoder. /// WhiteIsZero = 0, @@ -31,58 +30,50 @@ public enum TiffPhotometricInterpretation : ushort PaletteColor = 3, /// - /// A transparency mask. - /// - /// Not supported by the TiffEncoder. + /// A transparency mask. + /// Not supported by the TiffEncoder. /// TransparencyMask = 4, /// - /// Separated: usually CMYK (see Section 16 of the TIFF 6.0 specification). - /// - /// Not supported by the TiffEncoder. + /// Separated: usually CMYK (see Section 16 of the TIFF 6.0 specification). + /// Not supported by the TiffEncoder. /// Separated = 5, /// - /// YCbCr (see Section 21 of the TIFF 6.0 specification). - /// - /// Not supported by the TiffEncoder. + /// YCbCr (see Section 21 of the TIFF 6.0 specification). + /// Not supported by the TiffEncoder. /// YCbCr = 6, /// - /// 1976 CIE L*a*b* (see Section 23 of the TIFF 6.0 specification). - /// - /// Not supported by the TiffEncoder. + /// 1976 CIE L*a*b* (see Section 23 of the TIFF 6.0 specification). + /// Not supported by the TiffEncoder. /// CieLab = 8, /// - /// ICC L*a*b* (see TIFF Specification, supplement 1). - /// - /// Not supported by the TiffEncoder. + /// ICC L*a*b* (see TIFF Specification, supplement 1). + /// Not supported by the TiffEncoder. /// IccLab = 9, /// - /// ITU L*a*b* (see RFC2301). - /// - /// Not supported by the TiffEncoder. + /// ITU L*a*b* (see RFC2301). + /// Not supported by the TiffEncoder. /// ItuLab = 10, /// - /// Color Filter Array (see the DNG specification). - /// - /// Not supported by the TiffEncoder. + /// Color Filter Array (see the DNG specification). + /// Not supported by the TiffEncoder. /// ColorFilterArray = 32803, /// - /// Linear Raw (see the DNG specification). - /// - /// Not supported by the TiffEncoder. + /// Linear Raw (see the DNG specification). + /// Not supported by the TiffEncoder. /// LinearRaw = 34892 } diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs index dc57c12f19..6cbacec354 100644 --- a/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs +++ b/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs @@ -19,9 +19,8 @@ public enum TiffBitsPerPixel Bit4 = 4, /// - /// 6 bits per pixel. 2 bit for each color channel. - /// - /// Note: The TiffEncoder does not yet support 2 bits per color channel and will default to 24 bits per pixel instead. + /// 6 bits per pixel. 2 bit for each color channel. + /// Note: The TiffEncoder does not yet support 2 bits per color channel and will default to 24 bits per pixel instead. /// Bit6 = 6, @@ -31,30 +30,26 @@ public enum TiffBitsPerPixel Bit8 = 8, /// - /// 10 bits per pixel, for gray images. - /// - /// Note: The TiffEncoder does not yet support 10 bits per pixel and will default to 24 bits per pixel instead. + /// 10 bits per pixel, for gray images. + /// Note: The TiffEncoder does not yet support 10 bits per pixel and will default to 24 bits per pixel instead. /// Bit10 = 10, /// - /// 12 bits per pixel. 4 bit for each color channel. - /// - /// Note: The TiffEncoder does not yet support 4 bits per color channel and will default to 24 bits per pixel instead. + /// 12 bits per pixel. 4 bit for each color channel. + /// Note: The TiffEncoder does not yet support 4 bits per color channel and will default to 24 bits per pixel instead. /// Bit12 = 12, /// - /// 14 bits per pixel, for gray images. - /// - /// Note: The TiffEncoder does not yet support 14 bits per pixel images and will default to 24 bits per pixel instead. + /// 14 bits per pixel, for gray images. + /// Note: The TiffEncoder does not yet support 14 bits per pixel images and will default to 24 bits per pixel instead. /// Bit14 = 14, /// - /// 16 bits per pixel, for gray images. - /// - /// Note: The TiffEncoder does not yet support 16 bits per color channel and will default to 16 bits grayscale instead. + /// 16 bits per pixel, for gray images. + /// Note: The TiffEncoder does not yet support 16 bits per color channel and will default to 16 bits grayscale instead. /// Bit16 = 16, @@ -64,9 +59,8 @@ public enum TiffBitsPerPixel Bit24 = 24, /// - /// 30 bits per pixel. 10 bit for each color channel. - /// - /// Note: The TiffEncoder does not yet support 10 bits per color channel and will default to 24 bits per pixel instead. + /// 30 bits per pixel. 10 bit for each color channel. + /// Note: The TiffEncoder does not yet support 10 bits per color channel and will default to 24 bits per pixel instead. /// Bit30 = 30, @@ -76,23 +70,26 @@ public enum TiffBitsPerPixel Bit32 = 32, /// - /// 36 bits per pixel. 12 bit for each color channel. - /// - /// Note: The TiffEncoder does not yet support 12 bits per color channel and will default to 24 bits per pixel instead. + /// 36 bits per pixel. 12 bit for each color channel. + /// Note: The TiffEncoder does not yet support 12 bits per color channel and will default to 24 bits per pixel instead. /// Bit36 = 36, /// - /// 42 bits per pixel. 14 bit for each color channel. - /// - /// Note: The TiffEncoder does not yet support 14 bits per color channel and will default to 24 bits per pixel instead. + /// 42 bits per pixel. 14 bit for each color channel. + /// Note: The TiffEncoder does not yet support 14 bits per color channel and will default to 24 bits per pixel instead. /// Bit42 = 42, /// - /// 48 bits per pixel. 16 bit for each color channel. - /// - /// Note: The TiffEncoder does not yet support 16 bits per color channel and will default to 24 bits per pixel instead. + /// 48 bits per pixel. 16 bit for each color channel. + /// Note: The TiffEncoder does not yet support 16 bits per color channel and will default to 24 bits per pixel instead. /// Bit48 = 48, + + /// + /// 64 bits per pixel. 16 bit for each color channel. + /// Note: The TiffEncoder does not yet support 16 bits per color channel and will default to 32 bits per pixel instead. + /// + Bit64 = 64, } diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index 94c7fb2b1e..ba8d2bd23e 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -391,6 +391,10 @@ private void SanitizeAndSetEncoderOptions( // Encoding not yet supported bits per pixel will default to 24 bits. this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, TiffPredictor.None); break; + case TiffBitsPerPixel.Bit64: + // Encoding not yet supported bits per pixel will default to 32 bits. + this.SetEncoderOptions(TiffBitsPerPixel.Bit32, TiffPhotometricInterpretation.Rgb, compression, TiffPredictor.None); + break; default: this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.Rgb, compression, predictor); break; diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index 40346cdd89..b671addf95 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -111,7 +111,7 @@ public void Identify_DetectsCorrectByteOrder(string imagePath, ByteOrder expecte [Theory] [InlineData(Cmyk, 1, TiffBitsPerPixel.Bit32, TiffPhotometricInterpretation.Separated, TiffInkSet.Cmyk)] - [InlineData(Cmyk64BitDeflate, 1, 64, TiffPhotometricInterpretation.Separated, TiffInkSet.Cmyk)] + [InlineData(Cmyk64BitDeflate, 1, TiffBitsPerPixel.Bit64, TiffPhotometricInterpretation.Separated, TiffInkSet.Cmyk)] [InlineData(YCbCrJpegCompressed, 1, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.YCbCr, null)] public void Identify_Frames(string imagePath, int framesCount, TiffBitsPerPixel bitsPerPixel, TiffPhotometricInterpretation photometric, TiffInkSet? inkSet) { From 24a0a5f9a207428430dd225de87de3b74819953d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 1 Mar 2023 12:02:09 +1000 Subject: [PATCH 14/14] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 2aa59a3ddd..4fd2e43105 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -198,10 +198,14 @@ jobs: - name: Feedz Publish shell: pwsh - run: dotnet nuget push .\artifacts\*.nupkg -k ${{secrets.FEEDZ_TOKEN}} -s https://f.feedz.io/sixlabors/sixlabors/nuget/index.json -ss https://f.feedz.io/sixlabors/sixlabors/symbols --skip-duplicate + run: | + dotnet nuget push .\artifacts\*.nupkg -k ${{secrets.FEEDZ_TOKEN}} -s https://f.feedz.io/sixlabors/sixlabors/nuget/index.json --skip-duplicate + dotnet nuget push .\artifacts\*.snupkg -k ${{secrets.FEEDZ_TOKEN}} -s https://f.feedz.io/sixlabors/sixlabors/symbols --skip-duplicate - name: NuGet Publish if: ${{ startsWith(github.ref, 'refs/tags/') }} shell: pwsh - run: dotnet nuget push .\artifacts\*.nupkg -k ${{secrets.NUGET_TOKEN}} -s https://api.nuget.org/v3/index.json --skip-duplicate + run: | + dotnet nuget push .\artifacts\*.nupkg -k ${{secrets.NUGET_TOKEN}} -s https://api.nuget.org/v3/index.json --skip-duplicate + dotnet nuget push .\artifacts\*.snupkg -k ${{secrets.NUGET_TOKEN}} -s https://api.nuget.org/v3/index.json --skip-duplicate