Browse Source

WIP Metadata refactory - implement TiffEncoderEntriesCollector; implement setters for metadata properties and possibility of saving

pull/1570/head
Ildar Khayrutdinov 5 years ago
parent
commit
688d3dde9d
  1. 5
      src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
  2. 33
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  3. 72
      src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs
  4. 114
      src/ImageSharp/Formats/Tiff/TiffDecoderMetadataHelpers.cs
  5. 3
      src/ImageSharp/Formats/Tiff/TiffEncoder.cs
  6. 197
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  7. 328
      src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs
  8. 236
      src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
  9. 201
      src/ImageSharp/Formats/Tiff/TiffFrameMetadataExtensions.cs
  10. 3
      src/ImageSharp/Formats/Tiff/TiffMetadata.cs
  11. 8
      src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
  12. 119
      tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs

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

@ -36,5 +36,10 @@ 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; }
}
}

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

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@ -154,7 +155,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
var framesMetadata = new List<TiffFrameMetadata>();
foreach (IExifValue[] ifd in directories)
{
framesMetadata.Add(new TiffFrameMetadata() { Tags = ifd });
var meta = new TiffFrameMetadata();
meta.FrameTags.AddRange(ifd);
framesMetadata.Add(meta);
}
this.SetTiffFormatMetaData(framesMetadata, tiffStream.ByteOrder);
@ -173,32 +176,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
{
this.metadata = framesMetadata.CreateMetadata(this.ignoreMetadata, byteOrder);
this.tiffMetaData = this.metadata.GetTiffMetadata();
TiffFrameMetadata firstFrameMetaData = framesMetadata.First();
this.SetBitsPerPixel(firstFrameMetaData);
this.tiffMetaData.Compression = firstFrameMetaData.Compression;
}
private void SetBitsPerPixel(TiffFrameMetadata firstFrameMetaData)
{
ushort[] bitsPerSample = firstFrameMetaData.BitsPerSample;
var bitsPerPixel = 0;
foreach (var bps in bitsPerSample)
{
bitsPerPixel += bps;
}
if (bitsPerPixel == 24)
{
this.tiffMetaData.BitsPerPixel = TiffBitsPerPixel.Pixel24;
}
else if (bitsPerPixel == 8)
{
this.tiffMetaData.BitsPerPixel = TiffBitsPerPixel.Pixel8;
}
else if (bitsPerPixel == 1)
{
this.tiffMetaData.BitsPerPixel = TiffBitsPerPixel.Pixel1;
}
}
private static TiffStream CreateStream(Stream stream)
@ -246,7 +223,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
{
var coreMetadata = new ImageFrameMetadata();
frameMetaData = coreMetadata.GetTiffMetadata();
frameMetaData.Tags = tags;
frameMetaData.FrameTags.AddRange(tags);
TiffFrameMetadata tiffFormatMetaData = coreMetadata.GetFormatMetadata(TiffFormat.Instance);
TiffPredictor predictor = tiffFormatMetaData.Predictor;

72
src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs

@ -1,15 +1,9 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
{
@ -18,72 +12,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary>
internal static class TiffDecoderHelpers
{
public static ImageMetadata CreateMetadata(this IList<TiffFrameMetadata> frames, bool ignoreMetadata, ByteOrder byteOrder)
{
var coreMetadata = new ImageMetadata();
TiffMetadata tiffMetadata = coreMetadata.GetTiffMetadata();
tiffMetadata.ByteOrder = byteOrder;
TiffFrameMetadata rootFrameMetadata = frames.First();
switch (rootFrameMetadata.ResolutionUnit)
{
case TiffResolutionUnit.None:
coreMetadata.ResolutionUnits = PixelResolutionUnit.AspectRatio;
break;
case TiffResolutionUnit.Inch:
coreMetadata.ResolutionUnits = PixelResolutionUnit.PixelsPerInch;
break;
case TiffResolutionUnit.Centimeter:
coreMetadata.ResolutionUnits = PixelResolutionUnit.PixelsPerCentimeter;
break;
}
if (rootFrameMetadata.HorizontalResolution != null)
{
coreMetadata.HorizontalResolution = rootFrameMetadata.HorizontalResolution.Value;
}
if (rootFrameMetadata.VerticalResolution != null)
{
coreMetadata.VerticalResolution = rootFrameMetadata.VerticalResolution.Value;
}
if (!ignoreMetadata)
{
foreach (TiffFrameMetadata frame in frames)
{
if (tiffMetadata.XmpProfile == null)
{
byte[] buf = frame.GetArray<byte>(ExifTag.XMP, true);
if (buf != null)
{
tiffMetadata.XmpProfile = buf;
}
}
if (coreMetadata.IptcProfile == null)
{
byte[] buf = frame.GetArray<byte>(ExifTag.IPTC, true);
if (buf != null)
{
coreMetadata.IptcProfile = new IptcProfile(buf);
}
}
if (coreMetadata.IccProfile == null)
{
byte[] buf = frame.GetArray<byte>(ExifTag.IccProfile, true);
if (buf != null)
{
coreMetadata.IccProfile = new IccProfile(buf);
}
}
}
}
return coreMetadata;
}
/// <summary>
/// Determines the TIFF compression and color types, and reads any associated parameters.
/// </summary>

114
src/ImageSharp/Formats/Tiff/TiffDecoderMetadataHelpers.cs

@ -0,0 +1,114 @@
// Copyright (c) Six Labors.
// 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;
using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
{
/// <summary>
/// The decoder metadata helper methods.
/// </summary>
internal static class TiffDecoderMetadataHelpers
{
public static ImageMetadata CreateMetadata(this IList<TiffFrameMetadata> frames, bool ignoreMetadata, ByteOrder byteOrder)
{
var coreMetadata = new ImageMetadata();
TiffFrameMetadata rootFrameMetadata = frames.First();
switch (rootFrameMetadata.ResolutionUnit)
{
case TiffResolutionUnit.None:
coreMetadata.ResolutionUnits = PixelResolutionUnit.AspectRatio;
break;
case TiffResolutionUnit.Inch:
coreMetadata.ResolutionUnits = PixelResolutionUnit.PixelsPerInch;
break;
case TiffResolutionUnit.Centimeter:
coreMetadata.ResolutionUnits = PixelResolutionUnit.PixelsPerCentimeter;
break;
}
if (rootFrameMetadata.HorizontalResolution != null)
{
coreMetadata.HorizontalResolution = rootFrameMetadata.HorizontalResolution.Value;
}
if (rootFrameMetadata.VerticalResolution != null)
{
coreMetadata.VerticalResolution = rootFrameMetadata.VerticalResolution.Value;
}
TiffMetadata tiffMetadata = coreMetadata.GetTiffMetadata();
tiffMetadata.ByteOrder = byteOrder;
tiffMetadata.BitsPerPixel = GetBitsPerPixel(rootFrameMetadata);
tiffMetadata.Compression = rootFrameMetadata.Compression;
if (!ignoreMetadata)
{
foreach (TiffFrameMetadata frame in frames)
{
if (tiffMetadata.XmpProfile == null)
{
byte[] buf = frame.GetArray<byte>(ExifTag.XMP, true);
if (buf != null)
{
tiffMetadata.XmpProfile = buf;
}
}
if (coreMetadata.IptcProfile == null)
{
byte[] buf = frame.GetArray<byte>(ExifTag.IPTC, true);
if (buf != null)
{
coreMetadata.IptcProfile = new IptcProfile(buf);
}
}
if (coreMetadata.IccProfile == null)
{
byte[] buf = frame.GetArray<byte>(ExifTag.IccProfile, true);
if (buf != null)
{
coreMetadata.IccProfile = new IccProfile(buf);
}
}
}
}
return coreMetadata;
}
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;
}
}
}

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

@ -32,6 +32,9 @@ 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>

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

@ -54,6 +54,8 @@ 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>
@ -67,22 +69,25 @@ 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>
/// Gets or sets the photometric interpretation implementation to use when encoding the image.
/// Gets the photometric interpretation implementation to use when encoding the image.
/// </summary>
private TiffPhotometricInterpretation PhotometricInterpretation { get; set; }
internal TiffPhotometricInterpretation PhotometricInterpretation { get; private set; }
/// <summary>
/// Gets the compression implementation to use when encoding the image.
/// </summary>
private TiffEncoderCompression CompressionType { get; }
internal TiffEncoderCompression CompressionType { get; }
/// <summary>
/// Gets or sets the encoding mode to use. RGB, RGB with color palette or gray.
/// Gets the encoding mode to use. RGB, RGB with color palette or gray.
/// </summary>
private TiffEncodingMode Mode { get; set; }
internal TiffEncodingMode Mode { get; private set; }
internal bool UseHorizontalPredictor => this.useHorizontalPredictor;
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
@ -154,8 +159,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
public long WriteImage<TPixel>(TiffWriter writer, Image<TPixel> image, long ifdOffset)
where TPixel : unmanaged, IPixel<TPixel>
{
IExifValue colorMap = null;
var ifdEntries = new List<IExifValue>();
var entriesCollector = new TiffEncoderEntriesCollector();
// Write the image bytes to the steam.
var imageDataStart = (uint)writer.Position;
@ -163,7 +167,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
switch (this.Mode)
{
case TiffEncodingMode.ColorPalette:
imageDataBytes = writer.WritePalettedRgb(image, this.quantizer, this.CompressionType, this.compressionLevel, this.useHorizontalPredictor, out colorMap);
imageDataBytes = writer.WritePalettedRgb(image, this.quantizer, this.CompressionType, this.compressionLevel, this.useHorizontalPredictor, entriesCollector);
break;
case TiffEncodingMode.Gray:
imageDataBytes = writer.WriteGray(image, this.CompressionType, this.compressionLevel, this.useHorizontalPredictor);
@ -176,15 +180,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
break;
}
// Write info's about the image to the stream.
this.AddImageFormat(image, ifdEntries, imageDataStart, imageDataBytes);
if (this.PhotometricInterpretation == TiffPhotometricInterpretation.PaletteColor)
{
ifdEntries.Add(colorMap);
}
this.AddStripTags(image, entriesCollector, imageDataStart, imageDataBytes);
entriesCollector.ProcessImageFormat(this);
entriesCollector.ProcessGeneral(image, this.preserveMetadata);
writer.WriteMarker(ifdOffset, (uint)writer.Position);
long nextIfdMarker = this.WriteIfd(writer, ifdEntries);
long nextIfdMarker = this.WriteIfd(writer, entriesCollector.Entries);
return nextIfdMarker + imageDataBytes;
}
@ -250,51 +251,19 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// Adds image format information to the specified IFD.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="ifdEntries">The image format entries to add to the IFD.</param>
/// <param name="image">The <see cref="Image{TPixel}" /> to encode from.</param>
/// <param name="entriesCollector">The entries collector.</param>
/// <param name="imageDataStartOffset">The start of the image data in the stream.</param>
/// <param name="imageDataBytes">The image data in bytes to write.</param>
public void AddImageFormat<TPixel>(Image<TPixel> image, List<IExifValue> ifdEntries, uint imageDataStartOffset, int imageDataBytes)
public void AddStripTags<TPixel>(Image<TPixel> image, TiffEncoderEntriesCollector entriesCollector, uint imageDataStartOffset, int imageDataBytes)
where TPixel : unmanaged, IPixel<TPixel>
{
var width = new ExifLong(ExifTagValue.ImageWidth)
{
Value = (uint)image.Width
};
var height = new ExifLong(ExifTagValue.ImageLength)
{
Value = (uint)image.Height
};
ushort[] bitsPerSampleValue = this.GetBitsPerSampleValue();
var bitPerSample = new ExifShortArray(ExifTagValue.BitsPerSample)
{
Value = bitsPerSampleValue
};
ushort compressionType = this.GetCompressionType();
var compression = new ExifShort(ExifTagValue.Compression)
{
Value = compressionType
};
var photometricInterpretation = new ExifShort(ExifTagValue.PhotometricInterpretation)
{
Value = (ushort)this.PhotometricInterpretation
};
var stripOffsets = new ExifLongArray(ExifTagValue.StripOffsets)
{
// TODO: we only write one image strip for the start.
Value = new[] { imageDataStartOffset }
};
var samplesPerPixel = new ExifLong(ExifTagValue.SamplesPerPixel)
{
Value = this.GetSamplesPerPixel()
};
var rowsPerStrip = new ExifLong(ExifTagValue.RowsPerStrip)
{
// All rows in one strip.
@ -306,51 +275,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
Value = new[] { (uint)imageDataBytes }
};
var xResolution = new ExifRational(ExifTagValue.XResolution)
{
// TODO: This field is required according to the spec, what to use here as a default?
Value = Rational.FromDouble(1.0d)
};
var yResolution = new ExifRational(ExifTagValue.YResolution)
{
// TODO: This field is required according to the spec, what to use here as a default?
Value = Rational.FromDouble(1.0d)
};
var resolutionUnit = new ExifShort(ExifTagValue.ResolutionUnit)
{
Value = 3 // 3 is centimeter.
};
var software = new ExifString(ExifTagValue.Software)
{
Value = "ImageSharp"
};
ifdEntries.Add(width);
ifdEntries.Add(height);
ifdEntries.Add(bitPerSample);
ifdEntries.Add(compression);
ifdEntries.Add(photometricInterpretation);
ifdEntries.Add(stripOffsets);
ifdEntries.Add(samplesPerPixel);
ifdEntries.Add(rowsPerStrip);
ifdEntries.Add(stripByteCounts);
ifdEntries.Add(xResolution);
ifdEntries.Add(yResolution);
ifdEntries.Add(resolutionUnit);
ifdEntries.Add(software);
if (this.useHorizontalPredictor)
{
if (this.Mode == TiffEncodingMode.Rgb || this.Mode == TiffEncodingMode.Gray || this.Mode == TiffEncodingMode.ColorPalette)
{
var predictor = new ExifShort(ExifTagValue.Predictor) { Value = (ushort)TiffPredictor.Horizontal };
ifdEntries.Add(predictor);
}
}
entriesCollector.Add(stripOffsets);
entriesCollector.Add(rowsPerStrip);
entriesCollector.Add(stripByteCounts);
}
private void SetPhotometricInterpretation()
@ -381,85 +308,5 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
break;
}
}
private uint GetSamplesPerPixel()
{
switch (this.PhotometricInterpretation)
{
case TiffPhotometricInterpretation.Rgb:
return 3;
case TiffPhotometricInterpretation.PaletteColor:
case TiffPhotometricInterpretation.BlackIsZero:
case TiffPhotometricInterpretation.WhiteIsZero:
return 1;
default:
return 3;
}
}
private ushort[] GetBitsPerSampleValue()
{
switch (this.PhotometricInterpretation)
{
case TiffPhotometricInterpretation.PaletteColor:
return new ushort[] { 8 };
case TiffPhotometricInterpretation.Rgb:
return new ushort[] { 8, 8, 8 };
case TiffPhotometricInterpretation.WhiteIsZero:
if (this.Mode == TiffEncodingMode.BiColor)
{
return new ushort[] { 1 };
}
return new ushort[] { 8 };
case TiffPhotometricInterpretation.BlackIsZero:
if (this.Mode == TiffEncodingMode.BiColor)
{
return new ushort[] { 1 };
}
return new ushort[] { 8 };
default:
return new ushort[] { 8, 8, 8 };
}
}
private ushort GetCompressionType()
{
switch (this.CompressionType)
{
case TiffEncoderCompression.Deflate:
// Deflate is allowed for all modes.
return (ushort)TiffCompression.Deflate;
case TiffEncoderCompression.PackBits:
// PackBits is allowed for all modes.
return (ushort)TiffCompression.PackBits;
case TiffEncoderCompression.Lzw:
if (this.Mode == TiffEncodingMode.Rgb || this.Mode == TiffEncodingMode.Gray || this.Mode == TiffEncodingMode.ColorPalette)
{
return (ushort)TiffCompression.Lzw;
}
break;
case TiffEncoderCompression.CcittGroup3Fax:
if (this.Mode == TiffEncodingMode.BiColor)
{
return (ushort)TiffCompression.CcittGroup3Fax;
}
break;
case TiffEncoderCompression.ModifiedHuffman:
if (this.Mode == TiffEncodingMode.BiColor)
{
return (ushort)TiffCompression.Ccitt1D;
}
break;
}
return (ushort)TiffCompression.None;
}
}
}

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

@ -0,0 +1,328 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
{
internal class TiffEncoderEntriesCollector
{
public List<IExifValue> Entries { get; } = new List<IExifValue>();
public void ProcessGeneral<TPixel>(Image<TPixel> image, bool preserveMetadata)
where TPixel : unmanaged, IPixel<TPixel>
=> new GeneralProcessor(this).Process(image, preserveMetadata);
public void ProcessImageFormat(TiffEncoderCore encoder)
=> new ImageFormatProcessor(this).Process(encoder);
public void Add(IExifValue entry)
{
IExifValue exist = this.Entries.Find(t => t.Tag == entry.Tag);
if (exist != null)
{
this.Entries.Remove(exist);
}
this.Entries.Add(entry);
}
private void AddInternal(IExifValue entry) => this.Entries.Add(entry);
private class GeneralProcessor
{
private readonly TiffEncoderEntriesCollector collector;
public GeneralProcessor(TiffEncoderEntriesCollector collector) => this.collector = collector;
public void Process<TPixel>(Image<TPixel> image, bool preserveMetadata)
where TPixel : unmanaged, IPixel<TPixel>
{
TiffFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetTiffMetadata();
var width = new ExifLong(ExifTagValue.ImageWidth)
{
Value = (uint)image.Width
};
var height = new ExifLong(ExifTagValue.ImageLength)
{
Value = (uint)image.Height
};
var software = new ExifString(ExifTagValue.Software)
{
Value = "ImageSharp"
};
this.collector.AddInternal(width);
this.collector.AddInternal(height);
this.collector.AddInternal(software);
this.ProcessResolution(image.Metadata, frameMetadata);
if (preserveMetadata)
{
this.ProcessMetadata(frameMetadata);
}
}
private void ProcessResolution(ImageMetadata imageMetadata, TiffFrameMetadata frameMetadata)
{
SynchResolution(imageMetadata, frameMetadata);
var xResolution = new ExifRational(ExifTagValue.XResolution)
{
Value = frameMetadata.GetSingle<Rational>(ExifTag.XResolution)
};
var yResolution = new ExifRational(ExifTagValue.YResolution)
{
Value = frameMetadata.GetSingle<Rational>(ExifTag.YResolution)
};
var resolutionUnit = new ExifShort(ExifTagValue.ResolutionUnit)
{
Value = frameMetadata.GetSingle<ushort>(ExifTag.ResolutionUnit)
};
this.collector.AddInternal(xResolution);
this.collector.AddInternal(yResolution);
this.collector.AddInternal(resolutionUnit);
}
private void ProcessMetadata(TiffFrameMetadata frameMetadata)
{
foreach (IExifValue entry in frameMetadata.FrameTags)
{
// todo: skip subIfd
if (entry.DataType == ExifDataType.Ifd)
{
continue;
}
switch (ExifTags.GetPart(entry.Tag))
{
case ExifParts.ExifTags:
case ExifParts.GpsTags:
break;
case ExifParts.IfdTags:
if (!IsMetadata(entry.Tag))
{
continue;
}
break;
}
if (!this.collector.Entries.Exists(t => t.Tag == entry.Tag))
{
this.collector.AddInternal(entry.DeepClone());
}
}
}
private static void SynchResolution(ImageMetadata imageMetadata, TiffFrameMetadata tiffFrameMetadata)
{
double xres = imageMetadata.HorizontalResolution;
double yres = imageMetadata.VerticalResolution;
switch (imageMetadata.ResolutionUnits)
{
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:
{
tiffFrameMetadata.ResolutionUnit = TiffResolutionUnit.Centimeter;
xres = UnitConverter.MeterToCm(xres);
yres = UnitConverter.MeterToCm(yres);
}
break;
default:
tiffFrameMetadata.ResolutionUnit = TiffResolutionUnit.None;
break;
}
tiffFrameMetadata.HorizontalResolution = xres;
tiffFrameMetadata.VerticalResolution = yres;
}
private static bool IsMetadata(ExifTag tag)
{
switch ((ExifTagValue)(ushort)tag)
{
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 class ImageFormatProcessor
{
private readonly TiffEncoderEntriesCollector collector;
public ImageFormatProcessor(TiffEncoderEntriesCollector collector) => this.collector = collector;
public void Process(TiffEncoderCore encoder)
{
var samplesPerPixel = new ExifLong(ExifTagValue.SamplesPerPixel)
{
Value = GetSamplesPerPixel(encoder)
};
ushort[] bitsPerSampleValue = GetBitsPerSampleValue(encoder);
var bitPerSample = new ExifShortArray(ExifTagValue.BitsPerSample)
{
Value = bitsPerSampleValue
};
ushort compressionType = GetCompressionType(encoder);
var compression = new ExifShort(ExifTagValue.Compression)
{
Value = compressionType
};
var photometricInterpretation = new ExifShort(ExifTagValue.PhotometricInterpretation)
{
Value = (ushort)encoder.PhotometricInterpretation
};
this.collector.Add(samplesPerPixel);
this.collector.Add(bitPerSample);
this.collector.Add(compression);
this.collector.Add(photometricInterpretation);
if (encoder.UseHorizontalPredictor)
{
if (encoder.Mode == TiffEncodingMode.Rgb || encoder.Mode == TiffEncodingMode.Gray || encoder.Mode == TiffEncodingMode.ColorPalette)
{
var predictor = new ExifShort(ExifTagValue.Predictor) { Value = (ushort)TiffPredictor.Horizontal };
this.collector.Add(predictor);
}
}
}
private static uint GetSamplesPerPixel(TiffEncoderCore encoder)
{
switch (encoder.PhotometricInterpretation)
{
case TiffPhotometricInterpretation.Rgb:
return 3;
case TiffPhotometricInterpretation.PaletteColor:
case TiffPhotometricInterpretation.BlackIsZero:
case TiffPhotometricInterpretation.WhiteIsZero:
return 1;
default:
return 3;
}
}
private static ushort[] GetBitsPerSampleValue(TiffEncoderCore encoder)
{
switch (encoder.PhotometricInterpretation)
{
case TiffPhotometricInterpretation.PaletteColor:
return new ushort[] { 8 };
case TiffPhotometricInterpretation.Rgb:
return new ushort[] { 8, 8, 8 };
case TiffPhotometricInterpretation.WhiteIsZero:
if (encoder.Mode == TiffEncodingMode.BiColor)
{
return new ushort[] { 1 };
}
return new ushort[] { 8 };
case TiffPhotometricInterpretation.BlackIsZero:
if (encoder.Mode == TiffEncodingMode.BiColor)
{
return new ushort[] { 1 };
}
return new ushort[] { 8 };
default:
return new ushort[] { 8, 8, 8 };
}
}
private static ushort GetCompressionType(TiffEncoderCore encoder)
{
switch (encoder.CompressionType)
{
case TiffEncoderCompression.Deflate:
// Deflate is allowed for all modes.
return (ushort)TiffCompression.Deflate;
case TiffEncoderCompression.PackBits:
// PackBits is allowed for all modes.
return (ushort)TiffCompression.PackBits;
case TiffEncoderCompression.Lzw:
if (encoder.Mode == TiffEncodingMode.Rgb || encoder.Mode == TiffEncodingMode.Gray || encoder.Mode == TiffEncodingMode.ColorPalette)
{
return (ushort)TiffCompression.Lzw;
}
break;
case TiffEncoderCompression.CcittGroup3Fax:
if (encoder.Mode == TiffEncodingMode.BiColor)
{
return (ushort)TiffCompression.CcittGroup3Fax;
}
break;
case TiffEncoderCompression.ModifiedHuffman:
if (encoder.Mode == TiffEncodingMode.BiColor)
{
return (ushort)TiffCompression.Ccitt1D;
}
break;
}
return (ushort)TiffCompression.None;
}
}
}
}

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

@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// <summary>
/// Gets or sets the Tiff directory tags list.
/// </summary>
public IList<IExifValue> Tags { get; set; }
public List<IExifValue> FrameTags { get; 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>
@ -70,19 +70,31 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
internal TiffFillOrder FillOrder => this.GetSingleEnum<TiffFillOrder, ushort>(ExifTag.FillOrder, TiffFillOrder.MostSignificantBitFirst);
/// <summary>
/// Gets the a string that describes the subject of the image.
/// Gets or sets the a string that describes the subject of the image.
/// </summary>
public string ImageDescription => this.GetString(ExifTag.ImageDescription);
public string ImageDescription
{
get => this.GetString(ExifTag.ImageDescription);
set => this.SetString(ExifTag.ImageDescription, value);
}
/// <summary>
/// Gets the scanner manufacturer.
/// Gets or sets the scanner manufacturer.
/// </summary>
public string Make => this.GetString(ExifTag.Make);
public string Make
{
get => this.GetString(ExifTag.Make);
set => this.SetString(ExifTag.Make, value);
}
/// <summary>
/// Gets the scanner model name or number.
/// Gets or sets the scanner model name or number.
/// </summary>
public string Model => this.GetString(ExifTag.Model);
public string Model
{
get => this.GetString(ExifTag.Model);
set => this.SetString(ExifTag.Model, value);
}
/// <summary>Gets for each strip, the byte offset of that strip..</summary>
public uint[] StripOffsets => this.GetArray<uint>(ExifTag.StripOffsets);
@ -108,18 +120,26 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
{
get
{
if (this.ResolutionUnit != TiffResolutionUnit.None)
if (this.TryGetSingle(ExifTag.XResolution, out Rational xResolution))
{
double resolutionUnitFactor = this.ResolutionUnit == TiffResolutionUnit.Centimeter ? 2.54 : 1.0;
if (this.TryGetSingle(ExifTag.XResolution, out Rational xResolution))
{
return xResolution.ToDouble() * resolutionUnitFactor;
}
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>
@ -130,18 +150,26 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
{
get
{
if (this.ResolutionUnit != TiffResolutionUnit.None)
if (this.TryGetSingle(ExifTag.YResolution, out Rational yResolution))
{
double resolutionUnitFactor = this.ResolutionUnit == TiffResolutionUnit.Centimeter ? 2.54 : 1.0;
if (this.TryGetSingle(ExifTag.YResolution, out Rational yResolution))
{
return yResolution.ToDouble() * resolutionUnitFactor;
}
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>
@ -152,27 +180,47 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// <summary>
/// Gets the unit of measurement for XResolution and YResolution.
/// </summary>
public TiffResolutionUnit ResolutionUnit => this.GetSingleEnum<TiffResolutionUnit, ushort>(ExifTag.ResolutionUnit, DefaultResolutionUnit);
public TiffResolutionUnit ResolutionUnit
{
get => this.GetSingleEnum<TiffResolutionUnit, ushort>(ExifTag.ResolutionUnit, DefaultResolutionUnit);
internal set => this.SetSingleEnum<TiffResolutionUnit, ushort>(ExifTag.ResolutionUnit, value);
}
/// <summary>
/// Gets the name and version number of the software package(s) used to create the image.
/// Gets or sets the name and version number of the software package(s) used to create the image.
/// </summary>
public string Software => this.GetString(ExifTag.Software);
public string Software
{
get => this.GetString(ExifTag.Software);
set => this.SetString(ExifTag.Software, value);
}
/// <summary>
/// Gets the date and time of image creation.
/// Gets or sets the date and time of image creation.
/// </summary>
public string DateTime => this.GetString(ExifTag.DateTime);
public string DateTime
{
get => this.GetString(ExifTag.DateTime);
set => this.SetString(ExifTag.DateTime, value);
}
/// <summary>
/// Gets the person who created the image.
/// Gets or sets the person who created the image.
/// </summary>
public string Artist => this.GetString(ExifTag.Artist);
public string Artist
{
get => this.GetString(ExifTag.Artist);
set => this.SetString(ExifTag.Artist, value);
}
/// <summary>
/// Gets the computer and/or operating system in use at the time of image creation.
/// Gets or sets the computer and/or operating system in use at the time of image creation.
/// </summary>
public string HostComputer => this.GetString(ExifTag.HostComputer);
public string HostComputer
{
get => this.GetString(ExifTag.HostComputer);
set => this.SetString(ExifTag.HostComputer, value);
}
/// <summary>
/// Gets a color map for palette color images.
@ -185,9 +233,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
public ushort[] ExtraSamples => this.GetArray<ushort>(ExifTag.ExtraSamples, true);
/// <summary>
/// Gets the copyright notice.
/// Gets or sets the copyright notice.
/// </summary>
public string Copyright => this.GetString(ExifTag.Copyright);
public string Copyright
{
get => this.GetString(ExifTag.Copyright);
set => this.SetString(ExifTag.Copyright, value);
}
/// <summary>
/// Gets a mathematical operator that is applied to the image data before an encoding scheme is applied.
@ -200,133 +252,35 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// </summary>
public TiffSampleFormat[] SampleFormat => this.GetEnumArray<TiffSampleFormat, ushort>(ExifTag.SampleFormat, true);
internal T[] GetArray<T>(ExifTag tag, bool optional = false)
where T : struct
{
if (this.TryGetArray(tag, out T[] result))
{
return result;
}
if (!optional)
{
TiffThrowHelper.ThrowTagNotFound(nameof(tag));
}
return null;
}
private bool TryGetArray<T>(ExifTag tag, out T[] result)
where T : struct
private double ResolutionFactor
{
foreach (IExifValue entry in this.Tags)
get
{
if (entry.Tag == tag)
TiffResolutionUnit unit = this.ResolutionUnit;
if (unit == TiffResolutionUnit.Centimeter)
{
DebugGuard.IsTrue(entry.IsArray, "Expected array entry");
result = (T[])entry.GetValue();
return true;
return 2.54;
}
}
result = null;
return false;
}
private TEnum[] GetEnumArray<TEnum, TTagValue>(ExifTag tag, bool optional = false)
where TEnum : struct
where TTagValue : struct
{
if (this.TryGetArray(tag, out TTagValue[] result))
{
// todo: improve
return result.Select(a => (TEnum)(object)a).ToArray();
}
if (!optional)
{
TiffThrowHelper.ThrowTagNotFound(nameof(tag));
}
return null;
}
private string GetString(ExifTag tag)
{
foreach (IExifValue entry in this.Tags)
{
if (entry.Tag == tag)
else if (unit == TiffResolutionUnit.Inch)
{
DebugGuard.IsTrue(entry.DataType == ExifDataType.Ascii, "Expected string entry");
object value = entry.GetValue();
DebugGuard.IsTrue(value is string, "Expected string entry");
return (string)value;
return 1.0;
}
}
return null;
}
private TEnum? GetSingleEnumNullable<TEnum, TTagValue>(ExifTag tag)
where TEnum : struct
where TTagValue : struct
{
if (!this.TryGetSingle(tag, out TTagValue value))
{
return null;
// DefaultResolutionUnit is Inch
return 1.0;
}
return (TEnum)(object)value;
}
private TEnum GetSingleEnum<TEnum, TTagValue>(ExifTag tag, TEnum? defaultValue = null)
where TEnum : struct
where TTagValue : struct
=> this.GetSingleEnumNullable<TEnum, TTagValue>(tag) ?? (defaultValue != null ? defaultValue.Value : throw TiffThrowHelper.TagNotFound(nameof(tag)));
private T GetSingle<T>(ExifTag tag)
where T : struct
{
if (this.TryGetSingle(tag, out T result))
{
return result;
}
throw TiffThrowHelper.TagNotFound(nameof(tag));
}
private bool TryGetSingle<T>(ExifTag tag, out T result)
where T : struct
{
foreach (IExifValue entry in this.Tags)
{
if (entry.Tag == tag)
{
DebugGuard.IsTrue(!entry.IsArray, "Expected non array entry");
object value = entry.GetValue();
result = (T)value;
return true;
}
}
result = default;
return false;
}
/// <inheritdoc/>
public IDeepCloneable DeepClone()
{
var tags = new List<IExifValue>();
foreach (IExifValue entry in this.Tags)
foreach (IExifValue entry in this.FrameTags)
{
tags.Add(entry.DeepClone());
}
return new TiffFrameMetadata() { Tags = tags };
return new TiffFrameMetadata() { FrameTags = tags };
}
}
}

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

@ -0,0 +1,201 @@
// 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
{
/// <summary>
/// The tiff metadata extensions
/// </summary>
internal static class TiffFrameMetadataExtensions
{
public static T[] GetArray<T>(this TiffFrameMetadata meta, ExifTag tag, bool optional = false)
where T : struct
{
if (meta.TryGetArray(tag, out T[] result))
{
return result;
}
if (!optional)
{
TiffThrowHelper.ThrowTagNotFound(nameof(tag));
}
return null;
}
public static bool TryGetArray<T>(this TiffFrameMetadata meta, ExifTag tag, out T[] result)
where T : struct
{
foreach (IExifValue entry in meta.FrameTags)
{
if (entry.Tag == tag)
{
DebugGuard.IsTrue(entry.IsArray, "Expected array entry");
result = (T[])entry.GetValue();
return true;
}
}
result = null;
return false;
}
public static TEnum[] GetEnumArray<TEnum, TTagValue>(this TiffFrameMetadata meta, ExifTag tag, bool optional = false)
where TEnum : struct
where TTagValue : struct
{
if (meta.TryGetArray(tag, out TTagValue[] result))
{
// todo: improve
return result.Select(a => (TEnum)(object)a).ToArray();
}
if (!optional)
{
TiffThrowHelper.ThrowTagNotFound(nameof(tag));
}
return null;
}
public static string GetString(this TiffFrameMetadata meta, ExifTag tag)
{
foreach (IExifValue entry in meta.FrameTags)
{
if (entry.Tag == tag)
{
DebugGuard.IsTrue(entry.DataType == ExifDataType.Ascii, "Expected string entry");
object value = entry.GetValue();
DebugGuard.IsTrue(value is string, "Expected string entry");
return (string)value;
}
}
return null;
}
public static bool SetString(this TiffFrameMetadata meta, ExifTag tag, string value)
{
IExifValue obj = FindOrCreate(meta, tag);
DebugGuard.IsTrue(obj.DataType == ExifDataType.Ascii, "Expected string entry");
return obj.TrySetValue(value);
}
public static TEnum? GetSingleEnumNullable<TEnum, TTagValue>(this TiffFrameMetadata meta, ExifTag tag)
where TEnum : struct
where TTagValue : struct
{
if (!meta.TryGetSingle(tag, out TTagValue value))
{
return null;
}
return (TEnum)(object)value;
}
public static TEnum GetSingleEnum<TEnum, TTagValue>(this TiffFrameMetadata meta, ExifTag tag, TEnum? defaultValue = null)
where TEnum : struct
where TTagValue : struct
=> meta.GetSingleEnumNullable<TEnum, TTagValue>(tag) ?? (defaultValue != null ? defaultValue.Value : throw TiffThrowHelper.TagNotFound(nameof(tag)));
public static bool SetSingleEnum<TEnum, TTagValue>(this TiffFrameMetadata meta, ExifTag tag, TEnum value)
where TEnum : struct
where TTagValue : struct
{
IExifValue obj = FindOrCreate(meta, tag);
object val = (TTagValue)(object)value;
return obj.TrySetValue(val);
}
public static T GetSingle<T>(this TiffFrameMetadata meta, ExifTag tag)
where T : struct
{
if (meta.TryGetSingle(tag, out T result))
{
return result;
}
throw TiffThrowHelper.TagNotFound(nameof(tag));
}
public static bool TryGetSingle<T>(this TiffFrameMetadata meta, ExifTag tag, out T result)
where T : struct
{
foreach (IExifValue entry in meta.FrameTags)
{
if (entry.Tag == tag)
{
DebugGuard.IsTrue(!entry.IsArray, "Expected non array entry");
object value = entry.GetValue();
result = (T)value;
return true;
}
}
result = default;
return false;
}
public static bool SetSingle<T>(this TiffFrameMetadata meta, ExifTag tag, T value)
where T : struct
{
IExifValue obj = FindOrCreate(meta, tag);
DebugGuard.IsTrue(!obj.IsArray, "Expected non array entry");
object val = (T)(object)value;
return obj.TrySetValue(val);
}
public static bool Remove(this TiffFrameMetadata meta, ExifTag tag)
{
IExifValue obj = null;
foreach (IExifValue entry in meta.FrameTags)
{
if (entry.Tag == tag)
{
obj = entry;
break;
}
}
if (obj != null)
{
return meta.FrameTags.Remove(obj);
}
return false;
}
private static IExifValue FindOrCreate(TiffFrameMetadata meta, ExifTag tag)
{
IExifValue obj = null;
foreach (IExifValue entry in meta.FrameTags)
{
if (entry.Tag == tag)
{
obj = entry;
break;
}
}
if (obj == null)
{
obj = ExifValues.Create(tag);
meta.FrameTags.Add(obj);
}
return obj;
}
}
}

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

@ -45,8 +45,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// <summary>
/// Gets or sets the XMP profile.
/// For internal use only. ImageSharp not support XMP profile.
/// </summary>
public byte[] XmpProfile { get; set; }
internal byte[] XmpProfile { get; set; }
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new TiffMetadata(this);

8
src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs

@ -285,9 +285,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
/// <param name="compression">The compression to use.</param>
/// <param name="compressionLevel">The compression level for deflate compression.</param>
/// <param name="useHorizontalPredictor">Indicates if horizontal prediction should be used. Should only be used in combination with deflate or LZW compression.</param>
/// <param name="colorMap">The color map.</param>
/// <param name="entriesCollector">The entries collector.</param>
/// <returns>The number of bytes written.</returns>
public int WritePalettedRgb<TPixel>(Image<TPixel> image, IQuantizer quantizer, TiffEncoderCompression compression, DeflateCompressionLevel compressionLevel, bool useHorizontalPredictor, out IExifValue colorMap)
public int WritePalettedRgb<TPixel>(Image<TPixel> image, IQuantizer quantizer, TiffEncoderCompression compression, DeflateCompressionLevel compressionLevel, bool useHorizontalPredictor, TiffEncoderEntriesCollector entriesCollector)
where TPixel : unmanaged, IPixel<TPixel>
{
int colorsPerChannel = 256;
@ -332,11 +332,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
palette[paletteIdx++] = quantizedColorRgb48[i].B;
}
colorMap = new ExifShortArray(ExifTagValue.ColorMap)
var colorMap = new ExifShortArray(ExifTagValue.ColorMap)
{
Value = palette
};
entriesCollector.Add(colorMap);
if (compression == TiffEncoderCompression.Deflate)
{
return this.WriteDeflateCompressedPalettedRgb(image, quantized, compressionLevel, useHorizontalPredictor);

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

@ -3,6 +3,7 @@
using System.IO;
using System.Linq;
using SixLabors.ImageSharp.Formats.Experimental.Tiff;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.Metadata;
@ -126,7 +127,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
}
[Theory]
[WithFile(TestImages.Tiff.SampleMetadata, PixelTypes.Rgba32)]
[WithFile(SampleMetadata, PixelTypes.Rgba32)]
public void BaselineTags<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -175,7 +176,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
}
[Theory]
[WithFile(TestImages.Tiff.MultiframeDeflateWithPreview, PixelTypes.Rgba32)]
[WithFile(MultiframeDeflateWithPreview, PixelTypes.Rgba32)]
public void SubfileType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -199,5 +200,119 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
Assert.Equal(255u, frame1.Height);
}
}
[Theory]
[WithFile(SampleMetadata, PixelTypes.Rgba32, true)]
[WithFile(SampleMetadata, PixelTypes.Rgba32, false)]
public void Tiff_PreserveMetadata<TPixel>(TestImageProvider<TPixel> provider, bool preserveMetadata)
where TPixel : unmanaged, IPixel<TPixel>
{
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();
// act
image.Save(ms, tiffEncoder);
// assert
ms.Position = 0;
using var output = Image.Load<Rgba32>(ms);
ImageMetadata coreMetaOut = output.Metadata;
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(frameMeta.Width, frameMetaOut.Width);
Assert.Equal(frameMeta.Height, frameMetaOut.Height);
Assert.Equal(frameMeta.ResolutionUnit, frameMetaOut.ResolutionUnit);
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);
if (preserveMetadata)
{
Assert.Equal("ImageSharp", frameMetaOut.Software);
Assert.Equal(frameMeta.ImageDescription, frameMetaOut.ImageDescription);
Assert.Equal(frameMeta.Make, frameMetaOut.Make);
Assert.Equal(frameMeta.Copyright, frameMetaOut.Copyright);
}
else
{
Assert.Equal("ImageSharp", frameMetaOut.Software);
Assert.Null(frameMetaOut.ImageDescription);
Assert.Null(frameMetaOut.Make);
Assert.Null(frameMetaOut.Copyright);
}
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void Tiff_CreateMetadata(bool preserveMetadata)
{
var tiffEncoder = new TiffEncoder { Mode = TiffEncodingMode.Default, Compression = TiffEncoderCompression.Deflate, PreserveMetadata = preserveMetadata };
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();
coreMeta.ResolutionUnits = PixelResolutionUnit.PixelsPerInch;
coreMeta.HorizontalResolution = 45;
coreMeta.VerticalResolution = 54;
frameMeta.ImageDescription = "test ImageDescription";
frameMeta.DateTime = System.DateTime.Now.ToString();
using var ms = new MemoryStream();
input.Save(ms, tiffEncoder);
// assert
ms.Position = 0;
using var output = Image.Load<Rgba32>(ms);
TiffMetadata meta = output.Metadata.GetTiffMetadata();
ImageMetadata coreMetaOut = output.Metadata;
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((uint)w, frameMetaOut.Width);
Assert.Equal((uint)h, frameMetaOut.Height);
Assert.Equal(frameMeta.ResolutionUnit, frameMetaOut.ResolutionUnit);
Assert.Equal(frameMeta.HorizontalResolution, frameMetaOut.HorizontalResolution);
Assert.Equal(frameMeta.VerticalResolution, frameMetaOut.VerticalResolution);
if (preserveMetadata)
{
Assert.Equal(frameMeta.ImageDescription, frameMetaOut.ImageDescription);
Assert.Equal(frameMeta.DateTime, frameMetaOut.DateTime);
}
else
{
Assert.Null(frameMetaOut.ImageDescription);
Assert.Null(frameMetaOut.DateTime);
}
}
}
}

Loading…
Cancel
Save