Browse Source

Improvements of tiff metadata - API and saving

pull/1570/head
Ildar Khayrutdinov 5 years ago
parent
commit
9d39c3810d
  1. 2
      src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs
  2. 12
      src/ImageSharp/Formats/Tiff/Compression/T4BitReader.cs
  3. 26
      src/ImageSharp/Formats/Tiff/Constants/TiffResolutionUnit.cs
  4. 10
      src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
  5. 9
      src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs
  6. 5
      src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs
  7. 82
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  8. 60
      src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs
  9. 40
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
  10. 6
      src/ImageSharp/Formats/Tiff/TiffEncoder.cs
  11. 19
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  12. 159
      src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs
  13. 225
      src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
  14. 5
      src/ImageSharp/Formats/Tiff/TiffFrameMetadataExtensions.cs
  15. 13
      src/ImageSharp/Formats/Tiff/TiffMetadata.cs
  16. 137
      tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs

2
src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs

@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
/// <summary>
/// Methods for undoing the horizontal prediction used in combination with deflate and LZW compressed TIFF images.
/// </summary>
public static class HorizontalPredictor
internal static class HorizontalPredictor
{
/// <summary>
/// Inverts the horizontal prediction.

12
src/ImageSharp/Formats/Tiff/Compression/T4BitReader.cs

@ -5,7 +5,7 @@ using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
@ -673,27 +673,27 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
{
case 4:
{
return WhiteLen4TermCodes.Keys.Contains(this.value);
return WhiteLen4TermCodes.ContainsKey(this.value);
}
case 5:
{
return WhiteLen5TermCodes.Keys.Contains(this.value);
return WhiteLen5TermCodes.ContainsKey(this.value);
}
case 6:
{
return WhiteLen6TermCodes.Keys.Contains(this.value);
return WhiteLen6TermCodes.ContainsKey(this.value);
}
case 7:
{
return WhiteLen7TermCodes.Keys.Contains(this.value);
return WhiteLen7TermCodes.ContainsKey(this.value);
}
case 8:
{
return WhiteLen8TermCodes.Keys.Contains(this.value);
return WhiteLen8TermCodes.ContainsKey(this.value);
}
}

26
src/ImageSharp/Formats/Tiff/Constants/TiffResolutionUnit.cs

@ -1,26 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants
{
/// <summary>
/// Enumeration representing the resolution units defined by the Tiff file-format.
/// </summary>
public enum TiffResolutionUnit : ushort
{
/// <summary>
/// No absolute unit of measurement.
/// </summary>
None = 1,
/// <summary>
/// Inch.
/// </summary>
Inch = 2,
/// <summary>
/// Centimeter.
/// </summary>
Centimeter = 3
}
}

10
src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs

@ -11,6 +11,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary>
internal interface ITiffEncoderOptions
{
/// <summary>
/// Gets the byte order to use.
/// </summary>
ByteOrder ByteOrder { get; }
/// <summary>
/// Gets the compression type to use.
/// </summary>
@ -36,10 +41,5 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// Gets the quantizer for creating a color palette image.
/// </summary>
IQuantizer Quantizer { get; }
/// <summary>
/// Gets a value indicating whether preserve metadata.
/// </summary>
bool PreserveMetadata { get; }
}
}

9
src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs

@ -116,6 +116,15 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
private bool ReadValueOrOffset(ExifValue entry, ExifDataType rawDataType, uint count)
{
if (entry.Tag == ExifTag.SubIFDOffset
|| entry.Tag == ExifTag.GPSIFDOffset
/*|| entry.Tag == ExifTagValue.SubIFDs*/)
{
// todo: ignore subIfds (exif, gps)
this.stream.Skip(4);
return false;
}
if (HasExtData(entry, count))
{
uint offset = this.stream.ReadUInt32();

5
src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs

@ -13,6 +13,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary>
Pixel1 = 1,
/// <summary>
/// 4 bits per pixel, grayscale or indexed image. Each pixel consists of 4 bit.
/// </summary>
Pixel4 = 4,
/// <summary>
/// 8 bits per pixel, grayscale or indexed image. Each pixel consists of 1 byte.
/// </summary>

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

@ -1,10 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression;
@ -38,16 +36,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary>
private readonly bool ignoreMetadata;
/// <summary>
/// The image metadata.
/// </summary>
private ImageMetadata metadata;
/// <summary>
/// The tiff specific metadata.
/// </summary>
private TiffMetadata tiffMetaData;
/// <summary>
/// The stream to decode from.
/// </summary>
@ -72,6 +60,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary>
public ushort[] BitsPerSample { get; set; }
public int ChunkyBitsPerPixel { get; set; }
/// <summary>
/// Gets or sets the lookup table for RGB palette colored images.
/// </summary>
@ -97,6 +87,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary>
public TiffPhotometricInterpretation PhotometricInterpretation { get; set; }
/// <summary>
/// Gets or sets the predictor.
/// </summary>
public TiffPredictor Predictor { get; set; }
/// <inheritdoc/>
public Configuration Configuration => this.configuration;
@ -122,12 +117,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
framesMetadata.Add(frameMetadata);
}
this.metadata = framesMetadata.CreateMetadata(this.ignoreMetadata, tiffStream.ByteOrder);
this.SetTiffFormatMetaData(framesMetadata, tiffStream.ByteOrder);
ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.ignoreMetadata, tiffStream.ByteOrder);
// todo: tiff frames can have different sizes
{
ImageFrame<TPixel> root = frames.First();
ImageFrame<TPixel> root = frames[0];
this.Dimensions = root.Size();
foreach (ImageFrame<TPixel> frame in frames)
{
@ -138,7 +132,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
}
var image = new Image<TPixel>(this.configuration, this.metadata, frames);
var image = new Image<TPixel>(this.configuration, metadata, frames);
return image;
}
@ -160,22 +154,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
framesMetadata.Add(meta);
}
this.SetTiffFormatMetaData(framesMetadata, tiffStream.ByteOrder);
TiffFrameMetadata root = framesMetadata.First();
int bitsPerPixel = 0;
foreach (var bits in root.BitsPerSample)
{
bitsPerPixel += bits;
}
return new ImageInfo(new PixelTypeInfo(bitsPerPixel), (int)root.Width, (int)root.Height, this.metadata);
}
ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.ignoreMetadata, tiffStream.ByteOrder);
private void SetTiffFormatMetaData(List<TiffFrameMetadata> framesMetadata, ByteOrder byteOrder)
{
this.metadata = framesMetadata.CreateMetadata(this.ignoreMetadata, byteOrder);
this.tiffMetaData = this.metadata.GetTiffMetadata();
TiffFrameMetadata root = framesMetadata[0];
return new ImageInfo(new PixelTypeInfo(root.BitsPerPixel), (int)root.Width, (int)root.Height, metadata);
}
private static TiffStream CreateStream(Stream stream)
@ -224,10 +206,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
var coreMetadata = new ImageFrameMetadata();
frameMetaData = coreMetadata.GetTiffMetadata();
frameMetaData.FrameTags.AddRange(tags);
TiffFrameMetadata tiffFormatMetaData = coreMetadata.GetFormatMetadata(TiffFormat.Instance);
TiffPredictor predictor = tiffFormatMetaData.Predictor;
this.VerifyAndParseOptions(frameMetaData);
this.VerifyAndParse(frameMetaData);
int width = (int)frameMetaData.Width;
int height = (int)frameMetaData.Height;
@ -239,11 +219,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar)
{
this.DecodeStripsPlanar(frame, rowsPerStrip, stripOffsets, stripByteCounts, width, predictor);
this.DecodeStripsPlanar(frame, rowsPerStrip, stripOffsets, stripByteCounts, width);
}
else
{
this.DecodeStripsChunky(frame, rowsPerStrip, stripOffsets, stripByteCounts, width, predictor);
this.DecodeStripsChunky(frame, rowsPerStrip, stripOffsets, stripByteCounts, width);
}
return frame;
@ -258,22 +238,19 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// <returns>The size (in bytes) of the required pixel buffer.</returns>
private int CalculateStripBufferSize(int width, int height, int plane = -1)
{
uint bitsPerPixel = 0;
int bitsPerPixel = 0;
if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky)
{
DebugGuard.IsTrue(plane == -1, "Excepted Chunky planar.");
for (int i = 0; i < this.BitsPerSample.Length; i++)
{
bitsPerPixel += this.BitsPerSample[i];
}
bitsPerPixel = this.ChunkyBitsPerPixel;
}
else
{
bitsPerPixel = this.BitsPerSample[plane];
}
int bytesPerRow = ((width * (int)bitsPerPixel) + 7) / 8;
int bytesPerRow = ((width * bitsPerPixel) + 7) / 8;
int stripBytes = bytesPerRow * height;
return stripBytes;
@ -288,17 +265,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// <param name="stripOffsets">An array of byte offsets to each strip in the image.</param>
/// <param name="stripByteCounts">An array of the size of each strip (in bytes).</param>
/// <param name="width">The image width.</param>
/// <param name="predictor">The tiff predictor used.</param>
private void DecodeStripsPlanar<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts, int width, TiffPredictor predictor)
private void DecodeStripsPlanar<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts, int width)
where TPixel : unmanaged, IPixel<TPixel>
{
int stripsPerPixel = this.BitsPerSample.Length;
int stripsPerPlane = stripOffsets.Length / stripsPerPixel;
int bitsPerPixel = 0;
foreach (var bits in this.BitsPerSample)
{
bitsPerPixel += bits;
}
int bitsPerPixel = this.ChunkyBitsPerPixel; // todo?
Buffer2D<TPixel> pixels = frame.PixelBuffer;
@ -312,7 +284,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
stripBuffers[stripIndex] = this.memoryAllocator.AllocateManagedByteBuffer(uncompressedStripSize);
}
TiffBaseCompression decompressor = TiffCompressionFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, width, bitsPerPixel, predictor);
TiffBaseCompression decompressor = TiffCompressionFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, width, bitsPerPixel, this.Predictor);
RgbPlanarTiffColor<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap);
@ -339,21 +311,17 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
}
private void DecodeStripsChunky<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts, int width, TiffPredictor predictor)
private void DecodeStripsChunky<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts, int width)
where TPixel : unmanaged, IPixel<TPixel>
{
int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip);
int bitsPerPixel = 0;
foreach (var bits in this.BitsPerSample)
{
bitsPerPixel += bits;
}
int bitsPerPixel = this.ChunkyBitsPerPixel;
using IManagedByteBuffer stripBuffer = this.memoryAllocator.AllocateManagedByteBuffer(uncompressedStripSize, AllocationOptions.Clean);
Buffer2D<TPixel> pixels = frame.PixelBuffer;
TiffBaseCompression decompressor = TiffCompressionFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, width, bitsPerPixel, predictor);
TiffBaseCompression decompressor = TiffCompressionFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, width, bitsPerPixel, this.Predictor);
TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(this.ColorType, this.BitsPerSample, this.ColorMap);

60
src/ImageSharp/Formats/Tiff/TiffDecoderMetadataHelpers.cs → src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs

@ -2,9 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
@ -13,28 +11,21 @@ using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
{
/// <summary>
/// The decoder metadata helper methods.
/// The decoder metadata creator.
/// </summary>
internal static class TiffDecoderMetadataHelpers
internal static class TiffDecoderMetadataCreator
{
public static ImageMetadata CreateMetadata(this IList<TiffFrameMetadata> frames, bool ignoreMetadata, ByteOrder byteOrder)
public static ImageMetadata Create(List<TiffFrameMetadata> frames, bool ignoreMetadata, ByteOrder byteOrder)
{
var coreMetadata = new ImageMetadata();
TiffFrameMetadata rootFrameMetadata = frames.First();
switch (rootFrameMetadata.ResolutionUnit)
if (frames.Count < 1)
{
case TiffResolutionUnit.None:
coreMetadata.ResolutionUnits = PixelResolutionUnit.AspectRatio;
break;
case TiffResolutionUnit.Inch:
coreMetadata.ResolutionUnits = PixelResolutionUnit.PixelsPerInch;
break;
case TiffResolutionUnit.Centimeter:
coreMetadata.ResolutionUnits = PixelResolutionUnit.PixelsPerCentimeter;
break;
TiffThrowHelper.ThrowImageFormatException("Expected at least one frame.");
}
var coreMetadata = new ImageMetadata();
TiffFrameMetadata rootFrameMetadata = frames[0];
coreMetadata.ResolutionUnits = rootFrameMetadata.ResolutionUnit;
if (rootFrameMetadata.HorizontalResolution != null)
{
coreMetadata.HorizontalResolution = rootFrameMetadata.HorizontalResolution.Value;
@ -63,6 +54,15 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
}
if (coreMetadata.ExifProfile == null)
{
byte[] buf = frame.GetArray<byte>(ExifTag.SubIFDOffset, true);
if (buf != null)
{
coreMetadata.ExifProfile = new ExifProfile(buf);
}
}
if (coreMetadata.IptcProfile == null)
{
byte[] buf = frame.GetArray<byte>(ExifTag.IPTC, true);
@ -87,28 +87,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
private static TiffBitsPerPixel GetBitsPerPixel(TiffFrameMetadata firstFrameMetaData)
{
ushort[] bitsPerSample = firstFrameMetaData.BitsPerSample;
var bitsPerPixel = 0;
foreach (var bps in bitsPerSample)
{
bitsPerPixel += bps;
}
if (bitsPerPixel == 24)
{
return TiffBitsPerPixel.Pixel24;
}
else if (bitsPerPixel == 8)
{
return TiffBitsPerPixel.Pixel8;
}
else if (bitsPerPixel == 1)
{
return TiffBitsPerPixel.Pixel1;
}
return 0;
}
=> (TiffBitsPerPixel)firstFrameMetaData.BitsPerPixel;
}
}

40
src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs → src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs

@ -8,16 +8,16 @@ using SixLabors.ImageSharp.Metadata.Profiles.Exif;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
{
/// <summary>
/// The decoder helper methods.
/// The decoder options parser.
/// </summary>
internal static class TiffDecoderHelpers
internal static class TiffDecoderOptionsParser
{
/// <summary>
/// Determines the TIFF compression and color types, and reads any associated parameters.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="entries">The IFD entries container to read the image format information for.</param>
public static void VerifyAndParseOptions(this TiffDecoderCore options, TiffFrameMetadata entries)
public static void VerifyAndParse(this TiffDecoderCore options, TiffFrameMetadata entries)
{
if (entries.ExtraSamples != null)
{
@ -50,15 +50,16 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
}
ParseCompression(options, entries.Compression);
options.PlanarConfiguration = entries.PlanarConfiguration;
options.Predictor = entries.Predictor;
ParsePhotometric(options, entries);
ParseBitsPerSample(options, entries);
// todo: There is no default for PhotometricInterpretation, and it is required.
options.PhotometricInterpretation = entries.PhotometricInterpretation;
options.BitsPerSample = entries.BitsPerSample;
options.ChunkyBitsPerPixel = entries.BitsPerPixel;
ParseColorType(options, entries);
ParseCompression(options, entries.Compression);
}
private static void ParseColorType(this TiffDecoderCore options, TiffFrameMetadata entries)
@ -209,29 +210,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
}
private static void ParseBitsPerSample(this TiffDecoderCore options, TiffFrameMetadata entries)
{
options.BitsPerSample = entries.BitsPerSample;
if (options.BitsPerSample == null)
{
if (options.PhotometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero
|| options.PhotometricInterpretation == TiffPhotometricInterpretation.BlackIsZero)
{
options.BitsPerSample = new[] { (ushort)1 };
}
else
{
TiffThrowHelper.ThrowNotSupported("The TIFF BitsPerSample entry is missing.");
}
}
}
private static void ParsePhotometric(this TiffDecoderCore options, TiffFrameMetadata entries)
{
// There is no default for PhotometricInterpretation, and it is required.
options.PhotometricInterpretation = entries.PhotometricInterpretation;
}
private static void ParseCompression(this TiffDecoderCore options, TiffCompression compression)
{
switch (compression)

6
src/ImageSharp/Formats/Tiff/TiffEncoder.cs

@ -17,6 +17,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary>
public class TiffEncoder : IImageEncoder, ITiffEncoderOptions
{
/// <inheritdoc/>
public ByteOrder ByteOrder { get; } = TiffEncoderCore.ByteOrder;
/// <inheritdoc/>
public TiffEncoderCompression Compression { get; set; } = TiffEncoderCompression.None;
@ -32,9 +35,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// <inheritdoc/>
public IQuantizer Quantizer { get; set; }
/// <inheritdoc/>
public bool PreserveMetadata { get; set; }
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>

19
src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs

@ -24,6 +24,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary>
internal sealed class TiffEncoderCore : IImageEncoderInternals
{
public static readonly ByteOrder ByteOrder = BitConverter.IsLittleEndian ? ByteOrder.LittleEndian : ByteOrder.BigEndian;
private static readonly ushort ByteOrderMarker = BitConverter.IsLittleEndian
? TiffConstants.ByteOrderLittleEndianShort
: TiffConstants.ByteOrderBigEndianShort;
/// <summary>
/// Used for allocating memory during processing operations.
/// </summary>
@ -54,8 +60,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary>
private readonly DeflateCompressionLevel compressionLevel;
private readonly bool preserveMetadata;
/// <summary>
/// Initializes a new instance of the <see cref="TiffEncoderCore"/> class.
/// </summary>
@ -69,7 +73,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
this.useHorizontalPredictor = options.UseHorizontalPredictor;
this.compressionLevel = options.CompressionLevel;
this.preserveMetadata = options.PreserveMetadata;
}
/// <summary>
@ -137,12 +140,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// <returns>The marker to write the first IFD offset.</returns>
public long WriteHeader(TiffWriter writer)
{
ushort byteOrderMarker = BitConverter.IsLittleEndian
? TiffConstants.ByteOrderLittleEndianShort
: TiffConstants.ByteOrderBigEndianShort;
writer.Write(byteOrderMarker);
writer.Write((ushort)42);
writer.Write(ByteOrderMarker);
writer.Write((ushort)TiffConstants.HeaderMagicNumber);
long firstIfdMarker = writer.PlaceMarker();
return firstIfdMarker;
@ -182,7 +181,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
this.AddStripTags(image, entriesCollector, imageDataStart, imageDataBytes);
entriesCollector.ProcessImageFormat(this);
entriesCollector.ProcessGeneral(image, this.preserveMetadata);
entriesCollector.ProcessGeneral(image);
writer.WriteMarker(ifdOffset, (uint)writer.Position);
long nextIfdMarker = this.WriteIfd(writer, entriesCollector.Entries);

159
src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs

@ -15,9 +15,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
{
public List<IExifValue> Entries { get; } = new List<IExifValue>();
public void ProcessGeneral<TPixel>(Image<TPixel> image, bool preserveMetadata)
public void ProcessGeneral<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
=> new GeneralProcessor(this).Process(image, preserveMetadata);
=> new GeneralProcessor(this).Process(image);
public void ProcessImageFormat(TiffEncoderCore encoder)
=> new ImageFormatProcessor(this).Process(encoder);
@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
public GeneralProcessor(TiffEncoderEntriesCollector collector) => this.collector = collector;
public void Process<TPixel>(Image<TPixel> image, bool preserveMetadata)
public void Process<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
TiffFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetTiffMetadata();
@ -67,15 +67,51 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
this.ProcessResolution(image.Metadata, frameMetadata);
if (preserveMetadata)
this.ProcessProfiles(image.Metadata, frameMetadata);
this.ProcessMetadata(frameMetadata);
}
private static bool IsMetadata(ExifTag tag)
{
switch ((ExifTagValue)(ushort)tag)
{
this.ProcessMetadata(frameMetadata);
case ExifTagValue.DocumentName:
case ExifTagValue.ImageDescription:
case ExifTagValue.Make:
case ExifTagValue.Model:
case ExifTagValue.Software:
case ExifTagValue.DateTime:
case ExifTagValue.Artist:
case ExifTagValue.HostComputer:
case ExifTagValue.TargetPrinter:
case ExifTagValue.XMP:
case ExifTagValue.Rating:
case ExifTagValue.RatingPercent:
case ExifTagValue.ImageID:
case ExifTagValue.Copyright:
case ExifTagValue.MDLabName:
case ExifTagValue.MDSampleInfo:
case ExifTagValue.MDPrepDate:
case ExifTagValue.MDPrepTime:
case ExifTagValue.MDFileUnits:
case ExifTagValue.SEMInfo:
case ExifTagValue.XPTitle:
case ExifTagValue.XPComment:
case ExifTagValue.XPAuthor:
case ExifTagValue.XPKeywords:
case ExifTagValue.XPSubject:
return true;
default:
return false;
}
}
private void ProcessResolution(ImageMetadata imageMetadata, TiffFrameMetadata frameMetadata)
{
SynchResolution(imageMetadata, frameMetadata);
frameMetadata.SetResolutions(
imageMetadata.ResolutionUnits,
imageMetadata.HorizontalResolution,
imageMetadata.VerticalResolution);
var xResolution = new ExifRational(ExifTagValue.XResolution)
{
@ -107,6 +143,17 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
continue;
}
switch ((ExifTagValue)(ushort)entry.Tag)
{
case ExifTagValue.SubIFDOffset:
case ExifTagValue.GPSIFDOffset:
case ExifTagValue.SubIFDs:
case ExifTagValue.XMP:
case ExifTagValue.IPTC:
case ExifTagValue.IccProfile:
continue;
}
switch (ExifTags.GetPart(entry.Tag))
{
case ExifParts.ExifTags:
@ -129,71 +176,59 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
}
private static void SynchResolution(ImageMetadata imageMetadata, TiffFrameMetadata tiffFrameMetadata)
private void ProcessProfiles(ImageMetadata imageMetadata, TiffFrameMetadata tiffFrameMetadata)
{
double xres = imageMetadata.HorizontalResolution;
double yres = imageMetadata.VerticalResolution;
if (imageMetadata.ExifProfile != null)
{
// todo: implement processing exif profile
}
else
{
tiffFrameMetadata.Remove(ExifTag.SubIFDOffset);
}
switch (imageMetadata.ResolutionUnits)
if (imageMetadata.IptcProfile != null)
{
case PixelResolutionUnit.AspectRatio:
tiffFrameMetadata.ResolutionUnit = TiffResolutionUnit.None;
break;
case PixelResolutionUnit.PixelsPerInch:
tiffFrameMetadata.ResolutionUnit = TiffResolutionUnit.Inch;
break;
case PixelResolutionUnit.PixelsPerCentimeter:
tiffFrameMetadata.ResolutionUnit = TiffResolutionUnit.Centimeter;
break;
case PixelResolutionUnit.PixelsPerMeter:
imageMetadata.IptcProfile.UpdateData();
var iptc = new ExifByteArray(ExifTagValue.IPTC, ExifDataType.Byte)
{
tiffFrameMetadata.ResolutionUnit = TiffResolutionUnit.Centimeter;
xres = UnitConverter.MeterToCm(xres);
yres = UnitConverter.MeterToCm(yres);
}
Value = imageMetadata.IptcProfile.Data
};
break;
default:
tiffFrameMetadata.ResolutionUnit = TiffResolutionUnit.None;
break;
this.collector.AddInternal(iptc);
}
else
{
tiffFrameMetadata.Remove(ExifTag.IPTC);
}
tiffFrameMetadata.HorizontalResolution = xres;
tiffFrameMetadata.VerticalResolution = yres;
}
if (imageMetadata.IccProfile != null)
{
var icc = new ExifByteArray(ExifTagValue.IccProfile, ExifDataType.Undefined)
{
Value = imageMetadata.IccProfile.ToByteArray()
};
private static bool IsMetadata(ExifTag tag)
{
switch ((ExifTagValue)(ushort)tag)
this.collector.AddInternal(icc);
}
else
{
case ExifTagValue.DocumentName:
case ExifTagValue.ImageDescription:
case ExifTagValue.Make:
case ExifTagValue.Model:
case ExifTagValue.Software:
case ExifTagValue.DateTime:
case ExifTagValue.Artist:
case ExifTagValue.HostComputer:
case ExifTagValue.TargetPrinter:
case ExifTagValue.XMP:
case ExifTagValue.Rating:
case ExifTagValue.RatingPercent:
case ExifTagValue.ImageID:
case ExifTagValue.Copyright:
case ExifTagValue.MDLabName:
case ExifTagValue.MDSampleInfo:
case ExifTagValue.MDPrepDate:
case ExifTagValue.MDPrepTime:
case ExifTagValue.MDFileUnits:
case ExifTagValue.SEMInfo:
case ExifTagValue.XPTitle:
case ExifTagValue.XPComment:
case ExifTagValue.XPAuthor:
case ExifTagValue.XPKeywords:
case ExifTagValue.XPSubject:
return true;
default:
return false;
tiffFrameMetadata.Remove(ExifTag.IccProfile);
}
TiffMetadata tiffMetadata = imageMetadata.GetTiffMetadata();
if (tiffMetadata.XmpProfile != null)
{
var xmp = new ExifByteArray(ExifTagValue.XMP, ExifDataType.Byte)
{
Value = tiffMetadata.XmpProfile
};
this.collector.AddInternal(xmp);
}
else
{
tiffFrameMetadata.Remove(ExifTag.XMP);
}
}
}

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

@ -2,9 +2,10 @@
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
@ -14,7 +15,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary>
public class TiffFrameMetadata : IDeepCloneable
{
private const TiffResolutionUnit DefaultResolutionUnit = TiffResolutionUnit.Inch;
// 2 (Inch)
private const ushort DefaultResolutionUnit = 2;
private const TiffPlanarConfiguration DefaultPlanarConfiguration = TiffPlanarConfiguration.Chunky;
@ -28,9 +30,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
/// <summary>
/// Gets or sets the Tiff directory tags list.
/// Gets the Tiff directory tags list.
/// </summary>
public List<IExifValue> FrameTags { get; set; } = new List<IExifValue>();
public List<IExifValue> FrameTags { get; internal set; } = new List<IExifValue>();
/// <summary>Gets a general indication of the kind of data contained in this subfile.</summary>
/// <value>A general indication of the kind of data contained in this subfile.</value>
@ -53,7 +55,41 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// <summary>
/// Gets the number of bits per component.
/// </summary>
public ushort[] BitsPerSample => this.GetArray<ushort>(ExifTag.BitsPerSample, true);
public ushort[] BitsPerSample
{
get
{
var bits = this.GetArray<ushort>(ExifTag.BitsPerSample, true);
if (bits == null)
{
if (this.PhotometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero
|| this.PhotometricInterpretation == TiffPhotometricInterpretation.BlackIsZero)
{
bits = new[] { (ushort)1 };
}
else
{
TiffThrowHelper.ThrowNotSupported("The TIFF BitsPerSample entry is missing.");
}
}
return bits;
}
}
internal int BitsPerPixel
{
get
{
int bitsPerPixel = 0;
foreach (var bits in this.BitsPerSample)
{
bitsPerPixel += bits;
}
return bitsPerPixel;
}
}
/// <summary>Gets the compression scheme used on the image data.</summary>
/// <value>The compression scheme used on the image data.</value>
@ -116,61 +152,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// <summary>Gets the resolution of the image in x- direction.</summary>
/// <value>The density of the image in x- direction.</value>
public double? HorizontalResolution
{
get
{
if (this.TryGetSingle(ExifTag.XResolution, out Rational xResolution))
{
return xResolution.ToDouble() * this.ResolutionFactor;
}
return null;
}
internal set
{
if (value == null)
{
this.Remove(ExifTag.XResolution);
}
else
{
double tag = value.Value / this.ResolutionFactor;
this.SetSingle(ExifTag.XResolution, new Rational(tag));
}
}
}
public double? HorizontalResolution => this.GetResolution(ExifTag.XResolution);
/// <summary>
/// Gets the resolution of the image in y- direction.
/// </summary>
/// <value>The density of the image in y- direction.</value>
public double? VerticalResolution
{
get
{
if (this.TryGetSingle(ExifTag.YResolution, out Rational yResolution))
{
return yResolution.ToDouble() * this.ResolutionFactor;
}
return null;
}
internal set
{
if (value == null)
{
this.Remove(ExifTag.YResolution);
}
else
{
double tag = value.Value / this.ResolutionFactor;
this.SetSingle(ExifTag.YResolution, new Rational(tag));
}
}
}
public double? VerticalResolution => this.GetResolution(ExifTag.YResolution);
/// <summary>
/// Gets how the components of each pixel are stored.
@ -180,10 +168,17 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// <summary>
/// Gets the unit of measurement for XResolution and YResolution.
/// </summary>
public TiffResolutionUnit ResolutionUnit
public PixelResolutionUnit ResolutionUnit
{
get => this.GetSingleEnum<TiffResolutionUnit, ushort>(ExifTag.ResolutionUnit, DefaultResolutionUnit);
internal set => this.SetSingleEnum<TiffResolutionUnit, ushort>(ExifTag.ResolutionUnit, value);
get
{
if (!this.TryGetSingle(ExifTag.ResolutionUnit, out ushort res))
{
res = DefaultResolutionUnit;
}
return (PixelResolutionUnit)(res - 1);
}
}
/// <summary>
@ -252,23 +247,34 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary>
public TiffSampleFormat[] SampleFormat => this.GetEnumArray<TiffSampleFormat, ushort>(ExifTag.SampleFormat, true);
private double ResolutionFactor
/// <summary>
/// Clears the metadata.
/// </summary>
public void ClearMetadata()
{
get
var tags = new List<IExifValue>();
foreach (IExifValue entry in this.FrameTags)
{
TiffResolutionUnit unit = this.ResolutionUnit;
if (unit == TiffResolutionUnit.Centimeter)
{
return 2.54;
}
else if (unit == TiffResolutionUnit.Inch)
switch ((ExifTagValue)(ushort)entry.Tag)
{
return 1.0;
case ExifTagValue.ImageWidth:
case ExifTagValue.ImageLength:
case ExifTagValue.ResolutionUnit:
case ExifTagValue.XResolution:
case ExifTagValue.YResolution:
//// image format tags
case ExifTagValue.Predictor:
case ExifTagValue.PlanarConfiguration:
case ExifTagValue.PhotometricInterpretation:
case ExifTagValue.BitsPerSample:
case ExifTagValue.ColorMap:
tags.Add(entry);
break;
}
// DefaultResolutionUnit is Inch
return 1.0;
}
this.FrameTags = tags;
}
/// <inheritdoc/>
@ -282,5 +288,84 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
return new TiffFrameMetadata() { FrameTags = tags };
}
internal void SetResolutions(PixelResolutionUnit unit, double horizontal, double vertical)
{
switch (unit)
{
case PixelResolutionUnit.AspectRatio:
case PixelResolutionUnit.PixelsPerInch:
case PixelResolutionUnit.PixelsPerCentimeter:
break;
case PixelResolutionUnit.PixelsPerMeter:
{
unit = PixelResolutionUnit.PixelsPerCentimeter;
horizontal = UnitConverter.MeterToCm(horizontal);
vertical = UnitConverter.MeterToCm(vertical);
}
break;
default:
unit = PixelResolutionUnit.PixelsPerInch;
break;
}
this.SetSingle(ExifTag.ResolutionUnit, (ushort)unit + 1);
this.SetResolution(ExifTag.XResolution, horizontal);
this.SetResolution(ExifTag.YResolution, vertical);
}
private double? GetResolution(ExifTag tag)
{
if (!this.TryGetSingle(tag, out Rational resolution))
{
return null;
}
double res = resolution.ToDouble();
switch (this.ResolutionUnit)
{
case PixelResolutionUnit.AspectRatio:
return 0;
case PixelResolutionUnit.PixelsPerCentimeter:
return UnitConverter.CmToInch(res);
case PixelResolutionUnit.PixelsPerMeter:
return UnitConverter.MeterToInch(res);
case PixelResolutionUnit.PixelsPerInch:
default:
// DefaultResolutionUnit is Inch
return res;
}
}
private void SetResolution(ExifTag tag, double? value)
{
if (value == null)
{
this.Remove(tag);
return;
}
else
{
double res = value.Value;
switch (this.ResolutionUnit)
{
case PixelResolutionUnit.AspectRatio:
res = 0;
break;
case PixelResolutionUnit.PixelsPerCentimeter:
res = UnitConverter.InchToCm(res);
break;
case PixelResolutionUnit.PixelsPerMeter:
res = UnitConverter.InchToMeter(res);
break;
case PixelResolutionUnit.PixelsPerInch:
default:
break;
}
this.SetSingle(tag, new Rational(res));
}
}
}
}

5
src/ImageSharp/Formats/Tiff/TiffFrameMetadataExtensions.cs

@ -1,8 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Linq;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
@ -52,8 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
{
if (meta.TryGetArray(tag, out TTagValue[] result))
{
// todo: improve
return result.Select(a => (TEnum)(object)a).ToArray();
return System.Array.ConvertAll(result, a => (TEnum)(object)a);
}
if (!optional)

13
src/ImageSharp/Formats/Tiff/TiffMetadata.cs

@ -26,22 +26,23 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
this.ByteOrder = other.ByteOrder;
this.XmpProfile = other.XmpProfile;
this.BitsPerPixel = other.BitsPerPixel;
this.Compression = other.Compression;
}
/// <summary>
/// Gets or sets the byte order.
/// Gets the byte order.
/// </summary>
public ByteOrder ByteOrder { get; set; }
public ByteOrder ByteOrder { get; internal set; }
/// <summary>
/// Gets or sets the number of bits per pixel.
/// Gets the number of bits per pixel.
/// </summary>
public TiffBitsPerPixel BitsPerPixel { get; set; } = TiffBitsPerPixel.Pixel24;
public TiffBitsPerPixel BitsPerPixel { get; internal set; } = TiffBitsPerPixel.Pixel24;
/// <summary>
/// Gets or sets the compression used to create the TIFF file.
/// Gets the compression used to create the TIFF file.
/// </summary>
public TiffCompression Compression { get; set; } = TiffCompression.None;
public TiffCompression Compression { get; internal set; } = TiffCompression.None;
/// <summary>
/// Gets or sets the XMP profile.

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

@ -1,12 +1,15 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using System.Linq;
using SixLabors.ImageSharp.Formats.Experimental.Tiff;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
@ -157,7 +160,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
Assert.Equal(10, frame.HorizontalResolution);
Assert.Equal(10, frame.VerticalResolution);
Assert.Equal(TiffPlanarConfiguration.Chunky, frame.PlanarConfiguration);
Assert.Equal(TiffResolutionUnit.Inch, frame.ResolutionUnit);
Assert.Equal(PixelResolutionUnit.PixelsPerInch, frame.ResolutionUnit);
Assert.Equal("IrfanView", frame.Software);
Assert.Null(frame.DateTime);
Assert.Equal("This is author1;Author2", frame.Artist);
@ -204,22 +207,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[Theory]
[WithFile(SampleMetadata, PixelTypes.Rgba32, true)]
[WithFile(SampleMetadata, PixelTypes.Rgba32, false)]
public void Tiff_PreserveMetadata<TPixel>(TestImageProvider<TPixel> provider, bool preserveMetadata)
public void PreserveMetadata<TPixel>(TestImageProvider<TPixel> provider, bool preserveMetadata)
where TPixel : unmanaged, IPixel<TPixel>
{
// Load Tiff image
using Image<TPixel> image = provider.GetImage(new TiffDecoder() { IgnoreMetadata = false });
ImageMetadata coreMeta = image.Metadata;
TiffMetadata tiffMeta = image.Metadata.GetTiffMetadata();
TiffFrameMetadata frameMeta = image.Frames.RootFrame.Metadata.GetTiffMetadata();
var tiffEncoder = new TiffEncoder() { PreserveMetadata = preserveMetadata };
using var ms = new MemoryStream();
// Save to Tiff
var tiffEncoder = new TiffEncoder() { Mode = TiffEncodingMode.Rgb };
if (!preserveMetadata)
{
ClearMeta(image);
}
// act
using var ms = new MemoryStream();
image.Save(ms, tiffEncoder);
// assert
// Assert
ms.Position = 0;
using var output = Image.Load<Rgba32>(ms);
@ -227,6 +235,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
TiffMetadata tiffMetaOut = output.Metadata.GetTiffMetadata();
TiffFrameMetadata frameMetaOut = output.Frames.RootFrame.Metadata.GetTiffMetadata();
Assert.Equal(TiffBitsPerPixel.Pixel4, tiffMeta.BitsPerPixel);
Assert.Equal(TiffBitsPerPixel.Pixel24, tiffMetaOut.BitsPerPixel);
Assert.Equal(TiffCompression.Lzw, tiffMeta.Compression);
Assert.Equal(TiffCompression.None, tiffMetaOut.Compression);
Assert.Equal(coreMeta.HorizontalResolution, coreMetaOut.HorizontalResolution);
Assert.Equal(coreMeta.VerticalResolution, coreMetaOut.VerticalResolution);
Assert.Equal(coreMeta.ResolutionUnits, coreMetaOut.ResolutionUnits);
@ -237,14 +250,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
Assert.Equal(frameMeta.HorizontalResolution, frameMetaOut.HorizontalResolution);
Assert.Equal(frameMeta.VerticalResolution, frameMetaOut.VerticalResolution);
Assert.Equal("IrfanView", frameMeta.Software);
Assert.Equal("This is Название", frameMeta.ImageDescription);
Assert.Equal("This is Изготовитель камеры", frameMeta.Make);
Assert.Equal("This is Авторские права", frameMeta.Copyright);
Assert.Equal("ImageSharp", frameMetaOut.Software);
if (preserveMetadata)
{
Assert.Equal("ImageSharp", frameMetaOut.Software);
Assert.Equal(tiffMeta.XmpProfile, tiffMetaOut.XmpProfile);
Assert.Equal("IrfanView", frameMeta.Software);
Assert.Equal("This is Название", frameMeta.ImageDescription);
Assert.Equal("This is Изготовитель камеры", frameMeta.Make);
Assert.Equal("This is Авторские права", frameMeta.Copyright);
Assert.Equal(frameMeta.ImageDescription, frameMetaOut.ImageDescription);
Assert.Equal(frameMeta.Make, frameMetaOut.Make);
@ -252,7 +267,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
}
else
{
Assert.Equal("ImageSharp", frameMetaOut.Software);
Assert.Null(tiffMetaOut.XmpProfile);
Assert.Null(frameMeta.Software);
Assert.Null(frameMeta.ImageDescription);
Assert.Null(frameMeta.Make);
Assert.Null(frameMeta.Copyright);
Assert.Null(frameMetaOut.ImageDescription);
Assert.Null(frameMetaOut.Make);
@ -263,28 +283,50 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[Theory]
[InlineData(true)]
[InlineData(false)]
public void Tiff_CreateMetadata(bool preserveMetadata)
public void CreateMetadata(bool preserveMetadata)
{
var tiffEncoder = new TiffEncoder { Mode = TiffEncodingMode.Default, Compression = TiffEncoderCompression.Deflate, PreserveMetadata = preserveMetadata };
// Create image
int w = 10;
int h = 20;
using Image input = new Image<Rgb24>(w, h);
ImageMetadata coreMeta = input.Metadata;
TiffMetadata tiffMeta = input.Metadata.GetTiffMetadata();
TiffFrameMetadata frameMeta = input.Frames.RootFrame.Metadata.GetTiffMetadata();
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.PixelsPerInch;
coreMeta.HorizontalResolution = 45;
coreMeta.VerticalResolution = 54;
coreMeta.ResolutionUnits = PixelResolutionUnit.PixelsPerMeter;
coreMeta.HorizontalResolution = 4500;
coreMeta.VerticalResolution = 5400;
var datetime = System.DateTime.Now.ToString();
frameMeta.ImageDescription = "test ImageDescription";
frameMeta.DateTime = System.DateTime.Now.ToString();
frameMeta.DateTime = datetime;
// Save to Tiff
var tiffEncoder = new TiffEncoder { Mode = TiffEncodingMode.Default, Compression = TiffEncoderCompression.Deflate };
if (!preserveMetadata)
{
ClearMeta(image);
}
using var ms = new MemoryStream();
input.Save(ms, tiffEncoder);
image.Save(ms, tiffEncoder);
// assert
// Assert
ms.Position = 0;
using var output = Image.Load<Rgba32>(ms);
TiffMetadata meta = output.Metadata.GetTiffMetadata();
@ -293,9 +335,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
TiffMetadata tiffMetaOut = output.Metadata.GetTiffMetadata();
TiffFrameMetadata frameMetaOut = output.Frames.RootFrame.Metadata.GetTiffMetadata();
Assert.Equal(coreMeta.HorizontalResolution, coreMetaOut.HorizontalResolution);
Assert.Equal(coreMeta.VerticalResolution, coreMetaOut.VerticalResolution);
Assert.Equal(coreMeta.ResolutionUnits, coreMetaOut.ResolutionUnits);
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.Pixel24, tiffMetaOut.BitsPerPixel);
Assert.Equal((uint)w, frameMetaOut.Width);
Assert.Equal((uint)h, frameMetaOut.Height);
@ -303,16 +348,52 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
Assert.Equal(frameMeta.HorizontalResolution, frameMetaOut.HorizontalResolution);
Assert.Equal(frameMeta.VerticalResolution, frameMetaOut.VerticalResolution);
Assert.Equal("ImageSharp", frameMetaOut.Software);
if (preserveMetadata)
{
Assert.NotNull(tiffMeta.XmpProfile);
Assert.NotNull(coreMeta.IptcProfile);
Assert.NotNull(coreMeta.IccProfile);
Assert.Equal(tiffMeta.XmpProfile, tiffMetaOut.XmpProfile);
//// todo: failure Assert.Equal(coreMeta.IptcProfile, coreMetaOut.IptcProfile);
Assert.Equal(coreMeta.IptcProfile.Data, coreMetaOut.IptcProfile.Data);
Assert.Equal(coreMeta.IccProfile.ToByteArray(), coreMetaOut.IccProfile.ToByteArray());
Assert.Equal("test ImageDescription", frameMeta.ImageDescription);
Assert.Equal(datetime, frameMeta.DateTime);
Assert.Equal(frameMeta.ImageDescription, frameMetaOut.ImageDescription);
Assert.Equal(frameMeta.DateTime, frameMetaOut.DateTime);
}
else
{
Assert.Null(tiffMetaOut.XmpProfile);
Assert.Null(coreMetaOut.IptcProfile);
Assert.Null(coreMetaOut.IccProfile);
Assert.Null(frameMeta.ImageDescription);
Assert.Null(frameMeta.DateTime);
Assert.Null(frameMetaOut.ImageDescription);
Assert.Null(frameMetaOut.DateTime);
}
}
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;
tiffMeta.XmpProfile = null;
frameMeta.ClearMetadata();
}
}
}

Loading…
Cancel
Save