diff --git a/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs b/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
index 46fbf1c576..37492ea31c 100644
--- a/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
+++ b/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.
///
IQuantizer Quantizer { get; }
+
+ ///
+ /// Gets a value indicating whether preserve metadata.
+ ///
+ bool PreserveMetadata { get; }
}
}
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
index a62c5946f7..b4350c287f 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
+++ b/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();
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;
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs
index 25f43a0a82..7fd76d04d4 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs
+++ b/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
///
internal static class TiffDecoderHelpers
{
- public static ImageMetadata CreateMetadata(this IList 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(ExifTag.XMP, true);
- if (buf != null)
- {
- tiffMetadata.XmpProfile = buf;
- }
- }
-
- if (coreMetadata.IptcProfile == null)
- {
- byte[] buf = frame.GetArray(ExifTag.IPTC, true);
- if (buf != null)
- {
- coreMetadata.IptcProfile = new IptcProfile(buf);
- }
- }
-
- if (coreMetadata.IccProfile == null)
- {
- byte[] buf = frame.GetArray(ExifTag.IccProfile, true);
- if (buf != null)
- {
- coreMetadata.IccProfile = new IccProfile(buf);
- }
- }
- }
- }
-
- return coreMetadata;
- }
-
///
/// Determines the TIFF compression and color types, and reads any associated parameters.
///
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataHelpers.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataHelpers.cs
new file mode 100644
index 0000000000..0eddca4bb7
--- /dev/null
+++ b/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
+{
+ ///
+ /// The decoder metadata helper methods.
+ ///
+ internal static class TiffDecoderMetadataHelpers
+ {
+ public static ImageMetadata CreateMetadata(this IList 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(ExifTag.XMP, true);
+ if (buf != null)
+ {
+ tiffMetadata.XmpProfile = buf;
+ }
+ }
+
+ if (coreMetadata.IptcProfile == null)
+ {
+ byte[] buf = frame.GetArray(ExifTag.IPTC, true);
+ if (buf != null)
+ {
+ coreMetadata.IptcProfile = new IptcProfile(buf);
+ }
+ }
+
+ if (coreMetadata.IccProfile == null)
+ {
+ byte[] buf = frame.GetArray(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;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs
index 0f333679e9..1ed416878d 100644
--- a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs
@@ -32,6 +32,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
///
public IQuantizer Quantizer { get; set; }
+ ///
+ public bool PreserveMetadata { get; set; }
+
///
public void Encode(Image image, Stream stream)
where TPixel : unmanaged, IPixel
diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
index 1d16d51c4d..44a3140deb 100644
--- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
@@ -54,6 +54,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
///
private readonly DeflateCompressionLevel compressionLevel;
+ private readonly bool preserveMetadata;
+
///
/// Initializes a new instance of the class.
///
@@ -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;
}
///
- /// Gets or sets the photometric interpretation implementation to use when encoding the image.
+ /// Gets the photometric interpretation implementation to use when encoding the image.
///
- private TiffPhotometricInterpretation PhotometricInterpretation { get; set; }
+ internal TiffPhotometricInterpretation PhotometricInterpretation { get; private set; }
///
/// Gets the compression implementation to use when encoding the image.
///
- private TiffEncoderCompression CompressionType { get; }
+ internal TiffEncoderCompression CompressionType { get; }
///
- /// 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.
///
- private TiffEncodingMode Mode { get; set; }
+ internal TiffEncodingMode Mode { get; private set; }
+
+ internal bool UseHorizontalPredictor => this.useHorizontalPredictor;
///
/// Encodes the image to the specified stream from the .
@@ -154,8 +159,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
public long WriteImage(TiffWriter writer, Image image, long ifdOffset)
where TPixel : unmanaged, IPixel
{
- IExifValue colorMap = null;
- var ifdEntries = new List();
+ 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.
///
/// The pixel format.
- /// The to encode from.
- /// The image format entries to add to the IFD.
+ /// The to encode from.
+ /// The entries collector.
/// The start of the image data in the stream.
/// The image data in bytes to write.
- public void AddImageFormat(Image image, List ifdEntries, uint imageDataStartOffset, int imageDataBytes)
+ public void AddStripTags(Image image, TiffEncoderEntriesCollector entriesCollector, uint imageDataStartOffset, int imageDataBytes)
where TPixel : unmanaged, IPixel
{
- 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;
- }
}
}
diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs
new file mode 100644
index 0000000000..656ce858b6
--- /dev/null
+++ b/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 Entries { get; } = new List();
+
+ public void ProcessGeneral(Image image, bool preserveMetadata)
+ where TPixel : unmanaged, IPixel
+ => 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(Image image, bool preserveMetadata)
+ where TPixel : unmanaged, IPixel
+ {
+ 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(ExifTag.XResolution)
+ };
+
+ var yResolution = new ExifRational(ExifTagValue.YResolution)
+ {
+ Value = frameMetadata.GetSingle(ExifTag.YResolution)
+ };
+
+ var resolutionUnit = new ExifShort(ExifTagValue.ResolutionUnit)
+ {
+ Value = frameMetadata.GetSingle(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;
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
index d4577159f5..36fd3ad200 100644
--- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
@@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
///
/// Gets or sets the Tiff directory tags list.
///
- public IList Tags { get; set; }
+ public List FrameTags { get; set; } = new List();
/// Gets a general indication of the kind of data contained in this subfile.
/// A general indication of the kind of data contained in this subfile.
@@ -70,19 +70,31 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
internal TiffFillOrder FillOrder => this.GetSingleEnum(ExifTag.FillOrder, TiffFillOrder.MostSignificantBitFirst);
///
- /// Gets the a string that describes the subject of the image.
+ /// Gets or sets the a string that describes the subject of the image.
///
- public string ImageDescription => this.GetString(ExifTag.ImageDescription);
+ public string ImageDescription
+ {
+ get => this.GetString(ExifTag.ImageDescription);
+ set => this.SetString(ExifTag.ImageDescription, value);
+ }
///
- /// Gets the scanner manufacturer.
+ /// Gets or sets the scanner manufacturer.
///
- public string Make => this.GetString(ExifTag.Make);
+ public string Make
+ {
+ get => this.GetString(ExifTag.Make);
+ set => this.SetString(ExifTag.Make, value);
+ }
///
- /// Gets the scanner model name or number.
+ /// Gets or sets the scanner model name or number.
///
- public string Model => this.GetString(ExifTag.Model);
+ public string Model
+ {
+ get => this.GetString(ExifTag.Model);
+ set => this.SetString(ExifTag.Model, value);
+ }
/// Gets for each strip, the byte offset of that strip..
public uint[] StripOffsets => this.GetArray(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));
+ }
+ }
}
///
@@ -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));
+ }
+ }
}
///
@@ -152,27 +180,47 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
///
/// Gets the unit of measurement for XResolution and YResolution.
///
- public TiffResolutionUnit ResolutionUnit => this.GetSingleEnum(ExifTag.ResolutionUnit, DefaultResolutionUnit);
+ public TiffResolutionUnit ResolutionUnit
+ {
+ get => this.GetSingleEnum(ExifTag.ResolutionUnit, DefaultResolutionUnit);
+ internal set => this.SetSingleEnum(ExifTag.ResolutionUnit, value);
+ }
///
- /// 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.
///
- public string Software => this.GetString(ExifTag.Software);
+ public string Software
+ {
+ get => this.GetString(ExifTag.Software);
+ set => this.SetString(ExifTag.Software, value);
+ }
///
- /// Gets the date and time of image creation.
+ /// Gets or sets the date and time of image creation.
///
- public string DateTime => this.GetString(ExifTag.DateTime);
+ public string DateTime
+ {
+ get => this.GetString(ExifTag.DateTime);
+ set => this.SetString(ExifTag.DateTime, value);
+ }
///
- /// Gets the person who created the image.
+ /// Gets or sets the person who created the image.
///
- public string Artist => this.GetString(ExifTag.Artist);
+ public string Artist
+ {
+ get => this.GetString(ExifTag.Artist);
+ set => this.SetString(ExifTag.Artist, value);
+ }
///
- /// 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.
///
- public string HostComputer => this.GetString(ExifTag.HostComputer);
+ public string HostComputer
+ {
+ get => this.GetString(ExifTag.HostComputer);
+ set => this.SetString(ExifTag.HostComputer, value);
+ }
///
/// Gets a color map for palette color images.
@@ -185,9 +233,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
public ushort[] ExtraSamples => this.GetArray(ExifTag.ExtraSamples, true);
///
- /// Gets the copyright notice.
+ /// Gets or sets the copyright notice.
///
- public string Copyright => this.GetString(ExifTag.Copyright);
+ public string Copyright
+ {
+ get => this.GetString(ExifTag.Copyright);
+ set => this.SetString(ExifTag.Copyright, value);
+ }
///
/// 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
///
public TiffSampleFormat[] SampleFormat => this.GetEnumArray(ExifTag.SampleFormat, true);
- internal T[] GetArray(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(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(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(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(ExifTag tag, TEnum? defaultValue = null)
- where TEnum : struct
- where TTagValue : struct
- => this.GetSingleEnumNullable(tag) ?? (defaultValue != null ? defaultValue.Value : throw TiffThrowHelper.TagNotFound(nameof(tag)));
-
- private T GetSingle(ExifTag tag)
- where T : struct
- {
- if (this.TryGetSingle(tag, out T result))
- {
- return result;
- }
-
- throw TiffThrowHelper.TagNotFound(nameof(tag));
- }
-
- private bool TryGetSingle(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;
}
///
public IDeepCloneable DeepClone()
{
var tags = new List();
- 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 };
}
}
}
diff --git a/src/ImageSharp/Formats/Tiff/TiffFrameMetadataExtensions.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadataExtensions.cs
new file mode 100644
index 0000000000..263b8cd553
--- /dev/null
+++ b/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
+{
+ ///
+ /// The tiff metadata extensions
+ ///
+ internal static class TiffFrameMetadataExtensions
+ {
+ public static T[] GetArray(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(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(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(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(this TiffFrameMetadata meta, ExifTag tag, TEnum? defaultValue = null)
+ where TEnum : struct
+ where TTagValue : struct
+ => meta.GetSingleEnumNullable(tag) ?? (defaultValue != null ? defaultValue.Value : throw TiffThrowHelper.TagNotFound(nameof(tag)));
+
+ public static bool SetSingleEnum(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(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(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(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;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs
index 276e8ad809..6f31fd5495 100644
--- a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs
@@ -45,8 +45,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
///
/// Gets or sets the XMP profile.
+ /// For internal use only. ImageSharp not support XMP profile.
///
- public byte[] XmpProfile { get; set; }
+ internal byte[] XmpProfile { get; set; }
///
public IDeepCloneable DeepClone() => new TiffMetadata(this);
diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
index 3605869256..f8f5f8ab8a 100644
--- a/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
+++ b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
@@ -285,9 +285,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Utils
/// The compression to use.
/// The compression level for deflate compression.
/// Indicates if horizontal prediction should be used. Should only be used in combination with deflate or LZW compression.
- /// The color map.
+ /// The entries collector.
/// The number of bytes written.
- public int WritePalettedRgb(Image image, IQuantizer quantizer, TiffEncoderCompression compression, DeflateCompressionLevel compressionLevel, bool useHorizontalPredictor, out IExifValue colorMap)
+ public int WritePalettedRgb(Image image, IQuantizer quantizer, TiffEncoderCompression compression, DeflateCompressionLevel compressionLevel, bool useHorizontalPredictor, TiffEncoderEntriesCollector entriesCollector)
where TPixel : unmanaged, IPixel
{
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);
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs
index 4afbb5469d..ce20fb77d9 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs
+++ b/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(TestImageProvider provider)
where TPixel : unmanaged, IPixel
{
@@ -175,7 +176,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
}
[Theory]
- [WithFile(TestImages.Tiff.MultiframeDeflateWithPreview, PixelTypes.Rgba32)]
+ [WithFile(MultiframeDeflateWithPreview, PixelTypes.Rgba32)]
public void SubfileType(TestImageProvider provider)
where TPixel : unmanaged, IPixel
{
@@ -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(TestImageProvider provider, bool preserveMetadata)
+ where TPixel : unmanaged, IPixel
+ {
+ using Image 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(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(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(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);
+ }
+ }
}
}