diff --git a/ImageSharp.sln b/ImageSharp.sln
index 8f3bc68602..40282d8bcd 100644
--- a/ImageSharp.sln
+++ b/ImageSharp.sln
@@ -1,4 +1,3 @@
-
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.12
diff --git a/src/ImageSharp/Formats/Tiff/Compression/DeflateTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/DeflateTiffCompression.cs
new file mode 100644
index 0000000000..00d69dbd5c
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Compression/DeflateTiffCompression.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.IO;
+using System.IO.Compression;
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Class to handle cases where TIFF image data is compressed using Deflate compression.
+ ///
+ ///
+ /// Note that the 'OldDeflate' compression type is identical to the 'Deflate' compression type.
+ ///
+ internal static class DeflateTiffCompression
+ {
+ ///
+ /// Decompresses image data into the supplied buffer.
+ ///
+ /// The to read image data from.
+ /// The number of bytes to read from the input stream.
+ /// The output buffer for uncompressed data.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Decompress(Stream stream, int byteCount, byte[] buffer)
+ {
+ // Read the 'zlib' header information
+ int cmf = stream.ReadByte();
+ int flag = stream.ReadByte();
+
+ if ((cmf & 0x0f) != 8)
+ {
+ throw new Exception($"Bad compression method for ZLIB header: cmf={cmf}");
+ }
+
+ // If the 'fdict' flag is set then we should skip the next four bytes
+ bool fdict = (flag & 32) != 0;
+
+ if (fdict)
+ {
+ stream.ReadByte();
+ stream.ReadByte();
+ stream.ReadByte();
+ stream.ReadByte();
+ }
+
+ // The subsequent data is the Deflate compressed data (except for the last four bytes of checksum)
+ int headerLength = fdict ? 10 : 6;
+ SubStream subStream = new SubStream(stream, byteCount - headerLength);
+ using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress, true))
+ {
+ deflateStream.ReadFull(buffer);
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/Compression/LzwTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/LzwTiffCompression.cs
new file mode 100644
index 0000000000..5de966554d
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Compression/LzwTiffCompression.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.IO;
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Class to handle cases where TIFF image data is compressed using LZW compression.
+ ///
+ internal static class LzwTiffCompression
+ {
+ ///
+ /// Decompresses image data into the supplied buffer.
+ ///
+ /// The to read image data from.
+ /// The number of bytes to read from the input stream.
+ /// The output buffer for uncompressed data.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Decompress(Stream stream, int byteCount, byte[] buffer)
+ {
+ SubStream subStream = new SubStream(stream, byteCount);
+ using (var decoder = new TiffLzwDecoder(subStream))
+ {
+ decoder.DecodePixels(buffer.Length, 8, buffer);
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/Compression/NoneTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/NoneTiffCompression.cs
new file mode 100644
index 0000000000..a9587d1990
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Compression/NoneTiffCompression.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.IO;
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Class to handle cases where TIFF image data is not compressed.
+ ///
+ internal static class NoneTiffCompression
+ {
+ ///
+ /// Decompresses image data into the supplied buffer.
+ ///
+ /// The to read image data from.
+ /// The number of bytes to read from the input stream.
+ /// The output buffer for uncompressed data.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Decompress(Stream stream, int byteCount, byte[] buffer)
+ {
+ stream.ReadFull(buffer, byteCount);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/Compression/PackBitsTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/PackBitsTiffCompression.cs
new file mode 100644
index 0000000000..a6cd8f88d6
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Compression/PackBitsTiffCompression.cs
@@ -0,0 +1,77 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using System.IO;
+using System.Runtime.CompilerServices;
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Class to handle cases where TIFF image data is compressed using PackBits compression.
+ ///
+ internal static class PackBitsTiffCompression
+ {
+ ///
+ /// Decompresses image data into the supplied buffer.
+ ///
+ /// The to read image data from.
+ /// The number of bytes to read from the input stream.
+ /// The output buffer for uncompressed data.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Decompress(Stream stream, int byteCount, byte[] buffer)
+ {
+ byte[] compressedData = ArrayPool.Shared.Rent(byteCount);
+
+ try
+ {
+ stream.ReadFull(compressedData, byteCount);
+ int compressedOffset = 0;
+ int decompressedOffset = 0;
+
+ while (compressedOffset < byteCount)
+ {
+ byte headerByte = compressedData[compressedOffset];
+
+ if (headerByte <= (byte)127)
+ {
+ int literalOffset = compressedOffset + 1;
+ int literalLength = compressedData[compressedOffset] + 1;
+
+ Array.Copy(compressedData, literalOffset, buffer, decompressedOffset, literalLength);
+
+ compressedOffset += literalLength + 1;
+ decompressedOffset += literalLength;
+ }
+ else if (headerByte == (byte)0x80)
+ {
+ compressedOffset += 1;
+ }
+ else
+ {
+ byte repeatData = compressedData[compressedOffset + 1];
+ int repeatLength = 257 - headerByte;
+
+ ArrayCopyRepeat(repeatData, buffer, decompressedOffset, repeatLength);
+
+ compressedOffset += 2;
+ decompressedOffset += repeatLength;
+ }
+ }
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(compressedData);
+ }
+ }
+
+ private static void ArrayCopyRepeat(byte value, byte[] destinationArray, int destinationIndex, int length)
+ {
+ for (int i = 0; i < length; i++)
+ {
+ destinationArray[i + destinationIndex] = value;
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs
new file mode 100644
index 0000000000..4121f90b2d
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Provides enumeration of the various TIFF compression types.
+ ///
+ internal enum TiffCompressionType
+ {
+ ///
+ /// Image data is stored uncompressed in the TIFF file.
+ ///
+ None = 0,
+
+ ///
+ /// Image data is compressed using PackBits compression.
+ ///
+ PackBits = 1,
+
+ ///
+ /// Image data is compressed using Deflate compression.
+ ///
+ Deflate = 2,
+
+ ///
+ /// Image data is compressed using LZW compression.
+ ///
+ Lzw = 3,
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs
new file mode 100644
index 0000000000..e5ee8b1952
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs
@@ -0,0 +1,71 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Enumeration representing the compression formats defined by the Tiff file-format.
+ ///
+ internal enum TiffCompression
+ {
+ ///
+ /// No compression.
+ ///
+ None = 1,
+
+ ///
+ /// CCITT Group 3 1-Dimensional Modified Huffman run-length encoding.
+ ///
+ Ccitt1D = 2,
+
+ ///
+ /// PackBits compression
+ ///
+ PackBits = 32773,
+
+ ///
+ /// T4-encoding: CCITT T.4 bi-level encoding (see Section 11 of the TIFF 6.0 specification).
+ ///
+ CcittGroup3Fax = 3,
+
+ ///
+ /// T6-encoding: CCITT T.6 bi-level encoding (see Section 11 of the TIFF 6.0 specification).
+ ///
+ CcittGroup4Fax = 4,
+
+ ///
+ /// LZW compression (see Section 13 of the TIFF 6.0 specification).
+ ///
+ Lzw = 5,
+
+ ///
+ /// JPEG compression - obsolete (see Section 22 of the TIFF 6.0 specification).
+ ///
+ OldJpeg = 6,
+
+ ///
+ /// JPEG compression (see TIFF Specification, supplement 2).
+ ///
+ Jpeg = 7,
+
+ ///
+ /// Deflate compression, using zlib data format (see TIFF Specification, supplement 2).
+ ///
+ Deflate = 8,
+
+ ///
+ /// Deflate compression - old.
+ ///
+ OldDeflate = 32946,
+
+ ///
+ /// ITU-T Rec. T.82 coding, applying ITU-T Rec. T.85 (JBIG) (see RFC2301).
+ ///
+ ItuTRecT82 = 9,
+
+ ///
+ /// ITU-T Rec. T.43 representation, using ITU-T Rec. T.82 (JBIG) (see RFC2301).
+ ///
+ ItuTRecT43 = 10
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs
new file mode 100644
index 0000000000..a2044314ae
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs
@@ -0,0 +1,83 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Collections.Generic;
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Defines constants defined in the TIFF specification.
+ ///
+ internal static class TiffConstants
+ {
+ ///
+ /// Byte order markers for indicating little endian encoding.
+ ///
+ public const byte ByteOrderLittleEndian = 0x49;
+
+ ///
+ /// Byte order markers for indicating big endian encoding.
+ ///
+ public const byte ByteOrderBigEndian = 0x4D;
+
+ ///
+ /// Byte order markers for indicating little endian encoding.
+ ///
+ public const ushort ByteOrderLittleEndianShort = 0x4949;
+
+ ///
+ /// Byte order markers for indicating big endian encoding.
+ ///
+ public const ushort ByteOrderBigEndianShort = 0x4D4D;
+
+ ///
+ /// Magic number used within the image file header to identify a TIFF format file.
+ ///
+ public const ushort HeaderMagicNumber = 42;
+
+ ///
+ /// Size (in bytes) of the TIFF file header.
+ ///
+ public const int SizeOfTiffHeader = 8;
+
+ ///
+ /// Size (in bytes) of each individual TIFF IFD entry
+ ///
+ public const int SizeOfIfdEntry = 12;
+
+ ///
+ /// Size (in bytes) of the Short and SShort data types
+ ///
+ public const int SizeOfShort = 2;
+
+ ///
+ /// Size (in bytes) of the Long and SLong data types
+ ///
+ public const int SizeOfLong = 4;
+
+ ///
+ /// Size (in bytes) of the Rational and SRational data types
+ ///
+ public const int SizeOfRational = 8;
+
+ ///
+ /// Size (in bytes) of the Float data type
+ ///
+ public const int SizeOfFloat = 4;
+
+ ///
+ /// Size (in bytes) of the Double data type
+ ///
+ public const int SizeOfDouble = 8;
+
+ ///
+ /// The list of mimetypes that equate to a tiff.
+ ///
+ public static readonly IEnumerable MimeTypes = new[] { "image/tiff", "image/tiff-fx" };
+
+ ///
+ /// The list of file extensions that equate to a tiff.
+ ///
+ public static readonly IEnumerable FileExtensions = new[] { "tiff", "tif" };
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffExtraSamples.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffExtraSamples.cs
new file mode 100644
index 0000000000..d34d999b95
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Constants/TiffExtraSamples.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Enumeration representing the possible uses of extra components in TIFF format files.
+ ///
+ internal enum TiffExtraSamples
+ {
+ ///
+ /// Unspecified data.
+ ///
+ Unspecified = 0,
+
+ ///
+ /// Associated alpha data (with pre-multiplied color).
+ ///
+ AssociatedAlpha = 1,
+
+ ///
+ /// Unassociated alpha data.
+ ///
+ UnassociatedAlpha = 2
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffFillOrder.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffFillOrder.cs
new file mode 100644
index 0000000000..e4d30a324d
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Constants/TiffFillOrder.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Enumeration representing the fill orders defined by the Tiff file-format.
+ ///
+ internal enum TiffFillOrder
+ {
+ ///
+ /// Pixels with lower column values are stored in the higher-order bits of the byte.
+ ///
+ MostSignificantBitFirst = 1,
+
+ ///
+ /// Pixels with lower column values are stored in the lower-order bits of the byte.
+ ///
+ LeastSignificantBitFirst = 2
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffNewSubfileType.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffNewSubfileType.cs
new file mode 100644
index 0000000000..b881ac209f
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Constants/TiffNewSubfileType.cs
@@ -0,0 +1,44 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Enumeration representing the sub-file types defined by the Tiff file-format.
+ ///
+ [Flags]
+ internal enum TiffNewSubfileType
+ {
+ ///
+ /// A full-resolution image.
+ ///
+ FullImage = 0x0000,
+
+ ///
+ /// Reduced-resolution version of another image in this TIFF file.
+ ///
+ Preview = 0x0001,
+
+ ///
+ /// A single page of a multi-page image.
+ ///
+ SinglePage = 0x0002,
+
+ ///
+ /// A transparency mask for another image in this TIFF file.
+ ///
+ TransparencyMask = 0x0004,
+
+ ///
+ /// Alternative reduced-resolution version of another image in this TIFF file (see DNG specification).
+ ///
+ AlternativePreview = 0x10000,
+
+ ///
+ /// Mixed raster content (see RFC2301).
+ ///
+ MixedRasterContent = 0x0008
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffOrientation.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffOrientation.cs
new file mode 100644
index 0000000000..035f88809c
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Constants/TiffOrientation.cs
@@ -0,0 +1,51 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Enumeration representing the image orientations defined by the Tiff file-format.
+ ///
+ internal enum TiffOrientation
+ {
+ ///
+ /// The 0th row and 0th column represent the visual top and left-hand side of the image respectively.
+ ///
+ TopLeft = 1,
+
+ ///
+ /// The 0th row and 0th column represent the visual top and right-hand side of the image respectively.
+ ///
+ TopRight = 2,
+
+ ///
+ /// The 0th row and 0th column represent the visual bottom and right-hand side of the image respectively.
+ ///
+ BottomRight = 3,
+
+ ///
+ /// The 0th row and 0th column represent the visual bottom and left-hand side of the image respectively.
+ ///
+ BottomLeft = 4,
+
+ ///
+ /// The 0th row and 0th column represent the visual left-hand side and top of the image respectively.
+ ///
+ LeftTop = 5,
+
+ ///
+ /// The 0th row and 0th column represent the visual right-hand side and top of the image respectively.
+ ///
+ RightTop = 6,
+
+ ///
+ /// The 0th row and 0th column represent the visual right-hand side and bottom of the image respectively.
+ ///
+ RightBottom = 7,
+
+ ///
+ /// The 0th row and 0th column represent the visual left-hand side and bottom of the image respectively.
+ ///
+ LeftBottom = 8
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs
new file mode 100644
index 0000000000..dd4d923b8c
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs
@@ -0,0 +1,71 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Enumeration representing the photometric interpretation formats defined by the Tiff file-format.
+ ///
+ internal enum TiffPhotometricInterpretation
+ {
+ ///
+ /// Bilevel and grayscale: 0 is imaged as white. The maximum value is imaged as black.
+ ///
+ WhiteIsZero = 0,
+
+ ///
+ /// Bilevel and grayscale: 0 is imaged as black. The maximum value is imaged as white.
+ ///
+ BlackIsZero = 1,
+
+ ///
+ /// RGB
+ ///
+ Rgb = 2,
+
+ ///
+ /// Palette Color
+ ///
+ PaletteColor = 3,
+
+ ///
+ /// A transparency mask
+ ///
+ TransparencyMask = 4,
+
+ ///
+ /// Separated: usually CMYK (see Section 16 of the TIFF 6.0 specification).
+ ///
+ Separated = 5,
+
+ ///
+ /// YCbCr (see Section 21 of the TIFF 6.0 specification).
+ ///
+ YCbCr = 6,
+
+ ///
+ /// 1976 CIE L*a*b* (see Section 23 of the TIFF 6.0 specification).
+ ///
+ CieLab = 8,
+
+ ///
+ /// ICC L*a*b* (see TIFF Specification, supplement 1).
+ ///
+ IccLab = 9,
+
+ ///
+ /// ITU L*a*b* (see RFC2301).
+ ///
+ ItuLab = 10,
+
+ ///
+ /// Color Filter Array (see the DNG specification).
+ ///
+ ColorFilterArray = 32803,
+
+ ///
+ /// Linear Raw (see the DNG specification).
+ ///
+ LinearRaw = 34892
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs
new file mode 100644
index 0000000000..4fc0aa4c86
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs
@@ -0,0 +1,21 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Enumeration representing how the components of each pixel are stored the Tiff file-format.
+ ///
+ internal enum TiffPlanarConfiguration
+ {
+ ///
+ /// Chunky format.
+ ///
+ Chunky = 1,
+
+ ///
+ /// Planar format.
+ ///
+ Planar = 2
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffResolutionUnit.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffResolutionUnit.cs
new file mode 100644
index 0000000000..7bb3dbd6e3
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Constants/TiffResolutionUnit.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Enumeration representing the resolution units defined by the Tiff file-format.
+ ///
+ internal enum TiffResolutionUnit
+ {
+ ///
+ /// No absolute unit of measurement.
+ ///
+ None = 1,
+
+ ///
+ /// Inch.
+ ///
+ Inch = 2,
+
+ ///
+ /// Centimeter.
+ ///
+ Centimeter = 3
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs
new file mode 100644
index 0000000000..4039ae9e2d
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Enumeration representing the sub-file types defined by the Tiff file-format.
+ ///
+ internal enum TiffSubfileType
+ {
+ ///
+ /// Full-resolution image data.
+ ///
+ FullImage = 1,
+
+ ///
+ /// Reduced-resolution image data.
+ ///
+ Preview = 2,
+
+ ///
+ /// A single page of a multi-page image.
+ ///
+ SinglePage = 3
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffTags.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffTags.cs
new file mode 100644
index 0000000000..38cf4280e1
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Constants/TiffTags.cs
@@ -0,0 +1,716 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Constants representing tag IDs in the Tiff file-format.
+ ///
+ internal class TiffTags
+ {
+ ///
+ /// Artist (see Section 8: Baseline Fields).
+ ///
+ public const int Artist = 315;
+
+ ///
+ /// BitsPerSample (see Section 8: Baseline Fields).
+ ///
+ public const int BitsPerSample = 258;
+
+ ///
+ /// CellLength (see Section 8: Baseline Fields).
+ ///
+ public const int CellLength = 265;
+
+ ///
+ /// CellWidth (see Section 8: Baseline Fields).
+ ///
+ public const int CellWidth = 264;
+
+ ///
+ /// ColorMap (see Section 8: Baseline Fields).
+ ///
+ public const int ColorMap = 320;
+
+ ///
+ /// Compression (see Section 8: Baseline Fields).
+ ///
+ public const int Compression = 259;
+
+ ///
+ /// Copyright (see Section 8: Baseline Fields).
+ ///
+ public const int Copyright = 33432;
+
+ ///
+ /// DateTime (see Section 8: Baseline Fields).
+ ///
+ public const int DateTime = 306;
+
+ ///
+ /// ExtraSamples (see Section 8: Baseline Fields).
+ ///
+ public const int ExtraSamples = 338;
+
+ ///
+ /// FillOrder (see Section 8: Baseline Fields).
+ ///
+ public const int FillOrder = 266;
+
+ ///
+ /// FreeByteCounts (see Section 8: Baseline Fields).
+ ///
+ public const int FreeByteCounts = 289;
+
+ ///
+ /// FreeOffsets (see Section 8: Baseline Fields).
+ ///
+ public const int FreeOffsets = 288;
+
+ ///
+ /// GrayResponseCurve (see Section 8: Baseline Fields).
+ ///
+ public const int GrayResponseCurve = 291;
+
+ ///
+ /// GrayResponseUnit (see Section 8: Baseline Fields).
+ ///
+ public const int GrayResponseUnit = 290;
+
+ ///
+ /// HostComputer (see Section 8: Baseline Fields).
+ ///
+ public const int HostComputer = 316;
+
+ ///
+ /// ImageDescription (see Section 8: Baseline Fields).
+ ///
+ public const int ImageDescription = 270;
+
+ ///
+ /// ImageLength (see Section 8: Baseline Fields).
+ ///
+ public const int ImageLength = 257;
+
+ ///
+ /// ImageWidth (see Section 8: Baseline Fields).
+ ///
+ public const int ImageWidth = 256;
+
+ ///
+ /// Make (see Section 8: Baseline Fields).
+ ///
+ public const int Make = 271;
+
+ ///
+ /// MaxSampleValue (see Section 8: Baseline Fields).
+ ///
+ public const int MaxSampleValue = 281;
+
+ ///
+ /// MinSampleValue (see Section 8: Baseline Fields).
+ ///
+ public const int MinSampleValue = 280;
+
+ ///
+ /// Model (see Section 8: Baseline Fields).
+ ///
+ public const int Model = 272;
+
+ ///
+ /// NewSubfileType (see Section 8: Baseline Fields).
+ ///
+ public const int NewSubfileType = 254;
+
+ ///
+ /// Orientation (see Section 8: Baseline Fields).
+ ///
+ public const int Orientation = 274;
+
+ ///
+ /// PhotometricInterpretation (see Section 8: Baseline Fields).
+ ///
+ public const int PhotometricInterpretation = 262;
+
+ ///
+ /// PlanarConfiguration (see Section 8: Baseline Fields).
+ ///
+ public const int PlanarConfiguration = 284;
+
+ ///
+ /// ResolutionUnit (see Section 8: Baseline Fields).
+ ///
+ public const int ResolutionUnit = 296;
+
+ ///
+ /// RowsPerStrip (see Section 8: Baseline Fields).
+ ///
+ public const int RowsPerStrip = 278;
+
+ ///
+ /// SamplesPerPixel (see Section 8: Baseline Fields).
+ ///
+ public const int SamplesPerPixel = 277;
+
+ ///
+ /// Software (see Section 8: Baseline Fields).
+ ///
+ public const int Software = 305;
+
+ ///
+ /// StripByteCounts (see Section 8: Baseline Fields).
+ ///
+ public const int StripByteCounts = 279;
+
+ ///
+ /// StripOffsets (see Section 8: Baseline Fields).
+ ///
+ public const int StripOffsets = 273;
+
+ ///
+ /// SubfileType (see Section 8: Baseline Fields).
+ ///
+ public const int SubfileType = 255;
+
+ ///
+ /// Threshholding (see Section 8: Baseline Fields).
+ ///
+ public const int Threshholding = 263;
+
+ ///
+ /// XResolution (see Section 8: Baseline Fields).
+ ///
+ public const int XResolution = 282;
+
+ ///
+ /// YResolution (see Section 8: Baseline Fields).
+ ///
+ public const int YResolution = 283;
+
+ ///
+ /// T4Options (see Section 11: CCITT Bilevel Encodings).
+ ///
+ public const int T4Options = 292;
+
+ ///
+ /// T6Options (see Section 11: CCITT Bilevel Encodings).
+ ///
+ public const int T6Options = 293;
+
+ ///
+ /// DocumentName (see Section 12: Document Storage and Retrieval).
+ ///
+ public const int DocumentName = 269;
+
+ ///
+ /// PageName (see Section 12: Document Storage and Retrieval).
+ ///
+ public const int PageName = 285;
+
+ ///
+ /// PageNumber (see Section 12: Document Storage and Retrieval).
+ ///
+ public const int PageNumber = 297;
+
+ ///
+ /// XPosition (see Section 12: Document Storage and Retrieval).
+ ///
+ public const int XPosition = 286;
+
+ ///
+ /// YPosition (see Section 12: Document Storage and Retrieval).
+ ///
+ public const int YPosition = 287;
+
+ ///
+ /// Predictor (see Section 14: Differencing Predictor).
+ ///
+ public const int Predictor = 317;
+
+ ///
+ /// TileWidth (see Section 15: Tiled Images).
+ ///
+ public const int TileWidth = 322;
+
+ ///
+ /// TileLength (see Section 15: Tiled Images).
+ ///
+ public const int TileLength = 323;
+
+ ///
+ /// TileOffsets (see Section 15: Tiled Images).
+ ///
+ public const int TileOffsets = 324;
+
+ ///
+ /// TileByteCounts (see Section 15: Tiled Images).
+ ///
+ public const int TileByteCounts = 325;
+
+ ///
+ /// InkSet (see Section 16: CMYK Images).
+ ///
+ public const int InkSet = 332;
+
+ ///
+ /// NumberOfInks (see Section 16: CMYK Images).
+ ///
+ public const int NumberOfInks = 334;
+
+ ///
+ /// InkNames (see Section 16: CMYK Images).
+ ///
+ public const int InkNames = 333;
+
+ ///
+ /// DotRange (see Section 16: CMYK Images).
+ ///
+ public const int DotRange = 336;
+
+ ///
+ /// TargetPrinter (see Section 16: CMYK Images).
+ ///
+ public const int TargetPrinter = 337;
+
+ ///
+ /// HalftoneHints (see Section 17: Halftone Hints).
+ ///
+ public const int HalftoneHints = 321;
+
+ ///
+ /// SampleFormat (see Section 19: Data Sample Format).
+ ///
+ public const int SampleFormat = 339;
+
+ ///
+ /// SMinSampleValue (see Section 19: Data Sample Format).
+ ///
+ public const int SMinSampleValue = 340;
+
+ ///
+ /// SMaxSampleValue (see Section 19: Data Sample Format).
+ ///
+ public const int SMaxSampleValue = 341;
+
+ ///
+ /// WhitePoint (see Section 20: RGB Image Colorimetry).
+ ///
+ public const int WhitePoint = 318;
+
+ ///
+ /// PrimaryChromaticities (see Section 20: RGB Image Colorimetry).
+ ///
+ public const int PrimaryChromaticities = 319;
+
+ ///
+ /// TransferFunction (see Section 20: RGB Image Colorimetry).
+ ///
+ public const int TransferFunction = 301;
+
+ ///
+ /// TransferRange (see Section 20: RGB Image Colorimetry).
+ ///
+ public const int TransferRange = 342;
+
+ ///
+ /// ReferenceBlackWhite (see Section 20: RGB Image Colorimetry).
+ ///
+ public const int ReferenceBlackWhite = 532;
+
+ ///
+ /// YCbCrCoefficients (see Section 21: YCbCr Images).
+ ///
+ public const int YCbCrCoefficients = 529;
+
+ ///
+ /// YCbCrSubSampling (see Section 21: YCbCr Images).
+ ///
+ public const int YCbCrSubSampling = 530;
+
+ ///
+ /// YCbCrPositioning (see Section 21: YCbCr Images).
+ ///
+ public const int YCbCrPositioning = 531;
+
+ ///
+ /// JpegProc (see Section 22: JPEG Compression).
+ ///
+ public const int JpegProc = 512;
+
+ ///
+ /// JpegInterchangeFormat (see Section 22: JPEG Compression).
+ ///
+ public const int JpegInterchangeFormat = 513;
+
+ ///
+ /// JpegInterchangeFormatLength (see Section 22: JPEG Compression).
+ ///
+ public const int JpegInterchangeFormatLength = 514;
+
+ ///
+ /// JpegRestartInterval (see Section 22: JPEG Compression).
+ ///
+ public const int JpegRestartInterval = 515;
+
+ ///
+ /// JpegLosslessPredictors (see Section 22: JPEG Compression).
+ ///
+ public const int JpegLosslessPredictors = 517;
+
+ ///
+ /// JpegPointTransforms (see Section 22: JPEG Compression).
+ ///
+ public const int JpegPointTransforms = 518;
+
+ ///
+ /// JpegQTables (see Section 22: JPEG Compression).
+ ///
+ public const int JpegQTables = 519;
+
+ ///
+ /// JpegDCTables (see Section 22: JPEG Compression).
+ ///
+ public const int JpegDCTables = 520;
+
+ ///
+ /// JpegACTables (see Section 22: JPEG Compression).
+ ///
+ public const int JpegACTables = 521;
+
+ ///
+ /// SubIFDs (see TIFF Supplement 1: Adobe Pagemaker 6.0).
+ ///
+ public const int SubIFDs = 330;
+
+ ///
+ /// ClipPath (see TIFF Supplement 1: Adobe Pagemaker 6.0).
+ ///
+ public const int ClipPath = 343;
+
+ ///
+ /// XClipPathUnits (see TIFF Supplement 1: Adobe Pagemaker 6.0).
+ ///
+ public const int XClipPathUnits = 344;
+
+ ///
+ /// YClipPathUnits (see TIFF Supplement 1: Adobe Pagemaker 6.0).
+ ///
+ public const int YClipPathUnits = 345;
+
+ ///
+ /// Indexed (see TIFF Supplement 1: Adobe Pagemaker 6.0).
+ ///
+ public const int Indexed = 346;
+
+ ///
+ /// ImageID (see TIFF Supplement 1: Adobe Pagemaker 6.0).
+ ///
+ public const int ImageID = 32781;
+
+ ///
+ /// OpiProxy (see TIFF Supplement 1: Adobe Pagemaker 6.0).
+ ///
+ public const int OpiProxy = 351;
+
+ ///
+ /// ImageSourceData (see TIFF Supplement 2: Adobe Photoshop).
+ ///
+ public const int ImageSourceData = 37724;
+
+ ///
+ /// JPEGTables (see TIFF/EP Specification: Additional Tags).
+ ///
+ public const int JPEGTables = 0x015B;
+
+ ///
+ /// CFARepeatPatternDim (see TIFF/EP Specification: Additional Tags).
+ ///
+ public const int CFARepeatPatternDim = 0x828D;
+
+ ///
+ /// BatteryLevel (see TIFF/EP Specification: Additional Tags).
+ ///
+ public const int BatteryLevel = 0x828F;
+
+ ///
+ /// Interlace (see TIFF/EP Specification: Additional Tags).
+ ///
+ public const int Interlace = 0x8829;
+
+ ///
+ /// TimeZoneOffset (see TIFF/EP Specification: Additional Tags).
+ ///
+ public const int TimeZoneOffset = 0x882A;
+
+ ///
+ /// SelfTimerMode (see TIFF/EP Specification: Additional Tags).
+ ///
+ public const int SelfTimerMode = 0x882B;
+
+ ///
+ /// Noise (see TIFF/EP Specification: Additional Tags).
+ ///
+ public const int Noise = 0x920D;
+
+ ///
+ /// ImageNumber (see TIFF/EP Specification: Additional Tags).
+ ///
+ public const int ImageNumber = 0x9211;
+
+ ///
+ /// SecurityClassification (see TIFF/EP Specification: Additional Tags).
+ ///
+ public const int SecurityClassification = 0x9212;
+
+ ///
+ /// ImageHistory (see TIFF/EP Specification: Additional Tags).
+ ///
+ public const int ImageHistory = 0x9213;
+
+ ///
+ /// TiffEPStandardID (see TIFF/EP Specification: Additional Tags).
+ ///
+ public const int TiffEPStandardID = 0x9216;
+
+ ///
+ /// BadFaxLines (see RFC2301: TIFF-F/FX Specification).
+ ///
+ public const int BadFaxLines = 326;
+
+ ///
+ /// CleanFaxData (see RFC2301: TIFF-F/FX Specification).
+ ///
+ public const int CleanFaxData = 327;
+
+ ///
+ /// ConsecutiveBadFaxLines (see RFC2301: TIFF-F/FX Specification).
+ ///
+ public const int ConsecutiveBadFaxLines = 328;
+
+ ///
+ /// GlobalParametersIFD (see RFC2301: TIFF-F/FX Specification).
+ ///
+ public const int GlobalParametersIFD = 400;
+
+ ///
+ /// ProfileType (see RFC2301: TIFF-F/FX Specification).
+ ///
+ public const int ProfileType = 401;
+
+ ///
+ /// FaxProfile (see RFC2301: TIFF-F/FX Specification).
+ ///
+ public const int FaxProfile = 402;
+
+ ///
+ /// CodingMethod (see RFC2301: TIFF-F/FX Specification).
+ ///
+ public const int CodingMethod = 403;
+
+ ///
+ /// VersionYear (see RFC2301: TIFF-F/FX Specification).
+ ///
+ public const int VersionYear = 404;
+
+ ///
+ /// ModeNumber (see RFC2301: TIFF-F/FX Specification).
+ ///
+ public const int ModeNumber = 405;
+
+ ///
+ /// Decode (see RFC2301: TIFF-F/FX Specification).
+ ///
+ public const int Decode = 433;
+
+ ///
+ /// DefaultImageColor (see RFC2301: TIFF-F/FX Specification).
+ ///
+ public const int DefaultImageColor = 434;
+
+ ///
+ /// StripRowCounts (see RFC2301: TIFF-F/FX Specification).
+ ///
+ public const int StripRowCounts = 559;
+
+ ///
+ /// ImageLayer (see RFC2301: TIFF-F/FX Specification).
+ ///
+ public const int ImageLayer = 34732;
+
+ ///
+ /// Xmp (Embedded Metadata).
+ ///
+ public const int Xmp = 700;
+
+ ///
+ /// Iptc (Embedded Metadata).
+ ///
+ public const int Iptc = 33723;
+
+ ///
+ /// Photoshop (Embedded Metadata).
+ ///
+ public const int Photoshop = 34377;
+
+ ///
+ /// ExifIFD (Embedded Metadata).
+ ///
+ public const int ExifIFD = 34665;
+
+ ///
+ /// GpsIFD (Embedded Metadata).
+ ///
+ public const int GpsIFD = 34853;
+
+ ///
+ /// InteroperabilityIFD (Embedded Metadata).
+ ///
+ public const int InteroperabilityIFD = 40965;
+
+ ///
+ /// WangAnnotation (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int WangAnnotation = 32932;
+
+ ///
+ /// MDFileTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int MDFileTag = 33445;
+
+ ///
+ /// MDScalePixel (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int MDScalePixel = 33446;
+
+ ///
+ /// MDColorTable (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int MDColorTable = 33447;
+
+ ///
+ /// MDLabName (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int MDLabName = 33448;
+
+ ///
+ /// MDSampleInfo (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int MDSampleInfo = 33449;
+
+ ///
+ /// MDPrepDate (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int MDPrepDate = 33450;
+
+ ///
+ /// MDPrepTime (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int MDPrepTime = 33451;
+
+ ///
+ /// MDFileUnits (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int MDFileUnits = 33452;
+
+ ///
+ /// ModelPixelScaleTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int ModelPixelScaleTag = 33550;
+
+ ///
+ /// IngrPacketDataTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int IngrPacketDataTag = 33918;
+
+ ///
+ /// IngrFlagRegisters (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int IngrFlagRegisters = 33919;
+
+ ///
+ /// IrasBTransformationMatrix (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int IrasBTransformationMatrix = 33920;
+
+ ///
+ /// ModelTiePointTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int ModelTiePointTag = 33922;
+
+ ///
+ /// ModelTransformationTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int ModelTransformationTag = 34264;
+
+ ///
+ /// IccProfile (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int IccProfile = 34675;
+
+ ///
+ /// GeoKeyDirectoryTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int GeoKeyDirectoryTag = 34735;
+
+ ///
+ /// GeoDoubleParamsTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int GeoDoubleParamsTag = 34736;
+
+ ///
+ /// GeoAsciiParamsTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int GeoAsciiParamsTag = 34737;
+
+ ///
+ /// HylaFAXFaxRecvParams (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int HylaFAXFaxRecvParams = 34908;
+
+ ///
+ /// HylaFAXFaxSubAddress (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int HylaFAXFaxSubAddress = 34909;
+
+ ///
+ /// HylaFAXFaxRecvTime (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int HylaFAXFaxRecvTime = 34910;
+
+ ///
+ /// GdalMetadata (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int GdalMetadata = 42112;
+
+ ///
+ /// GdalNodata (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int GdalNodata = 42113;
+
+ ///
+ /// OceScanjobDescription (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int OceScanjobDescription = 50215;
+
+ ///
+ /// OceApplicationSelector (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int OceApplicationSelector = 50216;
+
+ ///
+ /// OceIdentificationNumber (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int OceIdentificationNumber = 50217;
+
+ ///
+ /// OceImageLogicCharacteristics (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int OceImageLogicCharacteristics = 50218;
+
+ ///
+ /// AliasLayerMetadata (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
+ ///
+ public const int AliasLayerMetadata = 50784;
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffThreshholding.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffThreshholding.cs
new file mode 100644
index 0000000000..0a398d231b
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Constants/TiffThreshholding.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Enumeration representing the threshholding applied to image data defined by the Tiff file-format.
+ ///
+ internal enum TiffThreshholding
+ {
+ ///
+ /// No dithering or halftoning.
+ ///
+ None = 1,
+
+ ///
+ /// An ordered dither or halftone technique.
+ ///
+ Ordered = 2,
+
+ ///
+ /// A randomized process such as error diffusion.
+ ///
+ Random = 3
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffType.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffType.cs
new file mode 100644
index 0000000000..8e55d80cc7
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Constants/TiffType.cs
@@ -0,0 +1,76 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Enumeration representing the data types understood by the Tiff file-format.
+ ///
+ internal enum TiffType
+ {
+ ///
+ /// Unsigned 8-bit integer.
+ ///
+ Byte = 1,
+
+ ///
+ /// ASCII formatted text.
+ ///
+ Ascii = 2,
+
+ ///
+ /// Unsigned 16-bit integer.
+ ///
+ Short = 3,
+
+ ///
+ /// Unsigned 32-bit integer.
+ ///
+ Long = 4,
+
+ ///
+ /// Unsigned rational number.
+ ///
+ Rational = 5,
+
+ ///
+ /// Signed 8-bit integer.
+ ///
+ SByte = 6,
+
+ ///
+ /// Undefined data type.
+ ///
+ Undefined = 7,
+
+ ///
+ /// Signed 16-bit integer.
+ ///
+ SShort = 8,
+
+ ///
+ /// Signed 32-bit integer.
+ ///
+ SLong = 9,
+
+ ///
+ /// Signed rational number.
+ ///
+ SRational = 10,
+
+ ///
+ /// Single precision (4-byte) IEEE format.
+ ///
+ Float = 11,
+
+ ///
+ /// Double precision (8-byte) IEEE format.
+ ///
+ Double = 12,
+
+ ///
+ /// Reference to an IFD.
+ ///
+ Ifd = 13
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs b/src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs
new file mode 100644
index 0000000000..c718102b8b
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats
+{
+ ///
+ /// Encapsulates the options for the .
+ ///
+ public interface ITiffDecoderOptions
+ {
+ ///
+ /// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
+ ///
+ bool IgnoreMetadata { get; }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs b/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
new file mode 100644
index 0000000000..e10396d5fc
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats
+{
+ ///
+ /// Encapsulates the options for the .
+ ///
+ public interface ITiffEncoderOptions
+ {
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/ImageExtensions.cs b/src/ImageSharp/Formats/Tiff/ImageExtensions.cs
new file mode 100644
index 0000000000..3414f84d3b
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/ImageExtensions.cs
@@ -0,0 +1,51 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.IO;
+using SixLabors.ImageSharp.Formats;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp
+{
+ ///
+ /// Extension methods for the type.
+ ///
+ public static partial class ImageExtensions
+ {
+ ///
+ /// Saves the image to the given stream with the tiff format.
+ ///
+ /// The pixel format.
+ /// The image this method extends.
+ /// The stream to save the image to.
+ /// Thrown if the stream is null.
+ ///
+ /// The .
+ ///
+ public static Image SaveAsTiff(this Image source, Stream stream)
+ where TPixel : struct, IPixel
+ {
+ return SaveAsTiff(source, stream, null);
+ }
+
+ ///
+ /// Saves the image to the given stream with the tiff format.
+ ///
+ /// The pixel format.
+ /// The image this method extends.
+ /// The stream to save the image to.
+ /// The options for the encoder.
+ /// Thrown if the stream is null.
+ ///
+ /// The .
+ ///
+ public static Image SaveAsTiff(this Image source, Stream stream, TiffEncoder encoder)
+ where TPixel : struct, IPixel
+ {
+ encoder = encoder ?? new TiffEncoder();
+ encoder.Encode(source, stream);
+
+ return source;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor.cs
new file mode 100644
index 0000000000..e317a7af7f
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor.cs
@@ -0,0 +1,52 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Implements the 'BlackIsZero' photometric interpretation (optimised for bilevel images).
+ ///
+ internal static class BlackIsZero1TiffColor
+ {
+ ///
+ /// Decodes pixel data using the current photometric interpretation.
+ ///
+ /// The pixel format.
+ /// The buffer to read image data from.
+ /// The image buffer to write pixels to.
+ /// The x-coordinate of the left-hand side of the image block.
+ /// The y-coordinate of the top of the image block.
+ /// The width of the image block.
+ /// The height of the image block.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Decode(byte[] data, Buffer2D pixels, int left, int top, int width, int height)
+ where TPixel : struct, IPixel
+ {
+ TPixel color = default(TPixel);
+
+ uint offset = 0;
+
+ for (int y = top; y < top + height; y++)
+ {
+ for (int x = left; x < left + width; x += 8)
+ {
+ byte b = data[offset++];
+ int maxShift = Math.Min(left + width - x, 8);
+
+ for (int shift = 0; shift < maxShift; shift++)
+ {
+ int bit = (b >> (7 - shift)) & 1;
+ byte intensity = (bit == 1) ? (byte)255 : (byte)0;
+ color.FromRgba32(new Rgba32(intensity, intensity, intensity, 255));
+ pixels[x + shift, y] = color;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor.cs
new file mode 100644
index 0000000000..62fff4737c
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor.cs
@@ -0,0 +1,60 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Implements the 'BlackIsZero' photometric interpretation (optimised for 4-bit grayscale images).
+ ///
+ internal static class BlackIsZero4TiffColor
+ {
+ ///
+ /// Decodes pixel data using the current photometric interpretation.
+ ///
+ /// The pixel format.
+ /// The buffer to read image data from.
+ /// The image buffer to write pixels to.
+ /// The x-coordinate of the left-hand side of the image block.
+ /// The y-coordinate of the top of the image block.
+ /// The width of the image block.
+ /// The height of the image block.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Decode(byte[] data, Buffer2D pixels, int left, int top, int width, int height)
+ where TPixel : struct, IPixel
+ {
+ TPixel color = default(TPixel);
+
+ uint offset = 0;
+ bool isOddWidth = (width & 1) == 1;
+
+ for (int y = top; y < top + height; y++)
+ {
+ for (int x = left; x < left + width - 1; x += 2)
+ {
+ byte byteData = data[offset++];
+
+ byte intensity1 = (byte)(((byteData & 0xF0) >> 4) * 17);
+ color.FromRgba32(new Rgba32(intensity1, intensity1, intensity1, 255));
+ pixels[x, y] = color;
+
+ byte intensity2 = (byte)((byteData & 0x0F) * 17);
+ color.FromRgba32(new Rgba32(intensity2, intensity2, intensity2, 255));
+ pixels[x + 1, y] = color;
+ }
+
+ if (isOddWidth)
+ {
+ byte byteData = data[offset++];
+
+ byte intensity1 = (byte)(((byteData & 0xF0) >> 4) * 17);
+ color.FromRgba32(new Rgba32(intensity1, intensity1, intensity1, 255));
+ pixels[left + width - 1, y] = color;
+ }
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor.cs
new file mode 100644
index 0000000000..949549d17b
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor.cs
@@ -0,0 +1,44 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Implements the 'BlackIsZero' photometric interpretation (optimised for 8-bit grayscale images).
+ ///
+ internal static class BlackIsZero8TiffColor
+ {
+ ///
+ /// Decodes pixel data using the current photometric interpretation.
+ ///
+ /// The pixel format.
+ /// The buffer to read image data from.
+ /// The image buffer to write pixels to.
+ /// The x-coordinate of the left-hand side of the image block.
+ /// The y-coordinate of the top of the image block.
+ /// The width of the image block.
+ /// The height of the image block.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Decode(byte[] data, Buffer2D pixels, int left, int top, int width, int height)
+ where TPixel : struct, IPixel
+ {
+ TPixel color = default(TPixel);
+
+ uint offset = 0;
+
+ for (int y = top; y < top + height; y++)
+ {
+ for (int x = left; x < left + width; x++)
+ {
+ byte intensity = data[offset++];
+ color.FromRgba32(new Rgba32(intensity, intensity, intensity, 255));
+ pixels[x, y] = color;
+ }
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor.cs
new file mode 100644
index 0000000000..689a305ca7
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor.cs
@@ -0,0 +1,51 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Implements the 'BlackIsZero' photometric interpretation (for all bit depths).
+ ///
+ internal static class BlackIsZeroTiffColor
+ {
+ ///
+ /// Decodes pixel data using the current photometric interpretation.
+ ///
+ /// The pixel format.
+ /// The buffer to read image data from.
+ /// The number of bits per sample for each pixel.
+ /// The image buffer to write pixels to.
+ /// The x-coordinate of the left-hand side of the image block.
+ /// The y-coordinate of the top of the image block.
+ /// The width of the image block.
+ /// The height of the image block.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Decode(byte[] data, uint[] bitsPerSample, Buffer2D pixels, int left, int top, int width, int height)
+ where TPixel : struct, IPixel
+ {
+ TPixel color = default(TPixel);
+
+ BitReader bitReader = new BitReader(data);
+ float factor = (float)Math.Pow(2, bitsPerSample[0]) - 1.0f;
+
+ for (int y = top; y < top + height; y++)
+ {
+ for (int x = left; x < left + width; x++)
+ {
+ int value = bitReader.ReadBits(bitsPerSample[0]);
+ float intensity = ((float)value) / factor;
+ color.FromVector4(new Vector4(intensity, intensity, intensity, 1.0f));
+ pixels[x, y] = color;
+ }
+
+ bitReader.NextRow();
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor.cs
new file mode 100644
index 0000000000..6c4bb56127
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor.cs
@@ -0,0 +1,71 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Implements the 'PaletteTiffColor' photometric interpretation (for all bit depths).
+ ///
+ internal static class PaletteTiffColor
+ {
+ ///
+ /// Decodes pixel data using the current photometric interpretation.
+ ///
+ /// The pixel format.
+ /// The buffer to read image data from.
+ /// The number of bits per sample for each pixel.
+ /// The RGB color lookup table to use for decoding the image.
+ /// The image buffer to write pixels to.
+ /// The x-coordinate of the left-hand side of the image block.
+ /// The y-coordinate of the top of the image block.
+ /// The width of the image block.
+ /// The height of the image block.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Decode(byte[] data, uint[] bitsPerSample, uint[] colorMap, Buffer2D pixels, int left, int top, int width, int height)
+ where TPixel : struct, IPixel
+ {
+ int colorCount = (int)Math.Pow(2, bitsPerSample[0]);
+ TPixel[] palette = GeneratePalette(colorMap, colorCount);
+
+ BitReader bitReader = new BitReader(data);
+
+ for (int y = top; y < top + height; y++)
+ {
+ for (int x = left; x < left + width; x++)
+ {
+ int index = bitReader.ReadBits(bitsPerSample[0]);
+ pixels[x, y] = palette[index];
+ }
+
+ bitReader.NextRow();
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static TPixel[] GeneratePalette(uint[] colorMap, int colorCount)
+ where TPixel : struct, IPixel
+ {
+ TPixel[] palette = new TPixel[colorCount];
+
+ int rOffset = 0;
+ int gOffset = colorCount;
+ int bOffset = colorCount * 2;
+
+ for (int i = 0; i < palette.Length; i++)
+ {
+ float r = colorMap[rOffset + i] / 65535F;
+ float g = colorMap[gOffset + i] / 65535F;
+ float b = colorMap[bOffset + i] / 65535F;
+ palette[i].FromVector4(new Vector4(r, g, b, 1.0f));
+ }
+
+ return palette;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor.cs
new file mode 100644
index 0000000000..7582220f7b
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Implements the 'RGB' photometric interpretation (optimised for 8-bit full color images).
+ ///
+ internal static class Rgb888TiffColor
+ {
+ ///
+ /// Decodes pixel data using the current photometric interpretation.
+ ///
+ /// The pixel format.
+ /// The buffer to read image data from.
+ /// The image buffer to write pixels to.
+ /// The x-coordinate of the left-hand side of the image block.
+ /// The y-coordinate of the top of the image block.
+ /// The width of the image block.
+ /// The height of the image block.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Decode(byte[] data, Buffer2D pixels, int left, int top, int width, int height)
+ where TPixel : struct, IPixel
+ {
+ TPixel color = default(TPixel);
+
+ uint offset = 0;
+
+ for (int y = top; y < top + height; y++)
+ {
+ for (int x = left; x < left + width; x++)
+ {
+ byte r = data[offset++];
+ byte g = data[offset++];
+ byte b = data[offset++];
+ color.FromRgba32(new Rgba32(r, g, b, 255));
+ pixels[x, y] = color;
+ }
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor.cs
new file mode 100644
index 0000000000..df7671d760
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor.cs
@@ -0,0 +1,58 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Implements the 'RGB' photometric interpretation with 'Planar' layout (for all bit depths).
+ ///
+ internal static class RgbPlanarTiffColor
+ {
+ ///
+ /// Decodes pixel data using the current photometric interpretation.
+ ///
+ /// The pixel format.
+ /// The buffers to read image data from.
+ /// The number of bits per sample for each pixel.
+ /// The image buffer to write pixels to.
+ /// The x-coordinate of the left-hand side of the image block.
+ /// The y-coordinate of the top of the image block.
+ /// The width of the image block.
+ /// The height of the image block.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Decode(byte[][] data, uint[] bitsPerSample, Buffer2D pixels, int left, int top, int width, int height)
+ where TPixel : struct, IPixel
+ {
+ TPixel color = default(TPixel);
+
+ BitReader rBitReader = new BitReader(data[0]);
+ BitReader gBitReader = new BitReader(data[1]);
+ BitReader bBitReader = new BitReader(data[2]);
+ float rFactor = (float)Math.Pow(2, bitsPerSample[0]) - 1.0f;
+ float gFactor = (float)Math.Pow(2, bitsPerSample[1]) - 1.0f;
+ float bFactor = (float)Math.Pow(2, bitsPerSample[2]) - 1.0f;
+
+ for (int y = top; y < top + height; y++)
+ {
+ for (int x = left; x < left + width; x++)
+ {
+ float r = ((float)rBitReader.ReadBits(bitsPerSample[0])) / rFactor;
+ float g = ((float)gBitReader.ReadBits(bitsPerSample[1])) / gFactor;
+ float b = ((float)bBitReader.ReadBits(bitsPerSample[2])) / bFactor;
+ color.FromVector4(new Vector4(r, g, b, 1.0f));
+ pixels[x, y] = color;
+ }
+
+ rBitReader.NextRow();
+ gBitReader.NextRow();
+ bBitReader.NextRow();
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor.cs
new file mode 100644
index 0000000000..ec33417998
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Implements the 'RGB' photometric interpretation (for all bit depths).
+ ///
+ internal static class RgbTiffColor
+ {
+ ///
+ /// Decodes pixel data using the current photometric interpretation.
+ ///
+ /// The pixel format.
+ /// The buffer to read image data from.
+ /// The number of bits per sample for each pixel.
+ /// The image buffer to write pixels to.
+ /// The x-coordinate of the left-hand side of the image block.
+ /// The y-coordinate of the top of the image block.
+ /// The width of the image block.
+ /// The height of the image block.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Decode(byte[] data, uint[] bitsPerSample, Buffer2D pixels, int left, int top, int width, int height)
+ where TPixel : struct, IPixel
+ {
+ TPixel color = default(TPixel);
+
+ BitReader bitReader = new BitReader(data);
+ float rFactor = (float)Math.Pow(2, bitsPerSample[0]) - 1.0f;
+ float gFactor = (float)Math.Pow(2, bitsPerSample[1]) - 1.0f;
+ float bFactor = (float)Math.Pow(2, bitsPerSample[2]) - 1.0f;
+
+ for (int y = top; y < top + height; y++)
+ {
+ for (int x = left; x < left + width; x++)
+ {
+ float r = ((float)bitReader.ReadBits(bitsPerSample[0])) / rFactor;
+ float g = ((float)bitReader.ReadBits(bitsPerSample[1])) / gFactor;
+ float b = ((float)bitReader.ReadBits(bitsPerSample[2])) / bFactor;
+ color.FromVector4(new Vector4(r, g, b, 1.0f));
+ pixels[x, y] = color;
+ }
+
+ bitReader.NextRow();
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs
new file mode 100644
index 0000000000..7aea15885a
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs
@@ -0,0 +1,71 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Provides enumeration of the various TIFF photometric interpretation implementation types.
+ ///
+ internal enum TiffColorType
+ {
+ ///
+ /// Grayscale: 0 is imaged as black. The maximum value is imaged as white.
+ ///
+ BlackIsZero,
+
+ ///
+ /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimised implementation for bilevel images.
+ ///
+ BlackIsZero1,
+
+ ///
+ /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimised implementation for 4-bit images.
+ ///
+ BlackIsZero4,
+
+ ///
+ /// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimised implementation for 8-bit images.
+ ///
+ BlackIsZero8,
+
+ ///
+ /// Grayscale: 0 is imaged as white. The maximum value is imaged as black.
+ ///
+ WhiteIsZero,
+
+ ///
+ /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimised implementation for bilevel images.
+ ///
+ WhiteIsZero1,
+
+ ///
+ /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimised implementation for 4-bit images.
+ ///
+ WhiteIsZero4,
+
+ ///
+ /// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimised implementation for 8-bit images.
+ ///
+ WhiteIsZero8,
+
+ ///
+ /// Palette-color.
+ ///
+ PaletteColor,
+
+ ///
+ /// RGB Full Color.
+ ///
+ Rgb,
+
+ ///
+ /// RGB Full Color. Optimised implementation for 8-bit images.
+ ///
+ Rgb888,
+
+ ///
+ /// RGB Full Color. Planar configuration of data.
+ ///
+ RgbPlanar,
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor.cs
new file mode 100644
index 0000000000..2d9914de71
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor.cs
@@ -0,0 +1,52 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Implements the 'WhiteIsZero' photometric interpretation (optimised for bilevel images).
+ ///
+ internal static class WhiteIsZero1TiffColor
+ {
+ ///
+ /// Decodes pixel data using the current photometric interpretation.
+ ///
+ /// The pixel format.
+ /// The buffer to read image data from.
+ /// The image buffer to write pixels to.
+ /// The x-coordinate of the left-hand side of the image block.
+ /// The y-coordinate of the top of the image block.
+ /// The width of the image block.
+ /// The height of the image block.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Decode(byte[] data, Buffer2D pixels, int left, int top, int width, int height)
+ where TPixel : struct, IPixel
+ {
+ TPixel color = default(TPixel);
+
+ uint offset = 0;
+
+ for (int y = top; y < top + height; y++)
+ {
+ for (int x = left; x < left + width; x += 8)
+ {
+ byte b = data[offset++];
+ int maxShift = Math.Min(left + width - x, 8);
+
+ for (int shift = 0; shift < maxShift; shift++)
+ {
+ int bit = (b >> (7 - shift)) & 1;
+ byte intensity = (bit == 1) ? (byte)0 : (byte)255;
+ color.FromRgba32(new Rgba32(intensity, intensity, intensity, 255));
+ pixels[x + shift, y] = color;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor.cs
new file mode 100644
index 0000000000..965abec819
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor.cs
@@ -0,0 +1,60 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Implements the 'WhiteIsZero' photometric interpretation (optimised for 4-bit grayscale images).
+ ///
+ internal static class WhiteIsZero4TiffColor
+ {
+ ///
+ /// Decodes pixel data using the current photometric interpretation.
+ ///
+ /// The pixel format.
+ /// The buffer to read image data from.
+ /// The image buffer to write pixels to.
+ /// The x-coordinate of the left-hand side of the image block.
+ /// The y-coordinate of the top of the image block.
+ /// The width of the image block.
+ /// The height of the image block.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Decode(byte[] data, Buffer2D pixels, int left, int top, int width, int height)
+ where TPixel : struct, IPixel
+ {
+ TPixel color = default(TPixel);
+
+ uint offset = 0;
+ bool isOddWidth = (width & 1) == 1;
+
+ for (int y = top; y < top + height; y++)
+ {
+ for (int x = left; x < left + width - 1; x += 2)
+ {
+ byte byteData = data[offset++];
+
+ byte intensity1 = (byte)((15 - ((byteData & 0xF0) >> 4)) * 17);
+ color.FromRgba32(new Rgba32(intensity1, intensity1, intensity1, 255));
+ pixels[x, y] = color;
+
+ byte intensity2 = (byte)((15 - (byteData & 0x0F)) * 17);
+ color.FromRgba32(new Rgba32(intensity2, intensity2, intensity2, 255));
+ pixels[x + 1, y] = color;
+ }
+
+ if (isOddWidth)
+ {
+ byte byteData = data[offset++];
+
+ byte intensity1 = (byte)((15 - ((byteData & 0xF0) >> 4)) * 17);
+ color.FromRgba32(new Rgba32(intensity1, intensity1, intensity1, 255));
+ pixels[left + width - 1, y] = color;
+ }
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor.cs
new file mode 100644
index 0000000000..fb209cecbc
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor.cs
@@ -0,0 +1,44 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Implements the 'WhiteIsZero' photometric interpretation (optimised for 8-bit grayscale images).
+ ///
+ internal static class WhiteIsZero8TiffColor
+ {
+ ///
+ /// Decodes pixel data using the current photometric interpretation.
+ ///
+ /// The pixel format.
+ /// The buffer to read image data from.
+ /// The image buffer to write pixels to.
+ /// The x-coordinate of the left-hand side of the image block.
+ /// The y-coordinate of the top of the image block.
+ /// The width of the image block.
+ /// The height of the image block.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Decode(byte[] data, Buffer2D pixels, int left, int top, int width, int height)
+ where TPixel : struct, IPixel
+ {
+ TPixel color = default(TPixel);
+
+ uint offset = 0;
+
+ for (int y = top; y < top + height; y++)
+ {
+ for (int x = left; x < left + width; x++)
+ {
+ byte intensity = (byte)(255 - data[offset++]);
+ color.FromRgba32(new Rgba32(intensity, intensity, intensity, 255));
+ pixels[x, y] = color;
+ }
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor.cs
new file mode 100644
index 0000000000..8bb720bb9c
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor.cs
@@ -0,0 +1,51 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Implements the 'WhiteIsZero' photometric interpretation (for all bit depths).
+ ///
+ internal static class WhiteIsZeroTiffColor
+ {
+ ///
+ /// Decodes pixel data using the current photometric interpretation.
+ ///
+ /// The pixel format.
+ /// The buffer to read image data from.
+ /// The number of bits per sample for each pixel.
+ /// The image buffer to write pixels to.
+ /// The x-coordinate of the left-hand side of the image block.
+ /// The y-coordinate of the top of the image block.
+ /// The width of the image block.
+ /// The height of the image block.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Decode(byte[] data, uint[] bitsPerSample, Buffer2D pixels, int left, int top, int width, int height)
+ where TPixel : struct, IPixel
+ {
+ TPixel color = default(TPixel);
+
+ BitReader bitReader = new BitReader(data);
+ float factor = (float)Math.Pow(2, bitsPerSample[0]) - 1.0f;
+
+ for (int y = top; y < top + height; y++)
+ {
+ for (int x = left; x < left + width; x++)
+ {
+ int value = bitReader.ReadBits(bitsPerSample[0]);
+ float intensity = 1.0f - (((float)value) / factor);
+ color.FromVector4(new Vector4(intensity, intensity, intensity, 1.0f));
+ pixels[x, y] = color;
+ }
+
+ bitReader.NextRow();
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/README.md b/src/ImageSharp/Formats/Tiff/README.md
new file mode 100644
index 0000000000..c2527b0080
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/README.md
@@ -0,0 +1,250 @@
+# ImageSharp TIFF codec
+
+## References
+- TIFF
+ - [TIFF 6.0 Specification](http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf),(http://www.npes.org/pdf/TIFF-v6.pdf)
+ - [TIFF Supplement 1](http://partners.adobe.com/public/developer/en/tiff/TIFFPM6.pdf)
+ - [TIFF Supplement 2](http://partners.adobe.com/public/developer/en/tiff/TIFFphotoshop.pdf)
+ - [TIFF Supplement 3](http://chriscox.org/TIFFTN3d1.pdf)
+ - [TIFF-F/FX Extension (RFC2301)](http://www.ietf.org/rfc/rfc2301.txt)
+ - [TIFF/EP Extension (Wikipedia)](https://en.wikipedia.org/wiki/TIFF/EP)
+ - [Adobe TIFF Pages](http://partners.adobe.com/public/developer/tiff/index.html)
+ - [Unofficial TIFF FAQ](http://www.awaresystems.be/imaging/tiff/faq.html)
+
+- DNG
+ - [Adobe DNG Pages](https://helpx.adobe.com/photoshop/digital-negative.html)
+
+- Metadata (EXIF)
+ - [EXIF 2.3 Specification](http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf)
+
+- Metadata (XMP)
+ - [Adobe XMP Pages](http://www.adobe.com/products/xmp.html)
+ - [Adobe XMP Developer Centre](http://www.adobe.com/devnet/xmp.html)
+
+## Implementation Status
+
+### Deviations from the TIFF spec (to be fixed)
+
+- Decoder
+ - A Baseline TIFF reader must skip over extra components (e.g. RGB with 4 samples per pixels)
+ - NB: Need to handle this for both planar and chunky data
+ - If the SampleFormat field is present and not 1 - fail gracefully if you cannot handle this
+ - Compression=None should treat 16/32-BitsPerSample for all samples as SHORT/LONG (for byte order and padding rows)
+ - RowsPerStrip should default to 2^32-1 (effectively infinity) to store the image as a single strip
+ - Check Planar format data - is this encoded as strips in order RGBRGBRGB or RRRGGGBBB?
+ - Make sure we ignore any strips that are not needed for the image (if too many are present)
+
+### Compression Formats
+
+| |Encoder|Decoder|Comments |
+|---------------------------|:-----:|:-----:|--------------------------|
+|None | | Y | |
+|Ccitt1D | | | |
+|PackBits | | Y | |
+|CcittGroup3Fax | | | |
+|CcittGroup4Fax | | | |
+|Lzw | | Y | Based on ImageSharp GIF LZW implementation - this code could be modified to be (i) shared, or (ii) optimised for each case |
+|Old Jpeg | | | |
+|Jpeg (Technote 2) | | | |
+|Deflate (Technote 2) | | Y | |
+|Old Deflate (Technote 2) | | Y | |
+
+### Photometric Interpretation Formats
+
+| |Encoder|Decoder|Comments |
+|---------------------------|:-----:|:-----:|--------------------------|
+|WhiteIsZero | | Y | General + 1/4/8-bit optimised implementations |
+|BlackIsZero | | Y | General + 1/4/8-bit optimised implementations |
+|Rgb (Chunky) | | Y | General + Rgb888 optimised implementation |
+|Rgb (Planar) | | Y | General implementation only |
+|PaletteColor | | Y | General implementation only |
+|TransparencyMask | | | |
+|Separated (TIFF Extension) | | | |
+|YCbCr (TIFF Extension) | | | |
+|CieLab (TIFF Extension) | | | |
+|IccLab (TechNote 1) | | | |
+
+### Baseline TIFF Tags
+
+| |Encoder|Decoder|Comments |
+|---------------------------|:-----:|:-----:|--------------------------|
+|NewSubfileType | | | |
+|SubfileType | | | |
+|ImageWidth | | Y | |
+|ImageLength | | Y | |
+|BitsPerSample | | Y | |
+|Compression | | Y | |
+|PhotometricInterpretation | | Y | |
+|Threshholding | | | |
+|CellWidth | | | |
+|CellLength | | | |
+|FillOrder | | | |
+|ImageDescription | | Y | |
+|Make | | Y | |
+|Model | | Y | |
+|StripOffsets | | Y | |
+|Orientation | | | |
+|SamplesPerPixel | | | Currently ignored, as can be inferred from count of BitsPerSample |
+|RowsPerStrip | | Y | |
+|StripByteCounts | | Y | |
+|MinSampleValue | | | |
+|MaxSampleValue | | | |
+|XResolution | | Y | |
+|YResolution | | Y | |
+|PlanarConfiguration | | Y | |
+|FreeOffsets | | | |
+|FreeByteCounts | | | |
+|GrayResponseUnit | | | |
+|GrayResponseCurve | | | |
+|ResolutionUnit | | Y | |
+|Software | | Y | |
+|DateTime | | Y | |
+|Artist | | Y | |
+|HostComputer | | Y | |
+|ColorMap | | Y | |
+|ExtraSamples | | | |
+|Copyright | | Y | |
+
+### Extension TIFF Tags
+
+| |Encoder|Decoder|Comments |
+|---------------------------|:-----:|:-----:|--------------------------|
+|NewSubfileType | | | |
+|DocumentName | | | |
+|PageName | | | |
+|XPosition | | | |
+|YPosition | | | |
+|T4Options | | | |
+|T6Options | | | |
+|PageNumber | | | |
+|TransferFunction | | | |
+|Predictor | | | |
+|WhitePoint | | | |
+|PrimaryChromaticities | | | |
+|HalftoneHints | | | |
+|TileWidth | | | |
+|TileLength | | | |
+|TileOffsets | | | |
+|TileByteCounts | | | |
+|BadFaxLines | | | |
+|CleanFaxData | | | |
+|ConsecutiveBadFaxLines | | | |
+|SubIFDs | | | |
+|InkSet | | | |
+|InkNames | | | |
+|NumberOfInks | | | |
+|DotRange | | | |
+|TargetPrinter | | | |
+|SampleFormat | | | |
+|SMinSampleValue | | | |
+|SMaxSampleValue | | | |
+|TransferRange | | | |
+|ClipPath | | | |
+|XClipPathUnits | | | |
+|YClipPathUnits | | | |
+|Indexed | | | |
+|JPEGTables | | | |
+|OPIProxy | | | |
+|GlobalParametersIFD | | | |
+|ProfileType | | | |
+|FaxProfile | | | |
+|CodingMethods | | | |
+|VersionYear | | | |
+|ModeNumber | | | |
+|Decode | | | |
+|DefaultImageColor | | | |
+|JPEGProc | | | |
+|JPEGInterchangeFormat | | | |
+|JPEGInterchangeFormatLength| | | |
+|JPEGRestartInterval | | | |
+|JPEGLosslessPredictors | | | |
+|JPEGPointTransforms | | | |
+|JPEGQTables | | | |
+|JPEGDCTables | | | |
+|JPEGACTables | | | |
+|YCbCrCoefficients | | | |
+|YCbCrSubSampling | | | |
+|YCbCrPositioning | | | |
+|ReferenceBlackWhite | | | |
+|StripRowCounts | | | |
+|XMP | | | |
+|ImageID | | | |
+|ImageLayer | | | |
+
+### Private TIFF Tags
+
+| |Encoder|Decoder|Comments |
+|---------------------------|:-----:|:-----:|--------------------------|
+|Wang Annotation | | | |
+|MD FileTag | | | |
+|MD ScalePixel | | | |
+|MD ColorTable | | | |
+|MD LabName | | | |
+|MD SampleInfo | | | |
+|MD PrepDate | | | |
+|MD PrepTime | | | |
+|MD FileUnits | | | |
+|ModelPixelScaleTag | | | |
+|IPTC | | | |
+|INGR Packet Data Tag | | | |
+|INGR Flag Registers | | | |
+|IrasB Transformation Matrix| | | |
+|ModelTiepointTag | | | |
+|ModelTransformationTag | | | |
+|Photoshop | | | |
+|Exif IFD | | | |
+|ICC Profile | | | |
+|GeoKeyDirectoryTag | | | |
+|GeoDoubleParamsTag | | | |
+|GeoAsciiParamsTag | | | |
+|GPS IFD | | | |
+|HylaFAX FaxRecvParams | | | |
+|HylaFAX FaxSubAddress | | | |
+|HylaFAX FaxRecvTime | | | |
+|ImageSourceData | | | |
+|Interoperability IFD | | | |
+|GDAL_METADATA | | | |
+|GDAL_NODATA | | | |
+|Oce Scanjob Description | | | |
+|Oce Application Selector | | | |
+|Oce Identification Number | | | |
+|Oce ImageLogic Characteristics| | | |
+|DNGVersion | | | |
+|DNGBackwardVersion | | | |
+|UniqueCameraModel | | | |
+|LocalizedCameraModel | | | |
+|CFAPlaneColor | | | |
+|CFALayout | | | |
+|LinearizationTable | | | |
+|BlackLevelRepeatDim | | | |
+|BlackLevel | | | |
+|BlackLevelDeltaH | | | |
+|BlackLevelDeltaV | | | |
+|WhiteLevel | | | |
+|DefaultScale | | | |
+|DefaultCropOrigin | | | |
+|DefaultCropSize | | | |
+|ColorMatrix1 | | | |
+|ColorMatrix2 | | | |
+|CameraCalibration1 | | | |
+|CameraCalibration2 | | | |
+|ReductionMatrix1 | | | |
+|ReductionMatrix2 | | | |
+|AnalogBalance | | | |
+|AsShotNeutral | | | |
+|AsShotWhiteXY | | | |
+|BaselineExposure | | | |
+|BaselineNoise | | | |
+|BaselineSharpness | | | |
+|BayerGreenSplit | | | |
+|LinearResponseLimit | | | |
+|CameraSerialNumber | | | |
+|LensInfo | | | |
+|ChromaBlurRadius | | | |
+|AntiAliasStrength | | | |
+|DNGPrivateData | | | |
+|MakerNoteSafety | | | |
+|CalibrationIlluminant1 | | | |
+|CalibrationIlluminant2 | | | |
+|BestQualityScale | | | |
+|Alias Layer Metadata | | | |
diff --git a/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs b/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs
new file mode 100644
index 0000000000..0da193239d
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats
+{
+ ///
+ /// Registers the image encoders, decoders and mime type detectors for the TIFF format.
+ ///
+ public sealed class TiffConfigurationModule : IConfigurationModule
+ {
+ ///
+ public void Configure(Configuration configuration)
+ {
+ configuration.ImageFormatsManager.SetEncoder(TiffFormat.Instance, new TiffEncoder());
+ configuration.ImageFormatsManager.SetDecoder(TiffFormat.Instance, new TiffDecoder());
+ configuration.ImageFormatsManager.AddImageFormatDetector(new TiffImageFormatDetector());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoder.cs b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs
new file mode 100644
index 0000000000..1d4521b0b6
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoder.cs
@@ -0,0 +1,32 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.IO;
+
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats
+{
+ ///
+ /// Image decoder for generating an image out of a TIFF stream.
+ ///
+ public class TiffDecoder : IImageDecoder, ITiffDecoderOptions
+ {
+ ///
+ /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
+ ///
+ public bool IgnoreMetadata { get; set; }
+
+ ///
+ public Image Decode(Configuration configuration, Stream stream)
+ where TPixel : struct, IPixel
+ {
+ Guard.NotNull(stream, "stream");
+
+ using (TiffDecoderCore decoder = new TiffDecoderCore(configuration, this))
+ {
+ return decoder.Decode(stream);
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
new file mode 100644
index 0000000000..608c2e5583
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
@@ -0,0 +1,1281 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using System.IO;
+using System.Text;
+using SixLabors.ImageSharp.Formats.Tiff;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.MetaData;
+using SixLabors.ImageSharp.MetaData.Profiles.Exif;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Primitives;
+
+namespace SixLabors.ImageSharp.Formats
+{
+ ///
+ /// Performs the tiff decoding operation.
+ ///
+ internal class TiffDecoderCore : IDisposable
+ {
+ ///
+ /// The global configuration
+ ///
+ private readonly Configuration configuration;
+
+ ///
+ /// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
+ ///
+ private bool ignoreMetadata;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The configuration.
+ /// The decoder options.
+ public TiffDecoderCore(Configuration configuration, ITiffDecoderOptions options)
+ {
+ options = options ?? new TiffDecoder();
+
+ this.configuration = configuration ?? Configuration.Default;
+ this.ignoreMetadata = options.IgnoreMetadata;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The input stream.
+ /// A flag indicating if the file is encoded in little-endian or big-endian format.
+ /// The decoder options.
+ /// The configuration.
+ public TiffDecoderCore(Stream stream, bool isLittleEndian, Configuration configuration, ITiffDecoderOptions options)
+ : this(configuration, options)
+ {
+ this.InputStream = stream;
+ this.IsLittleEndian = isLittleEndian;
+ }
+
+ ///
+ /// Gets or sets the number of bits for each sample of the pixel format used to encode the image.
+ ///
+ public uint[] BitsPerSample { get; set; }
+
+ ///
+ /// Gets or sets the lookup table for RGB palette colored images.
+ ///
+ public uint[] ColorMap { get; set; }
+
+ ///
+ /// Gets or sets the photometric interpretation implementation to use when decoding the image.
+ ///
+ public TiffColorType ColorType { get; set; }
+
+ ///
+ /// Gets or sets the compression implementation to use when decoding the image.
+ ///
+ public TiffCompressionType CompressionType { get; set; }
+
+ ///
+ /// Gets the input stream.
+ ///
+ public Stream InputStream { get; private set; }
+
+ ///
+ /// Gets a value indicating whether the file is encoded in little-endian or big-endian format.
+ ///
+ public bool IsLittleEndian { get; private set; }
+
+ ///
+ /// Gets or sets the planar configuration type to use when decoding the image.
+ ///
+ public TiffPlanarConfiguration PlanarConfiguration { get; set; }
+
+ ///
+ /// Calculates the size (in bytes) of the data contained within an IFD entry.
+ ///
+ /// The IFD entry to calculate the size for.
+ /// The size of the data (in bytes).
+ public static uint GetSizeOfData(TiffIfdEntry entry) => SizeOfDataType(entry.Type) * entry.Count;
+
+ ///
+ /// Decodes the image from the specified and sets
+ /// the data to image.
+ ///
+ /// The pixel format.
+ /// The stream, where the image should be.
+ /// The decoded image.
+ public Image Decode(Stream stream)
+ where TPixel : struct, IPixel
+ {
+ this.InputStream = stream;
+
+ uint firstIfdOffset = this.ReadHeader();
+ TiffIfd firstIfd = this.ReadIfd(firstIfdOffset);
+ Image image = this.DecodeImage(firstIfd);
+
+ return image;
+ }
+
+ ///
+ /// Dispose
+ ///
+ public void Dispose()
+ {
+ }
+
+ ///
+ /// Reads the TIFF header from the input stream.
+ ///
+ /// The byte offset to the first IFD in the file.
+ ///
+ /// Thrown if the TIFF file header is invalid.
+ ///
+ public uint ReadHeader()
+ {
+ byte[] headerBytes = new byte[TiffConstants.SizeOfTiffHeader];
+ this.InputStream.ReadFull(headerBytes, TiffConstants.SizeOfTiffHeader);
+
+ if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian)
+ {
+ this.IsLittleEndian = true;
+ }
+ else if (headerBytes[0] != TiffConstants.ByteOrderBigEndian && headerBytes[1] != TiffConstants.ByteOrderBigEndian)
+ {
+ throw new ImageFormatException("Invalid TIFF file header.");
+ }
+
+ if (this.ToUInt16(headerBytes, 2) != TiffConstants.HeaderMagicNumber)
+ {
+ throw new ImageFormatException("Invalid TIFF file header.");
+ }
+
+ uint firstIfdOffset = this.ToUInt32(headerBytes, 4);
+ if (firstIfdOffset == 0)
+ {
+ throw new ImageFormatException("Invalid TIFF file header.");
+ }
+
+ return firstIfdOffset;
+ }
+
+ ///
+ /// Reads a from the input stream.
+ ///
+ /// The byte offset within the file to find the IFD.
+ /// A containing the retrieved data.
+ public TiffIfd ReadIfd(uint offset)
+ {
+ this.InputStream.Seek(offset, SeekOrigin.Begin);
+
+ byte[] buffer = new byte[TiffConstants.SizeOfIfdEntry];
+
+ this.InputStream.ReadFull(buffer, 2);
+ ushort entryCount = this.ToUInt16(buffer, 0);
+
+ TiffIfdEntry[] entries = new TiffIfdEntry[entryCount];
+ for (int i = 0; i < entryCount; i++)
+ {
+ this.InputStream.ReadFull(buffer, TiffConstants.SizeOfIfdEntry);
+
+ ushort tag = this.ToUInt16(buffer, 0);
+ TiffType type = (TiffType)this.ToUInt16(buffer, 2);
+ uint count = this.ToUInt32(buffer, 4);
+ byte[] value = new byte[] { buffer[8], buffer[9], buffer[10], buffer[11] };
+
+ entries[i] = new TiffIfdEntry(tag, type, count, value);
+ }
+
+ this.InputStream.ReadFull(buffer, 4);
+ uint nextIfdOffset = this.ToUInt32(buffer, 0);
+
+ return new TiffIfd(entries, nextIfdOffset);
+ }
+
+ ///
+ /// Decodes the image data from a specified IFD.
+ ///
+ /// The pixel format.
+ /// The IFD to read the image from.
+ /// The decoded image.
+ public Image DecodeImage(TiffIfd ifd)
+ where TPixel : struct, IPixel
+ {
+ if (!ifd.TryGetIfdEntry(TiffTags.ImageLength, out TiffIfdEntry imageLengthEntry)
+ || !ifd.TryGetIfdEntry(TiffTags.ImageWidth, out TiffIfdEntry imageWidthEntry))
+ {
+ throw new ImageFormatException("The TIFF IFD does not specify the image dimensions.");
+ }
+
+ int width = (int)this.ReadUnsignedInteger(ref imageWidthEntry);
+ int height = (int)this.ReadUnsignedInteger(ref imageLengthEntry);
+
+ Image image = new Image(this.configuration, width, height);
+
+ this.ReadMetadata(ifd, image);
+ this.ReadImageFormat(ifd);
+
+ if (ifd.TryGetIfdEntry(TiffTags.RowsPerStrip, out TiffIfdEntry rowsPerStripEntry)
+ && ifd.TryGetIfdEntry(TiffTags.StripOffsets, out TiffIfdEntry stripOffsetsEntry)
+ && ifd.TryGetIfdEntry(TiffTags.StripByteCounts, out TiffIfdEntry stripByteCountsEntry))
+ {
+ int rowsPerStrip = (int)this.ReadUnsignedInteger(ref rowsPerStripEntry);
+ uint[] stripOffsets = this.ReadUnsignedIntegerArray(ref stripOffsetsEntry);
+ uint[] stripByteCounts = this.ReadUnsignedIntegerArray(ref stripByteCountsEntry);
+ this.DecodeImageStrips(image, rowsPerStrip, stripOffsets, stripByteCounts);
+ }
+
+ return image;
+ }
+
+ ///
+ /// Reads the image metadata from a specified IFD.
+ ///
+ /// The pixel format.
+ /// The IFD to read the image from.
+ /// The image to write the metadata to.
+ public void ReadMetadata(TiffIfd ifd, Image image)
+ where TPixel : struct, IPixel
+ {
+ TiffResolutionUnit resolutionUnit = (TiffResolutionUnit)this.ReadUnsignedInteger(ifd, TiffTags.ResolutionUnit, (uint)TiffResolutionUnit.Inch);
+
+ if (resolutionUnit != TiffResolutionUnit.None)
+ {
+ double resolutionUnitFactor = resolutionUnit == TiffResolutionUnit.Centimeter ? 2.54 : 1.0;
+
+ if (ifd.TryGetIfdEntry(TiffTags.XResolution, out TiffIfdEntry xResolutionEntry))
+ {
+ Rational xResolution = this.ReadUnsignedRational(ref xResolutionEntry);
+ image.MetaData.HorizontalResolution = xResolution.ToDouble() * resolutionUnitFactor;
+ }
+
+ if (ifd.TryGetIfdEntry(TiffTags.YResolution, out TiffIfdEntry yResolutionEntry))
+ {
+ Rational yResolution = this.ReadUnsignedRational(ref yResolutionEntry);
+ image.MetaData.VerticalResolution = yResolution.ToDouble() * resolutionUnitFactor;
+ }
+ }
+
+ if (!this.ignoreMetadata)
+ {
+ if (ifd.TryGetIfdEntry(TiffTags.Artist, out TiffIfdEntry artistEntry))
+ {
+ image.MetaData.Properties.Add(new ImageProperty(TiffMetadataNames.Artist, this.ReadString(ref artistEntry)));
+ }
+
+ if (ifd.TryGetIfdEntry(TiffTags.Copyright, out TiffIfdEntry copyrightEntry))
+ {
+ image.MetaData.Properties.Add(new ImageProperty(TiffMetadataNames.Copyright, this.ReadString(ref copyrightEntry)));
+ }
+
+ if (ifd.TryGetIfdEntry(TiffTags.DateTime, out TiffIfdEntry dateTimeEntry))
+ {
+ image.MetaData.Properties.Add(new ImageProperty(TiffMetadataNames.DateTime, this.ReadString(ref dateTimeEntry)));
+ }
+
+ if (ifd.TryGetIfdEntry(TiffTags.HostComputer, out TiffIfdEntry hostComputerEntry))
+ {
+ image.MetaData.Properties.Add(new ImageProperty(TiffMetadataNames.HostComputer, this.ReadString(ref hostComputerEntry)));
+ }
+
+ if (ifd.TryGetIfdEntry(TiffTags.ImageDescription, out TiffIfdEntry imageDescriptionEntry))
+ {
+ image.MetaData.Properties.Add(new ImageProperty(TiffMetadataNames.ImageDescription, this.ReadString(ref imageDescriptionEntry)));
+ }
+
+ if (ifd.TryGetIfdEntry(TiffTags.Make, out TiffIfdEntry makeEntry))
+ {
+ image.MetaData.Properties.Add(new ImageProperty(TiffMetadataNames.Make, this.ReadString(ref makeEntry)));
+ }
+
+ if (ifd.TryGetIfdEntry(TiffTags.Model, out TiffIfdEntry modelEntry))
+ {
+ image.MetaData.Properties.Add(new ImageProperty(TiffMetadataNames.Model, this.ReadString(ref modelEntry)));
+ }
+
+ if (ifd.TryGetIfdEntry(TiffTags.Software, out TiffIfdEntry softwareEntry))
+ {
+ image.MetaData.Properties.Add(new ImageProperty(TiffMetadataNames.Software, this.ReadString(ref softwareEntry)));
+ }
+ }
+ }
+
+ ///
+ /// Determines the TIFF compression and color types, and reads any associated parameters.
+ ///
+ /// The IFD to read the image format information for.
+ public void ReadImageFormat(TiffIfd ifd)
+ {
+ TiffCompression compression = (TiffCompression)this.ReadUnsignedInteger(ifd, TiffTags.Compression, (uint)TiffCompression.None);
+
+ switch (compression)
+ {
+ case TiffCompression.None:
+ {
+ this.CompressionType = TiffCompressionType.None;
+ break;
+ }
+
+ case TiffCompression.PackBits:
+ {
+ this.CompressionType = TiffCompressionType.PackBits;
+ break;
+ }
+
+ case TiffCompression.Deflate:
+ case TiffCompression.OldDeflate:
+ {
+ this.CompressionType = TiffCompressionType.Deflate;
+ break;
+ }
+
+ case TiffCompression.Lzw:
+ {
+ this.CompressionType = TiffCompressionType.Lzw;
+ break;
+ }
+
+ default:
+ {
+ throw new NotSupportedException("The specified TIFF compression format is not supported.");
+ }
+ }
+
+ this.PlanarConfiguration = (TiffPlanarConfiguration)this.ReadUnsignedInteger(ifd, TiffTags.PlanarConfiguration, (uint)TiffPlanarConfiguration.Chunky);
+
+ TiffPhotometricInterpretation photometricInterpretation;
+
+ if (ifd.TryGetIfdEntry(TiffTags.PhotometricInterpretation, out TiffIfdEntry photometricInterpretationEntry))
+ {
+ photometricInterpretation = (TiffPhotometricInterpretation)this.ReadUnsignedInteger(ref photometricInterpretationEntry);
+ }
+ else
+ {
+ if (compression == TiffCompression.Ccitt1D)
+ {
+ photometricInterpretation = TiffPhotometricInterpretation.WhiteIsZero;
+ }
+ else
+ {
+ throw new ImageFormatException("The TIFF photometric interpretation entry is missing.");
+ }
+ }
+
+ if (ifd.TryGetIfdEntry(TiffTags.BitsPerSample, out TiffIfdEntry bitsPerSampleEntry))
+ {
+ this.BitsPerSample = this.ReadUnsignedIntegerArray(ref bitsPerSampleEntry);
+ }
+ else
+ {
+ if (photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero ||
+ photometricInterpretation == TiffPhotometricInterpretation.BlackIsZero)
+ {
+ this.BitsPerSample = new[] { 1u };
+ }
+ else
+ {
+ throw new ImageFormatException("The TIFF BitsPerSample entry is missing.");
+ }
+ }
+
+ switch (photometricInterpretation)
+ {
+ case TiffPhotometricInterpretation.WhiteIsZero:
+ {
+ if (this.BitsPerSample.Length == 1)
+ {
+ switch (this.BitsPerSample[0])
+ {
+ case 8:
+ {
+ this.ColorType = TiffColorType.WhiteIsZero8;
+ break;
+ }
+
+ case 4:
+ {
+ this.ColorType = TiffColorType.WhiteIsZero4;
+ break;
+ }
+
+ case 1:
+ {
+ this.ColorType = TiffColorType.WhiteIsZero1;
+ break;
+ }
+
+ default:
+ {
+ this.ColorType = TiffColorType.WhiteIsZero;
+ break;
+ }
+ }
+ }
+ else
+ {
+ throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported.");
+ }
+
+ break;
+ }
+
+ case TiffPhotometricInterpretation.BlackIsZero:
+ {
+ if (this.BitsPerSample.Length == 1)
+ {
+ switch (this.BitsPerSample[0])
+ {
+ case 8:
+ {
+ this.ColorType = TiffColorType.BlackIsZero8;
+ break;
+ }
+
+ case 4:
+ {
+ this.ColorType = TiffColorType.BlackIsZero4;
+ break;
+ }
+
+ case 1:
+ {
+ this.ColorType = TiffColorType.BlackIsZero1;
+ break;
+ }
+
+ default:
+ {
+ this.ColorType = TiffColorType.BlackIsZero;
+ break;
+ }
+ }
+ }
+ else
+ {
+ throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported.");
+ }
+
+ break;
+ }
+
+ case TiffPhotometricInterpretation.Rgb:
+ {
+ if (this.BitsPerSample.Length == 3)
+ {
+ if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky)
+ {
+ if (this.BitsPerSample[0] == 8 && this.BitsPerSample[1] == 8 && this.BitsPerSample[2] == 8)
+ {
+ this.ColorType = TiffColorType.Rgb888;
+ }
+ else
+ {
+ this.ColorType = TiffColorType.Rgb;
+ }
+ }
+ else
+ {
+ this.ColorType = TiffColorType.RgbPlanar;
+ }
+ }
+ else
+ {
+ throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported.");
+ }
+
+ break;
+ }
+
+ case TiffPhotometricInterpretation.PaletteColor:
+ {
+ if (ifd.TryGetIfdEntry(TiffTags.ColorMap, out TiffIfdEntry colorMapEntry))
+ {
+ this.ColorMap = this.ReadUnsignedIntegerArray(ref colorMapEntry);
+
+ if (this.BitsPerSample.Length == 1)
+ {
+ switch (this.BitsPerSample[0])
+ {
+ default:
+ {
+ this.ColorType = TiffColorType.PaletteColor;
+ break;
+ }
+ }
+ }
+ else
+ {
+ throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported.");
+ }
+ }
+ else
+ {
+ throw new ImageFormatException("The TIFF ColorMap entry is missing for a pallete color image.");
+ }
+
+ break;
+ }
+
+ default:
+ throw new NotSupportedException("The specified TIFF photometric interpretation is not supported.");
+ }
+ }
+
+ ///
+ /// Calculates the size (in bytes) for a pixel buffer using the determined color format.
+ ///
+ /// The width for the desired pixel buffer.
+ /// The height for the desired pixel buffer.
+ /// The index of the plane for planar image configuration (or zero for chunky).
+ /// The size (in bytes) of the required pixel buffer.
+ public int CalculateImageBufferSize(int width, int height, int plane)
+ {
+ uint bitsPerPixel = 0;
+
+ if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky)
+ {
+ for (int i = 0; i < this.BitsPerSample.Length; i++)
+ {
+ bitsPerPixel += this.BitsPerSample[i];
+ }
+ }
+ else
+ {
+ bitsPerPixel = this.BitsPerSample[plane];
+ }
+
+ int bytesPerRow = ((width * (int)bitsPerPixel) + 7) / 8;
+ return bytesPerRow * height;
+ }
+
+ ///
+ /// Decompresses an image block from the input stream into the specified buffer.
+ ///
+ /// The offset within the file of the image block.
+ /// The size (in bytes) of the compressed data.
+ /// The buffer to write the uncompressed data.
+ public void DecompressImageBlock(uint offset, uint byteCount, byte[] buffer)
+ {
+ this.InputStream.Seek(offset, SeekOrigin.Begin);
+
+ switch (this.CompressionType)
+ {
+ case TiffCompressionType.None:
+ NoneTiffCompression.Decompress(this.InputStream, (int)byteCount, buffer);
+ break;
+ case TiffCompressionType.PackBits:
+ PackBitsTiffCompression.Decompress(this.InputStream, (int)byteCount, buffer);
+ break;
+ case TiffCompressionType.Deflate:
+ DeflateTiffCompression.Decompress(this.InputStream, (int)byteCount, buffer);
+ break;
+ case TiffCompressionType.Lzw:
+ LzwTiffCompression.Decompress(this.InputStream, (int)byteCount, buffer);
+ break;
+ default:
+ throw new InvalidOperationException();
+ }
+ }
+
+ ///
+ /// Decodes pixel data using the current photometric interpretation (chunky configuration).
+ ///
+ /// The pixel format.
+ /// The buffer to read image data from.
+ /// The image buffer to write pixels to.
+ /// The x-coordinate of the left-hand side of the image block.
+ /// The y-coordinate of the top of the image block.
+ /// The width of the image block.
+ /// The height of the image block.
+ public void ProcessImageBlockChunky(byte[] data, Buffer2D pixels, int left, int top, int width, int height)
+ where TPixel : struct, IPixel
+ {
+ switch (this.ColorType)
+ {
+ case TiffColorType.WhiteIsZero:
+ WhiteIsZeroTiffColor.Decode(data, this.BitsPerSample, pixels, left, top, width, height);
+ break;
+ case TiffColorType.WhiteIsZero1:
+ WhiteIsZero1TiffColor.Decode(data, pixels, left, top, width, height);
+ break;
+ case TiffColorType.WhiteIsZero4:
+ WhiteIsZero4TiffColor.Decode(data, pixels, left, top, width, height);
+ break;
+ case TiffColorType.WhiteIsZero8:
+ WhiteIsZero8TiffColor.Decode(data, pixels, left, top, width, height);
+ break;
+ case TiffColorType.BlackIsZero:
+ BlackIsZeroTiffColor.Decode(data, this.BitsPerSample, pixels, left, top, width, height);
+ break;
+ case TiffColorType.BlackIsZero1:
+ BlackIsZero1TiffColor.Decode(data, pixels, left, top, width, height);
+ break;
+ case TiffColorType.BlackIsZero4:
+ BlackIsZero4TiffColor.Decode(data, pixels, left, top, width, height);
+ break;
+ case TiffColorType.BlackIsZero8:
+ BlackIsZero8TiffColor.Decode(data, pixels, left, top, width, height);
+ break;
+ case TiffColorType.Rgb:
+ RgbTiffColor.Decode(data, this.BitsPerSample, pixels, left, top, width, height);
+ break;
+ case TiffColorType.Rgb888:
+ Rgb888TiffColor.Decode(data, pixels, left, top, width, height);
+ break;
+ case TiffColorType.PaletteColor:
+ PaletteTiffColor.Decode(data, this.BitsPerSample, this.ColorMap, pixels, left, top, width, height);
+ break;
+ default:
+ throw new InvalidOperationException();
+ }
+ }
+
+ ///
+ /// Decodes pixel data using the current photometric interpretation (planar configuration).
+ ///
+ /// The pixel format.
+ /// The buffer to read image data from.
+ /// The image buffer to write pixels to.
+ /// The x-coordinate of the left-hand side of the image block.
+ /// The y-coordinate of the top of the image block.
+ /// The width of the image block.
+ /// The height of the image block.
+ public void ProcessImageBlockPlanar(byte[][] data, Buffer2D pixels, int left, int top, int width, int height)
+ where TPixel : struct, IPixel
+ {
+ switch (this.ColorType)
+ {
+ case TiffColorType.RgbPlanar:
+ RgbPlanarTiffColor.Decode(data, this.BitsPerSample, pixels, left, top, width, height);
+ break;
+ default:
+ throw new InvalidOperationException();
+ }
+ }
+
+ ///
+ /// Reads the data from a as an array of bytes.
+ ///
+ /// The to read.
+ /// The data.
+ public byte[] ReadBytes(ref TiffIfdEntry entry)
+ {
+ uint byteLength = GetSizeOfData(entry);
+
+ if (entry.Value.Length < byteLength)
+ {
+ uint offset = this.ToUInt32(entry.Value, 0);
+ this.InputStream.Seek(offset, SeekOrigin.Begin);
+
+ byte[] data = new byte[byteLength];
+ this.InputStream.ReadFull(data, (int)byteLength);
+ entry.Value = data;
+ }
+
+ return entry.Value;
+ }
+
+ ///
+ /// Reads the data from a as an unsigned integer value.
+ ///
+ /// The to read.
+ /// The data.
+ ///
+ /// Thrown if the data-type specified by the file cannot be converted to a , or if
+ /// there is an array of items.
+ ///
+ public uint ReadUnsignedInteger(ref TiffIfdEntry entry)
+ {
+ if (entry.Count != 1)
+ {
+ throw new ImageFormatException($"Cannot read a single value from an array of multiple items.");
+ }
+
+ switch (entry.Type)
+ {
+ case TiffType.Byte:
+ return (uint)this.ToByte(entry.Value, 0);
+ case TiffType.Short:
+ return (uint)this.ToUInt16(entry.Value, 0);
+ case TiffType.Long:
+ return this.ToUInt32(entry.Value, 0);
+ default:
+ throw new ImageFormatException($"A value of type '{entry.Type}' cannot be converted to an unsigned integer.");
+ }
+ }
+
+ ///
+ /// Reads the data for a specified tag of a as an unsigned integer value.
+ ///
+ /// The to read from.
+ /// The tag ID to search for.
+ /// The default value if the entry is missing
+ /// The data.
+ ///
+ /// Thrown if the data-type specified by the file cannot be converted to a , or if
+ /// there is an array of items.
+ ///
+ public uint ReadUnsignedInteger(TiffIfd ifd, ushort tag, uint defaultValue)
+ {
+ if (ifd.TryGetIfdEntry(tag, out TiffIfdEntry entry))
+ {
+ return this.ReadUnsignedInteger(ref entry);
+ }
+ else
+ {
+ return defaultValue;
+ }
+ }
+
+ ///
+ /// Reads the data from a as a signed integer value.
+ ///
+ /// The to read.
+ /// The data.
+ ///
+ /// Thrown if the data-type specified by the file cannot be converted to an , or if
+ /// there is an array of items.
+ ///
+ public int ReadSignedInteger(ref TiffIfdEntry entry)
+ {
+ if (entry.Count != 1)
+ {
+ throw new ImageFormatException($"Cannot read a single value from an array of multiple items.");
+ }
+
+ switch (entry.Type)
+ {
+ case TiffType.SByte:
+ return (int)this.ToSByte(entry.Value, 0);
+ case TiffType.SShort:
+ return (int)this.ToInt16(entry.Value, 0);
+ case TiffType.SLong:
+ return this.ToInt32(entry.Value, 0);
+ default:
+ throw new ImageFormatException($"A value of type '{entry.Type}' cannot be converted to a signed integer.");
+ }
+ }
+
+ ///
+ /// Reads the data from a as an array of unsigned integer values.
+ ///
+ /// The to read.
+ /// The data.
+ ///
+ /// Thrown if the data-type specified by the file cannot be converted to a .
+ ///
+ public uint[] ReadUnsignedIntegerArray(ref TiffIfdEntry entry)
+ {
+ byte[] bytes = this.ReadBytes(ref entry);
+ uint[] result = new uint[entry.Count];
+
+ switch (entry.Type)
+ {
+ case TiffType.Byte:
+ {
+ for (int i = 0; i < result.Length; i++)
+ {
+ result[i] = (uint)this.ToByte(bytes, i);
+ }
+
+ break;
+ }
+
+ case TiffType.Short:
+ {
+ for (int i = 0; i < result.Length; i++)
+ {
+ result[i] = (uint)this.ToUInt16(bytes, i * TiffConstants.SizeOfShort);
+ }
+
+ break;
+ }
+
+ case TiffType.Long:
+ {
+ for (int i = 0; i < result.Length; i++)
+ {
+ result[i] = this.ToUInt32(bytes, i * TiffConstants.SizeOfLong);
+ }
+
+ break;
+ }
+
+ default:
+ throw new ImageFormatException($"A value of type '{entry.Type}' cannot be converted to an unsigned integer.");
+ }
+
+ return result;
+ }
+
+ ///
+ /// Reads the data from a as an array of signed integer values.
+ ///
+ /// The to read.
+ /// The data.
+ ///
+ /// Thrown if the data-type specified by the file cannot be converted to an .
+ ///
+ public int[] ReadSignedIntegerArray(ref TiffIfdEntry entry)
+ {
+ byte[] bytes = this.ReadBytes(ref entry);
+ int[] result = new int[entry.Count];
+
+ switch (entry.Type)
+ {
+ case TiffType.SByte:
+ {
+ for (int i = 0; i < result.Length; i++)
+ {
+ result[i] = (int)this.ToSByte(bytes, i);
+ }
+
+ break;
+ }
+
+ case TiffType.SShort:
+ {
+ for (int i = 0; i < result.Length; i++)
+ {
+ result[i] = (int)this.ToInt16(bytes, i * TiffConstants.SizeOfShort);
+ }
+
+ break;
+ }
+
+ case TiffType.SLong:
+ {
+ for (int i = 0; i < result.Length; i++)
+ {
+ result[i] = this.ToInt32(bytes, i * TiffConstants.SizeOfLong);
+ }
+
+ break;
+ }
+
+ default:
+ throw new ImageFormatException($"A value of type '{entry.Type}' cannot be converted to a signed integer.");
+ }
+
+ return result;
+ }
+
+ ///
+ /// Reads the data from a as a value.
+ ///
+ /// The to read.
+ /// The data.
+ ///
+ /// Thrown if the data-type specified by the file cannot be converted to a .
+ ///
+ public string ReadString(ref TiffIfdEntry entry)
+ {
+ if (entry.Type != TiffType.Ascii)
+ {
+ throw new ImageFormatException($"A value of type '{entry.Type}' cannot be converted to a string.");
+ }
+
+ byte[] bytes = this.ReadBytes(ref entry);
+
+ if (bytes[entry.Count - 1] != 0)
+ {
+ throw new ImageFormatException("The retrieved string is not null terminated.");
+ }
+
+ return Encoding.UTF8.GetString(bytes, 0, (int)entry.Count - 1);
+ }
+
+ ///
+ /// Reads the data from a as a value.
+ ///
+ /// The to read.
+ /// The data.
+ ///
+ /// Thrown if the data-type specified by the file cannot be converted to a , or if
+ /// there is an array of items.
+ ///
+ public Rational ReadUnsignedRational(ref TiffIfdEntry entry)
+ {
+ if (entry.Count != 1)
+ {
+ throw new ImageFormatException($"Cannot read a single value from an array of multiple items.");
+ }
+
+ return this.ReadUnsignedRationalArray(ref entry)[0];
+ }
+
+ ///
+ /// Reads the data from a as a value.
+ ///
+ /// The to read.
+ /// The data.
+ ///
+ /// Thrown if the data-type specified by the file cannot be converted to a , or if
+ /// there is an array of items.
+ ///
+ public SignedRational ReadSignedRational(ref TiffIfdEntry entry)
+ {
+ if (entry.Count != 1)
+ {
+ throw new ImageFormatException($"Cannot read a single value from an array of multiple items.");
+ }
+
+ return this.ReadSignedRationalArray(ref entry)[0];
+ }
+
+ ///
+ /// Reads the data from a as an array of values.
+ ///
+ /// The to read.
+ /// The data.
+ ///
+ /// Thrown if the data-type specified by the file cannot be converted to a .
+ ///
+ public Rational[] ReadUnsignedRationalArray(ref TiffIfdEntry entry)
+ {
+ if (entry.Type != TiffType.Rational)
+ {
+ throw new ImageFormatException($"A value of type '{entry.Type}' cannot be converted to a Rational.");
+ }
+
+ byte[] bytes = this.ReadBytes(ref entry);
+ Rational[] result = new Rational[entry.Count];
+
+ for (int i = 0; i < result.Length; i++)
+ {
+ uint numerator = this.ToUInt32(bytes, i * TiffConstants.SizeOfRational);
+ uint denominator = this.ToUInt32(bytes, (i * TiffConstants.SizeOfRational) + TiffConstants.SizeOfLong);
+ result[i] = new Rational(numerator, denominator);
+ }
+
+ return result;
+ }
+
+ ///
+ /// Reads the data from a as an array of values.
+ ///
+ /// The to read.
+ /// The data.
+ ///
+ /// Thrown if the data-type specified by the file cannot be converted to a .
+ ///
+ public SignedRational[] ReadSignedRationalArray(ref TiffIfdEntry entry)
+ {
+ if (entry.Type != TiffType.SRational)
+ {
+ throw new ImageFormatException($"A value of type '{entry.Type}' cannot be converted to a SignedRational.");
+ }
+
+ byte[] bytes = this.ReadBytes(ref entry);
+ SignedRational[] result = new SignedRational[entry.Count];
+
+ for (int i = 0; i < result.Length; i++)
+ {
+ int numerator = this.ToInt32(bytes, i * TiffConstants.SizeOfRational);
+ int denominator = this.ToInt32(bytes, (i * TiffConstants.SizeOfRational) + TiffConstants.SizeOfLong);
+ result[i] = new SignedRational(numerator, denominator);
+ }
+
+ return result;
+ }
+
+ ///
+ /// Reads the data from a as a value.
+ ///
+ /// The to read.
+ /// The data.
+ ///
+ /// Thrown if the data-type specified by the file cannot be converted to a , or if
+ /// there is an array of items.
+ ///
+ public float ReadFloat(ref TiffIfdEntry entry)
+ {
+ if (entry.Count != 1)
+ {
+ throw new ImageFormatException($"Cannot read a single value from an array of multiple items.");
+ }
+
+ if (entry.Type != TiffType.Float)
+ {
+ throw new ImageFormatException($"A value of type '{entry.Type}' cannot be converted to a float.");
+ }
+
+ return this.ToSingle(entry.Value, 0);
+ }
+
+ ///
+ /// Reads the data from a as a value.
+ ///
+ /// The to read.
+ /// The data.
+ ///
+ /// Thrown if the data-type specified by the file cannot be converted to a , or if
+ /// there is an array of items.
+ ///
+ public double ReadDouble(ref TiffIfdEntry entry)
+ {
+ if (entry.Count != 1)
+ {
+ throw new ImageFormatException($"Cannot read a single value from an array of multiple items.");
+ }
+
+ return this.ReadDoubleArray(ref entry)[0];
+ }
+
+ ///
+ /// Reads the data from a as an array of values.
+ ///
+ /// The to read.
+ /// The data.
+ ///
+ /// Thrown if the data-type specified by the file cannot be converted to a .
+ ///
+ public float[] ReadFloatArray(ref TiffIfdEntry entry)
+ {
+ if (entry.Type != TiffType.Float)
+ {
+ throw new ImageFormatException($"A value of type '{entry.Type}' cannot be converted to a float.");
+ }
+
+ byte[] bytes = this.ReadBytes(ref entry);
+ float[] result = new float[entry.Count];
+
+ for (int i = 0; i < result.Length; i++)
+ {
+ result[i] = this.ToSingle(bytes, i * TiffConstants.SizeOfFloat);
+ }
+
+ return result;
+ }
+
+ ///
+ /// Reads the data from a as an array of values.
+ ///
+ /// The to read.
+ /// The data.
+ ///
+ /// Thrown if the data-type specified by the file cannot be converted to a .
+ ///
+ public double[] ReadDoubleArray(ref TiffIfdEntry entry)
+ {
+ if (entry.Type != TiffType.Double)
+ {
+ throw new ImageFormatException($"A value of type '{entry.Type}' cannot be converted to a double.");
+ }
+
+ byte[] bytes = this.ReadBytes(ref entry);
+ double[] result = new double[entry.Count];
+
+ for (int i = 0; i < result.Length; i++)
+ {
+ result[i] = this.ToDouble(bytes, i * TiffConstants.SizeOfDouble);
+ }
+
+ return result;
+ }
+
+ ///
+ /// Calculates the size (in bytes) for the specified TIFF data-type.
+ ///
+ /// The data-type to calculate the size for.
+ /// The size of the data-type (in bytes).
+ private static uint SizeOfDataType(TiffType type)
+ {
+ switch (type)
+ {
+ case TiffType.Byte:
+ case TiffType.Ascii:
+ case TiffType.SByte:
+ case TiffType.Undefined:
+ return 1u;
+ case TiffType.Short:
+ case TiffType.SShort:
+ return 2u;
+ case TiffType.Long:
+ case TiffType.SLong:
+ case TiffType.Float:
+ case TiffType.Ifd:
+ return 4u;
+ case TiffType.Rational:
+ case TiffType.SRational:
+ case TiffType.Double:
+ return 8u;
+ default:
+ return 0u;
+ }
+ }
+
+ ///
+ /// Converts buffer data into an using the correct endianness.
+ ///
+ /// The buffer.
+ /// The byte offset within the buffer.
+ /// The converted value.
+ private sbyte ToSByte(byte[] bytes, int offset)
+ {
+ return (sbyte)bytes[offset];
+ }
+
+ ///
+ /// Converts buffer data into an using the correct endianness.
+ ///
+ /// The buffer.
+ /// The byte offset within the buffer.
+ /// The converted value.
+ private short ToInt16(byte[] bytes, int offset)
+ {
+ if (this.IsLittleEndian)
+ {
+ return (short)(bytes[offset + 0] | (bytes[offset + 1] << 8));
+ }
+ else
+ {
+ return (short)((bytes[offset + 0] << 8) | bytes[offset + 1]);
+ }
+ }
+
+ ///
+ /// Converts buffer data into an using the correct endianness.
+ ///
+ /// The buffer.
+ /// The byte offset within the buffer.
+ /// The converted value.
+ private int ToInt32(byte[] bytes, int offset)
+ {
+ if (this.IsLittleEndian)
+ {
+ return bytes[offset + 0] | (bytes[offset + 1] << 8) | (bytes[offset + 2] << 16) | (bytes[offset + 3] << 24);
+ }
+ else
+ {
+ return (bytes[offset + 0] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3];
+ }
+ }
+
+ ///
+ /// Converts buffer data into a using the correct endianness.
+ ///
+ /// The buffer.
+ /// The byte offset within the buffer.
+ /// The converted value.
+ private byte ToByte(byte[] bytes, int offset)
+ {
+ return bytes[offset];
+ }
+
+ ///
+ /// Converts buffer data into a using the correct endianness.
+ ///
+ /// The buffer.
+ /// The byte offset within the buffer.
+ /// The converted value.
+ private uint ToUInt32(byte[] bytes, int offset)
+ {
+ return (uint)this.ToInt32(bytes, offset);
+ }
+
+ ///
+ /// Converts buffer data into a using the correct endianness.
+ ///
+ /// The buffer.
+ /// The byte offset within the buffer.
+ /// The converted value.
+ private ushort ToUInt16(byte[] bytes, int offset)
+ {
+ return (ushort)this.ToInt16(bytes, offset);
+ }
+
+ ///
+ /// Converts buffer data into a using the correct endianness.
+ ///
+ /// The buffer.
+ /// The byte offset within the buffer.
+ /// The converted value.
+ private float ToSingle(byte[] bytes, int offset)
+ {
+ byte[] buffer = new byte[4];
+ Array.Copy(bytes, offset, buffer, 0, 4);
+
+ if (this.IsLittleEndian != BitConverter.IsLittleEndian)
+ {
+ Array.Reverse(buffer);
+ }
+
+ return BitConverter.ToSingle(buffer, 0);
+ }
+
+ ///
+ /// Converts buffer data into a using the correct endianness.
+ ///
+ /// The buffer.
+ /// The byte offset within the buffer.
+ /// The converted value.
+ private double ToDouble(byte[] bytes, int offset)
+ {
+ byte[] buffer = new byte[8];
+ Array.Copy(bytes, offset, buffer, 0, 8);
+
+ if (this.IsLittleEndian != BitConverter.IsLittleEndian)
+ {
+ Array.Reverse(buffer);
+ }
+
+ return BitConverter.ToDouble(buffer, 0);
+ }
+
+ ///
+ /// Decodes the image data for strip encoded data.
+ ///
+ /// The pixel format.
+ /// The image to decode data into.
+ /// The number of rows per strip of data.
+ /// An array of byte offsets to each strip in the image.
+ /// An array of the size of each strip (in bytes).
+ private void DecodeImageStrips(Image image, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts)
+ where TPixel : struct, IPixel
+ {
+ int stripsPerPixel = this.PlanarConfiguration == TiffPlanarConfiguration.Chunky ? 1 : this.BitsPerSample.Length;
+ int stripsPerPlane = stripOffsets.Length / stripsPerPixel;
+
+ Buffer2D pixels = image.GetRootFramePixelBuffer();
+
+ byte[][] stripBytes = new byte[stripsPerPixel][];
+
+ for (int stripIndex = 0; stripIndex < stripBytes.Length; stripIndex++)
+ {
+ int uncompressedStripSize = this.CalculateImageBufferSize(image.Width, rowsPerStrip, stripIndex);
+ stripBytes[stripIndex] = ArrayPool.Shared.Rent(uncompressedStripSize);
+ }
+
+ try
+ {
+ for (int i = 0; i < stripsPerPlane; i++)
+ {
+ int stripHeight = i < stripsPerPlane - 1 || image.Height % rowsPerStrip == 0 ? rowsPerStrip : image.Height % rowsPerStrip;
+
+ for (int planeIndex = 0; planeIndex < stripsPerPixel; planeIndex++)
+ {
+ int stripIndex = (i * stripsPerPixel) + planeIndex;
+ this.DecompressImageBlock(stripOffsets[stripIndex], stripByteCounts[stripIndex], stripBytes[planeIndex]);
+ }
+
+ if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky)
+ {
+ this.ProcessImageBlockChunky(stripBytes[0], pixels, 0, rowsPerStrip * i, image.Width, stripHeight);
+ }
+ else
+ {
+ this.ProcessImageBlockPlanar(stripBytes, pixels, 0, rowsPerStrip * i, image.Width, stripHeight);
+ }
+ }
+ }
+ finally
+ {
+ for (int stripIndex = 0; stripIndex < stripBytes.Length; stripIndex++)
+ {
+ ArrayPool.Shared.Return(stripBytes[stripIndex]);
+ }
+ }
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoder.cs b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs
new file mode 100644
index 0000000000..63886a0751
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/TiffEncoder.cs
@@ -0,0 +1,27 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.IO;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Formats
+{
+ ///
+ /// Encoder for writing the data image to a stream in TIFF format.
+ ///
+ public class TiffEncoder : IImageEncoder, ITiffEncoderOptions
+ {
+ ///
+ /// Encodes the image to the specified stream from the .
+ ///
+ /// The pixel format.
+ /// The to encode from.
+ /// The to encode the image data to.
+ public void Encode(Image image, Stream stream)
+ where TPixel : struct, IPixel
+ {
+ var encode = new TiffEncoderCore(this);
+ encode.Encode(image, stream);
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
new file mode 100644
index 0000000000..9ab72f316a
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
@@ -0,0 +1,230 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using SixLabors.ImageSharp.Formats.Tiff;
+using SixLabors.ImageSharp.MetaData;
+using SixLabors.ImageSharp.MetaData.Profiles.Exif;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Primitives;
+
+namespace SixLabors.ImageSharp.Formats
+{
+ ///
+ /// Performs the TIFF encoding operation.
+ ///
+ internal sealed class TiffEncoderCore
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The options for the encoder.
+ public TiffEncoderCore(ITiffEncoderOptions options)
+ {
+ options = options ?? new TiffEncoder();
+ }
+
+ ///
+ /// Gets or sets the photometric interpretation implementation to use when encoding the image.
+ ///
+ public TiffColorType ColorType { get; set; }
+
+ ///
+ /// Gets or sets the compression implementation to use when encoding the image.
+ ///
+ public TiffCompressionType CompressionType { get; set; }
+
+ ///
+ /// Encodes the image to the specified stream from the .
+ ///
+ /// The pixel format.
+ /// The to encode from.
+ /// The to encode the image data to.
+ public void Encode(Image image, Stream stream)
+ where TPixel : struct, IPixel
+ {
+ Guard.NotNull(image, nameof(image));
+ Guard.NotNull(stream, nameof(stream));
+
+ using (TiffWriter writer = new TiffWriter(stream))
+ {
+ long firstIfdMarker = this.WriteHeader(writer);
+ long nextIfdMarker = this.WriteImage(writer, image, firstIfdMarker);
+ }
+ }
+
+ ///
+ /// Writes the TIFF file header.
+ ///
+ /// The to write data to.
+ /// 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);
+ long firstIfdMarker = writer.PlaceMarker();
+
+ return firstIfdMarker;
+ }
+
+ ///
+ /// Writes a TIFF IFD block.
+ ///
+ /// The to write data to.
+ /// The IFD entries to write to the file.
+ /// The marker to write the next IFD offset (if present).
+ public long WriteIfd(TiffWriter writer, List entries)
+ {
+ if (entries.Count == 0)
+ {
+ throw new ArgumentException("There must be at least one entry per IFD.", nameof(entries));
+ }
+
+ uint dataOffset = (uint)writer.Position + (uint)(6 + (entries.Count * 12));
+ List largeDataBlocks = new List();
+
+ entries.Sort((a, b) => a.Tag - b.Tag);
+
+ writer.Write((ushort)entries.Count);
+
+ foreach (TiffIfdEntry entry in entries)
+ {
+ writer.Write(entry.Tag);
+ writer.Write((ushort)entry.Type);
+ writer.Write(entry.Count);
+
+ if (entry.Value.Length <= 4)
+ {
+ writer.WritePadded(entry.Value);
+ }
+ else
+ {
+ largeDataBlocks.Add(entry.Value);
+ writer.Write(dataOffset);
+ dataOffset += (uint)(entry.Value.Length + (entry.Value.Length % 2));
+ }
+ }
+
+ long nextIfdMarker = writer.PlaceMarker();
+
+ foreach (byte[] dataBlock in largeDataBlocks)
+ {
+ writer.Write(dataBlock);
+
+ if (dataBlock.Length % 2 == 1)
+ {
+ writer.Write((byte)0);
+ }
+ }
+
+ return nextIfdMarker;
+ }
+
+ ///
+ /// Writes all data required to define an image
+ ///
+ /// The pixel format.
+ /// The to write data to.
+ /// The to encode from.
+ /// The marker to write this IFD offset.
+ /// The marker to write the next IFD offset (if present).
+ public long WriteImage(TiffWriter writer, Image image, long ifdOffset)
+ where TPixel : struct, IPixel
+ {
+ List ifdEntries = new List();
+
+ this.AddImageFormat(image, ifdEntries);
+ this.AddMetadata(image, ifdEntries);
+
+ writer.WriteMarker(ifdOffset, (uint)writer.Position);
+ long nextIfdMarker = this.WriteIfd(writer, ifdEntries);
+
+ return nextIfdMarker;
+ }
+
+ ///
+ /// Adds image metadata to the specified IFD.
+ ///
+ /// The pixel format.
+ /// The to encode from.
+ /// The metadata entries to add to the IFD.
+ public void AddMetadata(Image image, List ifdEntries)
+ where TPixel : struct, IPixel
+ {
+ ifdEntries.AddUnsignedRational(TiffTags.XResolution, new Rational(image.MetaData.HorizontalResolution));
+ ifdEntries.AddUnsignedRational(TiffTags.YResolution, new Rational(image.MetaData.VerticalResolution));
+ ifdEntries.AddUnsignedShort(TiffTags.ResolutionUnit, (uint)TiffResolutionUnit.Inch);
+
+ foreach (ImageProperty metadata in image.MetaData.Properties)
+ {
+ switch (metadata.Name)
+ {
+ case TiffMetadataNames.Artist:
+ {
+ ifdEntries.AddAscii(TiffTags.Artist, metadata.Value);
+ break;
+ }
+
+ case TiffMetadataNames.Copyright:
+ {
+ ifdEntries.AddAscii(TiffTags.Copyright, metadata.Value);
+ break;
+ }
+
+ case TiffMetadataNames.DateTime:
+ {
+ ifdEntries.AddAscii(TiffTags.DateTime, metadata.Value);
+ break;
+ }
+
+ case TiffMetadataNames.HostComputer:
+ {
+ ifdEntries.AddAscii(TiffTags.HostComputer, metadata.Value);
+ break;
+ }
+
+ case TiffMetadataNames.ImageDescription:
+ {
+ ifdEntries.AddAscii(TiffTags.ImageDescription, metadata.Value);
+ break;
+ }
+
+ case TiffMetadataNames.Make:
+ {
+ ifdEntries.AddAscii(TiffTags.Make, metadata.Value);
+ break;
+ }
+
+ case TiffMetadataNames.Model:
+ {
+ ifdEntries.AddAscii(TiffTags.Model, metadata.Value);
+ break;
+ }
+
+ case TiffMetadataNames.Software:
+ {
+ ifdEntries.AddAscii(TiffTags.Software, metadata.Value);
+ break;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Adds image format information to the specified IFD.
+ ///
+ /// The pixel format.
+ /// The to encode from.
+ /// The image format entries to add to the IFD.
+ public void AddImageFormat(Image image, List ifdEntries)
+ where TPixel : struct, IPixel
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Tiff/TiffFormat.cs b/src/ImageSharp/Formats/Tiff/TiffFormat.cs
new file mode 100644
index 0000000000..3f2807a06f
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/TiffFormat.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.Collections.Generic;
+using SixLabors.ImageSharp.Formats.Tiff;
+
+namespace SixLabors.ImageSharp.Formats
+{
+ ///
+ /// Encapsulates the means to encode and decode Tiff images.
+ ///
+ public class TiffFormat : IImageFormat
+ {
+ private TiffFormat()
+ {
+ }
+
+ ///
+ /// Gets the current instance.
+ ///
+ public static TiffFormat Instance { get; } = new TiffFormat();
+
+ ///
+ public string Name => "TIFF";
+
+ ///
+ public string DefaultMimeType => "image/tiff";
+
+ ///
+ public IEnumerable MimeTypes => TiffConstants.MimeTypes;
+
+ ///
+ public IEnumerable FileExtensions => TiffConstants.FileExtensions;
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfd.cs b/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfd.cs
new file mode 100644
index 0000000000..a6534c1558
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfd.cs
@@ -0,0 +1,63 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Data structure for holding details of each TIFF IFD.
+ ///
+ internal struct TiffIfd
+ {
+ ///
+ /// An array of the entries within this IFD.
+ ///
+ public TiffIfdEntry[] Entries;
+
+ ///
+ /// Offset (in bytes) to the next IFD, or zero if this is the last IFD.
+ ///
+ public uint NextIfdOffset;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// An array of the entries within the IFD.
+ /// Offset (in bytes) to the next IFD, or zero if this is the last IFD.
+ public TiffIfd(TiffIfdEntry[] entries, uint nextIfdOffset)
+ {
+ this.Entries = entries;
+ this.NextIfdOffset = nextIfdOffset;
+ }
+
+ ///
+ /// Gets the child with the specified tag ID.
+ ///
+ /// The tag ID to search for.
+ /// The resulting , or null if it does not exists.
+ public TiffIfdEntry? GetIfdEntry(ushort tag)
+ {
+ for (int i = 0; i < this.Entries.Length; i++)
+ {
+ if (this.Entries[i].Tag == tag)
+ {
+ return this.Entries[i];
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Gets the child with the specified tag ID.
+ ///
+ /// The tag ID to search for.
+ /// The resulting , if it exists.
+ /// A flag indicating whether the requested entry exists.
+ public bool TryGetIfdEntry(ushort tag, out TiffIfdEntry entry)
+ {
+ TiffIfdEntry? nullableEntry = this.GetIfdEntry(tag);
+ entry = nullableEntry ?? default(TiffIfdEntry);
+ return nullableEntry.HasValue;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfdEntry.cs b/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfdEntry.cs
new file mode 100644
index 0000000000..de5974035c
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfdEntry.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Data structure for holding details of each TIFF IFD entry.
+ ///
+ internal struct TiffIfdEntry
+ {
+ ///
+ /// The Tag ID for this entry. See for typical values.
+ ///
+ public ushort Tag;
+
+ ///
+ /// The data-type of this entry.
+ ///
+ public TiffType Type;
+
+ ///
+ /// The number of array items in this entry, or one if only a single value.
+ ///
+ public uint Count;
+
+ ///
+ /// The raw byte data for this entry.
+ ///
+ public byte[] Value;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The Tag ID for this entry.
+ /// The data-type of this entry.
+ /// The number of array items in this entry.
+ /// The raw byte data for this entry.
+ public TiffIfdEntry(ushort tag, TiffType type, uint count, byte[] value)
+ {
+ this.Tag = tag;
+ this.Type = type;
+ this.Count = count;
+ this.Value = value;
+ }
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfdEntryCreator.cs b/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfdEntryCreator.cs
new file mode 100644
index 0000000000..35517d1901
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfdEntryCreator.cs
@@ -0,0 +1,354 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using SixLabors.ImageSharp.MetaData.Profiles.Exif;
+using SixLabors.ImageSharp.Primitives;
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Utility class for generating TIFF IFD entries.
+ ///
+ internal static class TiffIfdEntryCreator
+ {
+ ///
+ /// Adds a new of type 'Byte' from a unsigned integer.
+ ///
+ /// The list of to add the new entry to.
+ /// The tag for the resulting entry.
+ /// The value for the resulting entry.
+ public static void AddUnsignedByte(this List entries, ushort tag, uint value)
+ {
+ TiffIfdEntryCreator.AddUnsignedByte(entries, tag, new[] { value });
+ }
+
+ ///
+ /// Adds a new of type 'Byte' from an array of unsigned integers.
+ ///
+ /// The list of to add the new entry to.
+ /// The tag for the resulting entry.
+ /// The value for the resulting entry.
+ public static void AddUnsignedByte(this List entries, ushort tag, uint[] value)
+ {
+ byte[] bytes = new byte[value.Length];
+
+ for (int i = 0; i < value.Length; i++)
+ {
+ bytes[i] = (byte)value[i];
+ }
+
+ entries.Add(new TiffIfdEntry(tag, TiffType.Byte, (uint)value.Length, bytes));
+ }
+
+ ///
+ /// Adds a new of type 'Short' from a unsigned integer.
+ ///
+ /// The list of to add the new entry to.
+ /// The tag for the resulting entry.
+ /// The value for the resulting entry.
+ public static void AddUnsignedShort(this List entries, ushort tag, uint value)
+ {
+ TiffIfdEntryCreator.AddUnsignedShort(entries, tag, new[] { value });
+ }
+
+ ///
+ /// Adds a new of type 'Short' from an array of unsigned integers.
+ ///
+ /// The list of to add the new entry to.
+ /// The tag for the resulting entry.
+ /// The value for the resulting entry.
+ public static void AddUnsignedShort(this List entries, ushort tag, uint[] value)
+ {
+ byte[] bytes = new byte[value.Length * TiffConstants.SizeOfShort];
+
+ for (int i = 0; i < value.Length; i++)
+ {
+ ToBytes((ushort)value[i], bytes, i * TiffConstants.SizeOfShort);
+ }
+
+ entries.Add(new TiffIfdEntry(tag, TiffType.Short, (uint)value.Length, bytes));
+ }
+
+ ///
+ /// Adds a new of type 'Long' from a unsigned integer.
+ ///
+ /// The list of to add the new entry to.
+ /// The tag for the resulting entry.
+ /// The value for the resulting entry.
+ public static void AddUnsignedLong(this List entries, ushort tag, uint value)
+ {
+ TiffIfdEntryCreator.AddUnsignedLong(entries, tag, new[] { value });
+ }
+
+ ///
+ /// Adds a new of type 'Long' from an array of unsigned integers.
+ ///
+ /// The list of to add the new entry to.
+ /// The tag for the resulting entry.
+ /// The value for the resulting entry.
+ public static void AddUnsignedLong(this List entries, ushort tag, uint[] value)
+ {
+ byte[] bytes = new byte[value.Length * TiffConstants.SizeOfLong];
+
+ for (int i = 0; i < value.Length; i++)
+ {
+ ToBytes(value[i], bytes, i * TiffConstants.SizeOfLong);
+ }
+
+ entries.Add(new TiffIfdEntry(tag, TiffType.Long, (uint)value.Length, bytes));
+ }
+
+ ///
+ /// Adds a new of type 'SByte' from a signed integer.
+ ///
+ /// The list of to add the new entry to.
+ /// The tag for the resulting entry.
+ /// The value for the resulting entry.
+ public static void AddSignedByte(this List entries, ushort tag, int value)
+ {
+ TiffIfdEntryCreator.AddSignedByte(entries, tag, new[] { value });
+ }
+
+ ///
+ /// Adds a new of type 'SByte' from an array of signed integers.
+ ///
+ /// The list of to add the new entry to.
+ /// The tag for the resulting entry.
+ /// The value for the resulting entry.
+ public static void AddSignedByte(this List entries, ushort tag, int[] value)
+ {
+ byte[] bytes = new byte[value.Length];
+
+ for (int i = 0; i < value.Length; i++)
+ {
+ bytes[i] = (byte)((sbyte)value[i]);
+ }
+
+ entries.Add(new TiffIfdEntry(tag, TiffType.SByte, (uint)value.Length, bytes));
+ }
+
+ ///
+ /// Adds a new of type 'SShort' from a signed integer.
+ ///
+ /// The list of to add the new entry to.
+ /// The tag for the resulting entry.
+ /// The value for the resulting entry.
+ public static void AddSignedShort(this List entries, ushort tag, int value)
+ {
+ TiffIfdEntryCreator.AddSignedShort(entries, tag, new[] { value });
+ }
+
+ ///
+ /// Adds a new of type 'SShort' from an array of signed integers.
+ ///
+ /// The list of to add the new entry to.
+ /// The tag for the resulting entry.
+ /// The value for the resulting entry.
+ public static void AddSignedShort(this List entries, ushort tag, int[] value)
+ {
+ byte[] bytes = new byte[value.Length * TiffConstants.SizeOfShort];
+
+ for (int i = 0; i < value.Length; i++)
+ {
+ ToBytes((short)value[i], bytes, i * TiffConstants.SizeOfShort);
+ }
+
+ entries.Add(new TiffIfdEntry(tag, TiffType.SShort, (uint)value.Length, bytes));
+ }
+
+ ///
+ /// Adds a new of type 'SLong' from a signed integer.
+ ///
+ /// The list of to add the new entry to.
+ /// The tag for the resulting entry.
+ /// The value for the resulting entry.
+ public static void AddSignedLong(this List entries, ushort tag, int value)
+ {
+ TiffIfdEntryCreator.AddSignedLong(entries, tag, new[] { value });
+ }
+
+ ///
+ /// Adds a new of type 'SLong' from an array of signed integers.
+ ///
+ /// The list of to add the new entry to.
+ /// The tag for the resulting entry.
+ /// The value for the resulting entry.
+ public static void AddSignedLong(this List entries, ushort tag, int[] value)
+ {
+ byte[] bytes = new byte[value.Length * TiffConstants.SizeOfLong];
+
+ for (int i = 0; i < value.Length; i++)
+ {
+ ToBytes(value[i], bytes, i * TiffConstants.SizeOfLong);
+ }
+
+ entries.Add(new TiffIfdEntry(tag, TiffType.SLong, (uint)value.Length, bytes));
+ }
+
+ ///
+ /// Adds a new of type 'Ascii' from a string.
+ ///
+ /// The list of to add the new entry to.
+ /// The tag for the resulting entry.
+ /// The value for the resulting entry.
+ public static void AddAscii(this List entries, ushort tag, string value)
+ {
+ byte[] bytes = Encoding.UTF8.GetBytes(value + "\0");
+
+ entries.Add(new TiffIfdEntry(tag, TiffType.Ascii, (uint)bytes.Length, bytes));
+ }
+
+ ///
+ /// Adds a new of type 'Rational' from a .
+ ///
+ /// The list of to add the new entry to.
+ /// The tag for the resulting entry.
+ /// The value for the resulting entry.
+ public static void AddUnsignedRational(this List entries, ushort tag, Rational value)
+ {
+ TiffIfdEntryCreator.AddUnsignedRational(entries, tag, new[] { value });
+ }
+
+ ///
+ /// Adds a new of type 'Rational' from an array of values.
+ ///
+ /// The list of to add the new entry to.
+ /// The tag for the resulting entry.
+ /// The value for the resulting entry.
+ public static void AddUnsignedRational(this List entries, ushort tag, Rational[] value)
+ {
+ byte[] bytes = new byte[value.Length * TiffConstants.SizeOfRational];
+
+ for (int i = 0; i < value.Length; i++)
+ {
+ int offset = i * TiffConstants.SizeOfRational;
+ ToBytes(value[i].Numerator, bytes, offset);
+ ToBytes(value[i].Denominator, bytes, offset + TiffConstants.SizeOfLong);
+ }
+
+ entries.Add(new TiffIfdEntry(tag, TiffType.Rational, (uint)value.Length, bytes));
+ }
+
+ ///
+ /// Adds a new of type 'SRational' from a .
+ ///
+ /// The list of to add the new entry to.
+ /// The tag for the resulting entry.
+ /// The value for the resulting entry.
+ public static void AddSignedRational(this List entries, ushort tag, SignedRational value)
+ {
+ TiffIfdEntryCreator.AddSignedRational(entries, tag, new[] { value });
+ }
+
+ ///
+ /// Adds a new of type 'SRational' from an array of values.
+ ///
+ /// The list of to add the new entry to.
+ /// The tag for the resulting entry.
+ /// The value for the resulting entry.
+ public static void AddSignedRational(this List entries, ushort tag, SignedRational[] value)
+ {
+ byte[] bytes = new byte[value.Length * TiffConstants.SizeOfRational];
+
+ for (int i = 0; i < value.Length; i++)
+ {
+ int offset = i * TiffConstants.SizeOfRational;
+ ToBytes(value[i].Numerator, bytes, offset);
+ ToBytes(value[i].Denominator, bytes, offset + TiffConstants.SizeOfLong);
+ }
+
+ entries.Add(new TiffIfdEntry(tag, TiffType.SRational, (uint)value.Length, bytes));
+ }
+
+ ///
+ /// Adds a new of type 'Float' from a floating-point value.
+ ///
+ /// The list of to add the new entry to.
+ /// The tag for the resulting entry.
+ /// The value for the resulting entry.
+ public static void AddFloat(this List entries, ushort tag, float value)
+ {
+ TiffIfdEntryCreator.AddFloat(entries, tag, new[] { value });
+ }
+
+ ///
+ /// Adds a new of type 'Float' from an array of floating-point values.
+ ///
+ /// The list of to add the new entry to.
+ /// The tag for the resulting entry.
+ /// The value for the resulting entry.
+ public static void AddFloat(this List entries, ushort tag, float[] value)
+ {
+ byte[] bytes = new byte[value.Length * TiffConstants.SizeOfFloat];
+
+ for (int i = 0; i < value.Length; i++)
+ {
+ byte[] itemBytes = BitConverter.GetBytes(value[i]);
+ Array.Copy(itemBytes, 0, bytes, i * TiffConstants.SizeOfFloat, TiffConstants.SizeOfFloat);
+ }
+
+ entries.Add(new TiffIfdEntry(tag, TiffType.Float, (uint)value.Length, bytes));
+ }
+
+ ///
+ /// Adds a new of type 'Double' from a floating-point value.
+ ///
+ /// The list of to add the new entry to.
+ /// The tag for the resulting entry.
+ /// The value for the resulting entry.
+ public static void AddDouble(this List entries, ushort tag, double value)
+ {
+ TiffIfdEntryCreator.AddDouble(entries, tag, new[] { value });
+ }
+
+ ///
+ /// Adds a new of type 'Double' from an array of floating-point values.
+ ///
+ /// The list of to add the new entry to.
+ /// The tag for the resulting entry.
+ /// The value for the resulting entry.
+ public static void AddDouble(this List entries, ushort tag, double[] value)
+ {
+ byte[] bytes = new byte[value.Length * TiffConstants.SizeOfDouble];
+
+ for (int i = 0; i < value.Length; i++)
+ {
+ byte[] itemBytes = BitConverter.GetBytes(value[i]);
+ Array.Copy(itemBytes, 0, bytes, i * TiffConstants.SizeOfDouble, TiffConstants.SizeOfDouble);
+ }
+
+ entries.Add(new TiffIfdEntry(tag, TiffType.Double, (uint)value.Length, bytes));
+ }
+
+ private static void ToBytes(ushort value, byte[] bytes, int offset)
+ {
+ bytes[offset + 0] = (byte)value;
+ bytes[offset + 1] = (byte)(value >> 8);
+ }
+
+ private static void ToBytes(uint value, byte[] bytes, int offset)
+ {
+ bytes[offset + 0] = (byte)value;
+ bytes[offset + 1] = (byte)(value >> 8);
+ bytes[offset + 2] = (byte)(value >> 16);
+ bytes[offset + 3] = (byte)(value >> 24);
+ }
+
+ private static void ToBytes(short value, byte[] bytes, int offset)
+ {
+ bytes[offset + 0] = (byte)value;
+ bytes[offset + 1] = (byte)(value >> 8);
+ }
+
+ private static void ToBytes(int value, byte[] bytes, int offset)
+ {
+ bytes[offset + 0] = (byte)value;
+ bytes[offset + 1] = (byte)(value >> 8);
+ bytes[offset + 2] = (byte)(value >> 16);
+ bytes[offset + 3] = (byte)(value >> 24);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs b/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs
new file mode 100644
index 0000000000..12ffd5ed5a
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+
+namespace SixLabors.ImageSharp.Formats
+{
+ ///
+ /// Detects tiff file headers
+ ///
+ public sealed class TiffImageFormatDetector : IImageFormatDetector
+ {
+ ///
+ public int HeaderSize => 4;
+
+ ///
+ public IImageFormat DetectFormat(ReadOnlySpan header)
+ {
+ if (this.IsSupportedFileFormat(header))
+ {
+ return TiffFormat.Instance;
+ }
+
+ return null;
+ }
+
+ private bool IsSupportedFileFormat(ReadOnlySpan header)
+ {
+ return header.Length >= this.HeaderSize &&
+ ((header[0] == 0x49 && header[1] == 0x49 && header[2] == 0x2A && header[3] == 0x00) || // Little-endian
+ (header[0] == 0x4D && header[1] == 0x4D && header[2] == 0x00 && header[3] == 0x2A)); // Big-endian
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Tiff/TiffMetadataNames.cs b/src/ImageSharp/Formats/Tiff/TiffMetadataNames.cs
new file mode 100644
index 0000000000..10f558b29e
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/TiffMetadataNames.cs
@@ -0,0 +1,51 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats
+{
+ ///
+ /// Defines constants for each of the supported TIFF metadata types.
+ ///
+ public static class TiffMetadataNames
+ {
+ ///
+ /// Person who created the image.
+ ///
+ public const string Artist = "Artist";
+
+ ///
+ /// Copyright notice.
+ ///
+ public const string Copyright = "Copyright";
+
+ ///
+ /// Date and time of image creation.
+ ///
+ public const string DateTime = "DateTime";
+
+ ///
+ /// The computer and/or operating system in use at the time of image creation.
+ ///
+ public const string HostComputer = "HostComputer";
+
+ ///
+ /// A string that describes the subject of the image.
+ ///
+ public const string ImageDescription = "ImageDescription";
+
+ ///
+ /// The scanner/camera manufacturer.
+ ///
+ public const string Make = "Make";
+
+ ///
+ /// The scanner/camera model name or number.
+ ///
+ public const string Model = "Model";
+
+ ///
+ /// Name and version number of the software package(s) used to create the image.
+ ///
+ public const string Software = "Software";
+ }
+}
diff --git a/src/ImageSharp/Formats/Tiff/Utils/BitReader.cs b/src/ImageSharp/Formats/Tiff/Utils/BitReader.cs
new file mode 100644
index 0000000000..cbd7256ed6
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Utils/BitReader.cs
@@ -0,0 +1,62 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Utility class to read a sequence of bits from an array
+ ///
+ internal class BitReader
+ {
+ private readonly byte[] array;
+ private int offset;
+ private int bitOffset;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array to read data from.
+ public BitReader(byte[] array)
+ {
+ this.array = array;
+ }
+
+ ///
+ /// Reads the specified number of bits from the array.
+ ///
+ /// The number of bits to read.
+ /// The value read from the array.
+ public int ReadBits(uint bits)
+ {
+ int value = 0;
+
+ for (uint i = 0; i < bits; i++)
+ {
+ int bit = (this.array[this.offset] >> (7 - this.bitOffset)) & 0x01;
+ value = (value << 1) | bit;
+
+ this.bitOffset++;
+
+ if (this.bitOffset == 8)
+ {
+ this.bitOffset = 0;
+ this.offset++;
+ }
+ }
+
+ return value;
+ }
+
+ ///
+ /// Moves the reader to the next row of byte-aligned data.
+ ///
+ public void NextRow()
+ {
+ if (this.bitOffset > 0)
+ {
+ this.bitOffset = 0;
+ this.offset++;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Tiff/Utils/SubStream.cs b/src/ImageSharp/Formats/Tiff/Utils/SubStream.cs
new file mode 100644
index 0000000000..aaf9af23a7
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Utils/SubStream.cs
@@ -0,0 +1,176 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.IO;
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Utility class to encapsulate a sub-portion of another .
+ ///
+ ///
+ /// Note that disposing of the does not dispose the underlying
+ /// .
+ ///
+ internal class SubStream : Stream
+ {
+ private Stream innerStream;
+ private long offset;
+ private long endOffset;
+ private long length;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The underlying to wrap.
+ /// The length of the sub-stream.
+ ///
+ /// Note that calling the sub-stream with start from the current offset of the
+ /// underlying
+ ///
+ public SubStream(Stream innerStream, long length)
+ {
+ this.innerStream = innerStream;
+ this.offset = this.innerStream.Position;
+ this.endOffset = this.offset + length;
+ this.length = length;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The underlying to wrap.
+ /// The offset of the sub-stream within the underlying .
+ /// The length of the sub-stream.
+ ///
+ /// Note that calling the constructor will immediately move the underlying
+ /// to the specified offset.
+ ///
+ public SubStream(Stream innerStream, long offset, long length)
+ {
+ this.innerStream = innerStream;
+ this.offset = offset;
+ this.endOffset = offset + length;
+ this.length = length;
+
+ innerStream.Seek(offset, SeekOrigin.Begin);
+ }
+
+ ///
+ public override bool CanRead
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ ///
+ public override bool CanWrite
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ ///
+ public override bool CanSeek
+ {
+ get
+ {
+ return this.innerStream.CanSeek;
+ }
+ }
+
+ ///
+ public override long Length
+ {
+ get
+ {
+ return this.length;
+ }
+ }
+
+ ///
+ public override long Position
+ {
+ get
+ {
+ return this.innerStream.Position - this.offset;
+ }
+
+ set
+ {
+ this.Seek(value, SeekOrigin.Begin);
+ }
+ }
+
+ ///
+ public override void Flush()
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ long bytesRemaining = this.endOffset - this.innerStream.Position;
+
+ if (bytesRemaining < count)
+ {
+ count = (int)bytesRemaining;
+ }
+
+ return this.innerStream.Read(buffer, offset, count);
+ }
+
+ ///
+ public override int ReadByte()
+ {
+ if (this.innerStream.Position < this.endOffset)
+ {
+ return this.innerStream.ReadByte();
+ }
+ else
+ {
+ return -1;
+ }
+ }
+
+ ///
+ public override void Write(byte[] array, int offset, int count)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public override void WriteByte(byte value)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ public override long Seek(long offset, SeekOrigin origin)
+ {
+ switch (origin)
+ {
+ case SeekOrigin.Current:
+ return this.innerStream.Seek(offset, SeekOrigin.Current) - this.offset;
+ case SeekOrigin.Begin:
+ return this.innerStream.Seek(this.offset + offset, SeekOrigin.Begin) - this.offset;
+ case SeekOrigin.End:
+ return this.innerStream.Seek(this.endOffset - offset, SeekOrigin.Begin) - this.offset;
+ default:
+ throw new ArgumentException("Invalid seek origin.");
+ }
+ }
+
+ ///
+ public override void SetLength(long value)
+ {
+ throw new NotSupportedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffLzwDecoder.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffLzwDecoder.cs
new file mode 100644
index 0000000000..6ac09f3916
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Utils/TiffLzwDecoder.cs
@@ -0,0 +1,271 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using System.IO;
+using SixLabors.ImageSharp.Formats.Gif;
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Decompresses and decodes data using the dynamic LZW algorithms.
+ ///
+ ///
+ /// This code is based on the used for GIF decoding. There is potential
+ /// for a shared implementation. Differences between the GIF and TIFF implementations of the LZW
+ /// encoding are: (i) The GIF implementation includes an initial 'data size' byte, whilst this is
+ /// always 8 for TIFF. (ii) The GIF implementation writes a number of sub-blocks with an initial
+ /// byte indicating the length of the sub-block. In TIFF the data is written as a single block
+ /// with no length indicator (this can be determined from the 'StripByteCounts' entry).
+ ///
+ internal sealed class TiffLzwDecoder : IDisposable
+ {
+ ///
+ /// The max decoder pixel stack size.
+ ///
+ private const int MaxStackSize = 4096;
+
+ ///
+ /// The null code.
+ ///
+ private const int NullCode = -1;
+
+ ///
+ /// The stream to decode.
+ ///
+ private readonly Stream stream;
+
+ ///
+ /// The prefix buffer.
+ ///
+ private readonly int[] prefix;
+
+ ///
+ /// The suffix buffer.
+ ///
+ private readonly int[] suffix;
+
+ ///
+ /// The pixel stack buffer.
+ ///
+ private readonly int[] pixelStack;
+
+ ///
+ /// A value indicating whether this instance of the given entity has been disposed.
+ ///
+ /// if this instance has been disposed; otherwise, .
+ ///
+ /// If the entity is disposed, it must not be disposed a second
+ /// time. The isDisposed field is set the first time the entity
+ /// is disposed. If the isDisposed field is true, then the Dispose()
+ /// method will not dispose again. This help not to prolong the entity's
+ /// life in the Garbage Collector.
+ ///
+ private bool isDisposed;
+
+ ///
+ /// Initializes a new instance of the class
+ /// and sets the stream, where the compressed data should be read from.
+ ///
+ /// The stream to read from.
+ /// is null.
+ public TiffLzwDecoder(Stream stream)
+ {
+ Guard.NotNull(stream, nameof(stream));
+
+ this.stream = stream;
+
+ this.prefix = ArrayPool.Shared.Rent(MaxStackSize);
+ this.suffix = ArrayPool.Shared.Rent(MaxStackSize);
+ this.pixelStack = ArrayPool.Shared.Rent(MaxStackSize + 1);
+
+ Array.Clear(this.prefix, 0, MaxStackSize);
+ Array.Clear(this.suffix, 0, MaxStackSize);
+ Array.Clear(this.pixelStack, 0, MaxStackSize + 1);
+ }
+
+ ///
+ /// Decodes and decompresses all pixel indices from the stream.
+ ///
+ /// The length of the compressed data.
+ /// Size of the data.
+ /// The pixel array to decode to.
+ public void DecodePixels(int length, int dataSize, byte[] pixels)
+ {
+ Guard.MustBeLessThan(dataSize, int.MaxValue, nameof(dataSize));
+
+ // Calculate the clear code. The value of the clear code is 2 ^ dataSize
+ int clearCode = 1 << dataSize;
+
+ int codeSize = dataSize + 1;
+
+ // Calculate the end code
+ int endCode = clearCode + 1;
+
+ // Calculate the available code.
+ int availableCode = clearCode + 2;
+
+ // Jillzhangs Code see: http://giflib.codeplex.com/
+ // Adapted from John Cristy's ImageMagick.
+ int code;
+ int oldCode = NullCode;
+ int codeMask = (1 << codeSize) - 1;
+ int bits = 0;
+
+ int top = 0;
+ int count = 0;
+ int bi = 0;
+ int xyz = 0;
+
+ int data = 0;
+ int first = 0;
+
+ for (code = 0; code < clearCode; code++)
+ {
+ this.prefix[code] = 0;
+ this.suffix[code] = (byte)code;
+ }
+
+ byte[] buffer = new byte[255];
+ while (xyz < length)
+ {
+ if (top == 0)
+ {
+ if (bits < codeSize)
+ {
+ // Load bytes until there are enough bits for a code.
+ if (count == 0)
+ {
+ // Read a new data block.
+ count = this.ReadBlock(buffer);
+ if (count == 0)
+ {
+ break;
+ }
+
+ bi = 0;
+ }
+
+ data += buffer[bi] << bits;
+
+ bits += 8;
+ bi++;
+ count--;
+ continue;
+ }
+
+ // Get the next code
+ code = data & codeMask;
+ data >>= codeSize;
+ bits -= codeSize;
+
+ // Interpret the code
+ if (code > availableCode || code == endCode)
+ {
+ break;
+ }
+
+ if (code == clearCode)
+ {
+ // Reset the decoder
+ codeSize = dataSize + 1;
+ codeMask = (1 << codeSize) - 1;
+ availableCode = clearCode + 2;
+ oldCode = NullCode;
+ continue;
+ }
+
+ if (oldCode == NullCode)
+ {
+ this.pixelStack[top++] = this.suffix[code];
+ oldCode = code;
+ first = code;
+ continue;
+ }
+
+ int inCode = code;
+ if (code == availableCode)
+ {
+ this.pixelStack[top++] = (byte)first;
+
+ code = oldCode;
+ }
+
+ while (code > clearCode)
+ {
+ this.pixelStack[top++] = this.suffix[code];
+ code = this.prefix[code];
+ }
+
+ first = this.suffix[code];
+
+ this.pixelStack[top++] = this.suffix[code];
+
+ // Fix for Gifs that have "deferred clear code" as per here :
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=55918
+ if (availableCode < MaxStackSize)
+ {
+ this.prefix[availableCode] = oldCode;
+ this.suffix[availableCode] = first;
+ availableCode++;
+ if (availableCode == codeMask + 1 && availableCode < MaxStackSize)
+ {
+ codeSize++;
+ codeMask = (1 << codeSize) - 1;
+ }
+ }
+
+ oldCode = inCode;
+ }
+
+ // Pop a pixel off the pixel stack.
+ top--;
+
+ // Clear missing pixels
+ pixels[xyz++] = (byte)this.pixelStack[top];
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ this.Dispose(true);
+ }
+
+ ///
+ /// Reads the next data block from the stream. For consistency with the GIF decoder,
+ /// the image is read in blocks - For TIFF this is always a maximum of 255
+ ///
+ /// The buffer to store the block in.
+ ///
+ /// The .
+ ///
+ private int ReadBlock(byte[] buffer)
+ {
+ return this.stream.Read(buffer, 0, 255);
+ }
+
+ ///
+ /// Disposes the object and frees resources for the Garbage Collector.
+ ///
+ /// If true, the object gets disposed.
+ private void Dispose(bool disposing)
+ {
+ if (this.isDisposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ ArrayPool.Shared.Return(this.prefix);
+ ArrayPool.Shared.Return(this.suffix);
+ ArrayPool.Shared.Return(this.pixelStack);
+ }
+
+ this.isDisposed = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffLzwEncoder.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffLzwEncoder.cs
new file mode 100644
index 0000000000..e024b59fa4
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Utils/TiffLzwEncoder.cs
@@ -0,0 +1,495 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Buffers;
+using System.IO;
+using SixLabors.ImageSharp.Formats.Gif;
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Encodes and compresses the image data using dynamic Lempel-Ziv compression.
+ ///
+ ///
+ /// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. K Weiner 12/00
+ ///
+ /// GIFCOMPR.C - GIF Image compression routines
+ ///
+ ///
+ /// Lempel-Ziv compression based on 'compress'. GIF modifications by
+ /// David Rowley (mgardi@watdcsu.waterloo.edu)
+ ///
+ /// GIF Image compression - modified 'compress'
+ ///
+ /// Based on: compress.c - File compression ala IEEE Computer, June 1984.
+ /// By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas)
+ /// Jim McKie (decvax!mcvax!jim)
+ /// Steve Davies (decvax!vax135!petsd!peora!srd)
+ /// Ken Turkowski (decvax!decwrl!turtlevax!ken)
+ /// James A. Woods (decvax!ihnp4!ames!jaw)
+ /// Joe Orost (decvax!vax135!petsd!joe)
+ ///
+ ///
+ /// This code is based on the used for GIF encoding. There is potential
+ /// for a shared implementation. Differences between the GIF and TIFF implementations of the LZW
+ /// encoding are: (i) The GIF implementation includes an initial 'data size' byte, whilst this is
+ /// always 8 for TIFF. (ii) The GIF implementation writes a number of sub-blocks with an initial
+ /// byte indicating the length of the sub-block. In TIFF the data is written as a single block
+ /// with no length indicator (this can be determined from the 'StripByteCounts' entry).
+ ///
+ ///
+ internal sealed class TiffLzwEncoder : IDisposable
+ {
+ ///
+ /// The end-of-file marker
+ ///
+ private const int Eof = -1;
+
+ ///
+ /// The maximum number of bits.
+ ///
+ private const int Bits = 12;
+
+ ///
+ /// 80% occupancy
+ ///
+ private const int HashSize = 5003;
+
+ ///
+ /// Mask used when shifting pixel values
+ ///
+ private static readonly int[] Masks =
+ {
+ 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF,
+ 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF
+ };
+
+ ///
+ /// The working pixel array
+ ///
+ private readonly byte[] pixelArray;
+
+ ///
+ /// The initial code size.
+ ///
+ private readonly int initialCodeSize;
+
+ ///
+ /// The hash table.
+ ///
+ private readonly int[] hashTable;
+
+ ///
+ /// The code table.
+ ///
+ private readonly int[] codeTable;
+
+ ///
+ /// Define the storage for the packet accumulator.
+ ///
+ private readonly byte[] accumulators = new byte[256];
+
+ ///
+ /// A value indicating whether this instance of the given entity has been disposed.
+ ///
+ /// if this instance has been disposed; otherwise, .
+ ///
+ /// If the entity is disposed, it must not be disposed a second
+ /// time. The isDisposed field is set the first time the entity
+ /// is disposed. If the isDisposed field is true, then the Dispose()
+ /// method will not dispose again. This help not to prolong the entity's
+ /// life in the Garbage Collector.
+ ///
+ private bool isDisposed;
+
+ ///
+ /// The current pixel
+ ///
+ private int currentPixel;
+
+ ///
+ /// Number of bits/code
+ ///
+ private int bitCount;
+
+ ///
+ /// User settable max # bits/code
+ ///
+ private int maxbits = Bits;
+
+ ///
+ /// maximum code, given bitCount
+ ///
+ private int maxcode;
+
+ ///
+ /// should NEVER generate this code
+ ///
+ private int maxmaxcode = 1 << Bits;
+
+ ///
+ /// For dynamic table sizing
+ ///
+ private int hsize = HashSize;
+
+ ///
+ /// First unused entry
+ ///
+ private int freeEntry;
+
+ ///
+ /// Block compression parameters -- after all codes are used up,
+ /// and compression rate changes, start over.
+ ///
+ private bool clearFlag;
+
+ ///
+ /// Algorithm: use open addressing double hashing (no chaining) on the
+ /// prefix code / next character combination. We do a variant of Knuth's
+ /// algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime
+ /// secondary probe. Here, the modular division first probe is gives way
+ /// to a faster exclusive-or manipulation. Also do block compression with
+ /// an adaptive reset, whereby the code table is cleared when the compression
+ /// ratio decreases, but after the table fills. The variable-length output
+ /// codes are re-sized at this point, and a special CLEAR code is generated
+ /// for the decompressor. Late addition: construct the table according to
+ /// file size for noticeable speed improvement on small files. Please direct
+ /// questions about this implementation to ames!jaw.
+ ///
+ private int globalInitialBits;
+
+ ///
+ /// The clear code.
+ ///
+ private int clearCode;
+
+ ///
+ /// The end-of-file code.
+ ///
+ private int eofCode;
+
+ ///
+ /// Output the given code.
+ /// Inputs:
+ /// code: A bitCount-bit integer. If == -1, then EOF. This assumes
+ /// that bitCount =< wordsize - 1.
+ /// Outputs:
+ /// Outputs code to the file.
+ /// Assumptions:
+ /// Chars are 8 bits long.
+ /// Algorithm:
+ /// Maintain a BITS character long buffer (so that 8 codes will
+ /// fit in it exactly). Use the VAX insv instruction to insert each
+ /// code in turn. When the buffer fills up empty it and start over.
+ ///
+ private int currentAccumulator;
+
+ ///
+ /// The current bits.
+ ///
+ private int currentBits;
+
+ ///
+ /// Number of characters so far in this 'packet'
+ ///
+ private int accumulatorCount;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The array of indexed pixels.
+ /// The color depth in bits.
+ public TiffLzwEncoder(byte[] indexedPixels, int colorDepth)
+ {
+ this.pixelArray = indexedPixels;
+ this.initialCodeSize = Math.Max(2, colorDepth);
+
+ this.hashTable = ArrayPool.Shared.Rent(HashSize);
+ this.codeTable = ArrayPool.Shared.Rent(HashSize);
+ Array.Clear(this.hashTable, 0, HashSize);
+ Array.Clear(this.codeTable, 0, HashSize);
+ }
+
+ ///
+ /// Encodes and compresses the indexed pixels to the stream.
+ ///
+ /// The stream to write to.
+ public void Encode(Stream stream)
+ {
+ this.currentPixel = 0;
+
+ // Compress and write the pixel data
+ this.Compress(this.initialCodeSize + 1, stream);
+ }
+
+ ///
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ this.Dispose(true);
+ }
+
+ ///
+ /// Gets the maximum code value
+ ///
+ /// The number of bits
+ /// See
+ private static int GetMaxcode(int bitCount)
+ {
+ return (1 << bitCount) - 1;
+ }
+
+ ///
+ /// Add a character to the end of the current packet, and if it is 254 characters,
+ /// flush the packet to disk.
+ ///
+ /// The character to add.
+ /// The stream to write to.
+ private void AddCharacter(byte c, Stream stream)
+ {
+ this.accumulators[this.accumulatorCount++] = c;
+ if (this.accumulatorCount >= 254)
+ {
+ this.FlushPacket(stream);
+ }
+ }
+
+ ///
+ /// Table clear for block compress
+ ///
+ /// The output stream.
+ private void ClearBlock(Stream stream)
+ {
+ this.ResetCodeTable(this.hsize);
+ this.freeEntry = this.clearCode + 2;
+ this.clearFlag = true;
+
+ this.Output(this.clearCode, stream);
+ }
+
+ ///
+ /// Reset the code table.
+ ///
+ /// The hash size.
+ private void ResetCodeTable(int size)
+ {
+ for (int i = 0; i < size; ++i)
+ {
+ this.hashTable[i] = -1;
+ }
+ }
+
+ ///
+ /// Compress the packets to the stream.
+ ///
+ /// The initial bits.
+ /// The stream to write to.
+ private void Compress(int intialBits, Stream stream)
+ {
+ int fcode;
+ int c;
+ int ent;
+ int hsizeReg;
+ int hshift;
+
+ // Set up the globals: globalInitialBits - initial number of bits
+ this.globalInitialBits = intialBits;
+
+ // Set up the necessary values
+ this.clearFlag = false;
+ this.bitCount = this.globalInitialBits;
+ this.maxcode = GetMaxcode(this.bitCount);
+
+ this.clearCode = 1 << (intialBits - 1);
+ this.eofCode = this.clearCode + 1;
+ this.freeEntry = this.clearCode + 2;
+
+ this.accumulatorCount = 0; // clear packet
+
+ ent = this.NextPixel();
+
+ hshift = 0;
+ for (fcode = this.hsize; fcode < 65536; fcode *= 2)
+ {
+ ++hshift;
+ }
+
+ hshift = 8 - hshift; // set hash code range bound
+
+ hsizeReg = this.hsize;
+
+ this.ResetCodeTable(hsizeReg); // clear hash table
+
+ this.Output(this.clearCode, stream);
+
+ while ((c = this.NextPixel()) != Eof)
+ {
+ fcode = (c << this.maxbits) + ent;
+ int i = (c << hshift) ^ ent /* = 0 */;
+
+ if (this.hashTable[i] == fcode)
+ {
+ ent = this.codeTable[i];
+ continue;
+ }
+
+ // Non-empty slot
+ if (this.hashTable[i] >= 0)
+ {
+ int disp = hsizeReg - i;
+ if (i == 0)
+ {
+ disp = 1;
+ }
+
+ do
+ {
+ if ((i -= disp) < 0)
+ {
+ i += hsizeReg;
+ }
+
+ if (this.hashTable[i] == fcode)
+ {
+ ent = this.codeTable[i];
+ break;
+ }
+ }
+ while (this.hashTable[i] >= 0);
+
+ if (this.hashTable[i] == fcode)
+ {
+ continue;
+ }
+ }
+
+ this.Output(ent, stream);
+ ent = c;
+ if (this.freeEntry < this.maxmaxcode)
+ {
+ this.codeTable[i] = this.freeEntry++; // code -> hashtable
+ this.hashTable[i] = fcode;
+ }
+ else
+ {
+ this.ClearBlock(stream);
+ }
+ }
+
+ // Put out the final code.
+ this.Output(ent, stream);
+
+ this.Output(this.eofCode, stream);
+ }
+
+ ///
+ /// Flush the packet to disk, and reset the accumulator.
+ ///
+ /// The output stream.
+ private void FlushPacket(Stream outStream)
+ {
+ if (this.accumulatorCount > 0)
+ {
+ outStream.Write(this.accumulators, 0, this.accumulatorCount);
+ this.accumulatorCount = 0;
+ }
+ }
+
+ ///
+ /// Return the next pixel from the image
+ ///
+ ///
+ /// The
+ ///
+ private int NextPixel()
+ {
+ if (this.currentPixel == this.pixelArray.Length)
+ {
+ return Eof;
+ }
+
+ this.currentPixel++;
+ return this.pixelArray[this.currentPixel - 1] & 0xff;
+ }
+
+ ///
+ /// Output the current code to the stream.
+ ///
+ /// The code.
+ /// The stream to write to.
+ private void Output(int code, Stream outs)
+ {
+ this.currentAccumulator &= Masks[this.currentBits];
+
+ if (this.currentBits > 0)
+ {
+ this.currentAccumulator |= code << this.currentBits;
+ }
+ else
+ {
+ this.currentAccumulator = code;
+ }
+
+ this.currentBits += this.bitCount;
+
+ while (this.currentBits >= 8)
+ {
+ this.AddCharacter((byte)(this.currentAccumulator & 0xff), outs);
+ this.currentAccumulator >>= 8;
+ this.currentBits -= 8;
+ }
+
+ // If the next entry is going to be too big for the code size,
+ // then increase it, if possible.
+ if (this.freeEntry > this.maxcode || this.clearFlag)
+ {
+ if (this.clearFlag)
+ {
+ this.maxcode = GetMaxcode(this.bitCount = this.globalInitialBits);
+ this.clearFlag = false;
+ }
+ else
+ {
+ ++this.bitCount;
+ this.maxcode = this.bitCount == this.maxbits
+ ? this.maxmaxcode
+ : GetMaxcode(this.bitCount);
+ }
+ }
+
+ if (code == this.eofCode)
+ {
+ // At EOF, write the rest of the buffer.
+ while (this.currentBits > 0)
+ {
+ this.AddCharacter((byte)(this.currentAccumulator & 0xff), outs);
+ this.currentAccumulator >>= 8;
+ this.currentBits -= 8;
+ }
+
+ this.FlushPacket(outs);
+ }
+ }
+
+ ///
+ /// Disposes the object and frees resources for the Garbage Collector.
+ ///
+ /// If true, the object gets disposed.
+ private void Dispose(bool disposing)
+ {
+ if (this.isDisposed)
+ {
+ return;
+ }
+
+ if (disposing)
+ {
+ ArrayPool.Shared.Return(this.hashTable);
+ ArrayPool.Shared.Return(this.codeTable);
+ }
+
+ this.isDisposed = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs
new file mode 100644
index 0000000000..7842a71c1a
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs
@@ -0,0 +1,47 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System.IO;
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// TIFF specific utilities and extension methods.
+ ///
+ internal static class TiffUtils
+ {
+ ///
+ /// Reads a sequence of bytes from the input stream into a buffer.
+ ///
+ /// The stream to read from.
+ /// A buffer to store the retrieved data.
+ /// The number of bytes to read.
+ public static void ReadFull(this Stream stream, byte[] buffer, int count)
+ {
+ int offset = 0;
+
+ while (count > 0)
+ {
+ int bytesRead = stream.Read(buffer, offset, count);
+
+ if (bytesRead == 0)
+ {
+ break;
+ }
+
+ offset += bytesRead;
+ count -= bytesRead;
+ }
+ }
+
+ ///
+ /// Reads all bytes from the input stream into a buffer until the end of stream or the buffer is full.
+ ///
+ /// The stream to read from.
+ /// A buffer to store the retrieved data.
+ public static void ReadFull(this Stream stream, byte[] buffer)
+ {
+ ReadFull(stream, buffer, buffer.Length);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
new file mode 100644
index 0000000000..5aa59c1082
--- /dev/null
+++ b/src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
@@ -0,0 +1,108 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace SixLabors.ImageSharp.Formats.Tiff
+{
+ ///
+ /// Utility class for writing TIFF data to a .
+ ///
+ internal class TiffWriter : IDisposable
+ {
+ private readonly Stream output;
+
+ private readonly byte[] paddingBytes = new byte[4];
+
+ private readonly List references = new List();
+
+ /// Initializes a new instance of the class.
+ /// The output stream.
+ public TiffWriter(Stream output)
+ {
+ this.output = output;
+ }
+
+ ///
+ /// Gets a value indicating whether the architecture is little-endian.
+ ///
+ public bool IsLittleEndian => BitConverter.IsLittleEndian;
+
+ ///
+ /// Gets the current position within the stream.
+ ///
+ public long Position => this.output.Position;
+
+ /// Writes an empty four bytes to the stream, returning the offset to be written later.
+ /// The offset to be written later
+ public long PlaceMarker()
+ {
+ long offset = this.output.Position;
+ this.Write(0u);
+ return offset;
+ }
+
+ /// Writes an array of bytes to the current stream.
+ /// The bytes to write.
+ public void Write(byte[] value)
+ {
+ this.output.Write(value, 0, value.Length);
+ }
+
+ /// Writes a byte to the current stream.
+ /// The byte to write.
+ public void Write(byte value)
+ {
+ this.output.Write(new byte[] { value }, 0, 1);
+ }
+
+ /// Writes a two-byte unsigned integer to the current stream.
+ /// The two-byte unsigned integer to write.
+ public void Write(ushort value)
+ {
+ byte[] bytes = BitConverter.GetBytes(value);
+ this.output.Write(bytes, 0, 2);
+ }
+
+ /// Writes a four-byte unsigned integer to the current stream.
+ /// The four-byte unsigned integer to write.
+ public void Write(uint value)
+ {
+ byte[] bytes = BitConverter.GetBytes(value);
+ this.output.Write(bytes, 0, 4);
+ }
+
+ /// Writes an array of bytes to the current stream, padded to four-bytes.
+ /// The bytes to write.
+ public void WritePadded(byte[] value)
+ {
+ this.output.Write(value, 0, value.Length);
+
+ if (value.Length < 4)
+ {
+ this.output.Write(this.paddingBytes, 0, 4 - value.Length);
+ }
+ }
+
+ /// Writes a four-byte unsigned integer to the specified marker in the stream.
+ /// The offset returned when placing the marker
+ /// The four-byte unsigned integer to write.
+ public void WriteMarker(long offset, uint value)
+ {
+ long currentOffset = this.output.Position;
+ this.output.Seek(offset, SeekOrigin.Begin);
+ this.Write(value);
+ this.output.Seek(currentOffset, SeekOrigin.Begin);
+ }
+
+ ///
+ /// Disposes instance, ensuring any unwritten data is flushed.
+ ///
+ public void Dispose()
+ {
+ this.output.Flush();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs
index 1d3cb53afc..0cf8d6bbbf 100644
--- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs
+++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs
@@ -1,3810 +1,3810 @@
-// Copyright (c) Six Labors and contributors.
-// Licensed under the Apache License, Version 2.0.
-
-//
-using System;
-using System.Numerics;
-using System.Buffers;
-
-using SixLabors.ImageSharp.Memory;
-using SixLabors.Memory;
-
-namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
-{
- ///
- /// Collection of Porter Duff alpha blending functions applying different composition models.
- ///
- ///
- /// These functions are designed to be a general solution for all color cases,
- /// that is, they take in account the alpha value of both the backdrop
- /// and source, and there's no need to alpha-premultiply neither the backdrop
- /// nor the source.
- /// Note there are faster functions for when the backdrop color is known
- /// to be opaque
- ///
- internal static class DefaultPixelBlenders
- where TPixel : struct, IPixel
- {
-
- internal class NormalSrc : PixelBlender
- {
- ///
- /// Gets the static instance of this blender.
- ///
- public static NormalSrc Instance { get; } = new NormalSrc();
-
- ///
- public override TPixel Blend(TPixel background, TPixel source, float amount)
- {
- TPixel dest = default;
- dest.FromScaledVector4(PorterDuffFunctions.NormalSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1)));
- return dest;
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount)
- {
- amount = amount.Clamp(0, 1);
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], amount);
- }
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
- {
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], amount[i].Clamp(0, 1));
- }
- }
- }
-
- internal class MultiplySrc : PixelBlender
- {
- ///
- /// Gets the static instance of this blender.
- ///
- public static MultiplySrc Instance { get; } = new MultiplySrc();
-
- ///
- public override TPixel Blend(TPixel background, TPixel source, float amount)
- {
- TPixel dest = default;
- dest.FromScaledVector4(PorterDuffFunctions.MultiplySrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1)));
- return dest;
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount)
- {
- amount = amount.Clamp(0, 1);
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount);
- }
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
- {
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount[i].Clamp(0, 1));
- }
- }
- }
-
- internal class AddSrc : PixelBlender
- {
- ///
- /// Gets the static instance of this blender.
- ///
- public static AddSrc Instance { get; } = new AddSrc();
-
- ///
- public override TPixel Blend(TPixel background, TPixel source, float amount)
- {
- TPixel dest = default;
- dest.FromScaledVector4(PorterDuffFunctions.AddSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1)));
- return dest;
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount)
- {
- amount = amount.Clamp(0, 1);
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount);
- }
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
- {
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount[i].Clamp(0, 1));
- }
- }
- }
-
- internal class SubtractSrc : PixelBlender
- {
- ///
- /// Gets the static instance of this blender.
- ///
- public static SubtractSrc Instance { get; } = new SubtractSrc();
-
- ///
- public override TPixel Blend(TPixel background, TPixel source, float amount)
- {
- TPixel dest = default;
- dest.FromScaledVector4(PorterDuffFunctions.SubtractSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1)));
- return dest;
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount)
- {
- amount = amount.Clamp(0, 1);
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount);
- }
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
- {
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount[i].Clamp(0, 1));
- }
- }
- }
-
- internal class ScreenSrc : PixelBlender
- {
- ///
- /// Gets the static instance of this blender.
- ///
- public static ScreenSrc Instance { get; } = new ScreenSrc();
-
- ///
- public override TPixel Blend(TPixel background, TPixel source, float amount)
- {
- TPixel dest = default;
- dest.FromScaledVector4(PorterDuffFunctions.ScreenSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1)));
- return dest;
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount)
- {
- amount = amount.Clamp(0, 1);
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount);
- }
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
- {
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount[i].Clamp(0, 1));
- }
- }
- }
-
- internal class DarkenSrc : PixelBlender
- {
- ///
- /// Gets the static instance of this blender.
- ///
- public static DarkenSrc Instance { get; } = new DarkenSrc();
-
- ///
- public override TPixel Blend(TPixel background, TPixel source, float amount)
- {
- TPixel dest = default;
- dest.FromScaledVector4(PorterDuffFunctions.DarkenSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1)));
- return dest;
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount)
- {
- amount = amount.Clamp(0, 1);
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount);
- }
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
- {
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount[i].Clamp(0, 1));
- }
- }
- }
-
- internal class LightenSrc : PixelBlender
- {
- ///
- /// Gets the static instance of this blender.
- ///
- public static LightenSrc Instance { get; } = new LightenSrc();
-
- ///
- public override TPixel Blend(TPixel background, TPixel source, float amount)
- {
- TPixel dest = default;
- dest.FromScaledVector4(PorterDuffFunctions.LightenSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1)));
- return dest;
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount)
- {
- amount = amount.Clamp(0, 1);
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], amount);
- }
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
- {
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], amount[i].Clamp(0, 1));
- }
- }
- }
-
- internal class OverlaySrc : PixelBlender
- {
- ///
- /// Gets the static instance of this blender.
- ///
- public static OverlaySrc Instance { get; } = new OverlaySrc();
-
- ///
- public override TPixel Blend(TPixel background, TPixel source, float amount)
- {
- TPixel dest = default;
- dest.FromScaledVector4(PorterDuffFunctions.OverlaySrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1)));
- return dest;
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount)
- {
- amount = amount.Clamp(0, 1);
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount);
- }
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
- {
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount[i].Clamp(0, 1));
- }
- }
- }
-
- internal class HardLightSrc : PixelBlender
- {
- ///
- /// Gets the static instance of this blender.
- ///
- public static HardLightSrc Instance { get; } = new HardLightSrc();
-
- ///
- public override TPixel Blend(TPixel background, TPixel source, float amount)
- {
- TPixel dest = default;
- dest.FromScaledVector4(PorterDuffFunctions.HardLightSrc(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1)));
- return dest;
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount)
- {
- amount = amount.Clamp(0, 1);
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount);
- }
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
- {
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.HardLightSrc(background[i], source[i], amount[i].Clamp(0, 1));
- }
- }
- }
-
- internal class NormalSrcAtop : PixelBlender
- {
- ///
- /// Gets the static instance of this blender.
- ///
- public static NormalSrcAtop Instance { get; } = new NormalSrcAtop();
-
- ///
- public override TPixel Blend(TPixel background, TPixel source, float amount)
- {
- TPixel dest = default;
- dest.FromScaledVector4(PorterDuffFunctions.NormalSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1)));
- return dest;
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount)
- {
- amount = amount.Clamp(0, 1);
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount);
- }
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
- {
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.NormalSrcAtop(background[i], source[i], amount[i].Clamp(0, 1));
- }
- }
- }
-
- internal class MultiplySrcAtop : PixelBlender
- {
- ///
- /// Gets the static instance of this blender.
- ///
- public static MultiplySrcAtop Instance { get; } = new MultiplySrcAtop();
-
- ///
- public override TPixel Blend(TPixel background, TPixel source, float amount)
- {
- TPixel dest = default;
- dest.FromScaledVector4(PorterDuffFunctions.MultiplySrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1)));
- return dest;
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount)
- {
- amount = amount.Clamp(0, 1);
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount);
- }
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
- {
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.MultiplySrcAtop(background[i], source[i], amount[i].Clamp(0, 1));
- }
- }
- }
-
- internal class AddSrcAtop : PixelBlender
- {
- ///
- /// Gets the static instance of this blender.
- ///
- public static AddSrcAtop Instance { get; } = new AddSrcAtop();
-
- ///
- public override TPixel Blend(TPixel background, TPixel source, float amount)
- {
- TPixel dest = default;
- dest.FromScaledVector4(PorterDuffFunctions.AddSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1)));
- return dest;
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount)
- {
- amount = amount.Clamp(0, 1);
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount);
- }
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
- {
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.AddSrcAtop(background[i], source[i], amount[i].Clamp(0, 1));
- }
- }
- }
-
- internal class SubtractSrcAtop : PixelBlender
- {
- ///
- /// Gets the static instance of this blender.
- ///
- public static SubtractSrcAtop Instance { get; } = new SubtractSrcAtop();
-
- ///
- public override TPixel Blend(TPixel background, TPixel source, float amount)
- {
- TPixel dest = default;
- dest.FromScaledVector4(PorterDuffFunctions.SubtractSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1)));
- return dest;
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount)
- {
- amount = amount.Clamp(0, 1);
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount);
- }
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
- {
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.SubtractSrcAtop(background[i], source[i], amount[i].Clamp(0, 1));
- }
- }
- }
-
- internal class ScreenSrcAtop : PixelBlender
- {
- ///
- /// Gets the static instance of this blender.
- ///
- public static ScreenSrcAtop Instance { get; } = new ScreenSrcAtop();
-
- ///
- public override TPixel Blend(TPixel background, TPixel source, float amount)
- {
- TPixel dest = default;
- dest.FromScaledVector4(PorterDuffFunctions.ScreenSrcAtop(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1)));
- return dest;
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount)
- {
- amount = amount.Clamp(0, 1);
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount);
- }
- }
-
- ///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
- {
- for (int i = 0; i < destination.Length; i++)
- {
- destination[i] = PorterDuffFunctions.ScreenSrcAtop(background[i], source[i], amount[i].Clamp(0, 1));
- }
- }
- }
-
- internal class DarkenSrcAtop : PixelBlender
- {
- ///