Browse Source

Clone TIFF profiles into image metadata

Update TIFF metadata creation to build ImageMetadata after IFD tag processing and copy root-frame EXIF/ICC/IPTC/XMP profiles to image scope via deep clones. This keeps image-level metadata in sync for downstream single-frame encoders while preserving independent mutability between frame and image metadata. Tests now cover preserved ICC handling and verify image-level profile presence, equality, and non-shared instances for EXIF/XMP/IPTC, including ignore-metadata behavior.
pull/3159/head
James Jackson-South 3 weeks ago
parent
commit
494d2dd97b
  1. 18
      src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs
  2. 16
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  3. 13
      tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs

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

@ -22,8 +22,6 @@ internal static class TiffDecoderMetadataCreator
TiffThrowHelper.ThrowImageFormatException("Expected at least one frame.");
}
ImageMetadata imageMetaData = Create(byteOrder, isBigTiff, frames[0]);
if (!ignoreMetadata)
{
for (int i = 0; i < frames.Count; i++)
@ -43,12 +41,24 @@ internal static class TiffDecoderMetadataCreator
}
}
return imageMetaData;
// IPTC and XMP are materialized from IFD tags above, so image-level metadata
// must be created after all frame profiles are available.
return Create(byteOrder, isBigTiff, frames[0]);
}
private static ImageMetadata Create(ByteOrder byteOrder, bool isBigTiff, ImageFrameMetadata rootFrameMetadata)
{
ImageMetadata imageMetaData = new();
// TIFF stores metadata per IFD, while ImageMetadata is the source used by single-frame encoders.
// Mirror the root IFD profiles at image scope so cross-format encoding preserves them. Each profile
// is cloned because image-level and frame-level metadata are independently mutable public APIs.
ImageMetadata imageMetaData = new()
{
ExifProfile = rootFrameMetadata.ExifProfile?.DeepClone(),
IccProfile = rootFrameMetadata.IccProfile?.DeepClone(),
IptcProfile = rootFrameMetadata.IptcProfile?.DeepClone(),
XmpProfile = rootFrameMetadata.XmpProfile?.DeepClone()
};
SetResolution(imageMetaData, rootFrameMetadata.ExifProfile);
TiffMetadata tiffMetadata = imageMetaData.GetTiffMetadata();

16
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs

@ -370,6 +370,22 @@ public class TiffDecoderTests : TiffDecoderBaseTester
Assert.Null(image.Metadata.IccProfile);
}
[Theory]
[WithFile(Icc.PerceptualRgb8, PixelTypes.Rgba32)]
[WithFile(Icc.PerceptualRgb16, PixelTypes.Rgba32)]
public void Decode_WhenColorProfileHandlingIsPreserve_PreservesIccProfile<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
DecoderOptions options = new() { ColorProfileHandling = ColorProfileHandling.Preserve };
using Image<TPixel> image = provider.GetImage(TiffDecoder.Instance, options);
Assert.NotNull(image.Metadata.IccProfile);
Assert.NotSame(image.Frames.RootFrame.Metadata.IccProfile, image.Metadata.IccProfile);
Assert.Equal(
image.Frames.RootFrame.Metadata.IccProfile.ToByteArray(),
image.Metadata.IccProfile.ToByteArray());
}
[Theory]
[WithFile(Issues2454_A, PixelTypes.Rgba32)]
[WithFile(Issues2454_B, PixelTypes.Rgba32)]

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

@ -150,13 +150,22 @@ public class TiffMetadataTests
Assert.NotNull(meta);
if (ignoreMetadata)
{
Assert.Null(image.Metadata.XmpProfile);
Assert.Null(image.Metadata.ExifProfile);
Assert.Null(rootFrameMetaData.XmpProfile);
Assert.Null(rootFrameMetaData.ExifProfile);
}
else
{
Assert.NotNull(image.Metadata.XmpProfile);
Assert.NotNull(image.Metadata.ExifProfile);
Assert.NotNull(rootFrameMetaData.XmpProfile);
Assert.NotNull(rootFrameMetaData.ExifProfile);
Assert.NotSame(rootFrameMetaData.XmpProfile, image.Metadata.XmpProfile);
Assert.NotSame(rootFrameMetaData.ExifProfile, image.Metadata.ExifProfile);
Assert.Equal(rootFrameMetaData.XmpProfile.Data, image.Metadata.XmpProfile.Data);
Assert.Equal(rootFrameMetaData.ExifProfile.ToByteArray(), image.Metadata.ExifProfile.ToByteArray());
Assert.Equal(2596, rootFrameMetaData.XmpProfile.Data.Length); // padding bytes are trimmed
Assert.Equal(25, rootFrameMetaData.ExifProfile.Values.Count);
}
@ -171,6 +180,10 @@ public class TiffMetadataTests
IptcProfile iptcProfile = image.Frames.RootFrame.Metadata.IptcProfile;
Assert.NotNull(iptcProfile);
Assert.NotNull(image.Metadata.IptcProfile);
Assert.NotSame(iptcProfile, image.Metadata.IptcProfile);
Assert.Equal(iptcProfile.Data, image.Metadata.IptcProfile.Data);
IptcValue byline = iptcProfile.Values.FirstOrDefault(data => data.Tag == IptcTag.Byline);
Assert.NotNull(byline);
Assert.Equal("Studio Mantyniemi", byline.Value);

Loading…
Cancel
Save