From 411c7d6520d8ee01e4a7fe4420a0159c96656a4a Mon Sep 17 00:00:00 2001 From: Brian Popow Date: Wed, 12 May 2021 20:27:45 +0200 Subject: [PATCH] Add setters for TiffFrameMetaData properties, initialize properties from frame ExifProfile --- .../Formats/Tiff/Constants/TiffCompression.cs | 5 + .../Tiff/TiffBitsPerSampleExtensions.cs | 2 - .../Formats/Tiff/TiffDecoderCore.cs | 7 +- .../Tiff/TiffDecoderMetadataCreator.cs | 2 +- .../Formats/Tiff/TiffDecoderOptionsParser.cs | 18 +- .../Formats/Tiff/TiffEncoderCore.cs | 51 ++-- .../Formats/Tiff/TiffFrameMetadata.cs | 242 ++++++++-------- .../Formats/Tiff/TiffEncoderTests.cs | 15 +- .../Formats/Tiff/TiffMetadataTests.cs | 268 ++++++------------ .../Profiles/Exif/ExifProfileTests.cs | 14 +- 10 files changed, 264 insertions(+), 360 deletions(-) diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs index b40647a937..031494fc5d 100644 --- a/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs +++ b/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs @@ -8,6 +8,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants /// public enum TiffCompression : ushort { + /// + /// A invalid compression value. + /// + Invalid = 0, + /// /// No compression. /// diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs index a0c7eb0213..33e13d08ea 100644 --- a/src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs +++ b/src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. using System; -using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff.Constants; namespace SixLabors.ImageSharp.Formats.Tiff @@ -28,7 +27,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff return TiffConstants.BitsPerSampleRgb8Bit; default: - TiffThrowHelper.ThrowNotSupported("The bits per pixels are not supported"); return Array.Empty(); } } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs index 75cd063613..d67ffc0693 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs @@ -148,7 +148,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff var framesMetadata = new List(); foreach (ExifProfile ifd in directories) { - var meta = new TiffFrameMetadata() { ExifProfile = ifd }; + var meta = new TiffFrameMetadata(ifd); + meta.Initialize(ifd); framesMetadata.Add(meta); } @@ -158,7 +159,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff int width = GetImageWidth(root); int height = GetImageHeight(root); - return new ImageInfo(new PixelTypeInfo(root.BitsPerSample.BitsPerPixel()), width, height, metadata); + return new ImageInfo(new PixelTypeInfo(root.BitsPerPixel), width, height, metadata); } /// @@ -175,7 +176,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff { var coreMetadata = new ImageFrameMetadata(); frameMetaData = coreMetadata.GetTiffMetadata(); - frameMetaData.ExifProfile = tags; + frameMetaData.Initialize(tags); this.VerifyAndParse(frameMetaData); diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs index aa44b0b035..5b67c363af 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs @@ -126,6 +126,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff } private static TiffBitsPerPixel GetBitsPerPixel(TiffFrameMetadata firstFrameMetaData) - => (TiffBitsPerPixel)firstFrameMetaData.BitsPerSample.BitsPerPixel(); + => (TiffBitsPerPixel)firstFrameMetaData.BitsPerPixel; } } diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs index aaf4502cdc..b9f30a3efa 100644 --- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs +++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs @@ -20,6 +20,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff /// The IFD entries container to read the image format information for. public static void VerifyAndParse(this TiffDecoderCore options, TiffFrameMetadata entries) { + if (entries.TileOffsets != null) + { + TiffThrowHelper.ThrowNotSupported("Tiled images are not supported."); + } + if (entries.ExtraSamples != null) { TiffThrowHelper.ThrowNotSupported("ExtraSamples is not supported."); @@ -30,11 +35,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff TiffThrowHelper.ThrowNotSupported("The lower-order bits of the byte FillOrder is not supported."); } - if (entries.ExifProfile.GetValue(ExifTag.TileOffsets) != null) - { - TiffThrowHelper.ThrowNotSupported("Tiled images are not supported."); - } - if (entries.Predictor == TiffPredictor.FloatingPoint) { TiffThrowHelper.ThrowNotSupported("TIFF images with FloatingPoint horizontal predictor are not supported."); @@ -51,16 +51,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff } } - if (entries.ExifProfile.GetValue(ExifTag.StripRowCounts) != null) + if (entries.StripRowCounts != null) { TiffThrowHelper.ThrowNotSupported("Variable-sized strips are not supported."); } + entries.VerifyRequiredFieldsArePresent(); + options.PlanarConfiguration = entries.PlanarConfiguration; options.Predictor = entries.Predictor; options.PhotometricInterpretation = entries.PhotometricInterpretation; - options.BitsPerSample = entries.BitsPerSample; - options.BitsPerPixel = entries.BitsPerSample.BitsPerPixel(); + options.BitsPerSample = entries.BitsPerSample.GetValueOrDefault(); + options.BitsPerPixel = entries.BitsPerSample.GetValueOrDefault().BitsPerPixel(); ParseColorType(options, entries); ParseCompression(options, entries); diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs index 09fdffa249..24fd465263 100644 --- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs +++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs @@ -66,7 +66,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff this.quantizer = options.Quantizer ?? KnownQuantizers.Octree; this.BitsPerPixel = options.BitsPerPixel; this.HorizontalPredictor = options.HorizontalPredictor; - this.CompressionType = options.Compression; + this.CompressionType = options.Compression != TiffCompression.Invalid ? options.Compression : TiffCompression.None; this.compressionLevel = options.CompressionLevel; } @@ -113,7 +113,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff ImageMetadata metadata = image.Metadata; TiffMetadata tiffMetadata = metadata.GetTiffMetadata(); TiffFrameMetadata rootFrameMetaData = image.Frames.RootFrame.Metadata.GetTiffMetadata(); - TiffPhotometricInterpretation photometricInterpretation = rootFrameMetaData.PhotometricInterpretation; + TiffPhotometricInterpretation photometricInterpretation = this.Mode == TiffEncodingMode.ColorPalette + ? TiffPhotometricInterpretation.PaletteColor : rootFrameMetaData.PhotometricInterpretation; this.SetMode(tiffMetadata, photometricInterpretation); this.SetBitsPerPixel(tiffMetadata); @@ -159,31 +160,27 @@ namespace SixLabors.ImageSharp.Formats.Tiff // Write the image bytes to the steam. uint imageDataStart = (uint)writer.Position; - TiffBitsPerPixel? tiffBitsPerPixel = this.BitsPerPixel; - if (tiffBitsPerPixel != null) - { - using TiffBaseCompressor compressor = TiffCompressorFactory.Create( - this.CompressionType, - writer.BaseStream, - this.memoryAllocator, - image.Width, - (int)tiffBitsPerPixel, - this.compressionLevel, - this.HorizontalPredictor == TiffPredictor.Horizontal ? this.HorizontalPredictor : TiffPredictor.None); - - using TiffBaseColorWriter colorWriter = TiffColorWriterFactory.Create( - this.Mode, - image.Frames.RootFrame, - this.quantizer, - this.memoryAllocator, - this.configuration, - entriesCollector, - (int)tiffBitsPerPixel); - - int rowsPerStrip = this.CalcRowsPerStrip(image.Frames.RootFrame.Height, colorWriter.BytesPerRow); - - colorWriter.Write(compressor, rowsPerStrip); - } + using TiffBaseCompressor compressor = TiffCompressorFactory.Create( + this.CompressionType, + writer.BaseStream, + this.memoryAllocator, + image.Width, + (int)this.BitsPerPixel, + this.compressionLevel, + this.HorizontalPredictor == TiffPredictor.Horizontal ? this.HorizontalPredictor : TiffPredictor.None); + + using TiffBaseColorWriter colorWriter = TiffColorWriterFactory.Create( + this.Mode, + image.Frames.RootFrame, + this.quantizer, + this.memoryAllocator, + this.configuration, + entriesCollector, + (int)this.BitsPerPixel); + + int rowsPerStrip = this.CalcRowsPerStrip(image.Frames.RootFrame.Height, colorWriter.BytesPerRow); + + colorWriter.Write(compressor, rowsPerStrip); entriesCollector.ProcessImageFormat(this); entriesCollector.ProcessGeneral(image); diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs index e00fac1519..d98b3ac94a 100644 --- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs +++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs @@ -18,195 +18,201 @@ namespace SixLabors.ImageSharp.Formats.Tiff private const TiffPredictor DefaultPredictor = TiffPredictor.None; - private ExifProfile frameTags; - /// - /// Gets the Tiff directory tags. + /// Initializes a new instance of the class. /// - public ExifProfile ExifProfile - { - get => this.frameTags ??= new ExifProfile(); - internal set => this.frameTags = value; - } + public TiffFrameMetadata() => this.Initialize(new ExifProfile()); /// - /// Gets a general indication of the kind of data contained in this subfile. + /// Initializes a new instance of the class. /// - public TiffNewSubfileType SubfileType => (TiffNewSubfileType?)this.ExifProfile.GetValue(ExifTag.SubfileType)?.Value ?? TiffNewSubfileType.FullImage; + /// The Tiff frame directory tags. + public TiffFrameMetadata(ExifProfile frameTags) => this.Initialize(frameTags); /// - /// Gets a general indication of the kind of data contained in this subfile. + /// Initializes a new instance of the class with a given ExifProfile. /// - public TiffSubfileType? OldSubfileType => (TiffSubfileType?)this.ExifProfile.GetValue(ExifTag.OldSubfileType)?.Value; + /// The Tiff frame directory tags. + public void Initialize(ExifProfile frameTags) + { + this.ExifProfile = frameTags; + + this.FillOrder = (TiffFillOrder?)this.ExifProfile.GetValue(ExifTag.FillOrder)?.Value ?? TiffFillOrder.MostSignificantBitFirst; + this.Compression = this.ExifProfile.GetValue(ExifTag.Compression) != null ? (TiffCompression)this.ExifProfile.GetValue(ExifTag.Compression).Value : TiffCompression.None; + this.SubfileType = (TiffNewSubfileType?)this.ExifProfile.GetValue(ExifTag.SubfileType)?.Value ?? TiffNewSubfileType.FullImage; + this.OldSubfileType = (TiffSubfileType?)this.ExifProfile.GetValue(ExifTag.OldSubfileType)?.Value; + this.HorizontalResolution = this.ExifProfile.GetValue(ExifTag.XResolution)?.Value.ToDouble(); + this.VerticalResolution = this.ExifProfile.GetValue(ExifTag.YResolution)?.Value.ToDouble(); + this.PlanarConfiguration = (TiffPlanarConfiguration?)this.ExifProfile.GetValue(ExifTag.PlanarConfiguration)?.Value ?? DefaultPlanarConfiguration; + this.ResolutionUnit = UnitConverter.ExifProfileToResolutionUnit(this.ExifProfile); + this.ColorMap = this.ExifProfile.GetValue(ExifTag.ColorMap)?.Value; + this.ExtraSamples = this.ExifProfile.GetValue(ExifTag.ExtraSamples)?.Value; + this.Predictor = (TiffPredictor?)this.ExifProfile.GetValue(ExifTag.Predictor)?.Value ?? DefaultPredictor; + this.SampleFormat = this.ExifProfile.GetValue(ExifTag.SampleFormat)?.Value?.Select(a => (TiffSampleFormat)a).ToArray(); + this.SamplesPerPixel = this.ExifProfile.GetValue(ExifTag.SamplesPerPixel)?.Value; + this.StripRowCounts = this.ExifProfile.GetValue(ExifTag.StripRowCounts)?.Value; + this.RowsPerStrip = this.ExifProfile.GetValue(ExifTag.RowsPerStrip) != null ? this.ExifProfile.GetValue(ExifTag.RowsPerStrip).Value : TiffConstants.RowsPerStripInfinity; + this.TileOffsets = this.ExifProfile.GetValue(ExifTag.TileOffsets)?.Value; + + this.PhotometricInterpretation = this.ExifProfile.GetValue(ExifTag.PhotometricInterpretation) != null ? + (TiffPhotometricInterpretation)this.ExifProfile.GetValue(ExifTag.PhotometricInterpretation).Value : TiffPhotometricInterpretation.WhiteIsZero; + + // Required Fields for decoding the image. + this.StripOffsets = this.ExifProfile.GetValue(ExifTag.StripOffsets)?.Value; + this.StripByteCounts = this.ExifProfile.GetValue(ExifTag.StripByteCounts)?.Value; + + ushort[] bits = this.ExifProfile.GetValue(ExifTag.BitsPerSample)?.Value; + if (bits == null) + { + if (this.PhotometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero || this.PhotometricInterpretation == TiffPhotometricInterpretation.BlackIsZero) + { + this.BitsPerSample = TiffBitsPerSample.Bit1; + } + + this.BitsPerSample = null; + } + else + { + this.BitsPerSample = bits.GetBitsPerSample(); + } + + this.BitsPerPixel = this.BitsPerSample.GetValueOrDefault().BitsPerPixel(); + } /// - /// Gets the number of bits per component. + /// Verifies that the required fields for decoding an image are present. + /// If not, a ImageFormatException will be thrown. /// - public TiffBitsPerSample BitsPerSample + public void VerifyRequiredFieldsArePresent() { - get + if (this.StripOffsets == null) { - ushort[] bits = this.ExifProfile.GetValue(ExifTag.BitsPerSample)?.Value; - if (bits == null) - { - if (this.PhotometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero - || this.PhotometricInterpretation == TiffPhotometricInterpretation.BlackIsZero) - { - return TiffBitsPerSample.Bit1; - } + TiffThrowHelper.ThrowImageFormatException("StripOffsets are missing and are required for decoding the TIFF image!"); + } - TiffThrowHelper.ThrowNotSupported("The TIFF BitsPerSample entry is missing which is required to decode the image."); - } + if (this.StripByteCounts == null) + { + TiffThrowHelper.ThrowImageFormatException("StripByteCounts are missing and are required for decoding the TIFF image!"); + } - return bits.GetBitsPerSample(); + if (this.BitsPerSample == null) + { + TiffThrowHelper.ThrowNotSupported("The TIFF BitsPerSample entry is missing which is required to decode the image!"); } } /// - /// Gets the bits per pixel. + /// Gets the Tiff directory tags. /// - public int BitsPerPixel => this.BitsPerSample.BitsPerPixel(); + public ExifProfile ExifProfile { get; internal set; } /// - /// Gets the compression scheme used on the image data. + /// Gets or sets a general indication of the kind of data contained in this subfile. /// - /// The compression scheme used on the image data. - public TiffCompression Compression - { - get - { - IExifValue compression = this.ExifProfile.GetValue(ExifTag.Compression); - if (compression == null) - { - return TiffCompression.None; - } + public TiffNewSubfileType? SubfileType { get; set; } - return (TiffCompression)compression.Value; - } - } + /// + /// Gets or sets a general indication of the kind of data contained in this subfile. + /// + public TiffSubfileType? OldSubfileType { get; set; } /// - /// Gets the color space of the image data. + /// Gets or sets the number of bits per component. /// - public TiffPhotometricInterpretation PhotometricInterpretation - { - get - { - IExifValue photometricInterpretation = this.ExifProfile.GetValue(ExifTag.PhotometricInterpretation); - if (photometricInterpretation == null) - { - return TiffPhotometricInterpretation.WhiteIsZero; - } + public TiffBitsPerSample? BitsPerSample { get; set; } - return (TiffPhotometricInterpretation)photometricInterpretation.Value; - } - } + /// + /// Gets or sets the bits per pixel. + /// + public int BitsPerPixel { get; set; } /// - /// Gets the logical order of bits within a byte. + /// Gets or sets the compression scheme used on the image data. /// - internal TiffFillOrder FillOrder => (TiffFillOrder?)this.ExifProfile.GetValue(ExifTag.FillOrder)?.Value ?? TiffFillOrder.MostSignificantBitFirst; + /// The compression scheme used on the image data. + public TiffCompression Compression { get; set; } /// - /// Gets for each strip, the byte offset of that strip. + /// Gets or sets the color space of the image data. /// - public Number[] StripOffsets - { - get - { - IExifValue stripOffsets = this.ExifProfile.GetValue(ExifTag.StripOffsets); - if (stripOffsets == null) - { - TiffThrowHelper.ThrowImageFormatException("StripOffsets are missing"); - } + public TiffPhotometricInterpretation PhotometricInterpretation { get; set; } - return stripOffsets.Value; - } - } + /// + /// Gets or sets the logical order of bits within a byte. + /// + internal TiffFillOrder FillOrder { get; set; } /// - /// Gets the number of components per pixel. + /// Gets or sets for each strip, the byte offset of that strip. /// - public ushort SamplesPerPixel => this.ExifProfile.GetValue(ExifTag.SamplesPerPixel).Value; + public Number[] StripOffsets { get; set; } /// - /// Gets the number of rows per strip. + /// Gets or sets the strip row counts. /// - public Number RowsPerStrip - { - get - { - IExifValue rowsPerStrip = this.ExifProfile.GetValue(ExifTag.RowsPerStrip); - if (rowsPerStrip == null) - { - return TiffConstants.RowsPerStripInfinity; - } + public uint[] StripRowCounts { get; set; } - return rowsPerStrip.Value; - } - } + /// + /// Gets or sets the number of components per pixel. + /// + public ushort? SamplesPerPixel { get; set; } /// - /// Gets for each strip, the number of bytes in the strip after compression. + /// Gets or sets the number of rows per strip. /// - public Number[] StripByteCounts - { - get - { - IExifValue stripByteCounts = this.ExifProfile.GetValue(ExifTag.StripByteCounts); - if (stripByteCounts == null) - { - TiffThrowHelper.ThrowImageFormatException("StripByteCounts are missing"); - } + public Number RowsPerStrip { get; set; } - return stripByteCounts.Value; - } - } + /// + /// Gets or sets for each strip, the number of bytes in the strip after compression. + /// + public Number[] StripByteCounts { get; set; } + + /// + /// Gets or sets the resolution of the image in x-direction. + /// + public double? HorizontalResolution { get; set; } /// - /// Gets the resolution of the image in x- direction. + /// Gets or sets the resolution of the image in y-direction. /// - /// The density of the image in x- direction. - public double? HorizontalResolution => this.ExifProfile.GetValue(ExifTag.XResolution)?.Value.ToDouble(); + public double? VerticalResolution { get; set; } /// - /// Gets the resolution of the image in y- direction. + /// Gets or sets how the components of each pixel are stored. /// - /// The density of the image in y- direction. - public double? VerticalResolution => this.ExifProfile.GetValue(ExifTag.YResolution)?.Value.ToDouble(); + public TiffPlanarConfiguration PlanarConfiguration { get; set; } /// - /// Gets how the components of each pixel are stored. + /// Gets or sets the unit of measurement for XResolution and YResolution. /// - public TiffPlanarConfiguration PlanarConfiguration => (TiffPlanarConfiguration?)this.ExifProfile.GetValue(ExifTag.PlanarConfiguration)?.Value ?? DefaultPlanarConfiguration; + public PixelResolutionUnit ResolutionUnit { get; set; } /// - /// Gets the unit of measurement for XResolution and YResolution. + /// Gets or sets a color map for palette color images. /// - public PixelResolutionUnit ResolutionUnit => UnitConverter.ExifProfileToResolutionUnit(this.ExifProfile); + public ushort[] ColorMap { get; set; } /// - /// Gets a color map for palette color images. + /// Gets or sets the description of extra components. /// - public ushort[] ColorMap => this.ExifProfile.GetValue(ExifTag.ColorMap)?.Value; + public ushort[] ExtraSamples { get; set; } /// - /// Gets the description of extra components. + /// Gets or sets the tile offsets. /// - public ushort[] ExtraSamples => this.ExifProfile.GetValue(ExifTag.ExtraSamples)?.Value; + public uint[] TileOffsets { get; set; } /// - /// Gets a mathematical operator that is applied to the image data before an encoding scheme is applied. + /// Gets or sets a mathematical operator that is applied to the image data before an encoding scheme is applied. /// - public TiffPredictor Predictor => (TiffPredictor?)this.ExifProfile.GetValue(ExifTag.Predictor)?.Value ?? DefaultPredictor; + public TiffPredictor Predictor { get; set; } /// - /// Gets the specifies how to interpret each data sample in a pixel. - /// + /// Gets or sets the specifies how to interpret each data sample in a pixel. /// - public TiffSampleFormat[] SampleFormat => this.ExifProfile.GetValue(ExifTag.SampleFormat)?.Value?.Select(a => (TiffSampleFormat)a).ToArray(); + public TiffSampleFormat[] SampleFormat { get; set; } /// - public IDeepCloneable DeepClone() => new TiffFrameMetadata() { ExifProfile = this.ExifProfile.DeepClone() }; + public IDeepCloneable DeepClone() => new TiffFrameMetadata(this.ExifProfile.DeepClone()); } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs index dda695568c..77098c42ca 100644 --- a/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs @@ -254,7 +254,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff [WithFile(Rgb4BitPalette, PixelTypes.Rgba32)] public void TiffEncoder_EncodeColorPalette_With4Bit_Works(TestImageProvider provider) where TPixel : unmanaged, IPixel => - TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit8, TiffEncodingMode.ColorPalette, useExactComparer: false, compareTolerance: 0.001f); + // Note: The magick reference decoder does not support 4 bit tiff's, so we use our TIFF decoder instead. + TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit4, TiffEncodingMode.ColorPalette, useExactComparer: false, compareTolerance: 0.001f, imageDecoder: new TiffDecoder()); [Theory] [WithFile(Calliphora_PaletteUncompressed, PixelTypes.Rgba32)] @@ -384,8 +385,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff TiffCompression compression = TiffCompression.None, TiffPredictor predictor = TiffPredictor.None, bool useExactComparer = true, - int maxStripSize = 0, - float compareTolerance = 0.01f) + float compareTolerance = 0.01f, + IImageDecoder imageDecoder = null) where TPixel : unmanaged, IPixel { using Image image = provider.GetImage(); @@ -398,7 +399,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff }; // Does DebugSave & load reference CompareToReferenceInput(): - image.VerifyEncoder(provider, "tiff", bitsPerPixel, encoder, useExactComparer ? ImageComparer.Exact : ImageComparer.Tolerant(compareTolerance), referenceDecoder: ReferenceDecoder); + image.VerifyEncoder( + provider, + "tiff", + bitsPerPixel, + encoder, + useExactComparer ? ImageComparer.Exact : ImageComparer.Tolerant(compareTolerance), + referenceDecoder: imageDecoder ?? ReferenceDecoder); } } } diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs index 92412234bf..45b53eae8b 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.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; @@ -34,7 +35,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff } [Fact] - public void CloneIsDeep() + public void TiffMetadata_CloneIsDeep() { byte[] xmpData = { 1, 1, 1 }; var meta = new TiffMetadata @@ -55,6 +56,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff Assert.True(meta.XmpProfile.SequenceEqual(clone.XmpProfile)); } + [Theory] + [WithFile(SampleMetadata, PixelTypes.Rgba32)] + public void TiffFrameMetadata_CloneIsDeep(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + using (Image image = provider.GetImage(TiffDecoder)) + { + TiffFrameMetadata meta = image.Frames.RootFrame.Metadata.GetTiffMetadata(); + var cloneSameAsSampleMetaData = (TiffFrameMetadata)meta.DeepClone(); + VerifyExpectedFrameMetaDataIsPresent(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, TiffBitsPerPixel.Bit1)] [InlineData(GrayscaleUncompressed, TiffBitsPerPixel.Bit8)] @@ -130,53 +152,63 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff { using (Image image = provider.GetImage(TiffDecoder)) { - TiffMetadata meta = image.Metadata.GetTiffMetadata(); - - Assert.NotNull(meta); - Assert.Equal(ByteOrder.LittleEndian, meta.ByteOrder); - Assert.Equal(PixelResolutionUnit.PixelsPerInch, image.Metadata.ResolutionUnits); - Assert.Equal(10, image.Metadata.HorizontalResolution); - Assert.Equal(10, image.Metadata.VerticalResolution); - - TiffFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetTiffMetadata(); ImageFrame rootFrame = image.Frames.RootFrame; - Assert.Equal(30, frameMetadata.ExifProfile.Values.Count); - Assert.Equal(32, rootFrame.Width); Assert.Equal(32, rootFrame.Height); - Assert.Equal(TiffBitsPerSample.Bit4, frameMetadata.BitsPerSample); - Assert.Equal(TiffCompression.Lzw, frameMetadata.Compression); - Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frameMetadata.PhotometricInterpretation); - Assert.Equal("This is Название", frameMetadata.ExifProfile.GetValue(ExifTag.ImageDescription).Value); - Assert.Equal("This is Изготовитель камеры", frameMetadata.ExifProfile.GetValue(ExifTag.Make).Value); - Assert.Equal("This is Модель камеры", frameMetadata.ExifProfile.GetValue(ExifTag.Model).Value); - Assert.Equal(new Number[] { 8u }, frameMetadata.StripOffsets, new NumberComparer()); - Assert.Equal(1, frameMetadata.SamplesPerPixel); - 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("IrfanView", frameMetadata.ExifProfile.GetValue(ExifTag.Software).Value); - Assert.Null(frameMetadata.ExifProfile.GetValue(ExifTag.DateTime)?.Value); - Assert.Equal("This is author1;Author2", frameMetadata.ExifProfile.GetValue(ExifTag.Artist).Value); - Assert.Null(frameMetadata.ExifProfile.GetValue(ExifTag.HostComputer)?.Value); - 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); - Assert.Equal("This is Авторские права", frameMetadata.ExifProfile.GetValue(ExifTag.Copyright).Value); - Assert.Equal(4, frameMetadata.ExifProfile.GetValue(ExifTag.Rating).Value); - Assert.Equal(75, frameMetadata.ExifProfile.GetValue(ExifTag.RatingPercent).Value); + + TiffFrameMetadata frameMetaData = rootFrame.Metadata.GetTiffMetadata(); + Assert.NotNull(frameMetaData); + + 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); + Assert.Equal(TiffBitsPerPixel.Bit4, tiffMetaData.BitsPerPixel); + + VerifyExpectedFrameMetaDataIsPresent(frameMetaData); } } + private static void VerifyExpectedFrameMetaDataIsPresent(TiffFrameMetadata frameMetaData) + { + Assert.Equal(30, frameMetaData.ExifProfile.Values.Count); + Assert.Equal(TiffBitsPerSample.Bit4, frameMetaData.BitsPerSample); + Assert.Equal(TiffCompression.Lzw, frameMetaData.Compression); + Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frameMetaData.PhotometricInterpretation); + Assert.Equal("This is Название", frameMetaData.ExifProfile.GetValue(ExifTag.ImageDescription).Value); + Assert.Equal("This is Изготовитель камеры", frameMetaData.ExifProfile.GetValue(ExifTag.Make).Value); + Assert.Equal("This is Модель камеры", frameMetaData.ExifProfile.GetValue(ExifTag.Model).Value); + 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("IrfanView", frameMetaData.ExifProfile.GetValue(ExifTag.Software).Value); + Assert.Null(frameMetaData.ExifProfile.GetValue(ExifTag.DateTime)?.Value); + Assert.Equal("This is author1;Author2", frameMetaData.ExifProfile.GetValue(ExifTag.Artist).Value); + Assert.Null(frameMetaData.ExifProfile.GetValue(ExifTag.HostComputer)?.Value); + 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); + Assert.Equal("This is Авторские права", frameMetaData.ExifProfile.GetValue(ExifTag.Copyright).Value); + Assert.Equal(4, frameMetaData.ExifProfile.GetValue(ExifTag.Rating).Value); + Assert.Equal(75, frameMetaData.ExifProfile.GetValue(ExifTag.RatingPercent).Value); + } + [Theory] [WithFile(MultiframeDeflateWithPreview, PixelTypes.Rgba32)] public void SubfileType(TestImageProvider provider) @@ -204,9 +236,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff } [Theory] - [WithFile(SampleMetadata, PixelTypes.Rgba32, true)] - [WithFile(SampleMetadata, PixelTypes.Rgba32, false)] - public void PreserveMetadata(TestImageProvider provider, bool preserveMetadata) + [WithFile(SampleMetadata, PixelTypes.Rgba32)] + public void Encode_PreservesMetadata(TestImageProvider provider) where TPixel : unmanaged, IPixel { // Load Tiff image @@ -222,11 +253,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff // Save to Tiff var tiffEncoder = new TiffEncoder() { Mode = TiffEncodingMode.Rgb }; - if (!preserveMetadata) - { - ClearMeta(image); - } - using var ms = new MemoryStream(); image.Save(ms, tiffEncoder); @@ -252,142 +278,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff Assert.Equal(frameMetaInput.HorizontalResolution, tiffMetaDataEncodedRootFrame.HorizontalResolution); Assert.Equal(frameMetaInput.VerticalResolution, tiffMetaDataEncodedRootFrame.VerticalResolution); - if (preserveMetadata) - { - Assert.Equal(tiffMetaInput.XmpProfile, tiffMetaDataEncodedImage.XmpProfile); - - Assert.Equal("IrfanView", frameMetaInput.ExifProfile.GetValue(ExifTag.Software).Value); - Assert.Equal("This is Название", frameMetaInput.ExifProfile.GetValue(ExifTag.ImageDescription).Value); - Assert.Equal("This is Изготовитель камеры", frameMetaInput.ExifProfile.GetValue(ExifTag.Make).Value); - Assert.Equal("This is Авторские права", frameMetaInput.ExifProfile.GetValue(ExifTag.Copyright).Value); - - Assert.Equal(frameMetaInput.ExifProfile.GetValue(ExifTag.ImageDescription).Value, tiffMetaDataEncodedRootFrame.ExifProfile.GetValue(ExifTag.ImageDescription).Value); - Assert.Equal(frameMetaInput.ExifProfile.GetValue(ExifTag.Make).Value, tiffMetaDataEncodedRootFrame.ExifProfile.GetValue(ExifTag.Make).Value); - Assert.Equal(frameMetaInput.ExifProfile.GetValue(ExifTag.Copyright).Value, tiffMetaDataEncodedRootFrame.ExifProfile.GetValue(ExifTag.Copyright).Value); - } - else - { - Assert.Null(tiffMetaDataEncodedImage.XmpProfile); - - Assert.Equal("ImageSharp", tiffMetaDataEncodedRootFrame.ExifProfile.GetValue(ExifTag.Software).Value); - Assert.Null(frameMetaInput.ExifProfile.GetValue(ExifTag.Software)?.Value); - Assert.Null(frameMetaInput.ExifProfile.GetValue(ExifTag.ImageDescription)?.Value); - Assert.Null(frameMetaInput.ExifProfile.GetValue(ExifTag.Make)?.Value); - Assert.Null(frameMetaInput.ExifProfile.GetValue(ExifTag.Copyright)?.Value); - - Assert.Null(tiffMetaDataEncodedRootFrame.ExifProfile.GetValue(ExifTag.ImageDescription)?.Value); - Assert.Null(tiffMetaDataEncodedRootFrame.ExifProfile.GetValue(ExifTag.Make)?.Value); - Assert.Null(tiffMetaDataEncodedRootFrame.ExifProfile.GetValue(ExifTag.Copyright)?.Value); - } - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void CreateMetadata(bool preserveMetadata) - { - // Create image - int w = 10; - int h = 20; - using Image image = new Image(w, h); - - // set metadata - ImageMetadata coreMeta = image.Metadata; - TiffMetadata tiffMeta = image.Metadata.GetTiffMetadata(); - TiffFrameMetadata frameMeta = image.Frames.RootFrame.Metadata.GetTiffMetadata(); - - tiffMeta.XmpProfile = new byte[] { 1, 2, 3, 4, 5 }; - - coreMeta.IptcProfile = new IptcProfile(); - coreMeta.IptcProfile.SetValue(IptcTag.Caption, "iptc caption"); - - coreMeta.IccProfile = new IccProfile(new IccProfileHeader() { CreationDate = DateTime.Now }, new IccTagDataEntry[] { new IccTextTagDataEntry("test string"), new IccDataTagDataEntry(new byte[] { 11, 22, 33, 44 }) }); - - coreMeta.ResolutionUnits = PixelResolutionUnit.PixelsPerMeter; - coreMeta.HorizontalResolution = 4500; - coreMeta.VerticalResolution = 5400; - - var datetime = DateTime.Now.ToString(CultureInfo.InvariantCulture); - frameMeta.ExifProfile.SetValue(ExifTag.ImageDescription, "test ImageDescription"); - frameMeta.ExifProfile.SetValue(ExifTag.DateTime, datetime); - - // Save to Tiff - var tiffEncoder = new TiffEncoder { Mode = TiffEncodingMode.Default, Compression = TiffCompression.Deflate }; - if (!preserveMetadata) - { - ClearMeta(image); - } - - using var ms = new MemoryStream(); - image.Save(ms, tiffEncoder); - - // Assert - ms.Position = 0; - using var output = Image.Load(this.configuration, ms); - ImageFrame rootFrameOut = output.Frames.RootFrame; - - ImageMetadata coreMetaOut = output.Metadata; - TiffMetadata tiffMetaOut = output.Metadata.GetTiffMetadata(); - TiffFrameMetadata frameMetaOut = output.Frames.RootFrame.Metadata.GetTiffMetadata(); - - Assert.Equal(PixelResolutionUnit.PixelsPerCentimeter, coreMetaOut.ResolutionUnits); - Assert.Equal(45, coreMetaOut.HorizontalResolution); - Assert.Equal(54, coreMetaOut.VerticalResolution, 8); - - //// Assert.Equal(tiffEncoder.Compression, tiffMetaOut.Compression); - Assert.Equal(TiffBitsPerPixel.Bit24, tiffMetaOut.BitsPerPixel); - - Assert.Equal(w, rootFrameOut.Width); - Assert.Equal(h, rootFrameOut.Height); - Assert.Equal(frameMeta.ResolutionUnit, frameMetaOut.ResolutionUnit); - Assert.Equal(frameMeta.HorizontalResolution, frameMetaOut.HorizontalResolution); - Assert.Equal(frameMeta.VerticalResolution, frameMetaOut.VerticalResolution); - - Assert.Equal("ImageSharp", frameMetaOut.ExifProfile.GetValue(ExifTag.Software)?.Value); - - if (preserveMetadata) - { - Assert.NotNull(tiffMeta.XmpProfile); - Assert.NotNull(coreMeta.IptcProfile); - Assert.NotNull(coreMeta.IccProfile); - - Assert.Equal(tiffMeta.XmpProfile, tiffMetaOut.XmpProfile); - Assert.Equal(coreMeta.IptcProfile.Data, coreMetaOut.IptcProfile.Data); - Assert.Equal(coreMeta.IccProfile.ToByteArray(), coreMetaOut.IccProfile.ToByteArray()); - - Assert.Equal("test ImageDescription", frameMeta.ExifProfile.GetValue(ExifTag.ImageDescription).Value); - Assert.Equal(datetime, frameMeta.ExifProfile.GetValue(ExifTag.DateTime)?.Value); - - Assert.Equal(frameMeta.ExifProfile.GetValue(ExifTag.ImageDescription).Value, frameMetaOut.ExifProfile.GetValue(ExifTag.ImageDescription).Value); - Assert.Equal(frameMeta.ExifProfile.GetValue(ExifTag.DateTime).Value, frameMetaOut.ExifProfile.GetValue(ExifTag.DateTime).Value); - } - else - { - Assert.Null(tiffMetaOut.XmpProfile); - Assert.Null(coreMetaOut.IptcProfile); - Assert.Null(coreMetaOut.IccProfile); - - Assert.Null(frameMeta.ExifProfile.GetValue(ExifTag.ImageDescription)?.Value); - Assert.Null(frameMeta.ExifProfile.GetValue(ExifTag.DateTime)?.Value); - - Assert.Null(frameMetaOut.ExifProfile.GetValue(ExifTag.ImageDescription)?.Value); - Assert.Null(frameMetaOut.ExifProfile.GetValue(ExifTag.DateTime)?.Value); - } - } - - private static void ClearMeta(Image image) - { - ImageMetadata coreMeta = image.Metadata; - TiffMetadata tiffMeta = image.Metadata.GetTiffMetadata(); - TiffFrameMetadata frameMeta = image.Frames.RootFrame.Metadata.GetTiffMetadata(); - - coreMeta.ExifProfile = null; - coreMeta.IccProfile = null; - coreMeta.IptcProfile = null; + Assert.Equal(tiffMetaInput.XmpProfile, tiffMetaDataEncodedImage.XmpProfile); - tiffMeta.XmpProfile = null; + Assert.Equal("IrfanView", frameMetaInput.ExifProfile.GetValue(ExifTag.Software).Value); + Assert.Equal("This is Название", frameMetaInput.ExifProfile.GetValue(ExifTag.ImageDescription).Value); + Assert.Equal("This is Изготовитель камеры", frameMetaInput.ExifProfile.GetValue(ExifTag.Make).Value); + Assert.Equal("This is Авторские права", frameMetaInput.ExifProfile.GetValue(ExifTag.Copyright).Value); - frameMeta.ExifProfile = null; + Assert.Equal(frameMetaInput.ExifProfile.GetValue(ExifTag.ImageDescription).Value, tiffMetaDataEncodedRootFrame.ExifProfile.GetValue(ExifTag.ImageDescription).Value); + Assert.Equal(frameMetaInput.ExifProfile.GetValue(ExifTag.Make).Value, tiffMetaDataEncodedRootFrame.ExifProfile.GetValue(ExifTag.Make).Value); + Assert.Equal(frameMetaInput.ExifProfile.GetValue(ExifTag.Copyright).Value, tiffMetaDataEncodedRootFrame.ExifProfile.GetValue(ExifTag.Copyright).Value); } } } diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs index 208222a857..fef890a65e 100644 --- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs +++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs @@ -478,7 +478,7 @@ namespace SixLabors.ImageSharp.Tests case TestImageWriteFormat.Png: return WriteAndReadPng(image); default: - throw new ArgumentException("Unexpected test image format, only Jpeg, Png and Tiff are allowed"); + throw new ArgumentException("Unexpected test image format, only Jpeg and Png are allowed"); } } @@ -506,18 +506,6 @@ namespace SixLabors.ImageSharp.Tests } } - private static Image WriteAndReadTiff(Image image) - { - using (var memStream = new MemoryStream()) - { - image.SaveAsTiff(memStream, new TiffEncoder()); - image.Dispose(); - - memStream.Position = 0; - return Image.Load(memStream, new TiffDecoder()); - } - } - private static void TestProfile(ExifProfile profile) { Assert.NotNull(profile);