Browse Source

Add setters for TiffFrameMetaData properties, initialize properties from frame ExifProfile

pull/1553/head
Brian Popow 5 years ago
parent
commit
411c7d6520
  1. 5
      src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs
  2. 2
      src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs
  3. 7
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  4. 2
      src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs
  5. 18
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
  6. 51
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  7. 242
      src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
  8. 15
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
  9. 268
      tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs
  10. 14
      tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs

5
src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs

@ -8,6 +8,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants
/// </summary>
public enum TiffCompression : ushort
{
/// <summary>
/// A invalid compression value.
/// </summary>
Invalid = 0,
/// <summary>
/// No compression.
/// </summary>

2
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<ushort>();
}
}

7
src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs

@ -148,7 +148,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
var framesMetadata = new List<TiffFrameMetadata>();
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);
}
/// <summary>
@ -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);

2
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;
}
}

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

@ -20,6 +20,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <param name="entries">The IFD entries container to read the image format information for.</param>
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);

51
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<TPixel> 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<TPixel> 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);

242
src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs

@ -18,195 +18,201 @@ namespace SixLabors.ImageSharp.Formats.Tiff
private const TiffPredictor DefaultPredictor = TiffPredictor.None;
private ExifProfile frameTags;
/// <summary>
/// Gets the Tiff directory tags.
/// Initializes a new instance of the <see cref="TiffFrameMetadata"/> class.
/// </summary>
public ExifProfile ExifProfile
{
get => this.frameTags ??= new ExifProfile();
internal set => this.frameTags = value;
}
public TiffFrameMetadata() => this.Initialize(new ExifProfile());
/// <summary>
/// Gets a general indication of the kind of data contained in this subfile.
/// Initializes a new instance of the <see cref="TiffFrameMetadata"/> class.
/// </summary>
public TiffNewSubfileType SubfileType => (TiffNewSubfileType?)this.ExifProfile.GetValue(ExifTag.SubfileType)?.Value ?? TiffNewSubfileType.FullImage;
/// <param name="frameTags">The Tiff frame directory tags.</param>
public TiffFrameMetadata(ExifProfile frameTags) => this.Initialize(frameTags);
/// <summary>
/// Gets a general indication of the kind of data contained in this subfile.
/// Initializes a new instance of the <see cref="TiffFrameMetadata"/> class with a given ExifProfile.
/// </summary>
public TiffSubfileType? OldSubfileType => (TiffSubfileType?)this.ExifProfile.GetValue(ExifTag.OldSubfileType)?.Value;
/// <param name="frameTags">The Tiff frame directory tags.</param>
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();
}
/// <summary>
/// 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.
/// </summary>
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!");
}
}
/// <summary>
/// Gets the bits per pixel.
/// Gets the Tiff directory tags.
/// </summary>
public int BitsPerPixel => this.BitsPerSample.BitsPerPixel();
public ExifProfile ExifProfile { get; internal set; }
/// <summary>
/// Gets the compression scheme used on the image data.
/// Gets or sets a general indication of the kind of data contained in this subfile.
/// </summary>
/// <value>The compression scheme used on the image data.</value>
public TiffCompression Compression
{
get
{
IExifValue<ushort> compression = this.ExifProfile.GetValue(ExifTag.Compression);
if (compression == null)
{
return TiffCompression.None;
}
public TiffNewSubfileType? SubfileType { get; set; }
return (TiffCompression)compression.Value;
}
}
/// <summary>
/// Gets or sets a general indication of the kind of data contained in this subfile.
/// </summary>
public TiffSubfileType? OldSubfileType { get; set; }
/// <summary>
/// Gets the color space of the image data.
/// Gets or sets the number of bits per component.
/// </summary>
public TiffPhotometricInterpretation PhotometricInterpretation
{
get
{
IExifValue<ushort> photometricInterpretation = this.ExifProfile.GetValue(ExifTag.PhotometricInterpretation);
if (photometricInterpretation == null)
{
return TiffPhotometricInterpretation.WhiteIsZero;
}
public TiffBitsPerSample? BitsPerSample { get; set; }
return (TiffPhotometricInterpretation)photometricInterpretation.Value;
}
}
/// <summary>
/// Gets or sets the bits per pixel.
/// </summary>
public int BitsPerPixel { get; set; }
/// <summary>
/// Gets the logical order of bits within a byte.
/// Gets or sets the compression scheme used on the image data.
/// </summary>
internal TiffFillOrder FillOrder => (TiffFillOrder?)this.ExifProfile.GetValue(ExifTag.FillOrder)?.Value ?? TiffFillOrder.MostSignificantBitFirst;
/// <value>The compression scheme used on the image data.</value>
public TiffCompression Compression { get; set; }
/// <summary>
/// Gets for each strip, the byte offset of that strip.
/// Gets or sets the color space of the image data.
/// </summary>
public Number[] StripOffsets
{
get
{
IExifValue<Number[]> stripOffsets = this.ExifProfile.GetValue(ExifTag.StripOffsets);
if (stripOffsets == null)
{
TiffThrowHelper.ThrowImageFormatException("StripOffsets are missing");
}
public TiffPhotometricInterpretation PhotometricInterpretation { get; set; }
return stripOffsets.Value;
}
}
/// <summary>
/// Gets or sets the logical order of bits within a byte.
/// </summary>
internal TiffFillOrder FillOrder { get; set; }
/// <summary>
/// Gets the number of components per pixel.
/// Gets or sets for each strip, the byte offset of that strip.
/// </summary>
public ushort SamplesPerPixel => this.ExifProfile.GetValue(ExifTag.SamplesPerPixel).Value;
public Number[] StripOffsets { get; set; }
/// <summary>
/// Gets the number of rows per strip.
/// Gets or sets the strip row counts.
/// </summary>
public Number RowsPerStrip
{
get
{
IExifValue<Number> rowsPerStrip = this.ExifProfile.GetValue(ExifTag.RowsPerStrip);
if (rowsPerStrip == null)
{
return TiffConstants.RowsPerStripInfinity;
}
public uint[] StripRowCounts { get; set; }
return rowsPerStrip.Value;
}
}
/// <summary>
/// Gets or sets the number of components per pixel.
/// </summary>
public ushort? SamplesPerPixel { get; set; }
/// <summary>
/// Gets for each strip, the number of bytes in the strip after compression.
/// Gets or sets the number of rows per strip.
/// </summary>
public Number[] StripByteCounts
{
get
{
IExifValue<Number[]> stripByteCounts = this.ExifProfile.GetValue(ExifTag.StripByteCounts);
if (stripByteCounts == null)
{
TiffThrowHelper.ThrowImageFormatException("StripByteCounts are missing");
}
public Number RowsPerStrip { get; set; }
return stripByteCounts.Value;
}
}
/// <summary>
/// Gets or sets for each strip, the number of bytes in the strip after compression.
/// </summary>
public Number[] StripByteCounts { get; set; }
/// <summary>
/// Gets or sets the resolution of the image in x-direction.
/// </summary>
public double? HorizontalResolution { get; set; }
/// <summary>
/// Gets the resolution of the image in x- direction.
/// Gets or sets the resolution of the image in y-direction.
/// </summary>
/// <value>The density of the image in x- direction.</value>
public double? HorizontalResolution => this.ExifProfile.GetValue(ExifTag.XResolution)?.Value.ToDouble();
public double? VerticalResolution { get; set; }
/// <summary>
/// Gets the resolution of the image in y- direction.
/// Gets or sets how the components of each pixel are stored.
/// </summary>
/// <value>The density of the image in y- direction.</value>
public double? VerticalResolution => this.ExifProfile.GetValue(ExifTag.YResolution)?.Value.ToDouble();
public TiffPlanarConfiguration PlanarConfiguration { get; set; }
/// <summary>
/// Gets how the components of each pixel are stored.
/// Gets or sets the unit of measurement for XResolution and YResolution.
/// </summary>
public TiffPlanarConfiguration PlanarConfiguration => (TiffPlanarConfiguration?)this.ExifProfile.GetValue(ExifTag.PlanarConfiguration)?.Value ?? DefaultPlanarConfiguration;
public PixelResolutionUnit ResolutionUnit { get; set; }
/// <summary>
/// Gets the unit of measurement for XResolution and YResolution.
/// Gets or sets a color map for palette color images.
/// </summary>
public PixelResolutionUnit ResolutionUnit => UnitConverter.ExifProfileToResolutionUnit(this.ExifProfile);
public ushort[] ColorMap { get; set; }
/// <summary>
/// Gets a color map for palette color images.
/// Gets or sets the description of extra components.
/// </summary>
public ushort[] ColorMap => this.ExifProfile.GetValue(ExifTag.ColorMap)?.Value;
public ushort[] ExtraSamples { get; set; }
/// <summary>
/// Gets the description of extra components.
/// Gets or sets the tile offsets.
/// </summary>
public ushort[] ExtraSamples => this.ExifProfile.GetValue(ExifTag.ExtraSamples)?.Value;
public uint[] TileOffsets { get; set; }
/// <summary>
/// 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.
/// </summary>
public TiffPredictor Predictor => (TiffPredictor?)this.ExifProfile.GetValue(ExifTag.Predictor)?.Value ?? DefaultPredictor;
public TiffPredictor Predictor { get; set; }
/// <summary>
/// Gets the specifies how to interpret each data sample in a pixel.
/// <see cref="SamplesPerPixel"/>
/// Gets or sets the specifies how to interpret each data sample in a pixel.
/// </summary>
public TiffSampleFormat[] SampleFormat => this.ExifProfile.GetValue(ExifTag.SampleFormat)?.Value?.Select(a => (TiffSampleFormat)a).ToArray();
public TiffSampleFormat[] SampleFormat { get; set; }
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new TiffFrameMetadata() { ExifProfile = this.ExifProfile.DeepClone() };
public IDeepCloneable DeepClone() => new TiffFrameMetadata(this.ExifProfile.DeepClone());
}
}

15
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<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> =>
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<TPixel>
{
using Image<TPixel> 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);
}
}
}

268
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<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> 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<TPixel> 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<TPixel> 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<ushort>(ExifTag.Rating).Value);
Assert.Equal(75, frameMetadata.ExifProfile.GetValue<ushort>(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<TPixel>(TestImageProvider<TPixel> 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<TPixel>(TestImageProvider<TPixel> provider, bool preserveMetadata)
[WithFile(SampleMetadata, PixelTypes.Rgba32)]
public void Encode_PreservesMetadata<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
// 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<Rgb24>(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<Rgba32>(this.configuration, ms);
ImageFrame<Rgba32> 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);
}
}
}

14
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<Rgba32> WriteAndReadTiff(Image<Rgba32> image)
{
using (var memStream = new MemoryStream())
{
image.SaveAsTiff(memStream, new TiffEncoder());
image.Dispose();
memStream.Position = 0;
return Image.Load<Rgba32>(memStream, new TiffDecoder());
}
}
private static void TestProfile(ExifProfile profile)
{
Assert.NotNull(profile);

Loading…
Cancel
Save