Browse Source

Improvements of tiff metadata - API and saving

pull/1570/head
Ildar Khayrutdinov 6 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> /// <summary>
/// Methods for undoing the horizontal prediction used in combination with deflate and LZW compressed TIFF images. /// Methods for undoing the horizontal prediction used in combination with deflate and LZW compressed TIFF images.
/// </summary> /// </summary>
public static class HorizontalPredictor internal static class HorizontalPredictor
{ {
/// <summary> /// <summary>
/// Inverts the horizontal prediction. /// Inverts the horizontal prediction.

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

@ -5,7 +5,7 @@ using System;
using System.Buffers; using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
@ -673,27 +673,27 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
{ {
case 4: case 4:
{ {
return WhiteLen4TermCodes.Keys.Contains(this.value); return WhiteLen4TermCodes.ContainsKey(this.value);
} }
case 5: case 5:
{ {
return WhiteLen5TermCodes.Keys.Contains(this.value); return WhiteLen5TermCodes.ContainsKey(this.value);
} }
case 6: case 6:
{ {
return WhiteLen6TermCodes.Keys.Contains(this.value); return WhiteLen6TermCodes.ContainsKey(this.value);
} }
case 7: case 7:
{ {
return WhiteLen7TermCodes.Keys.Contains(this.value); return WhiteLen7TermCodes.ContainsKey(this.value);
} }
case 8: 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> /// </summary>
internal interface ITiffEncoderOptions internal interface ITiffEncoderOptions
{ {
/// <summary>
/// Gets the byte order to use.
/// </summary>
ByteOrder ByteOrder { get; }
/// <summary> /// <summary>
/// Gets the compression type to use. /// Gets the compression type to use.
/// </summary> /// </summary>
@ -36,10 +41,5 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// Gets the quantizer for creating a color palette image. /// Gets the quantizer for creating a color palette image.
/// </summary> /// </summary>
IQuantizer Quantizer { get; } 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) 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)) if (HasExtData(entry, count))
{ {
uint offset = this.stream.ReadUInt32(); uint offset = this.stream.ReadUInt32();

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

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

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

@ -1,10 +1,8 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Threading; using System.Threading;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression; using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression;
@ -38,16 +36,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary> /// </summary>
private readonly bool ignoreMetadata; private readonly bool ignoreMetadata;
/// <summary>
/// The image metadata.
/// </summary>
private ImageMetadata metadata;
/// <summary>
/// The tiff specific metadata.
/// </summary>
private TiffMetadata tiffMetaData;
/// <summary> /// <summary>
/// The stream to decode from. /// The stream to decode from.
/// </summary> /// </summary>
@ -72,6 +60,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary> /// </summary>
public ushort[] BitsPerSample { get; set; } public ushort[] BitsPerSample { get; set; }
public int ChunkyBitsPerPixel { get; set; }
/// <summary> /// <summary>
/// Gets or sets the lookup table for RGB palette colored images. /// Gets or sets the lookup table for RGB palette colored images.
/// </summary> /// </summary>
@ -97,6 +87,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary> /// </summary>
public TiffPhotometricInterpretation PhotometricInterpretation { get; set; } public TiffPhotometricInterpretation PhotometricInterpretation { get; set; }
/// <summary>
/// Gets or sets the predictor.
/// </summary>
public TiffPredictor Predictor { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public Configuration Configuration => this.configuration; public Configuration Configuration => this.configuration;
@ -122,12 +117,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
framesMetadata.Add(frameMetadata); framesMetadata.Add(frameMetadata);
} }
this.metadata = framesMetadata.CreateMetadata(this.ignoreMetadata, tiffStream.ByteOrder); ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.ignoreMetadata, tiffStream.ByteOrder);
this.SetTiffFormatMetaData(framesMetadata, tiffStream.ByteOrder);
// todo: tiff frames can have different sizes // todo: tiff frames can have different sizes
{ {
ImageFrame<TPixel> root = frames.First(); ImageFrame<TPixel> root = frames[0];
this.Dimensions = root.Size(); this.Dimensions = root.Size();
foreach (ImageFrame<TPixel> frame in frames) 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; return image;
} }
@ -160,22 +154,10 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
framesMetadata.Add(meta); framesMetadata.Add(meta);
} }
this.SetTiffFormatMetaData(framesMetadata, tiffStream.ByteOrder); ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.ignoreMetadata, 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);
}
private void SetTiffFormatMetaData(List<TiffFrameMetadata> framesMetadata, ByteOrder byteOrder) TiffFrameMetadata root = framesMetadata[0];
{ return new ImageInfo(new PixelTypeInfo(root.BitsPerPixel), (int)root.Width, (int)root.Height, metadata);
this.metadata = framesMetadata.CreateMetadata(this.ignoreMetadata, byteOrder);
this.tiffMetaData = this.metadata.GetTiffMetadata();
} }
private static TiffStream CreateStream(Stream stream) private static TiffStream CreateStream(Stream stream)
@ -224,10 +206,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
var coreMetadata = new ImageFrameMetadata(); var coreMetadata = new ImageFrameMetadata();
frameMetaData = coreMetadata.GetTiffMetadata(); frameMetaData = coreMetadata.GetTiffMetadata();
frameMetaData.FrameTags.AddRange(tags); 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 width = (int)frameMetaData.Width;
int height = (int)frameMetaData.Height; int height = (int)frameMetaData.Height;
@ -239,11 +219,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar) if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar)
{ {
this.DecodeStripsPlanar(frame, rowsPerStrip, stripOffsets, stripByteCounts, width, predictor); this.DecodeStripsPlanar(frame, rowsPerStrip, stripOffsets, stripByteCounts, width);
} }
else else
{ {
this.DecodeStripsChunky(frame, rowsPerStrip, stripOffsets, stripByteCounts, width, predictor); this.DecodeStripsChunky(frame, rowsPerStrip, stripOffsets, stripByteCounts, width);
} }
return frame; return frame;
@ -258,22 +238,19 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// <returns>The size (in bytes) of the required pixel buffer.</returns> /// <returns>The size (in bytes) of the required pixel buffer.</returns>
private int CalculateStripBufferSize(int width, int height, int plane = -1) private int CalculateStripBufferSize(int width, int height, int plane = -1)
{ {
uint bitsPerPixel = 0; int bitsPerPixel = 0;
if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky) if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky)
{ {
DebugGuard.IsTrue(plane == -1, "Excepted Chunky planar."); DebugGuard.IsTrue(plane == -1, "Excepted Chunky planar.");
for (int i = 0; i < this.BitsPerSample.Length; i++) bitsPerPixel = this.ChunkyBitsPerPixel;
{
bitsPerPixel += this.BitsPerSample[i];
}
} }
else else
{ {
bitsPerPixel = this.BitsPerSample[plane]; bitsPerPixel = this.BitsPerSample[plane];
} }
int bytesPerRow = ((width * (int)bitsPerPixel) + 7) / 8; int bytesPerRow = ((width * bitsPerPixel) + 7) / 8;
int stripBytes = bytesPerRow * height; int stripBytes = bytesPerRow * height;
return stripBytes; 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="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="stripByteCounts">An array of the size of each strip (in bytes).</param>
/// <param name="width">The image width.</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)
private void DecodeStripsPlanar<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts, int width, TiffPredictor predictor)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int stripsPerPixel = this.BitsPerSample.Length; int stripsPerPixel = this.BitsPerSample.Length;
int stripsPerPlane = stripOffsets.Length / stripsPerPixel; int stripsPerPlane = stripOffsets.Length / stripsPerPixel;
int bitsPerPixel = 0; int bitsPerPixel = this.ChunkyBitsPerPixel; // todo?
foreach (var bits in this.BitsPerSample)
{
bitsPerPixel += bits;
}
Buffer2D<TPixel> pixels = frame.PixelBuffer; Buffer2D<TPixel> pixels = frame.PixelBuffer;
@ -312,7 +284,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
stripBuffers[stripIndex] = this.memoryAllocator.AllocateManagedByteBuffer(uncompressedStripSize); 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); 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> where TPixel : unmanaged, IPixel<TPixel>
{ {
int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip); int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip);
int bitsPerPixel = 0; int bitsPerPixel = this.ChunkyBitsPerPixel;
foreach (var bits in this.BitsPerSample)
{
bitsPerPixel += bits;
}
using IManagedByteBuffer stripBuffer = this.memoryAllocator.AllocateManagedByteBuffer(uncompressedStripSize, AllocationOptions.Clean); using IManagedByteBuffer stripBuffer = this.memoryAllocator.AllocateManagedByteBuffer(uncompressedStripSize, AllocationOptions.Clean);
Buffer2D<TPixel> pixels = frame.PixelBuffer; 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); 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. // Licensed under the Apache License, Version 2.0.
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Icc;
@ -13,28 +11,21 @@ using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
{ {
/// <summary> /// <summary>
/// The decoder metadata helper methods. /// The decoder metadata creator.
/// </summary> /// </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(); if (frames.Count < 1)
TiffFrameMetadata rootFrameMetadata = frames.First();
switch (rootFrameMetadata.ResolutionUnit)
{ {
case TiffResolutionUnit.None: TiffThrowHelper.ThrowImageFormatException("Expected at least one frame.");
coreMetadata.ResolutionUnits = PixelResolutionUnit.AspectRatio;
break;
case TiffResolutionUnit.Inch:
coreMetadata.ResolutionUnits = PixelResolutionUnit.PixelsPerInch;
break;
case TiffResolutionUnit.Centimeter:
coreMetadata.ResolutionUnits = PixelResolutionUnit.PixelsPerCentimeter;
break;
} }
var coreMetadata = new ImageMetadata();
TiffFrameMetadata rootFrameMetadata = frames[0];
coreMetadata.ResolutionUnits = rootFrameMetadata.ResolutionUnit;
if (rootFrameMetadata.HorizontalResolution != null) if (rootFrameMetadata.HorizontalResolution != null)
{ {
coreMetadata.HorizontalResolution = rootFrameMetadata.HorizontalResolution.Value; 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) if (coreMetadata.IptcProfile == null)
{ {
byte[] buf = frame.GetArray<byte>(ExifTag.IPTC, true); byte[] buf = frame.GetArray<byte>(ExifTag.IPTC, true);
@ -87,28 +87,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
} }
private static TiffBitsPerPixel GetBitsPerPixel(TiffFrameMetadata firstFrameMetaData) private static TiffBitsPerPixel GetBitsPerPixel(TiffFrameMetadata firstFrameMetaData)
{ => (TiffBitsPerPixel)firstFrameMetaData.BitsPerPixel;
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;
}
} }
} }

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 namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
{ {
/// <summary> /// <summary>
/// The decoder helper methods. /// The decoder options parser.
/// </summary> /// </summary>
internal static class TiffDecoderHelpers internal static class TiffDecoderOptionsParser
{ {
/// <summary> /// <summary>
/// Determines the TIFF compression and color types, and reads any associated parameters. /// Determines the TIFF compression and color types, and reads any associated parameters.
/// </summary> /// </summary>
/// <param name="options">The options.</param> /// <param name="options">The options.</param>
/// <param name="entries">The IFD entries container to read the image format information for.</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) if (entries.ExtraSamples != null)
{ {
@ -50,15 +50,16 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
} }
} }
ParseCompression(options, entries.Compression);
options.PlanarConfiguration = entries.PlanarConfiguration; options.PlanarConfiguration = entries.PlanarConfiguration;
options.Predictor = entries.Predictor;
ParsePhotometric(options, entries); // todo: There is no default for PhotometricInterpretation, and it is required.
options.PhotometricInterpretation = entries.PhotometricInterpretation;
ParseBitsPerSample(options, entries); options.BitsPerSample = entries.BitsPerSample;
options.ChunkyBitsPerPixel = entries.BitsPerPixel;
ParseColorType(options, entries); ParseColorType(options, entries);
ParseCompression(options, entries.Compression);
} }
private static void ParseColorType(this TiffDecoderCore options, TiffFrameMetadata entries) 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) private static void ParseCompression(this TiffDecoderCore options, TiffCompression compression)
{ {
switch (compression) switch (compression)

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

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

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

@ -24,6 +24,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary> /// </summary>
internal sealed class TiffEncoderCore : IImageEncoderInternals 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> /// <summary>
/// Used for allocating memory during processing operations. /// Used for allocating memory during processing operations.
/// </summary> /// </summary>
@ -54,8 +60,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary> /// </summary>
private readonly DeflateCompressionLevel compressionLevel; private readonly DeflateCompressionLevel compressionLevel;
private readonly bool preserveMetadata;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="TiffEncoderCore"/> class. /// Initializes a new instance of the <see cref="TiffEncoderCore"/> class.
/// </summary> /// </summary>
@ -69,7 +73,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
this.quantizer = options.Quantizer ?? KnownQuantizers.Octree; this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
this.useHorizontalPredictor = options.UseHorizontalPredictor; this.useHorizontalPredictor = options.UseHorizontalPredictor;
this.compressionLevel = options.CompressionLevel; this.compressionLevel = options.CompressionLevel;
this.preserveMetadata = options.PreserveMetadata;
} }
/// <summary> /// <summary>
@ -137,12 +140,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// <returns>The marker to write the first IFD offset.</returns> /// <returns>The marker to write the first IFD offset.</returns>
public long WriteHeader(TiffWriter writer) public long WriteHeader(TiffWriter writer)
{ {
ushort byteOrderMarker = BitConverter.IsLittleEndian writer.Write(ByteOrderMarker);
? TiffConstants.ByteOrderLittleEndianShort writer.Write((ushort)TiffConstants.HeaderMagicNumber);
: TiffConstants.ByteOrderBigEndianShort;
writer.Write(byteOrderMarker);
writer.Write((ushort)42);
long firstIfdMarker = writer.PlaceMarker(); long firstIfdMarker = writer.PlaceMarker();
return firstIfdMarker; return firstIfdMarker;
@ -182,7 +181,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
this.AddStripTags(image, entriesCollector, imageDataStart, imageDataBytes); this.AddStripTags(image, entriesCollector, imageDataStart, imageDataBytes);
entriesCollector.ProcessImageFormat(this); entriesCollector.ProcessImageFormat(this);
entriesCollector.ProcessGeneral(image, this.preserveMetadata); entriesCollector.ProcessGeneral(image);
writer.WriteMarker(ifdOffset, (uint)writer.Position); writer.WriteMarker(ifdOffset, (uint)writer.Position);
long nextIfdMarker = this.WriteIfd(writer, entriesCollector.Entries); 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 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> where TPixel : unmanaged, IPixel<TPixel>
=> new GeneralProcessor(this).Process(image, preserveMetadata); => new GeneralProcessor(this).Process(image);
public void ProcessImageFormat(TiffEncoderCore encoder) public void ProcessImageFormat(TiffEncoderCore encoder)
=> new ImageFormatProcessor(this).Process(encoder); => new ImageFormatProcessor(this).Process(encoder);
@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
public GeneralProcessor(TiffEncoderEntriesCollector collector) => this.collector = collector; 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> where TPixel : unmanaged, IPixel<TPixel>
{ {
TiffFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetTiffMetadata(); TiffFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetTiffMetadata();
@ -67,15 +67,51 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
this.ProcessResolution(image.Metadata, frameMetadata); 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) private void ProcessResolution(ImageMetadata imageMetadata, TiffFrameMetadata frameMetadata)
{ {
SynchResolution(imageMetadata, frameMetadata); frameMetadata.SetResolutions(
imageMetadata.ResolutionUnits,
imageMetadata.HorizontalResolution,
imageMetadata.VerticalResolution);
var xResolution = new ExifRational(ExifTagValue.XResolution) var xResolution = new ExifRational(ExifTagValue.XResolution)
{ {
@ -107,6 +143,17 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
continue; 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)) switch (ExifTags.GetPart(entry.Tag))
{ {
case ExifParts.ExifTags: 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; if (imageMetadata.ExifProfile != null)
double yres = imageMetadata.VerticalResolution; {
// todo: implement processing exif profile
}
else
{
tiffFrameMetadata.Remove(ExifTag.SubIFDOffset);
}
switch (imageMetadata.ResolutionUnits) if (imageMetadata.IptcProfile != null)
{ {
case PixelResolutionUnit.AspectRatio: imageMetadata.IptcProfile.UpdateData();
tiffFrameMetadata.ResolutionUnit = TiffResolutionUnit.None; var iptc = new ExifByteArray(ExifTagValue.IPTC, ExifDataType.Byte)
break;
case PixelResolutionUnit.PixelsPerInch:
tiffFrameMetadata.ResolutionUnit = TiffResolutionUnit.Inch;
break;
case PixelResolutionUnit.PixelsPerCentimeter:
tiffFrameMetadata.ResolutionUnit = TiffResolutionUnit.Centimeter;
break;
case PixelResolutionUnit.PixelsPerMeter:
{ {
tiffFrameMetadata.ResolutionUnit = TiffResolutionUnit.Centimeter; Value = imageMetadata.IptcProfile.Data
xres = UnitConverter.MeterToCm(xres); };
yres = UnitConverter.MeterToCm(yres);
}
break; this.collector.AddInternal(iptc);
default: }
tiffFrameMetadata.ResolutionUnit = TiffResolutionUnit.None; else
break; {
tiffFrameMetadata.Remove(ExifTag.IPTC);
} }
tiffFrameMetadata.HorizontalResolution = xres; if (imageMetadata.IccProfile != null)
tiffFrameMetadata.VerticalResolution = yres; {
} var icc = new ExifByteArray(ExifTagValue.IccProfile, ExifDataType.Undefined)
{
Value = imageMetadata.IccProfile.ToByteArray()
};
private static bool IsMetadata(ExifTag tag) this.collector.AddInternal(icc);
{ }
switch ((ExifTagValue)(ushort)tag) else
{ {
case ExifTagValue.DocumentName: tiffFrameMetadata.Remove(ExifTag.IccProfile);
case ExifTagValue.ImageDescription: }
case ExifTagValue.Make:
case ExifTagValue.Model: TiffMetadata tiffMetadata = imageMetadata.GetTiffMetadata();
case ExifTagValue.Software: if (tiffMetadata.XmpProfile != null)
case ExifTagValue.DateTime: {
case ExifTagValue.Artist: var xmp = new ExifByteArray(ExifTagValue.XMP, ExifDataType.Byte)
case ExifTagValue.HostComputer: {
case ExifTagValue.TargetPrinter: Value = tiffMetadata.XmpProfile
case ExifTagValue.XMP: };
case ExifTagValue.Rating:
case ExifTagValue.RatingPercent: this.collector.AddInternal(xmp);
case ExifTagValue.ImageID: }
case ExifTagValue.Copyright: else
case ExifTagValue.MDLabName: {
case ExifTagValue.MDSampleInfo: tiffFrameMetadata.Remove(ExifTag.XMP);
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;
} }
} }
} }

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

@ -2,9 +2,10 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants; using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Exif;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
@ -14,7 +15,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary> /// </summary>
public class TiffFrameMetadata : IDeepCloneable public class TiffFrameMetadata : IDeepCloneable
{ {
private const TiffResolutionUnit DefaultResolutionUnit = TiffResolutionUnit.Inch; // 2 (Inch)
private const ushort DefaultResolutionUnit = 2;
private const TiffPlanarConfiguration DefaultPlanarConfiguration = TiffPlanarConfiguration.Chunky; private const TiffPlanarConfiguration DefaultPlanarConfiguration = TiffPlanarConfiguration.Chunky;
@ -28,9 +30,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
} }
/// <summary> /// <summary>
/// Gets or sets the Tiff directory tags list. /// Gets the Tiff directory tags list.
/// </summary> /// </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> /// <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> /// <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> /// <summary>
/// Gets the number of bits per component. /// Gets the number of bits per component.
/// </summary> /// </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> /// <summary>Gets the compression scheme used on the image data.</summary>
/// <value>The compression scheme used on the image data.</value> /// <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> /// <summary>Gets the resolution of the image in x- direction.</summary>
/// <value>The density of the image in x- direction.</value> /// <value>The density of the image in x- direction.</value>
public double? HorizontalResolution public double? HorizontalResolution => this.GetResolution(ExifTag.XResolution);
{
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));
}
}
}
/// <summary> /// <summary>
/// Gets the resolution of the image in y- direction. /// Gets the resolution of the image in y- direction.
/// </summary> /// </summary>
/// <value>The density of the image in y- direction.</value> /// <value>The density of the image in y- direction.</value>
public double? VerticalResolution public double? VerticalResolution => this.GetResolution(ExifTag.YResolution);
{
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));
}
}
}
/// <summary> /// <summary>
/// Gets how the components of each pixel are stored. /// Gets how the components of each pixel are stored.
@ -180,10 +168,17 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// <summary> /// <summary>
/// Gets the unit of measurement for XResolution and YResolution. /// Gets the unit of measurement for XResolution and YResolution.
/// </summary> /// </summary>
public TiffResolutionUnit ResolutionUnit public PixelResolutionUnit ResolutionUnit
{ {
get => this.GetSingleEnum<TiffResolutionUnit, ushort>(ExifTag.ResolutionUnit, DefaultResolutionUnit); get
internal set => this.SetSingleEnum<TiffResolutionUnit, ushort>(ExifTag.ResolutionUnit, value); {
if (!this.TryGetSingle(ExifTag.ResolutionUnit, out ushort res))
{
res = DefaultResolutionUnit;
}
return (PixelResolutionUnit)(res - 1);
}
} }
/// <summary> /// <summary>
@ -252,23 +247,34 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary> /// </summary>
public TiffSampleFormat[] SampleFormat => this.GetEnumArray<TiffSampleFormat, ushort>(ExifTag.SampleFormat, true); 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; switch ((ExifTagValue)(ushort)entry.Tag)
if (unit == TiffResolutionUnit.Centimeter)
{
return 2.54;
}
else if (unit == TiffResolutionUnit.Inch)
{ {
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/> /// <inheritdoc/>
@ -282,5 +288,84 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
return new TiffFrameMetadata() { FrameTags = tags }; 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. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Linq;
using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Exif;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
@ -52,8 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
{ {
if (meta.TryGetArray(tag, out TTagValue[] result)) if (meta.TryGetArray(tag, out TTagValue[] result))
{ {
// todo: improve return System.Array.ConvertAll(result, a => (TEnum)(object)a);
return result.Select(a => (TEnum)(object)a).ToArray();
} }
if (!optional) if (!optional)

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

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

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

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