// Copyright (c) Six Labors. // Licensed under the Apache License, Version 2.0. using System.IO; using System.Linq; using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Iptc; using SixLabors.ImageSharp.PixelFormats; using Xunit; using static SixLabors.ImageSharp.Tests.TestImages.Tiff; namespace SixLabors.ImageSharp.Tests.Formats.Tiff { [Trait("Format", "Tiff")] public class TiffMetadataTests { private readonly Configuration configuration; private static TiffDecoder TiffDecoder => new TiffDecoder(); public TiffMetadataTests() { this.configuration = new Configuration(); this.configuration.AddTiff(); } [Fact] public void TiffMetadata_CloneIsDeep() { var meta = new TiffMetadata { ByteOrder = ByteOrder.BigEndian, }; var clone = (TiffMetadata)meta.DeepClone(); clone.ByteOrder = ByteOrder.LittleEndian; Assert.False(meta.ByteOrder == clone.ByteOrder); } [Theory] [WithFile(SampleMetadata, PixelTypes.Rgba32)] public void TiffFrameMetadata_CloneIsDeep(TestImageProvider provider) where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(TiffDecoder)) { ExifProfile exifProfile = image.Frames.RootFrame.Metadata.ExifProfile; var meta = new TiffFrameMetadata(exifProfile); var cloneSameAsSampleMetaData = (TiffFrameMetadata)meta.DeepClone(); VerifyExpectedTiffFrameMetaDataIsPresent(cloneSameAsSampleMetaData); var clone = (TiffFrameMetadata)meta.DeepClone(); clone.BitsPerSample = TiffBitsPerSample.Bit1; clone.ColorMap = new ushort[] { 1, 2, 3 }; Assert.False(meta.BitsPerSample == clone.BitsPerSample); Assert.False(meta.ColorMap.SequenceEqual(clone.ColorMap)); } } [Theory] [InlineData(Calliphora_BiColorUncompressed, 1)] [InlineData(GrayscaleUncompressed, 8)] [InlineData(RgbUncompressed, 24)] public void Identify_DetectsCorrectBitPerPixel(string imagePath, int expectedBitsPerPixel) { var testFile = TestFile.Create(imagePath); using var stream = new MemoryStream(testFile.Bytes, false); IImageInfo imageInfo = Image.Identify(this.configuration, stream); Assert.NotNull(imageInfo); TiffMetadata tiffMetadata = imageInfo.Metadata.GetTiffMetadata(); Assert.NotNull(tiffMetadata); Assert.Equal(expectedBitsPerPixel, imageInfo.PixelType.BitsPerPixel); } [Theory] [InlineData(GrayscaleUncompressed, ByteOrder.BigEndian)] [InlineData(LittleEndianByteOrder, ByteOrder.LittleEndian)] public void Identify_DetectsCorrectByteOrder(string imagePath, ByteOrder expectedByteOrder) { var testFile = TestFile.Create(imagePath); using var stream = new MemoryStream(testFile.Bytes, false); IImageInfo imageInfo = Image.Identify(this.configuration, stream); Assert.NotNull(imageInfo); TiffMetadata tiffMetadata = imageInfo.Metadata.GetTiffMetadata(); Assert.NotNull(tiffMetadata); Assert.Equal(expectedByteOrder, tiffMetadata.ByteOrder); } [Theory] [WithFile(SampleMetadata, PixelTypes.Rgba32, false)] [WithFile(SampleMetadata, PixelTypes.Rgba32, true)] public void MetadataProfiles(TestImageProvider provider, bool ignoreMetadata) where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(new TiffDecoder() { IgnoreMetadata = ignoreMetadata })) { TiffMetadata meta = image.Metadata.GetTiffMetadata(); ImageFrameMetadata rootFrameMetaData = image.Frames.RootFrame.Metadata; Assert.NotNull(meta); if (ignoreMetadata) { Assert.Null(rootFrameMetaData.XmpProfile); Assert.Null(rootFrameMetaData.ExifProfile); } else { Assert.NotNull(rootFrameMetaData.XmpProfile); Assert.NotNull(rootFrameMetaData.ExifProfile); Assert.Equal(2599, rootFrameMetaData.XmpProfile.Length); Assert.Equal(30, rootFrameMetaData.ExifProfile.Values.Count); } } } [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(TiffDecoder)) { ImageFrame rootFrame = image.Frames.RootFrame; Assert.Equal(32, rootFrame.Width); Assert.Equal(32, rootFrame.Height); Assert.NotNull(rootFrame.Metadata.XmpProfile); Assert.Equal(2599, rootFrame.Metadata.XmpProfile.Length); ExifProfile exifProfile = rootFrame.Metadata.ExifProfile; Assert.NotNull(exifProfile); Assert.Equal(30, exifProfile.Values.Count); 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.Null(exifProfile.GetValue(ExifTag.DateTime)?.Value); Assert.Equal("This is author1;Author2", exifProfile.GetValue(ExifTag.Artist).Value); Assert.Null(exifProfile.GetValue(ExifTag.HostComputer)?.Value); Assert.Equal("This is Авторские права", exifProfile.GetValue(ExifTag.Copyright).Value); Assert.Equal(4, exifProfile.GetValue(ExifTag.Rating).Value); Assert.Equal(75, exifProfile.GetValue(ExifTag.RatingPercent).Value); ImageMetadata imageMetaData = image.Metadata; Assert.NotNull(imageMetaData); Assert.Equal(PixelResolutionUnit.PixelsPerInch, imageMetaData.ResolutionUnits); Assert.Equal(10, imageMetaData.HorizontalResolution); Assert.Equal(10, imageMetaData.VerticalResolution); TiffMetadata tiffMetaData = image.Metadata.GetTiffMetadata(); Assert.NotNull(tiffMetaData); Assert.Equal(ByteOrder.LittleEndian, tiffMetaData.ByteOrder); var frameMetaData = new TiffFrameMetadata(exifProfile); Assert.Equal(TiffBitsPerPixel.Bit4, frameMetaData.BitsPerPixel); var tiffFrameMetadata = new TiffFrameMetadata(exifProfile); VerifyExpectedTiffFrameMetaDataIsPresent(tiffFrameMetadata); } } private static void VerifyExpectedTiffFrameMetaDataIsPresent(TiffFrameMetadata frameMetaData) { Assert.NotNull(frameMetaData); Assert.Equal(TiffBitsPerSample.Bit4, frameMetaData.BitsPerSample); Assert.Equal(TiffCompression.Lzw, frameMetaData.Compression); Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frameMetaData.PhotometricInterpretation); Assert.Equal(new Number[] { 8u }, frameMetaData.StripOffsets, new NumberComparer()); Assert.Equal(1, frameMetaData.SamplesPerPixel.GetValueOrDefault()); Assert.Equal(32u, frameMetaData.RowsPerStrip); Assert.Equal(new Number[] { 297u }, frameMetaData.StripByteCounts, new NumberComparer()); Assert.Equal(PixelResolutionUnit.PixelsPerInch, frameMetaData.ResolutionUnit); Assert.Equal(10, frameMetaData.HorizontalResolution); Assert.Equal(10, frameMetaData.VerticalResolution); Assert.Equal(TiffPlanarConfiguration.Chunky, frameMetaData.PlanarConfiguration); Assert.Equal(48, frameMetaData.ColorMap.Length); Assert.Equal(10537, frameMetaData.ColorMap[0]); Assert.Equal(14392, frameMetaData.ColorMap[1]); Assert.Equal(58596, frameMetaData.ColorMap[46]); Assert.Equal(3855, frameMetaData.ColorMap[47]); Assert.Null(frameMetaData.ExtraSamples); Assert.Equal(TiffPredictor.None, frameMetaData.Predictor); Assert.Null(frameMetaData.SampleFormat); } [Theory] [WithFile(MultiframeDeflateWithPreview, PixelTypes.Rgba32)] public void SubfileType(TestImageProvider provider) where TPixel : unmanaged, IPixel { using (Image image = provider.GetImage(TiffDecoder)) { TiffMetadata meta = image.Metadata.GetTiffMetadata(); Assert.NotNull(meta); Assert.Equal(2, image.Frames.Count); var frame0MetaData = new TiffFrameMetadata(image.Frames[0].Metadata.ExifProfile); Assert.Equal(TiffNewSubfileType.FullImage, frame0MetaData.SubfileType); Assert.Equal(255, image.Frames[0].Width); Assert.Equal(255, image.Frames[0].Height); var frame1MetaData = new TiffFrameMetadata(image.Frames[1].Metadata.ExifProfile); Assert.Equal(TiffNewSubfileType.Preview, frame1MetaData.SubfileType); Assert.Equal(255, image.Frames[1].Width); Assert.Equal(255, image.Frames[1].Height); } } [Theory] [WithFile(SampleMetadata, PixelTypes.Rgba32)] public void Encode_PreservesMetadata(TestImageProvider provider) where TPixel : unmanaged, IPixel { // Load Tiff image using Image image = provider.GetImage(new TiffDecoder() { IgnoreMetadata = false }); ImageMetadata inputMetaData = image.Metadata; TiffMetadata tiffMetaInput = image.Metadata.GetTiffMetadata(); var frameMetaInput = new TiffFrameMetadata(image.Frames.RootFrame.Metadata.ExifProfile); ImageFrame rootFrameInput = image.Frames.RootFrame; byte[] xmpProfileInput = rootFrameInput.Metadata.XmpProfile; ExifProfile rootFrameExifProfileInput = rootFrameInput.Metadata.ExifProfile; Assert.Equal(TiffCompression.Lzw, frameMetaInput.Compression); Assert.Equal(TiffBitsPerPixel.Bit4, frameMetaInput.BitsPerPixel); // Save to Tiff var tiffEncoder = new TiffEncoder() { Mode = TiffEncodingMode.Rgb }; using var ms = new MemoryStream(); image.Save(ms, tiffEncoder); // Assert ms.Position = 0; using var encodedImage = Image.Load(this.configuration, ms); ImageMetadata encodedImageMetaData = encodedImage.Metadata; TiffMetadata tiffMetaDataEncodedImage = encodedImageMetaData.GetTiffMetadata(); ImageFrame rootFrameEncodedImage = encodedImage.Frames.RootFrame; var tiffMetaDataEncodedRootFrame = new TiffFrameMetadata(rootFrameEncodedImage.Metadata.ExifProfile); ExifProfile encodedImageExifProfile = rootFrameEncodedImage.Metadata.ExifProfile; byte[] encodedImageXmpProfile = rootFrameEncodedImage.Metadata.XmpProfile; Assert.Equal(TiffBitsPerPixel.Bit24, tiffMetaDataEncodedRootFrame.BitsPerPixel); Assert.Equal(TiffCompression.None, tiffMetaDataEncodedRootFrame.Compression); Assert.Equal(inputMetaData.HorizontalResolution, encodedImageMetaData.HorizontalResolution); Assert.Equal(inputMetaData.VerticalResolution, encodedImageMetaData.VerticalResolution); Assert.Equal(inputMetaData.ResolutionUnits, encodedImageMetaData.ResolutionUnits); Assert.Equal(rootFrameInput.Width, rootFrameEncodedImage.Width); Assert.Equal(rootFrameInput.Height, rootFrameEncodedImage.Height); Assert.Equal(frameMetaInput.ResolutionUnit, tiffMetaDataEncodedRootFrame.ResolutionUnit); Assert.Equal(frameMetaInput.HorizontalResolution, tiffMetaDataEncodedRootFrame.HorizontalResolution); Assert.Equal(frameMetaInput.VerticalResolution, tiffMetaDataEncodedRootFrame.VerticalResolution); Assert.Equal(xmpProfileInput, encodedImageXmpProfile); Assert.Equal("IrfanView", rootFrameExifProfileInput.GetValue(ExifTag.Software).Value); Assert.Equal("This is Название", rootFrameExifProfileInput.GetValue(ExifTag.ImageDescription).Value); Assert.Equal("This is Изготовитель камеры", rootFrameExifProfileInput.GetValue(ExifTag.Make).Value); Assert.Equal("This is Авторские права", rootFrameExifProfileInput.GetValue(ExifTag.Copyright).Value); Assert.Equal(rootFrameExifProfileInput.Values.Count, encodedImageExifProfile.Values.Count); Assert.Equal(rootFrameExifProfileInput.GetValue(ExifTag.ImageDescription).Value, encodedImageExifProfile.GetValue(ExifTag.ImageDescription).Value); Assert.Equal(rootFrameExifProfileInput.GetValue(ExifTag.Make).Value, encodedImageExifProfile.GetValue(ExifTag.Make).Value); Assert.Equal(rootFrameExifProfileInput.GetValue(ExifTag.Copyright).Value, encodedImageExifProfile.GetValue(ExifTag.Copyright).Value); } } }