From b45670878dc519d5a32920a85706600134b142f2 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 17 Nov 2022 20:33:53 +0100 Subject: [PATCH 1/5] Write padding byte, because the tiff spec requires ifd offset to begin on a word boundary. --- src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index 2013377ed6..94c2253d18 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -244,6 +244,12 @@ private long WriteFrame( entriesCollector.ProcessFrameInfo(frame, imageMetadata); entriesCollector.ProcessImageFormat(this); + if (writer.Position % 2 != 0) + { + // Write padding byte, because the tiff spec requires ifd offset to begin on a word boundary. + writer.Write(0); + } + this.frameMarkers.Add((ifdOffset, (uint)writer.Position)); return this.WriteIfd(writer, entriesCollector.Entries); From 59478543ee14a75a1407c8d23a79c9be20afdb2b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Thu, 17 Nov 2022 20:50:04 +0100 Subject: [PATCH 2/5] Add test for issue #2297 --- .../Formats/Tiff/TiffEncoderTests.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs index 7b55c1c5d0..2c30cc3d05 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -246,6 +246,25 @@ public void TiffEncoder_PreservesPredictor(TestImageProvider pro Assert.Equal(expectedPredictor, frameMetadata.Predictor); } + // https://github.com/SixLabors/ImageSharp/issues/2297 + [Fact] + public void TiffEncoder_WritesIfdOffsetAtWordBoundary() + { + // arrange + var tiffEncoder = new TiffEncoder(); + using var memStream = new MemoryStream(); + using Image image = new(1, 1); + byte[] expectedIfdOffsetBytes = { 12, 0 }; + + // act + image.Save(memStream, tiffEncoder); + + // assert + byte[] imageBytes = memStream.ToArray(); + Assert.Equal(imageBytes[4], expectedIfdOffsetBytes[0]); + Assert.Equal(imageBytes[5], expectedIfdOffsetBytes[1]); + } + [Theory] [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.CcittGroup3Fax, TiffCompression.CcittGroup3Fax)] [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffCompression.CcittGroup4Fax, TiffCompression.CcittGroup4Fax)] From 4aec7aae5645522e595623aa1f81d4567922a54b Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Fri, 18 Nov 2022 20:26:40 +0100 Subject: [PATCH 3/5] Change samples per pixel to ushort --- src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs index 64b300cd60..250442f371 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs @@ -278,7 +278,7 @@ public void Process(TiffEncoderCore encoder) Value = (ushort)TiffPlanarConfiguration.Chunky }; - ExifLong samplesPerPixel = new(ExifTagValue.SamplesPerPixel) + ExifShort samplesPerPixel = new(ExifTagValue.SamplesPerPixel) { Value = GetSamplesPerPixel(encoder) }; @@ -317,7 +317,7 @@ TiffPhotometricInterpretation.PaletteColor or } } - private static uint GetSamplesPerPixel(TiffEncoderCore encoder) + private static ushort GetSamplesPerPixel(TiffEncoderCore encoder) => encoder.PhotometricInterpretation switch { TiffPhotometricInterpretation.PaletteColor or From 4498518c94353954e73dcca47a8c6c654a5b8e21 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 20 Nov 2022 14:29:20 +0100 Subject: [PATCH 4/5] Fix broken meta data tiff --- .../Formats/Tiff/TiffMetadataTests.cs | 44 ++++++++----------- tests/Images/Input/Tiff/metadata_sample.tiff | 4 +- 2 files changed, 20 insertions(+), 28 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index 8f4345b2e8..ffe4c573b3 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs @@ -71,7 +71,6 @@ private static void VerifyExpectedTiffFrameMetaDataIsPresent(TiffFrameMetadata f Assert.Equal(TiffBitsPerPixel.Bit4, frameMetaData.BitsPerPixel); Assert.Equal(TiffCompression.Lzw, frameMetaData.Compression); Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frameMetaData.PhotometricInterpretation); - Assert.Equal(TiffPredictor.None, frameMetaData.Predictor); } [Theory] @@ -129,7 +128,7 @@ public void MetadataProfiles(TestImageProvider provider, bool ig Assert.NotNull(rootFrameMetaData.XmpProfile); Assert.NotNull(rootFrameMetaData.ExifProfile); Assert.Equal(2599, rootFrameMetaData.XmpProfile.Data.Length); - Assert.Equal(26, rootFrameMetaData.ExifProfile.Values.Count); + Assert.Equal(25, rootFrameMetaData.ExifProfile.Values.Count); } } @@ -163,38 +162,33 @@ public void BaselineTags(TestImageProvider provider) TiffFrameMetadata tiffFrameMetadata = rootFrame.Metadata.GetTiffMetadata(); Assert.NotNull(exifProfile); - // The original exifProfile has 30 values, but 4 of those values will be stored in the TiffFrameMetaData - // and removed from the profile on decode. - Assert.Equal(26, exifProfile.Values.Count); + Assert.Equal(25, exifProfile.Values.Count); Assert.Equal(TiffBitsPerPixel.Bit4, tiffFrameMetadata.BitsPerPixel); Assert.Equal(TiffCompression.Lzw, tiffFrameMetadata.Compression); - Assert.Equal("This is Название", exifProfile.GetValue(ExifTag.ImageDescription).Value); - Assert.Equal("This is Изготовитель камеры", exifProfile.GetValue(ExifTag.Make).Value); - Assert.Equal("This is Модель камеры", exifProfile.GetValue(ExifTag.Model).Value); - Assert.Equal("IrfanView", exifProfile.GetValue(ExifTag.Software).Value); + Assert.Equal("ImageDescription", exifProfile.GetValue(ExifTag.ImageDescription).Value); + Assert.Equal("Make", exifProfile.GetValue(ExifTag.Make).Value); + Assert.Equal("Model", exifProfile.GetValue(ExifTag.Model).Value); + Assert.Equal("ImageSharp", exifProfile.GetValue(ExifTag.Software).Value); Assert.Null(exifProfile.GetValue(ExifTag.DateTime)?.Value); - Assert.Equal("This is author1;Author2", exifProfile.GetValue(ExifTag.Artist).Value); + Assert.Equal("Artist", exifProfile.GetValue(ExifTag.Artist).Value); Assert.Null(exifProfile.GetValue(ExifTag.HostComputer)?.Value); - Assert.Equal("This is Авторские права", exifProfile.GetValue(ExifTag.Copyright).Value); + Assert.Equal("Copyright", exifProfile.GetValue(ExifTag.Copyright).Value); Assert.Equal(4, exifProfile.GetValue(ExifTag.Rating).Value); Assert.Equal(75, exifProfile.GetValue(ExifTag.RatingPercent).Value); - var expectedResolution = new Rational(10000, 1000, simplify: false); + var expectedResolution = new Rational(10, 1, simplify: false); Assert.Equal(expectedResolution, exifProfile.GetValue(ExifTag.XResolution).Value); Assert.Equal(expectedResolution, exifProfile.GetValue(ExifTag.YResolution).Value); Assert.Equal(new Number[] { 8u }, exifProfile.GetValue(ExifTag.StripOffsets)?.Value, new NumberComparer()); - Assert.Equal(new Number[] { 297u }, exifProfile.GetValue(ExifTag.StripByteCounts)?.Value, new NumberComparer()); + Assert.Equal(new Number[] { 285u }, exifProfile.GetValue(ExifTag.StripByteCounts)?.Value, new NumberComparer()); Assert.Null(exifProfile.GetValue(ExifTag.ExtraSamples)?.Value); Assert.Equal(32u, exifProfile.GetValue(ExifTag.RowsPerStrip).Value); Assert.Null(exifProfile.GetValue(ExifTag.SampleFormat)); - Assert.Equal(TiffPredictor.None, tiffFrameMetadata.Predictor); Assert.Equal(PixelResolutionUnit.PixelsPerInch, UnitConverter.ExifProfileToResolutionUnit(exifProfile)); ushort[] colorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value; Assert.NotNull(colorMap); Assert.Equal(48, colorMap.Length); - Assert.Equal(10537, colorMap[0]); - Assert.Equal(14392, colorMap[1]); - Assert.Equal(58596, colorMap[46]); - Assert.Equal(3855, colorMap[47]); + Assert.Equal(4369, colorMap[0]); + Assert.Equal(8738, colorMap[1]); Assert.Equal(TiffPhotometricInterpretation.PaletteColor, tiffFrameMetadata.PhotometricInterpretation); Assert.Equal(1u, exifProfile.GetValue(ExifTag.SamplesPerPixel).Value); @@ -238,7 +232,7 @@ public void Encode_PreservesMetadata(TestImageProvider provider) { // Load Tiff image DecoderOptions options = new() { SkipMetadata = false }; - using Image image = provider.GetImage(new TiffDecoder(), options); + using Image image = provider.GetImage(TiffDecoder, options); ImageMetadata inputMetaData = image.Metadata; ImageFrame rootFrameInput = image.Frames.RootFrame; @@ -284,17 +278,15 @@ public void Encode_PreservesMetadata(TestImageProvider provider) Assert.NotNull(encodedImageXmpProfile); Assert.Equal(xmpProfileInput.Data, encodedImageXmpProfile.Data); - Assert.Equal("IrfanView", exifProfileInput.GetValue(ExifTag.Software).Value); - Assert.Equal("This is Название", exifProfileInput.GetValue(ExifTag.ImageDescription).Value); - Assert.Equal("This is Изготовитель камеры", exifProfileInput.GetValue(ExifTag.Make).Value); - Assert.Equal("This is Авторские права", exifProfileInput.GetValue(ExifTag.Copyright).Value); - + Assert.Equal(exifProfileInput.GetValue(ExifTag.Software).Value, encodedImageExifProfile.GetValue(ExifTag.Software).Value); Assert.Equal(exifProfileInput.GetValue(ExifTag.ImageDescription).Value, encodedImageExifProfile.GetValue(ExifTag.ImageDescription).Value); Assert.Equal(exifProfileInput.GetValue(ExifTag.Make).Value, encodedImageExifProfile.GetValue(ExifTag.Make).Value); Assert.Equal(exifProfileInput.GetValue(ExifTag.Copyright).Value, encodedImageExifProfile.GetValue(ExifTag.Copyright).Value); + Assert.Equal(exifProfileInput.GetValue(ExifTag.Artist).Value, encodedImageExifProfile.GetValue(ExifTag.Artist).Value); + Assert.Equal(exifProfileInput.GetValue(ExifTag.Orientation).Value, encodedImageExifProfile.GetValue(ExifTag.Orientation).Value); + Assert.Equal(exifProfileInput.GetValue(ExifTag.Model).Value, encodedImageExifProfile.GetValue(ExifTag.Model).Value); - // Note that the encoded profile has PlanarConfiguration explicitly set, which is missing in the original image profile. Assert.Equal((ushort)TiffPlanarConfiguration.Chunky, encodedImageExifProfile.GetValue(ExifTag.PlanarConfiguration)?.Value); - Assert.Equal(exifProfileInput.Values.Count + 1, encodedImageExifProfile.Values.Count); + Assert.Equal(exifProfileInput.Values.Count, encodedImageExifProfile.Values.Count); } } diff --git a/tests/Images/Input/Tiff/metadata_sample.tiff b/tests/Images/Input/Tiff/metadata_sample.tiff index d767352688..9c0671ee7c 100644 --- a/tests/Images/Input/Tiff/metadata_sample.tiff +++ b/tests/Images/Input/Tiff/metadata_sample.tiff @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:72a1c8022d699e0e7248940f0734d01d6ab9bf4a71022e8b5626b64d66a5f39d -size 8107 +oid sha256:c2f20e3ebb51b743b635377b93e78a6dc91c07ca97be38f500c4d6d3c465d9c1 +size 3472 From 57a8fa532e6dae70628ce3fd5d650fac0629bd91 Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Sun, 20 Nov 2022 14:30:01 +0100 Subject: [PATCH 5/5] Use AddOrReplace for profiles to avoid adding duplicate --- src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs index 250442f371..a84b8aed52 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs @@ -178,7 +178,7 @@ private void ProcessProfiles(ImageMetadata imageMetadata, bool skipMetadata, Exi Value = imageMetadata.IptcProfile.Data }; - this.Collector.Add(iptc); + this.Collector.AddOrReplace(iptc); } else { @@ -192,7 +192,7 @@ private void ProcessProfiles(ImageMetadata imageMetadata, bool skipMetadata, Exi Value = imageMetadata.IccProfile.ToByteArray() }; - this.Collector.Add(icc); + this.Collector.AddOrReplace(icc); } else { @@ -206,7 +206,7 @@ private void ProcessProfiles(ImageMetadata imageMetadata, bool skipMetadata, Exi Value = xmpProfile.Data }; - this.Collector.Add(xmp); + this.Collector.AddOrReplace(xmp); } else {