From f0670af0453fc40c883c51e91c90b47731e047a2 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Tue, 2 Feb 2021 19:38:52 +0100 Subject: [PATCH] Handle TIFF image with incorrect IPTC data type (long instead of byte) --- .../Tiff/TiffDecoderMetadataCreator.cs | 57 ++++++++++++++++-- .../Formats/Tiff/TiffMetadataTests.cs | 22 ++++++- .../Profiles/IPTC/IptcProfileTests.cs | 59 +++++++++++++------ tests/ImageSharp.Tests/TestImages.cs | 4 ++ .../7324fcaff3aad96f27899da51c1bb5d9.tiff | 3 + tests/Images/Input/Tiff/iptc.tiff | 3 + 6 files changed, 122 insertions(+), 26 deletions(-) create mode 100644 tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff create mode 100644 tests/Images/Input/Tiff/iptc.tiff diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs index db0b04bd0..96149cba8 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs @@ -1,8 +1,11 @@ // Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. +using System; +using System.Buffers.Binary; using System.Collections.Generic; - +using System.Linq; +using System.Runtime.InteropServices; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Icc; @@ -56,10 +59,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff if (coreMetadata.IptcProfile == null) { - IExifValue val = frame.ExifProfile.GetValue(ExifTag.IPTC); - if (val != null) + if (TryGetIptc(frame.ExifProfile.Values, out var iptcBytes)) { - coreMetadata.IptcProfile = new IptcProfile(val.Value); + coreMetadata.IptcProfile = new IptcProfile(iptcBytes); } } @@ -77,6 +79,53 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff return coreMetadata; } + private static bool TryGetIptc(IReadOnlyList exifValues, out byte[] iptcBytes) + { + iptcBytes = null; + IExifValue iptc = exifValues.FirstOrDefault(f => f.Tag == ExifTag.IPTC); + + if (iptc != null) + { + if (iptc.DataType == ExifDataType.Byte) + { + iptcBytes = (byte[])iptc.GetValue(); + return true; + } + else if (iptc.DataType == ExifDataType.Long) + { + var iptcValues = (uint[])iptc.GetValue(); + iptcBytes = new byte[iptcValues.Length * 4]; + Buffer.BlockCopy(iptcValues, 0, iptcBytes, 0, iptcValues.Length * 4); + if (iptcBytes[0] == 0x1c) + { + return true; + } + else if (iptcBytes[3] != 0x1c) + { + return false; + } + + // Probably wrong endianess, swap byte order. + Span iptcBytesSpan = iptcBytes.AsSpan(); + Span buffer = stackalloc byte[4]; + for (int i = 0; i < iptcBytes.Length; i += 4) + { + iptcBytesSpan.Slice(i, 4).CopyTo(buffer); + iptcBytes[i] = buffer[3]; + iptcBytes[i + 1] = buffer[2]; + iptcBytes[i + 2] = buffer[1]; + iptcBytes[i + 3] = buffer[0]; + } + + return true; + } + + return false; + } + + return false; + } + private static TiffBitsPerPixel GetBitsPerPixel(TiffFrameMetadata firstFrameMetaData) => (TiffBitsPerPixel)firstFrameMetaData.BitsPerPixel; } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index 75538a3f7..2329baf76 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. using System; +using System.Globalization; using System.IO; using System.Linq; @@ -24,6 +25,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff { private readonly Configuration configuration; + private static TiffDecoder TiffDecoder => new TiffDecoder(); + public TiffMetadataTests() { this.configuration = new Configuration(); @@ -130,12 +133,25 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff } } + [Theory] + [WithFile(InvalidIptcData, PixelTypes.Rgba32)] + public void CanDecodeImage_WithIptcDataAsLong(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using Image image = provider.GetImage(TiffDecoder); + + Assert.NotNull(image.Metadata.IptcProfile); + IptcValue byline = image.Metadata.IptcProfile.Values.FirstOrDefault(data => data.Tag == IptcTag.Byline); + Assert.NotNull(byline); + Assert.Equal("Studio Mantyniemi", byline.Value); + } + [Theory] [WithFile(SampleMetadata, PixelTypes.Rgba32)] public void BaselineTags(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new TiffDecoder())) + using (Image image = provider.GetImage(TiffDecoder)) { TiffMetadata meta = image.Metadata.GetTiffMetadata(); @@ -188,7 +204,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff public void SubfileType(TestImageProvider provider) where TPixel : unmanaged, IPixel { - using (Image image = provider.GetImage(new TiffDecoder())) + using (Image image = provider.GetImage(TiffDecoder)) { TiffMetadata meta = image.Metadata.GetTiffMetadata(); Assert.NotNull(meta); @@ -311,7 +327,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff coreMeta.HorizontalResolution = 4500; coreMeta.VerticalResolution = 5400; - var datetime = DateTime.Now.ToString(); + var datetime = DateTime.Now.ToString(CultureInfo.InvariantCulture); frameMeta.ImageDescription = "test ImageDescription"; frameMeta.DateTime = datetime; diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs index 9e763536b..7ec1e90ac 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/IPTC/IptcProfileTests.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using SixLabors.ImageSharp.Formats.Experimental.Tiff; using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Metadata.Profiles.Iptc; using SixLabors.ImageSharp.PixelFormats; @@ -16,6 +17,8 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC { private static JpegDecoder JpegDecoder => new JpegDecoder() { IgnoreMetadata = false }; + private static TiffDecoder TiffDecoder => new TiffDecoder() { IgnoreMetadata = false }; + public static IEnumerable AllIptcTags() { foreach (object tag in Enum.GetValues(typeof(IptcTag))) @@ -100,34 +103,52 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.IPTC [Theory] [WithFile(TestImages.Jpeg.Baseline.Iptc, PixelTypes.Rgba32)] - public void ReadIptcMetadata_Works(TestImageProvider provider) + public void ReadIptcMetadata_FromJpg_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(JpegDecoder)) { Assert.NotNull(image.Metadata.IptcProfile); var iptcValues = image.Metadata.IptcProfile.Values.ToList(); - ContainsIptcValue(iptcValues, IptcTag.Caption, "description"); - ContainsIptcValue(iptcValues, IptcTag.CaptionWriter, "description writer"); - ContainsIptcValue(iptcValues, IptcTag.Headline, "headline"); - ContainsIptcValue(iptcValues, IptcTag.SpecialInstructions, "special instructions"); - ContainsIptcValue(iptcValues, IptcTag.Byline, "author"); - ContainsIptcValue(iptcValues, IptcTag.BylineTitle, "author title"); - ContainsIptcValue(iptcValues, IptcTag.Credit, "credits"); - ContainsIptcValue(iptcValues, IptcTag.Source, "source"); - ContainsIptcValue(iptcValues, IptcTag.Name, "title"); - ContainsIptcValue(iptcValues, IptcTag.CreatedDate, "20200414"); - ContainsIptcValue(iptcValues, IptcTag.City, "city"); - ContainsIptcValue(iptcValues, IptcTag.SubLocation, "sublocation"); - ContainsIptcValue(iptcValues, IptcTag.ProvinceState, "province-state"); - ContainsIptcValue(iptcValues, IptcTag.Country, "country"); - ContainsIptcValue(iptcValues, IptcTag.Category, "category"); - ContainsIptcValue(iptcValues, IptcTag.Urgency, "1"); - ContainsIptcValue(iptcValues, IptcTag.Keywords, "keywords"); - ContainsIptcValue(iptcValues, IptcTag.CopyrightNotice, "copyright"); + IptcProfileContainsExpectedValues(iptcValues); } } + [Theory] + [WithFile(TestImages.Tiff.IptcData, PixelTypes.Rgba32)] + public void ReadIptcMetadata_FromTiff_Works(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TiffDecoder)) + { + Assert.NotNull(image.Metadata.IptcProfile); + var iptcValues = image.Metadata.IptcProfile.Values.ToList(); + IptcProfileContainsExpectedValues(iptcValues); + } + } + + private static void IptcProfileContainsExpectedValues(List iptcValues) + { + ContainsIptcValue(iptcValues, IptcTag.Caption, "description"); + ContainsIptcValue(iptcValues, IptcTag.CaptionWriter, "description writer"); + ContainsIptcValue(iptcValues, IptcTag.Headline, "headline"); + ContainsIptcValue(iptcValues, IptcTag.SpecialInstructions, "special instructions"); + ContainsIptcValue(iptcValues, IptcTag.Byline, "author"); + ContainsIptcValue(iptcValues, IptcTag.BylineTitle, "author title"); + ContainsIptcValue(iptcValues, IptcTag.Credit, "credits"); + ContainsIptcValue(iptcValues, IptcTag.Source, "source"); + ContainsIptcValue(iptcValues, IptcTag.Name, "title"); + ContainsIptcValue(iptcValues, IptcTag.CreatedDate, "20200414"); + ContainsIptcValue(iptcValues, IptcTag.City, "city"); + ContainsIptcValue(iptcValues, IptcTag.SubLocation, "sublocation"); + ContainsIptcValue(iptcValues, IptcTag.ProvinceState, "province-state"); + ContainsIptcValue(iptcValues, IptcTag.Country, "country"); + ContainsIptcValue(iptcValues, IptcTag.Category, "category"); + ContainsIptcValue(iptcValues, IptcTag.Urgency, "1"); + ContainsIptcValue(iptcValues, IptcTag.Keywords, "keywords"); + ContainsIptcValue(iptcValues, IptcTag.CopyrightNotice, "copyright"); + } + [Theory] [WithFile(TestImages.Jpeg.Baseline.App13WithEmptyIptc, PixelTypes.Rgba32)] public void ReadApp13_WithEmptyIptc_Works(TestImageProvider provider) diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 300ab09b9..24ac7e0e8 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -574,6 +574,10 @@ namespace SixLabors.ImageSharp.Tests public const string SampleMetadata = "Tiff/metadata_sample.tiff"; + // Iptc data as long[] instead of byte[] + public const string InvalidIptcData = "Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff"; + public const string IptcData = "Tiff/iptc.tiff"; + public static readonly string[] Multiframes = { MultiframeDeflateWithPreview, MultiframeLzwPredictor /*, MultiFrameDifferentSize, MultiframeDifferentSizeTiled, MultiFrameDifferentVariants,*/ }; public static readonly string[] Metadata = { SampleMetadata }; diff --git a/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff b/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff new file mode 100644 index 000000000..5574bd58e --- /dev/null +++ b/tests/Images/Input/Tiff/7324fcaff3aad96f27899da51c1bb5d9.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:622d69dba0a8a67aa3b87e384a2b9ea8d29689eaa5cb5d0eee857f98ed660517 +size 15154924 diff --git a/tests/Images/Input/Tiff/iptc.tiff b/tests/Images/Input/Tiff/iptc.tiff new file mode 100644 index 000000000..ed06ff411 --- /dev/null +++ b/tests/Images/Input/Tiff/iptc.tiff @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7a8d89df97b35ab3be9745a345687defd427bf4ca519ad3c5a52208d98f7694 +size 15170