diff --git a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs
index 108b6ae6ed..10ac39747d 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs
@@ -12,7 +12,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
///
/// Methods for undoing the horizontal prediction used in combination with deflate and LZW compressed TIFF images.
///
- public static class HorizontalPredictor
+ internal static class HorizontalPredictor
{
///
/// Inverts the horizontal prediction.
diff --git a/src/ImageSharp/Formats/Tiff/Compression/T4BitReader.cs b/src/ImageSharp/Formats/Tiff/Compression/T4BitReader.cs
index a614235be8..f7b6200fa4 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/T4BitReader.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/T4BitReader.cs
@@ -5,7 +5,7 @@ using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
-using System.Linq;
+
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
@@ -673,27 +673,27 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression
{
case 4:
{
- return WhiteLen4TermCodes.Keys.Contains(this.value);
+ return WhiteLen4TermCodes.ContainsKey(this.value);
}
case 5:
{
- return WhiteLen5TermCodes.Keys.Contains(this.value);
+ return WhiteLen5TermCodes.ContainsKey(this.value);
}
case 6:
{
- return WhiteLen6TermCodes.Keys.Contains(this.value);
+ return WhiteLen6TermCodes.ContainsKey(this.value);
}
case 7:
{
- return WhiteLen7TermCodes.Keys.Contains(this.value);
+ return WhiteLen7TermCodes.ContainsKey(this.value);
}
case 8:
{
- return WhiteLen8TermCodes.Keys.Contains(this.value);
+ return WhiteLen8TermCodes.ContainsKey(this.value);
}
}
diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffResolutionUnit.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffResolutionUnit.cs
deleted file mode 100644
index 0fab224dec..0000000000
--- a/src/ImageSharp/Formats/Tiff/Constants/TiffResolutionUnit.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Apache License, Version 2.0.
-
-namespace SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants
-{
- ///
- /// Enumeration representing the resolution units defined by the Tiff file-format.
- ///
- public enum TiffResolutionUnit : ushort
- {
- ///
- /// No absolute unit of measurement.
- ///
- None = 1,
-
- ///
- /// Inch.
- ///
- Inch = 2,
-
- ///
- /// Centimeter.
- ///
- Centimeter = 3
- }
-}
diff --git a/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs b/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
index 46fbf1c576..4814631364 100644
--- a/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
+++ b/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
@@ -11,6 +11,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
///
internal interface ITiffEncoderOptions
{
+ ///
+ /// Gets the byte order to use.
+ ///
+ ByteOrder ByteOrder { get; }
+
///
/// Gets the compression type to use.
///
diff --git a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs
index b8532e0c59..da2f96211e 100644
--- a/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs
+++ b/src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs
@@ -116,6 +116,15 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
private bool ReadValueOrOffset(ExifValue entry, ExifDataType rawDataType, uint count)
{
+ if (entry.Tag == ExifTag.SubIFDOffset
+ || entry.Tag == ExifTag.GPSIFDOffset
+ /*|| entry.Tag == ExifTagValue.SubIFDs*/)
+ {
+ // todo: ignore subIfds (exif, gps)
+ this.stream.Skip(4);
+ return false;
+ }
+
if (HasExtData(entry, count))
{
uint offset = this.stream.ReadUInt32();
diff --git a/src/ImageSharp/Formats/Tiff/README.md b/src/ImageSharp/Formats/Tiff/README.md
index bdf5cf8a63..7e88310484 100644
--- a/src/ImageSharp/Formats/Tiff/README.md
+++ b/src/ImageSharp/Formats/Tiff/README.md
@@ -50,7 +50,7 @@ Decoder:
|PackBits | Y | Y | |
|CcittGroup3Fax | Y | Y | |
|CcittGroup4Fax | | | |
-|Lzw | | Y | Based on ImageSharp GIF LZW implementation - this code could be modified to be (i) shared, or (ii) optimised for each case |
+|Lzw | Y | Y | Based on ImageSharp GIF LZW implementation - this code could be modified to be (i) shared, or (ii) optimised for each case |
|Old Jpeg | | | We should not even try to support this |
|Jpeg (Technote 2) | | | |
|Deflate (Technote 2) | Y | Y | Based on PNG Deflate. |
@@ -103,14 +103,14 @@ Decoder:
|FreeByteCounts | | | |
|GrayResponseUnit | | | |
|GrayResponseCurve | | | |
-|ResolutionUnit | | Y | |
+|ResolutionUnit | Y | Y | |
|Software | Y | Y | |
-|DateTime | | Y | |
-|Artist | | Y | |
-|HostComputer | | Y | |
-|ColorMap | | Y | |
+|DateTime | Y | Y | |
+|Artist | Y | Y | |
+|HostComputer | Y | Y | |
+|ColorMap | Y | Y | |
|ExtraSamples | | - | |
-|Copyright | | Y | |
+|Copyright | Y | Y | |
### Extension TIFF Tags
@@ -125,7 +125,7 @@ Decoder:
|T6Options | | | |
|PageNumber | | | |
|TransferFunction | | | |
-|Predictor | | - | priority |
+|Predictor | Y | Y | only Horizontal |
|WhitePoint | | | |
|PrimaryChromaticities | | | |
|HalftoneHints | | | |
@@ -174,7 +174,7 @@ Decoder:
|YCbCrPositioning | | | |
|ReferenceBlackWhite | | | |
|StripRowCounts | | - | |
-|XMP | | Y | |
+|XMP | Y | Y | |
|ImageID | | | |
|ImageLayer | | | |
@@ -192,7 +192,7 @@ Decoder:
|MD PrepTime | | | |
|MD FileUnits | | | |
|ModelPixelScaleTag | | | |
-|IPTC | | Y | |
+|IPTC | Y | Y | |
|INGR Packet Data Tag | | | |
|INGR Flag Registers | | | |
|IrasB Transformation Matrix| | | |
@@ -200,7 +200,7 @@ Decoder:
|ModelTransformationTag | | | |
|Photoshop | | | |
|Exif IFD | | - | 0x8769 SubExif |
-|ICC Profile | | Y | |
+|ICC Profile | Y | Y | |
|GeoKeyDirectoryTag | | | |
|GeoDoubleParamsTag | | | |
|GeoAsciiParamsTag | | | |
diff --git a/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs b/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs
index 0e314e6ee0..ea53fa0617 100644
--- a/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs
@@ -13,6 +13,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
///
Pixel1 = 1,
+ ///
+ /// 4 bits per pixel, grayscale or indexed image. Each pixel consists of 4 bit.
+ ///
+ Pixel4 = 4,
+
///
/// 8 bits per pixel, grayscale or indexed image. Each pixel consists of 1 byte.
///
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
index a62c5946f7..db8b59858b 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
@@ -3,7 +3,6 @@
using System.Collections.Generic;
using System.IO;
-using System.Linq;
using System.Threading;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Compression;
@@ -37,16 +36,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
///
private readonly bool ignoreMetadata;
- ///
- /// The image metadata.
- ///
- private ImageMetadata metadata;
-
- ///
- /// The tiff specific metadata.
- ///
- private TiffMetadata tiffMetaData;
-
///
/// The stream to decode from.
///
@@ -71,6 +60,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
///
public ushort[] BitsPerSample { get; set; }
+ public int BitsPerPixel { get; set; }
+
///
/// Gets or sets the lookup table for RGB palette colored images.
///
@@ -96,6 +87,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
///
public TiffPhotometricInterpretation PhotometricInterpretation { get; set; }
+ ///
+ /// Gets or sets the predictor.
+ ///
+ public TiffPredictor Predictor { get; set; }
+
///
public Configuration Configuration => this.configuration;
@@ -121,12 +117,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
framesMetadata.Add(frameMetadata);
}
- this.metadata = framesMetadata.CreateMetadata(this.ignoreMetadata, tiffStream.ByteOrder);
- this.SetTiffFormatMetaData(framesMetadata, tiffStream.ByteOrder);
+ ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.ignoreMetadata, tiffStream.ByteOrder);
// todo: tiff frames can have different sizes
{
- ImageFrame root = frames.First();
+ ImageFrame root = frames[0];
this.Dimensions = root.Size();
foreach (ImageFrame frame in frames)
{
@@ -137,7 +132,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
}
- var image = new Image(this.configuration, this.metadata, frames);
+ var image = new Image(this.configuration, metadata, frames);
return image;
}
@@ -154,51 +149,15 @@ 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);
-
- TiffFrameMetadata root = framesMetadata.First();
- int bitsPerPixel = 0;
- foreach (var bits in root.BitsPerSample)
- {
- bitsPerPixel += bits;
- }
-
- return new ImageInfo(new PixelTypeInfo(bitsPerPixel), (int)root.Width, (int)root.Height, this.metadata);
- }
-
- private void SetTiffFormatMetaData(List framesMetadata, ByteOrder byteOrder)
- {
- 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;
- }
+ ImageMetadata metadata = TiffDecoderMetadataCreator.Create(framesMetadata, this.ignoreMetadata, tiffStream.ByteOrder);
- 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;
- }
+ TiffFrameMetadata root = framesMetadata[0];
+ return new ImageInfo(new PixelTypeInfo(root.BitsPerPixel), (int)root.Width, (int)root.Height, metadata);
}
private static TiffStream CreateStream(Stream stream)
@@ -246,11 +205,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
{
var coreMetadata = new ImageFrameMetadata();
frameMetaData = coreMetadata.GetTiffMetadata();
- frameMetaData.Tags = tags;
- TiffFrameMetadata tiffFormatMetaData = coreMetadata.GetFormatMetadata(TiffFormat.Instance);
- TiffPredictor predictor = tiffFormatMetaData.Predictor;
+ frameMetaData.FrameTags.AddRange(tags);
- this.VerifyAndParseOptions(frameMetaData);
+ this.VerifyAndParse(frameMetaData);
int width = (int)frameMetaData.Width;
int height = (int)frameMetaData.Height;
@@ -262,11 +219,11 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar)
{
- this.DecodeStripsPlanar(frame, rowsPerStrip, stripOffsets, stripByteCounts, width, predictor);
+ this.DecodeStripsPlanar(frame, rowsPerStrip, stripOffsets, stripByteCounts, width);
}
else
{
- this.DecodeStripsChunky(frame, rowsPerStrip, stripOffsets, stripByteCounts, width, predictor);
+ this.DecodeStripsChunky(frame, rowsPerStrip, stripOffsets, stripByteCounts, width);
}
return frame;
@@ -281,22 +238,19 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// The size (in bytes) of the required pixel buffer.
private int CalculateStripBufferSize(int width, int height, int plane = -1)
{
- uint bitsPerPixel = 0;
+ int bitsPerPixel = 0;
if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky)
{
DebugGuard.IsTrue(plane == -1, "Excepted Chunky planar.");
- for (int i = 0; i < this.BitsPerSample.Length; i++)
- {
- bitsPerPixel += this.BitsPerSample[i];
- }
+ bitsPerPixel = this.BitsPerPixel;
}
else
{
bitsPerPixel = this.BitsPerSample[plane];
}
- int bytesPerRow = ((width * (int)bitsPerPixel) + 7) / 8;
+ int bytesPerRow = ((width * bitsPerPixel) + 7) / 8;
int stripBytes = bytesPerRow * height;
return stripBytes;
@@ -311,17 +265,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// An array of byte offsets to each strip in the image.
/// An array of the size of each strip (in bytes).
/// The image width.
- /// The tiff predictor used.
- private void DecodeStripsPlanar(ImageFrame frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts, int width, TiffPredictor predictor)
+ private void DecodeStripsPlanar(ImageFrame frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts, int width)
where TPixel : unmanaged, IPixel
{
int stripsPerPixel = this.BitsPerSample.Length;
int stripsPerPlane = stripOffsets.Length / stripsPerPixel;
- int bitsPerPixel = 0;
- foreach (var bits in this.BitsPerSample)
- {
- bitsPerPixel += bits;
- }
+ int bitsPerPixel = this.BitsPerPixel;
Buffer2D pixels = frame.PixelBuffer;
@@ -335,7 +284,7 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
stripBuffers[stripIndex] = this.memoryAllocator.AllocateManagedByteBuffer(uncompressedStripSize);
}
- TiffBaseCompression decompressor = TiffCompressionFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, width, bitsPerPixel, predictor);
+ TiffBaseCompression decompressor = TiffCompressionFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, width, bitsPerPixel, this.Predictor);
RgbPlanarTiffColor colorDecoder = TiffColorDecoderFactory.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap);
@@ -362,21 +311,17 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
}
- private void DecodeStripsChunky(ImageFrame frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts, int width, TiffPredictor predictor)
+ private void DecodeStripsChunky(ImageFrame frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts, int width)
where TPixel : unmanaged, IPixel
{
int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip);
- int bitsPerPixel = 0;
- foreach (var bits in this.BitsPerSample)
- {
- bitsPerPixel += bits;
- }
+ int bitsPerPixel = this.BitsPerPixel;
using IManagedByteBuffer stripBuffer = this.memoryAllocator.AllocateManagedByteBuffer(uncompressedStripSize, AllocationOptions.Clean);
Buffer2D pixels = frame.PixelBuffer;
- TiffBaseCompression decompressor = TiffCompressionFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, width, bitsPerPixel, predictor);
+ TiffBaseCompression decompressor = TiffCompressionFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, width, bitsPerPixel, this.Predictor);
TiffBaseColorDecoder colorDecoder = TiffColorDecoderFactory.Create(this.ColorType, this.BitsPerSample, this.ColorMap);
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs
new file mode 100644
index 0000000000..99337a8b2a
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderMetadataCreator.cs
@@ -0,0 +1,92 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Collections.Generic;
+
+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 creator.
+ ///
+ internal static class TiffDecoderMetadataCreator
+ {
+ public static ImageMetadata Create(List frames, bool ignoreMetadata, ByteOrder byteOrder)
+ {
+ if (frames.Count < 1)
+ {
+ TiffThrowHelper.ThrowImageFormatException("Expected at least one frame.");
+ }
+
+ var coreMetadata = new ImageMetadata();
+ TiffFrameMetadata rootFrameMetadata = frames[0];
+
+ coreMetadata.ResolutionUnits = rootFrameMetadata.ResolutionUnit;
+ if (rootFrameMetadata.HorizontalResolution != null)
+ {
+ coreMetadata.HorizontalResolution = rootFrameMetadata.HorizontalResolution.Value;
+ }
+
+ 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.ExifProfile == null)
+ {
+ byte[] buf = frame.GetArray(ExifTag.SubIFDOffset, true);
+ if (buf != null)
+ {
+ coreMetadata.ExifProfile = new ExifProfile(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)
+ => (TiffBitsPerPixel)firstFrameMetaData.BitsPerPixel;
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
similarity index 70%
rename from src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs
rename to src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
index 25f43a0a82..713c85c06d 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
@@ -1,95 +1,23 @@
// 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
{
///
- /// The decoder helper methods.
+ /// The decoder options parser.
///
- internal static class TiffDecoderHelpers
+ internal static class TiffDecoderOptionsParser
{
- 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.
///
/// The options.
/// The IFD entries container to read the image format information for.
- public static void VerifyAndParseOptions(this TiffDecoderCore options, TiffFrameMetadata entries)
+ public static void VerifyAndParse(this TiffDecoderCore options, TiffFrameMetadata entries)
{
if (entries.ExtraSamples != null)
{
@@ -122,15 +50,14 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
}
- ParseCompression(options, entries.Compression);
-
options.PlanarConfiguration = entries.PlanarConfiguration;
-
- ParsePhotometric(options, entries);
-
- ParseBitsPerSample(options, entries);
+ options.Predictor = entries.Predictor;
+ options.PhotometricInterpretation = entries.PhotometricInterpretation;
+ options.BitsPerSample = entries.BitsPerSample;
+ options.BitsPerPixel = entries.BitsPerPixel;
ParseColorType(options, entries);
+ ParseCompression(options, entries.Compression);
}
private static void ParseColorType(this TiffDecoderCore options, TiffFrameMetadata entries)
@@ -281,29 +208,6 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
}
- private static void ParseBitsPerSample(this TiffDecoderCore options, TiffFrameMetadata entries)
- {
- options.BitsPerSample = entries.BitsPerSample;
- if (options.BitsPerSample == null)
- {
- if (options.PhotometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero
- || options.PhotometricInterpretation == TiffPhotometricInterpretation.BlackIsZero)
- {
- options.BitsPerSample = new[] { (ushort)1 };
- }
- else
- {
- TiffThrowHelper.ThrowNotSupported("The TIFF BitsPerSample entry is missing.");
- }
- }
- }
-
- private static void ParsePhotometric(this TiffDecoderCore options, TiffFrameMetadata entries)
- {
- // There is no default for PhotometricInterpretation, and it is required.
- options.PhotometricInterpretation = entries.PhotometricInterpretation;
- }
-
private static void ParseCompression(this TiffDecoderCore options, TiffCompression compression)
{
switch (compression)
diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs
index 0f333679e9..cddc962fcb 100644
--- a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs
@@ -17,6 +17,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
///
public class TiffEncoder : IImageEncoder, ITiffEncoderOptions
{
+ ///
+ public ByteOrder ByteOrder { get; } = TiffEncoderCore.ByteOrder;
+
///
public TiffEncoderCompression Compression { get; set; } = TiffEncoderCompression.None;
diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
index 1d16d51c4d..c7301049de 100644
--- a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
@@ -24,6 +24,12 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
///
internal sealed class TiffEncoderCore : IImageEncoderInternals
{
+ public static readonly ByteOrder ByteOrder = BitConverter.IsLittleEndian ? ByteOrder.LittleEndian : ByteOrder.BigEndian;
+
+ private static readonly ushort ByteOrderMarker = BitConverter.IsLittleEndian
+ ? TiffConstants.ByteOrderLittleEndianShort
+ : TiffConstants.ByteOrderBigEndianShort;
+
///
/// Used for allocating memory during processing operations.
///
@@ -70,19 +76,21 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
///
- /// 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 .
@@ -132,12 +140,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// The marker to write the first IFD offset.
public long WriteHeader(TiffWriter writer)
{
- ushort byteOrderMarker = BitConverter.IsLittleEndian
- ? TiffConstants.ByteOrderLittleEndianShort
- : TiffConstants.ByteOrderBigEndianShort;
-
- writer.Write(byteOrderMarker);
- writer.Write((ushort)42);
+ writer.Write(ByteOrderMarker);
+ writer.Write((ushort)TiffConstants.HeaderMagicNumber);
long firstIfdMarker = writer.PlaceMarker();
return firstIfdMarker;
@@ -154,8 +158,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 +166,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 +179,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);
writer.WriteMarker(ifdOffset, (uint)writer.Position);
- long nextIfdMarker = this.WriteIfd(writer, ifdEntries);
+ long nextIfdMarker = this.WriteIfd(writer, entriesCollector.Entries);
return nextIfdMarker + imageDataBytes;
}
@@ -250,51 +250,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 +274,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 +307,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..41d833299a
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs
@@ -0,0 +1,363 @@
+// 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)
+ where TPixel : unmanaged, IPixel
+ => new GeneralProcessor(this).Process(image);
+
+ 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)
+ 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);
+
+ this.ProcessProfiles(image.Metadata, frameMetadata);
+ this.ProcessMetadata(frameMetadata);
+ }
+
+ 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 void ProcessResolution(ImageMetadata imageMetadata, TiffFrameMetadata frameMetadata)
+ {
+ frameMetadata.SetResolutions(
+ imageMetadata.ResolutionUnits,
+ imageMetadata.HorizontalResolution,
+ imageMetadata.VerticalResolution);
+
+ 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 ((ExifTagValue)(ushort)entry.Tag)
+ {
+ case ExifTagValue.SubIFDOffset:
+ case ExifTagValue.GPSIFDOffset:
+ case ExifTagValue.SubIFDs:
+ case ExifTagValue.XMP:
+ case ExifTagValue.IPTC:
+ case ExifTagValue.IccProfile:
+ continue;
+ }
+
+ switch (ExifTags.GetPart(entry.Tag))
+ {
+ case ExifParts.ExifTags:
+ 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 void ProcessProfiles(ImageMetadata imageMetadata, TiffFrameMetadata tiffFrameMetadata)
+ {
+ if (imageMetadata.ExifProfile != null)
+ {
+ // todo: implement processing exif profile
+ }
+ else
+ {
+ tiffFrameMetadata.Remove(ExifTag.SubIFDOffset);
+ }
+
+ if (imageMetadata.IptcProfile != null)
+ {
+ imageMetadata.IptcProfile.UpdateData();
+ var iptc = new ExifByteArray(ExifTagValue.IPTC, ExifDataType.Byte)
+ {
+ Value = imageMetadata.IptcProfile.Data
+ };
+
+ this.collector.AddInternal(iptc);
+ }
+ else
+ {
+ tiffFrameMetadata.Remove(ExifTag.IPTC);
+ }
+
+ if (imageMetadata.IccProfile != null)
+ {
+ var icc = new ExifByteArray(ExifTagValue.IccProfile, ExifDataType.Undefined)
+ {
+ Value = imageMetadata.IccProfile.ToByteArray()
+ };
+
+ this.collector.AddInternal(icc);
+ }
+ else
+ {
+ tiffFrameMetadata.Remove(ExifTag.IccProfile);
+ }
+
+ TiffMetadata tiffMetadata = imageMetadata.GetTiffMetadata();
+ if (tiffMetadata.XmpProfile != null)
+ {
+ var xmp = new ExifByteArray(ExifTagValue.XMP, ExifDataType.Byte)
+ {
+ Value = tiffMetadata.XmpProfile
+ };
+
+ this.collector.AddInternal(xmp);
+ }
+ else
+ {
+ tiffFrameMetadata.Remove(ExifTag.XMP);
+ }
+ }
+ }
+
+ 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..c9bab385a8 100644
--- a/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
@@ -2,9 +2,9 @@
// 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;
namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
@@ -14,7 +14,8 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
///
public class TiffFrameMetadata : IDeepCloneable
{
- private const TiffResolutionUnit DefaultResolutionUnit = TiffResolutionUnit.Inch;
+ // 2 (Inch)
+ internal const ushort DefaultResolutionUnit = 2;
private const TiffPlanarConfiguration DefaultPlanarConfiguration = TiffPlanarConfiguration.Chunky;
@@ -28,9 +29,9 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
}
///
- /// Gets or sets the Tiff directory tags list.
+ /// Gets the Tiff directory tags list.
///
- public IList Tags { get; set; }
+ public List FrameTags { get; internal 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.
@@ -53,7 +54,41 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
///
/// Gets the number of bits per component.
///
- public ushort[] BitsPerSample => this.GetArray(ExifTag.BitsPerSample, true);
+ public ushort[] BitsPerSample
+ {
+ get
+ {
+ var bits = this.GetArray(ExifTag.BitsPerSample, true);
+ if (bits == null)
+ {
+ if (this.PhotometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero
+ || this.PhotometricInterpretation == TiffPhotometricInterpretation.BlackIsZero)
+ {
+ bits = new[] { (ushort)1 };
+ }
+ else
+ {
+ TiffThrowHelper.ThrowNotSupported("The TIFF BitsPerSample entry is missing.");
+ }
+ }
+
+ return bits;
+ }
+ }
+
+ internal int BitsPerPixel
+ {
+ get
+ {
+ int bitsPerPixel = 0;
+ foreach (var bits in this.BitsPerSample)
+ {
+ bitsPerPixel += bits;
+ }
+
+ return bitsPerPixel;
+ }
+ }
/// Gets the compression scheme used on the image data.
/// The compression scheme used on the image data.
@@ -70,19 +105,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);
@@ -104,45 +151,13 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
/// Gets the resolution of the image in x- direction.
/// The density of the image in x- direction.
- public double? HorizontalResolution
- {
- get
- {
- if (this.ResolutionUnit != TiffResolutionUnit.None)
- {
- double resolutionUnitFactor = this.ResolutionUnit == TiffResolutionUnit.Centimeter ? 2.54 : 1.0;
-
- if (this.TryGetSingle(ExifTag.XResolution, out Rational xResolution))
- {
- return xResolution.ToDouble() * resolutionUnitFactor;
- }
- }
-
- return null;
- }
- }
+ public double? HorizontalResolution => this.GetResolution(ExifTag.XResolution);
///
/// Gets the resolution of the image in y- direction.
///
/// The density of the image in y- direction.
- public double? VerticalResolution
- {
- get
- {
- if (this.ResolutionUnit != TiffResolutionUnit.None)
- {
- double resolutionUnitFactor = this.ResolutionUnit == TiffResolutionUnit.Centimeter ? 2.54 : 1.0;
-
- if (this.TryGetSingle(ExifTag.YResolution, out Rational yResolution))
- {
- return yResolution.ToDouble() * resolutionUnitFactor;
- }
- }
-
- return null;
- }
- }
+ public double? VerticalResolution => this.GetResolution(ExifTag.YResolution);
///
/// Gets how the components of each pixel are stored.
@@ -152,27 +167,43 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
///
/// Gets the unit of measurement for XResolution and YResolution.
///
- public TiffResolutionUnit ResolutionUnit => this.GetSingleEnum(ExifTag.ResolutionUnit, DefaultResolutionUnit);
+ public PixelResolutionUnit ResolutionUnit => this.GetResolutionUnit();
///
- /// 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 +216,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 +235,45 @@ 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
- {
- foreach (IExifValue entry in this.Tags)
- {
- if (entry.Tag == tag)
- {
- DebugGuard.IsTrue(entry.IsArray, "Expected array entry");
-
- result = (T[])entry.GetValue();
- return true;
- }
- }
-
- 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)
- {
- 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;
- }
-
- private TEnum? GetSingleEnumNullable(ExifTag tag)
- where TEnum : struct
- where TTagValue : struct
- {
- if (!this.TryGetSingle(tag, out TTagValue value))
- {
- return null;
- }
-
- 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
+ ///
+ /// Clears the metadata.
+ ///
+ public void ClearMetadata()
{
- foreach (IExifValue entry in this.Tags)
+ var tags = new List();
+ foreach (IExifValue entry in this.FrameTags)
{
- if (entry.Tag == tag)
+ switch ((ExifTagValue)(ushort)entry.Tag)
{
- DebugGuard.IsTrue(!entry.IsArray, "Expected non array entry");
-
- object value = entry.GetValue();
-
- result = (T)value;
- return true;
+ case ExifTagValue.ImageWidth:
+ case ExifTagValue.ImageLength:
+ case ExifTagValue.ResolutionUnit:
+ case ExifTagValue.XResolution:
+ case ExifTagValue.YResolution:
+ //// image format tags
+ case ExifTagValue.Predictor:
+ case ExifTagValue.PlanarConfiguration:
+ case ExifTagValue.PhotometricInterpretation:
+ case ExifTagValue.BitsPerSample:
+ case ExifTagValue.ColorMap:
+ tags.Add(entry);
+ break;
}
}
- result = default;
- return false;
+ this.FrameTags = tags;
}
///
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..78e2174845
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadataExtensions.cs
@@ -0,0 +1,200 @@
+// 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))
+ {
+ 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/TiffFrameMetadataResolutionExtensions.cs b/src/ImageSharp/Formats/Tiff/TiffFrameMetadataResolutionExtensions.cs
new file mode 100644
index 0000000000..f17e9dddc2
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/TiffFrameMetadataResolutionExtensions.cs
@@ -0,0 +1,101 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using SixLabors.ImageSharp.Common.Helpers;
+using SixLabors.ImageSharp.Metadata;
+using SixLabors.ImageSharp.Metadata.Profiles.Exif;
+
+namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
+{
+ internal static class TiffFrameMetadataResolutionExtensions
+ {
+ public static void SetResolutions(this TiffFrameMetadata meta, PixelResolutionUnit unit, double horizontal, double vertical)
+ {
+ switch (unit)
+ {
+ case PixelResolutionUnit.AspectRatio:
+ case PixelResolutionUnit.PixelsPerInch:
+ case PixelResolutionUnit.PixelsPerCentimeter:
+ break;
+ case PixelResolutionUnit.PixelsPerMeter:
+ {
+ unit = PixelResolutionUnit.PixelsPerCentimeter;
+ horizontal = UnitConverter.MeterToCm(horizontal);
+ vertical = UnitConverter.MeterToCm(vertical);
+ }
+
+ break;
+ default:
+ unit = PixelResolutionUnit.PixelsPerInch;
+ break;
+ }
+
+ meta.SetSingle(ExifTag.ResolutionUnit, (ushort)unit + 1);
+ meta.SetResolution(ExifTag.XResolution, horizontal);
+ meta.SetResolution(ExifTag.YResolution, vertical);
+ }
+
+ public static PixelResolutionUnit GetResolutionUnit(this TiffFrameMetadata meta)
+ {
+ if (!meta.TryGetSingle(ExifTag.ResolutionUnit, out ushort res))
+ {
+ res = TiffFrameMetadata.DefaultResolutionUnit;
+ }
+
+ return (PixelResolutionUnit)(res - 1);
+ }
+
+ public static double? GetResolution(this TiffFrameMetadata meta, ExifTag tag)
+ {
+ if (!meta.TryGetSingle(tag, out Rational resolution))
+ {
+ return null;
+ }
+
+ double res = resolution.ToDouble();
+ switch (meta.ResolutionUnit)
+ {
+ case PixelResolutionUnit.AspectRatio:
+ return 0;
+ case PixelResolutionUnit.PixelsPerCentimeter:
+ return UnitConverter.CmToInch(res);
+ case PixelResolutionUnit.PixelsPerMeter:
+ return UnitConverter.MeterToInch(res);
+ case PixelResolutionUnit.PixelsPerInch:
+ default:
+ // DefaultResolutionUnit is Inch
+ return res;
+ }
+ }
+
+ private static void SetResolution(this TiffFrameMetadata meta, ExifTag tag, double? value)
+ {
+ if (value == null)
+ {
+ meta.Remove(tag);
+ return;
+ }
+ else
+ {
+ double res = value.Value;
+ switch (meta.ResolutionUnit)
+ {
+ case PixelResolutionUnit.AspectRatio:
+ res = 0;
+ break;
+ case PixelResolutionUnit.PixelsPerCentimeter:
+ res = UnitConverter.InchToCm(res);
+ break;
+ case PixelResolutionUnit.PixelsPerMeter:
+ res = UnitConverter.InchToMeter(res);
+ break;
+ case PixelResolutionUnit.PixelsPerInch:
+ default:
+ break;
+ }
+
+ meta.SetSingle(tag, new Rational(res));
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs
index 276e8ad809..f8df21c1ea 100644
--- a/src/ImageSharp/Formats/Tiff/TiffMetadata.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffMetadata.cs
@@ -26,27 +26,29 @@ namespace SixLabors.ImageSharp.Formats.Experimental.Tiff
this.ByteOrder = other.ByteOrder;
this.XmpProfile = other.XmpProfile;
this.BitsPerPixel = other.BitsPerPixel;
+ this.Compression = other.Compression;
}
///
- /// Gets or sets the byte order.
+ /// Gets the byte order.
///
- public ByteOrder ByteOrder { get; set; }
+ public ByteOrder ByteOrder { get; internal set; }
///
- /// Gets or sets the number of bits per pixel.
+ /// Gets the number of bits per pixel.
///
- public TiffBitsPerPixel BitsPerPixel { get; set; } = TiffBitsPerPixel.Pixel24;
+ public TiffBitsPerPixel BitsPerPixel { get; internal set; } = TiffBitsPerPixel.Pixel24;
///
- /// Gets or sets the compression used to create the TIFF file.
+ /// Gets the compression used to create the TIFF file.
///
- public TiffCompression Compression { get; set; } = TiffCompression.None;
+ public TiffCompression Compression { get; internal set; } = TiffCompression.None;
///
/// 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 55003bb425..20826df8c3 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs
@@ -1,11 +1,15 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
+using System;
using System.IO;
using System.Linq;
+
using SixLabors.ImageSharp.Formats.Experimental.Tiff;
using SixLabors.ImageSharp.Formats.Experimental.Tiff.Constants;
using SixLabors.ImageSharp.Metadata;
+using SixLabors.ImageSharp.Metadata.Profiles.Icc;
+using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
@@ -126,7 +130,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
{
@@ -156,7 +160,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
Assert.Equal(10, frame.HorizontalResolution);
Assert.Equal(10, frame.VerticalResolution);
Assert.Equal(TiffPlanarConfiguration.Chunky, frame.PlanarConfiguration);
- Assert.Equal(TiffResolutionUnit.Inch, frame.ResolutionUnit);
+ Assert.Equal(PixelResolutionUnit.PixelsPerInch, frame.ResolutionUnit);
Assert.Equal("IrfanView", frame.Software);
Assert.Null(frame.DateTime);
Assert.Equal("This is author1;Author2", frame.Artist);
@@ -175,7 +179,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 +203,197 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
Assert.Equal(255u, frame1.Height);
}
}
+
+ [Theory]
+ [WithFile(SampleMetadata, PixelTypes.Rgba32, true)]
+ [WithFile(SampleMetadata, PixelTypes.Rgba32, false)]
+ public void PreserveMetadata(TestImageProvider provider, bool preserveMetadata)
+ where TPixel : unmanaged, IPixel
+ {
+ // Load Tiff image
+ 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();
+
+ // Save to Tiff
+ var tiffEncoder = new TiffEncoder() { Mode = TiffEncodingMode.Rgb };
+ if (!preserveMetadata)
+ {
+ ClearMeta(image);
+ }
+
+ using var ms = new MemoryStream();
+ 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(TiffBitsPerPixel.Pixel4, tiffMeta.BitsPerPixel);
+ Assert.Equal(TiffBitsPerPixel.Pixel24, tiffMetaOut.BitsPerPixel);
+ Assert.Equal(TiffCompression.Lzw, tiffMeta.Compression);
+ Assert.Equal(TiffCompression.None, tiffMetaOut.Compression);
+
+ Assert.Equal(coreMeta.HorizontalResolution, coreMetaOut.HorizontalResolution);
+ Assert.Equal(coreMeta.VerticalResolution, coreMetaOut.VerticalResolution);
+ Assert.Equal(coreMeta.ResolutionUnits, coreMetaOut.ResolutionUnits);
+
+ 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("ImageSharp", frameMetaOut.Software);
+
+ if (preserveMetadata)
+ {
+ Assert.Equal(tiffMeta.XmpProfile, tiffMetaOut.XmpProfile);
+
+ Assert.Equal("IrfanView", frameMeta.Software);
+ Assert.Equal("This is Название", frameMeta.ImageDescription);
+ Assert.Equal("This is Изготовитель камеры", frameMeta.Make);
+ Assert.Equal("This is Авторские права", frameMeta.Copyright);
+
+ Assert.Equal(frameMeta.ImageDescription, frameMetaOut.ImageDescription);
+ Assert.Equal(frameMeta.Make, frameMetaOut.Make);
+ Assert.Equal(frameMeta.Copyright, frameMetaOut.Copyright);
+ }
+ else
+ {
+ Assert.Null(tiffMetaOut.XmpProfile);
+
+ Assert.Null(frameMeta.Software);
+ Assert.Null(frameMeta.ImageDescription);
+ Assert.Null(frameMeta.Make);
+ Assert.Null(frameMeta.Copyright);
+
+ Assert.Null(frameMetaOut.ImageDescription);
+ Assert.Null(frameMetaOut.Make);
+ Assert.Null(frameMetaOut.Copyright);
+ }
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void CreateMetadata(bool preserveMetadata)
+ {
+ // Create image
+ int w = 10;
+ int h = 20;
+ using Image image = new Image(w, h);
+
+ // set metadata
+ ImageMetadata coreMeta = image.Metadata;
+ TiffMetadata tiffMeta = image.Metadata.GetTiffMetadata();
+ TiffFrameMetadata frameMeta = image.Frames.RootFrame.Metadata.GetTiffMetadata();
+
+ tiffMeta.XmpProfile = new byte[] { 1, 2, 3, 4, 5 };
+
+ coreMeta.IptcProfile = new IptcProfile();
+ coreMeta.IptcProfile.SetValue(IptcTag.Caption, "iptc caption");
+
+ coreMeta.IccProfile = new IccProfile(
+ new IccProfileHeader() { CreationDate = DateTime.Now },
+ new IccTagDataEntry[]
+ {
+ new IccTextTagDataEntry("test string"),
+ new IccDataTagDataEntry(new byte[] { 11, 22, 33, 44 })
+ });
+
+ coreMeta.ResolutionUnits = PixelResolutionUnit.PixelsPerMeter;
+ coreMeta.HorizontalResolution = 4500;
+ coreMeta.VerticalResolution = 5400;
+
+ var datetime = System.DateTime.Now.ToString();
+ frameMeta.ImageDescription = "test ImageDescription";
+ frameMeta.DateTime = datetime;
+
+ // Save to Tiff
+ var tiffEncoder = new TiffEncoder { Mode = TiffEncodingMode.Default, Compression = TiffEncoderCompression.Deflate };
+ if (!preserveMetadata)
+ {
+ ClearMeta(image);
+ }
+
+ using var ms = new MemoryStream();
+ image.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(PixelResolutionUnit.PixelsPerCentimeter, coreMetaOut.ResolutionUnits);
+ Assert.Equal(45, coreMetaOut.HorizontalResolution);
+ Assert.Equal(54, coreMetaOut.VerticalResolution, 8);
+
+ //// Assert.Equal(tiffEncoder.Compression, tiffMetaOut.Compression);
+ Assert.Equal(TiffBitsPerPixel.Pixel24, tiffMetaOut.BitsPerPixel);
+
+ Assert.Equal((uint)w, frameMetaOut.Width);
+ Assert.Equal((uint)h, frameMetaOut.Height);
+ Assert.Equal(frameMeta.ResolutionUnit, frameMetaOut.ResolutionUnit);
+ Assert.Equal(frameMeta.HorizontalResolution, frameMetaOut.HorizontalResolution);
+ Assert.Equal(frameMeta.VerticalResolution, frameMetaOut.VerticalResolution);
+
+ Assert.Equal("ImageSharp", frameMetaOut.Software);
+
+ if (preserveMetadata)
+ {
+ Assert.NotNull(tiffMeta.XmpProfile);
+ Assert.NotNull(coreMeta.IptcProfile);
+ Assert.NotNull(coreMeta.IccProfile);
+
+ Assert.Equal(tiffMeta.XmpProfile, tiffMetaOut.XmpProfile);
+ //// todo: failure Assert.Equal(coreMeta.IptcProfile, coreMetaOut.IptcProfile);
+ Assert.Equal(coreMeta.IptcProfile.Data, coreMetaOut.IptcProfile.Data);
+ Assert.Equal(coreMeta.IccProfile.ToByteArray(), coreMetaOut.IccProfile.ToByteArray());
+
+ Assert.Equal("test ImageDescription", frameMeta.ImageDescription);
+ Assert.Equal(datetime, frameMeta.DateTime);
+
+ Assert.Equal(frameMeta.ImageDescription, frameMetaOut.ImageDescription);
+ Assert.Equal(frameMeta.DateTime, frameMetaOut.DateTime);
+ }
+ else
+ {
+ Assert.Null(tiffMetaOut.XmpProfile);
+ Assert.Null(coreMetaOut.IptcProfile);
+ Assert.Null(coreMetaOut.IccProfile);
+
+ Assert.Null(frameMeta.ImageDescription);
+ Assert.Null(frameMeta.DateTime);
+
+ Assert.Null(frameMetaOut.ImageDescription);
+ Assert.Null(frameMetaOut.DateTime);
+ }
+ }
+
+ private static void ClearMeta(Image image)
+ {
+ ImageMetadata coreMeta = image.Metadata;
+ TiffMetadata tiffMeta = image.Metadata.GetTiffMetadata();
+ TiffFrameMetadata frameMeta = image.Frames.RootFrame.Metadata.GetTiffMetadata();
+
+ coreMeta.ExifProfile = null;
+ coreMeta.IccProfile = null;
+ coreMeta.IptcProfile = null;
+
+ tiffMeta.XmpProfile = null;
+
+ frameMeta.ClearMetadata();
+ }
}
}