Browse Source

Merge pull request #119 from Andy-Wilkinson/tiff-codec

[WIP] Implement Tiff codec
pull/1330/head
James Jackson-South 8 years ago
committed by GitHub
parent
commit
a61e83a03e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      ImageSharp.sln
  2. 57
      src/ImageSharp/Formats/Tiff/Compression/DeflateTiffCompression.cs
  3. 30
      src/ImageSharp/Formats/Tiff/Compression/LzwTiffCompression.cs
  4. 26
      src/ImageSharp/Formats/Tiff/Compression/NoneTiffCompression.cs
  5. 77
      src/ImageSharp/Formats/Tiff/Compression/PackBitsTiffCompression.cs
  6. 31
      src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs
  7. 71
      src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs
  8. 83
      src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs
  9. 26
      src/ImageSharp/Formats/Tiff/Constants/TiffExtraSamples.cs
  10. 21
      src/ImageSharp/Formats/Tiff/Constants/TiffFillOrder.cs
  11. 44
      src/ImageSharp/Formats/Tiff/Constants/TiffNewSubfileType.cs
  12. 51
      src/ImageSharp/Formats/Tiff/Constants/TiffOrientation.cs
  13. 71
      src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs
  14. 21
      src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs
  15. 26
      src/ImageSharp/Formats/Tiff/Constants/TiffResolutionUnit.cs
  16. 26
      src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs
  17. 716
      src/ImageSharp/Formats/Tiff/Constants/TiffTags.cs
  18. 26
      src/ImageSharp/Formats/Tiff/Constants/TiffThreshholding.cs
  19. 76
      src/ImageSharp/Formats/Tiff/Constants/TiffType.cs
  20. 16
      src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs
  21. 12
      src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
  22. 51
      src/ImageSharp/Formats/Tiff/ImageExtensions.cs
  23. 52
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor.cs
  24. 60
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor.cs
  25. 44
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor.cs
  26. 51
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor.cs
  27. 71
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor.cs
  28. 46
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor.cs
  29. 58
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor.cs
  30. 54
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor.cs
  31. 71
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs
  32. 52
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor.cs
  33. 60
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor.cs
  34. 44
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor.cs
  35. 51
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor.cs
  36. 250
      src/ImageSharp/Formats/Tiff/README.md
  37. 19
      src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs
  38. 32
      src/ImageSharp/Formats/Tiff/TiffDecoder.cs
  39. 1281
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  40. 27
      src/ImageSharp/Formats/Tiff/TiffEncoder.cs
  41. 230
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  42. 35
      src/ImageSharp/Formats/Tiff/TiffFormat.cs
  43. 63
      src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfd.cs
  44. 46
      src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfdEntry.cs
  45. 354
      src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfdEntryCreator.cs
  46. 34
      src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs
  47. 51
      src/ImageSharp/Formats/Tiff/TiffMetadataNames.cs
  48. 62
      src/ImageSharp/Formats/Tiff/Utils/BitReader.cs
  49. 176
      src/ImageSharp/Formats/Tiff/Utils/SubStream.cs
  50. 271
      src/ImageSharp/Formats/Tiff/Utils/TiffLzwDecoder.cs
  51. 495
      src/ImageSharp/Formats/Tiff/Utils/TiffLzwEncoder.cs
  52. 47
      src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs
  53. 108
      src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
  54. 7618
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs
  55. 226
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt
  56. 362
      src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs
  57. 53
      tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs
  58. 48
      tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
  59. 43
      tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs
  60. 26
      tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs
  61. 33
      tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs
  62. 164
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs
  63. 143
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs
  64. 66
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs
  65. 199
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs
  66. 161
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs
  67. 164
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColorTests.cs
  68. 111
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderHeaderTests.cs
  69. 846
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderIfdEntryTests.cs
  70. 109
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderIfdTests.cs
  71. 512
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderImageTests.cs
  72. 137
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderMetadataTests.cs
  73. 42
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs
  74. 297
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderIfdTests.cs
  75. 59
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMetadataTests.cs
  76. 24
      tests/ImageSharp.Tests/Formats/Tiff/TiffFormatTests.cs
  77. 409
      tests/ImageSharp.Tests/Formats/Tiff/TiffIfd/TiffIfdEntryCreatorTests.cs
  78. 23
      tests/ImageSharp.Tests/Formats/Tiff/TiffIfd/TiffIfdEntryTests.cs
  79. 93
      tests/ImageSharp.Tests/Formats/Tiff/TiffIfd/TiffIfdTests.cs
  80. 87
      tests/ImageSharp.Tests/Formats/Tiff/TiffImageFormatDetectorTests.cs
  81. 326
      tests/ImageSharp.Tests/Formats/Tiff/Utils/SubStreamTests.cs
  82. 132
      tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs
  83. BIN
      tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_grayscale_uncompressed.tiff
  84. BIN
      tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_palette_uncompressed.tiff
  85. BIN
      tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_deflate.tiff
  86. BIN
      tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_jpeg.tiff
  87. BIN
      tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_lzw.tiff
  88. BIN
      tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_packbits.tiff
  89. BIN
      tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_uncompressed.tiff
  90. 12
      tests/ImageSharp.Tests/TestImages/Formats/Tiff/genimages.ps1
  91. 25
      tests/ImageSharp.Tests/TestUtilities/ByteArrayUtility.cs
  92. 39
      tests/ImageSharp.Tests/TestUtilities/ByteBuffer.cs
  93. 15
      tests/ImageSharp.Tests/TestUtilities/Tiff/ITiffGenDataSource.cs
  94. 28
      tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenDataBlock.cs
  95. 20
      tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenDataReference.cs
  96. 204
      tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenEntry.cs
  97. 45
      tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenExtensions.cs
  98. 45
      tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenHeader.cs
  99. 88
      tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenIfd.cs
  100. 31
      tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenIfdExtensions.cs

1
ImageSharp.sln

@ -1,4 +1,3 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26730.12

57
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
{
/// <summary>
/// Class to handle cases where TIFF image data is compressed using Deflate compression.
/// </summary>
/// <remarks>
/// Note that the 'OldDeflate' compression type is identical to the 'Deflate' compression type.
/// </remarks>
internal static class DeflateTiffCompression
{
/// <summary>
/// Decompresses image data into the supplied buffer.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to read image data from.</param>
/// <param name="byteCount">The number of bytes to read from the input stream.</param>
/// <param name="buffer">The output buffer for uncompressed data.</param>
[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);
}
}
}
}

30
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
{
/// <summary>
/// Class to handle cases where TIFF image data is compressed using LZW compression.
/// </summary>
internal static class LzwTiffCompression
{
/// <summary>
/// Decompresses image data into the supplied buffer.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to read image data from.</param>
/// <param name="byteCount">The number of bytes to read from the input stream.</param>
/// <param name="buffer">The output buffer for uncompressed data.</param>
[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);
}
}
}
}

26
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
{
/// <summary>
/// Class to handle cases where TIFF image data is not compressed.
/// </summary>
internal static class NoneTiffCompression
{
/// <summary>
/// Decompresses image data into the supplied buffer.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to read image data from.</param>
/// <param name="byteCount">The number of bytes to read from the input stream.</param>
/// <param name="buffer">The output buffer for uncompressed data.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decompress(Stream stream, int byteCount, byte[] buffer)
{
stream.ReadFull(buffer, byteCount);
}
}
}

77
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
{
/// <summary>
/// Class to handle cases where TIFF image data is compressed using PackBits compression.
/// </summary>
internal static class PackBitsTiffCompression
{
/// <summary>
/// Decompresses image data into the supplied buffer.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to read image data from.</param>
/// <param name="byteCount">The number of bytes to read from the input stream.</param>
/// <param name="buffer">The output buffer for uncompressed data.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decompress(Stream stream, int byteCount, byte[] buffer)
{
byte[] compressedData = ArrayPool<byte>.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<byte>.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;
}
}
}
}

31
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
{
/// <summary>
/// Provides enumeration of the various TIFF compression types.
/// </summary>
internal enum TiffCompressionType
{
/// <summary>
/// Image data is stored uncompressed in the TIFF file.
/// </summary>
None = 0,
/// <summary>
/// Image data is compressed using PackBits compression.
/// </summary>
PackBits = 1,
/// <summary>
/// Image data is compressed using Deflate compression.
/// </summary>
Deflate = 2,
/// <summary>
/// Image data is compressed using LZW compression.
/// </summary>
Lzw = 3,
}
}

71
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
{
/// <summary>
/// Enumeration representing the compression formats defined by the Tiff file-format.
/// </summary>
internal enum TiffCompression
{
/// <summary>
/// No compression.
/// </summary>
None = 1,
/// <summary>
/// CCITT Group 3 1-Dimensional Modified Huffman run-length encoding.
/// </summary>
Ccitt1D = 2,
/// <summary>
/// PackBits compression
/// </summary>
PackBits = 32773,
/// <summary>
/// T4-encoding: CCITT T.4 bi-level encoding (see Section 11 of the TIFF 6.0 specification).
/// </summary>
CcittGroup3Fax = 3,
/// <summary>
/// T6-encoding: CCITT T.6 bi-level encoding (see Section 11 of the TIFF 6.0 specification).
/// </summary>
CcittGroup4Fax = 4,
/// <summary>
/// LZW compression (see Section 13 of the TIFF 6.0 specification).
/// </summary>
Lzw = 5,
/// <summary>
/// JPEG compression - obsolete (see Section 22 of the TIFF 6.0 specification).
/// </summary>
OldJpeg = 6,
/// <summary>
/// JPEG compression (see TIFF Specification, supplement 2).
/// </summary>
Jpeg = 7,
/// <summary>
/// Deflate compression, using zlib data format (see TIFF Specification, supplement 2).
/// </summary>
Deflate = 8,
/// <summary>
/// Deflate compression - old.
/// </summary>
OldDeflate = 32946,
/// <summary>
/// ITU-T Rec. T.82 coding, applying ITU-T Rec. T.85 (JBIG) (see RFC2301).
/// </summary>
ItuTRecT82 = 9,
/// <summary>
/// ITU-T Rec. T.43 representation, using ITU-T Rec. T.82 (JBIG) (see RFC2301).
/// </summary>
ItuTRecT43 = 10
}
}

83
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
{
/// <summary>
/// Defines constants defined in the TIFF specification.
/// </summary>
internal static class TiffConstants
{
/// <summary>
/// Byte order markers for indicating little endian encoding.
/// </summary>
public const byte ByteOrderLittleEndian = 0x49;
/// <summary>
/// Byte order markers for indicating big endian encoding.
/// </summary>
public const byte ByteOrderBigEndian = 0x4D;
/// <summary>
/// Byte order markers for indicating little endian encoding.
/// </summary>
public const ushort ByteOrderLittleEndianShort = 0x4949;
/// <summary>
/// Byte order markers for indicating big endian encoding.
/// </summary>
public const ushort ByteOrderBigEndianShort = 0x4D4D;
/// <summary>
/// Magic number used within the image file header to identify a TIFF format file.
/// </summary>
public const ushort HeaderMagicNumber = 42;
/// <summary>
/// Size (in bytes) of the TIFF file header.
/// </summary>
public const int SizeOfTiffHeader = 8;
/// <summary>
/// Size (in bytes) of each individual TIFF IFD entry
/// </summary>
public const int SizeOfIfdEntry = 12;
/// <summary>
/// Size (in bytes) of the Short and SShort data types
/// </summary>
public const int SizeOfShort = 2;
/// <summary>
/// Size (in bytes) of the Long and SLong data types
/// </summary>
public const int SizeOfLong = 4;
/// <summary>
/// Size (in bytes) of the Rational and SRational data types
/// </summary>
public const int SizeOfRational = 8;
/// <summary>
/// Size (in bytes) of the Float data type
/// </summary>
public const int SizeOfFloat = 4;
/// <summary>
/// Size (in bytes) of the Double data type
/// </summary>
public const int SizeOfDouble = 8;
/// <summary>
/// The list of mimetypes that equate to a tiff.
/// </summary>
public static readonly IEnumerable<string> MimeTypes = new[] { "image/tiff", "image/tiff-fx" };
/// <summary>
/// The list of file extensions that equate to a tiff.
/// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "tiff", "tif" };
}
}

26
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
{
/// <summary>
/// Enumeration representing the possible uses of extra components in TIFF format files.
/// </summary>
internal enum TiffExtraSamples
{
/// <summary>
/// Unspecified data.
/// </summary>
Unspecified = 0,
/// <summary>
/// Associated alpha data (with pre-multiplied color).
/// </summary>
AssociatedAlpha = 1,
/// <summary>
/// Unassociated alpha data.
/// </summary>
UnassociatedAlpha = 2
}
}

21
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
{
/// <summary>
/// Enumeration representing the fill orders defined by the Tiff file-format.
/// </summary>
internal enum TiffFillOrder
{
/// <summary>
/// Pixels with lower column values are stored in the higher-order bits of the byte.
/// </summary>
MostSignificantBitFirst = 1,
/// <summary>
/// Pixels with lower column values are stored in the lower-order bits of the byte.
/// </summary>
LeastSignificantBitFirst = 2
}
}

44
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
{
/// <summary>
/// Enumeration representing the sub-file types defined by the Tiff file-format.
/// </summary>
[Flags]
internal enum TiffNewSubfileType
{
/// <summary>
/// A full-resolution image.
/// </summary>
FullImage = 0x0000,
/// <summary>
/// Reduced-resolution version of another image in this TIFF file.
/// </summary>
Preview = 0x0001,
/// <summary>
/// A single page of a multi-page image.
/// </summary>
SinglePage = 0x0002,
/// <summary>
/// A transparency mask for another image in this TIFF file.
/// </summary>
TransparencyMask = 0x0004,
/// <summary>
/// Alternative reduced-resolution version of another image in this TIFF file (see DNG specification).
/// </summary>
AlternativePreview = 0x10000,
/// <summary>
/// Mixed raster content (see RFC2301).
/// </summary>
MixedRasterContent = 0x0008
}
}

51
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
{
/// <summary>
/// Enumeration representing the image orientations defined by the Tiff file-format.
/// </summary>
internal enum TiffOrientation
{
/// <summary>
/// The 0th row and 0th column represent the visual top and left-hand side of the image respectively.
/// </summary>
TopLeft = 1,
/// <summary>
/// The 0th row and 0th column represent the visual top and right-hand side of the image respectively.
/// </summary>
TopRight = 2,
/// <summary>
/// The 0th row and 0th column represent the visual bottom and right-hand side of the image respectively.
/// </summary>
BottomRight = 3,
/// <summary>
/// The 0th row and 0th column represent the visual bottom and left-hand side of the image respectively.
/// </summary>
BottomLeft = 4,
/// <summary>
/// The 0th row and 0th column represent the visual left-hand side and top of the image respectively.
/// </summary>
LeftTop = 5,
/// <summary>
/// The 0th row and 0th column represent the visual right-hand side and top of the image respectively.
/// </summary>
RightTop = 6,
/// <summary>
/// The 0th row and 0th column represent the visual right-hand side and bottom of the image respectively.
/// </summary>
RightBottom = 7,
/// <summary>
/// The 0th row and 0th column represent the visual left-hand side and bottom of the image respectively.
/// </summary>
LeftBottom = 8
}
}

71
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
{
/// <summary>
/// Enumeration representing the photometric interpretation formats defined by the Tiff file-format.
/// </summary>
internal enum TiffPhotometricInterpretation
{
/// <summary>
/// Bilevel and grayscale: 0 is imaged as white. The maximum value is imaged as black.
/// </summary>
WhiteIsZero = 0,
/// <summary>
/// Bilevel and grayscale: 0 is imaged as black. The maximum value is imaged as white.
/// </summary>
BlackIsZero = 1,
/// <summary>
/// RGB
/// </summary>
Rgb = 2,
/// <summary>
/// Palette Color
/// </summary>
PaletteColor = 3,
/// <summary>
/// A transparency mask
/// </summary>
TransparencyMask = 4,
/// <summary>
/// Separated: usually CMYK (see Section 16 of the TIFF 6.0 specification).
/// </summary>
Separated = 5,
/// <summary>
/// YCbCr (see Section 21 of the TIFF 6.0 specification).
/// </summary>
YCbCr = 6,
/// <summary>
/// 1976 CIE L*a*b* (see Section 23 of the TIFF 6.0 specification).
/// </summary>
CieLab = 8,
/// <summary>
/// ICC L*a*b* (see TIFF Specification, supplement 1).
/// </summary>
IccLab = 9,
/// <summary>
/// ITU L*a*b* (see RFC2301).
/// </summary>
ItuLab = 10,
/// <summary>
/// Color Filter Array (see the DNG specification).
/// </summary>
ColorFilterArray = 32803,
/// <summary>
/// Linear Raw (see the DNG specification).
/// </summary>
LinearRaw = 34892
}
}

21
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
{
/// <summary>
/// Enumeration representing how the components of each pixel are stored the Tiff file-format.
/// </summary>
internal enum TiffPlanarConfiguration
{
/// <summary>
/// Chunky format.
/// </summary>
Chunky = 1,
/// <summary>
/// Planar format.
/// </summary>
Planar = 2
}
}

26
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
{
/// <summary>
/// Enumeration representing the resolution units defined by the Tiff file-format.
/// </summary>
internal enum TiffResolutionUnit
{
/// <summary>
/// No absolute unit of measurement.
/// </summary>
None = 1,
/// <summary>
/// Inch.
/// </summary>
Inch = 2,
/// <summary>
/// Centimeter.
/// </summary>
Centimeter = 3
}
}

26
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
{
/// <summary>
/// Enumeration representing the sub-file types defined by the Tiff file-format.
/// </summary>
internal enum TiffSubfileType
{
/// <summary>
/// Full-resolution image data.
/// </summary>
FullImage = 1,
/// <summary>
/// Reduced-resolution image data.
/// </summary>
Preview = 2,
/// <summary>
/// A single page of a multi-page image.
/// </summary>
SinglePage = 3
}
}

716
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
{
/// <summary>
/// Constants representing tag IDs in the Tiff file-format.
/// </summary>
internal class TiffTags
{
/// <summary>
/// Artist (see Section 8: Baseline Fields).
/// </summary>
public const int Artist = 315;
/// <summary>
/// BitsPerSample (see Section 8: Baseline Fields).
/// </summary>
public const int BitsPerSample = 258;
/// <summary>
/// CellLength (see Section 8: Baseline Fields).
/// </summary>
public const int CellLength = 265;
/// <summary>
/// CellWidth (see Section 8: Baseline Fields).
/// </summary>
public const int CellWidth = 264;
/// <summary>
/// ColorMap (see Section 8: Baseline Fields).
/// </summary>
public const int ColorMap = 320;
/// <summary>
/// Compression (see Section 8: Baseline Fields).
/// </summary>
public const int Compression = 259;
/// <summary>
/// Copyright (see Section 8: Baseline Fields).
/// </summary>
public const int Copyright = 33432;
/// <summary>
/// DateTime (see Section 8: Baseline Fields).
/// </summary>
public const int DateTime = 306;
/// <summary>
/// ExtraSamples (see Section 8: Baseline Fields).
/// </summary>
public const int ExtraSamples = 338;
/// <summary>
/// FillOrder (see Section 8: Baseline Fields).
/// </summary>
public const int FillOrder = 266;
/// <summary>
/// FreeByteCounts (see Section 8: Baseline Fields).
/// </summary>
public const int FreeByteCounts = 289;
/// <summary>
/// FreeOffsets (see Section 8: Baseline Fields).
/// </summary>
public const int FreeOffsets = 288;
/// <summary>
/// GrayResponseCurve (see Section 8: Baseline Fields).
/// </summary>
public const int GrayResponseCurve = 291;
/// <summary>
/// GrayResponseUnit (see Section 8: Baseline Fields).
/// </summary>
public const int GrayResponseUnit = 290;
/// <summary>
/// HostComputer (see Section 8: Baseline Fields).
/// </summary>
public const int HostComputer = 316;
/// <summary>
/// ImageDescription (see Section 8: Baseline Fields).
/// </summary>
public const int ImageDescription = 270;
/// <summary>
/// ImageLength (see Section 8: Baseline Fields).
/// </summary>
public const int ImageLength = 257;
/// <summary>
/// ImageWidth (see Section 8: Baseline Fields).
/// </summary>
public const int ImageWidth = 256;
/// <summary>
/// Make (see Section 8: Baseline Fields).
/// </summary>
public const int Make = 271;
/// <summary>
/// MaxSampleValue (see Section 8: Baseline Fields).
/// </summary>
public const int MaxSampleValue = 281;
/// <summary>
/// MinSampleValue (see Section 8: Baseline Fields).
/// </summary>
public const int MinSampleValue = 280;
/// <summary>
/// Model (see Section 8: Baseline Fields).
/// </summary>
public const int Model = 272;
/// <summary>
/// NewSubfileType (see Section 8: Baseline Fields).
/// </summary>
public const int NewSubfileType = 254;
/// <summary>
/// Orientation (see Section 8: Baseline Fields).
/// </summary>
public const int Orientation = 274;
/// <summary>
/// PhotometricInterpretation (see Section 8: Baseline Fields).
/// </summary>
public const int PhotometricInterpretation = 262;
/// <summary>
/// PlanarConfiguration (see Section 8: Baseline Fields).
/// </summary>
public const int PlanarConfiguration = 284;
/// <summary>
/// ResolutionUnit (see Section 8: Baseline Fields).
/// </summary>
public const int ResolutionUnit = 296;
/// <summary>
/// RowsPerStrip (see Section 8: Baseline Fields).
/// </summary>
public const int RowsPerStrip = 278;
/// <summary>
/// SamplesPerPixel (see Section 8: Baseline Fields).
/// </summary>
public const int SamplesPerPixel = 277;
/// <summary>
/// Software (see Section 8: Baseline Fields).
/// </summary>
public const int Software = 305;
/// <summary>
/// StripByteCounts (see Section 8: Baseline Fields).
/// </summary>
public const int StripByteCounts = 279;
/// <summary>
/// StripOffsets (see Section 8: Baseline Fields).
/// </summary>
public const int StripOffsets = 273;
/// <summary>
/// SubfileType (see Section 8: Baseline Fields).
/// </summary>
public const int SubfileType = 255;
/// <summary>
/// Threshholding (see Section 8: Baseline Fields).
/// </summary>
public const int Threshholding = 263;
/// <summary>
/// XResolution (see Section 8: Baseline Fields).
/// </summary>
public const int XResolution = 282;
/// <summary>
/// YResolution (see Section 8: Baseline Fields).
/// </summary>
public const int YResolution = 283;
/// <summary>
/// T4Options (see Section 11: CCITT Bilevel Encodings).
/// </summary>
public const int T4Options = 292;
/// <summary>
/// T6Options (see Section 11: CCITT Bilevel Encodings).
/// </summary>
public const int T6Options = 293;
/// <summary>
/// DocumentName (see Section 12: Document Storage and Retrieval).
/// </summary>
public const int DocumentName = 269;
/// <summary>
/// PageName (see Section 12: Document Storage and Retrieval).
/// </summary>
public const int PageName = 285;
/// <summary>
/// PageNumber (see Section 12: Document Storage and Retrieval).
/// </summary>
public const int PageNumber = 297;
/// <summary>
/// XPosition (see Section 12: Document Storage and Retrieval).
/// </summary>
public const int XPosition = 286;
/// <summary>
/// YPosition (see Section 12: Document Storage and Retrieval).
/// </summary>
public const int YPosition = 287;
/// <summary>
/// Predictor (see Section 14: Differencing Predictor).
/// </summary>
public const int Predictor = 317;
/// <summary>
/// TileWidth (see Section 15: Tiled Images).
/// </summary>
public const int TileWidth = 322;
/// <summary>
/// TileLength (see Section 15: Tiled Images).
/// </summary>
public const int TileLength = 323;
/// <summary>
/// TileOffsets (see Section 15: Tiled Images).
/// </summary>
public const int TileOffsets = 324;
/// <summary>
/// TileByteCounts (see Section 15: Tiled Images).
/// </summary>
public const int TileByteCounts = 325;
/// <summary>
/// InkSet (see Section 16: CMYK Images).
/// </summary>
public const int InkSet = 332;
/// <summary>
/// NumberOfInks (see Section 16: CMYK Images).
/// </summary>
public const int NumberOfInks = 334;
/// <summary>
/// InkNames (see Section 16: CMYK Images).
/// </summary>
public const int InkNames = 333;
/// <summary>
/// DotRange (see Section 16: CMYK Images).
/// </summary>
public const int DotRange = 336;
/// <summary>
/// TargetPrinter (see Section 16: CMYK Images).
/// </summary>
public const int TargetPrinter = 337;
/// <summary>
/// HalftoneHints (see Section 17: Halftone Hints).
/// </summary>
public const int HalftoneHints = 321;
/// <summary>
/// SampleFormat (see Section 19: Data Sample Format).
/// </summary>
public const int SampleFormat = 339;
/// <summary>
/// SMinSampleValue (see Section 19: Data Sample Format).
/// </summary>
public const int SMinSampleValue = 340;
/// <summary>
/// SMaxSampleValue (see Section 19: Data Sample Format).
/// </summary>
public const int SMaxSampleValue = 341;
/// <summary>
/// WhitePoint (see Section 20: RGB Image Colorimetry).
/// </summary>
public const int WhitePoint = 318;
/// <summary>
/// PrimaryChromaticities (see Section 20: RGB Image Colorimetry).
/// </summary>
public const int PrimaryChromaticities = 319;
/// <summary>
/// TransferFunction (see Section 20: RGB Image Colorimetry).
/// </summary>
public const int TransferFunction = 301;
/// <summary>
/// TransferRange (see Section 20: RGB Image Colorimetry).
/// </summary>
public const int TransferRange = 342;
/// <summary>
/// ReferenceBlackWhite (see Section 20: RGB Image Colorimetry).
/// </summary>
public const int ReferenceBlackWhite = 532;
/// <summary>
/// YCbCrCoefficients (see Section 21: YCbCr Images).
/// </summary>
public const int YCbCrCoefficients = 529;
/// <summary>
/// YCbCrSubSampling (see Section 21: YCbCr Images).
/// </summary>
public const int YCbCrSubSampling = 530;
/// <summary>
/// YCbCrPositioning (see Section 21: YCbCr Images).
/// </summary>
public const int YCbCrPositioning = 531;
/// <summary>
/// JpegProc (see Section 22: JPEG Compression).
/// </summary>
public const int JpegProc = 512;
/// <summary>
/// JpegInterchangeFormat (see Section 22: JPEG Compression).
/// </summary>
public const int JpegInterchangeFormat = 513;
/// <summary>
/// JpegInterchangeFormatLength (see Section 22: JPEG Compression).
/// </summary>
public const int JpegInterchangeFormatLength = 514;
/// <summary>
/// JpegRestartInterval (see Section 22: JPEG Compression).
/// </summary>
public const int JpegRestartInterval = 515;
/// <summary>
/// JpegLosslessPredictors (see Section 22: JPEG Compression).
/// </summary>
public const int JpegLosslessPredictors = 517;
/// <summary>
/// JpegPointTransforms (see Section 22: JPEG Compression).
/// </summary>
public const int JpegPointTransforms = 518;
/// <summary>
/// JpegQTables (see Section 22: JPEG Compression).
/// </summary>
public const int JpegQTables = 519;
/// <summary>
/// JpegDCTables (see Section 22: JPEG Compression).
/// </summary>
public const int JpegDCTables = 520;
/// <summary>
/// JpegACTables (see Section 22: JPEG Compression).
/// </summary>
public const int JpegACTables = 521;
/// <summary>
/// SubIFDs (see TIFF Supplement 1: Adobe Pagemaker 6.0).
/// </summary>
public const int SubIFDs = 330;
/// <summary>
/// ClipPath (see TIFF Supplement 1: Adobe Pagemaker 6.0).
/// </summary>
public const int ClipPath = 343;
/// <summary>
/// XClipPathUnits (see TIFF Supplement 1: Adobe Pagemaker 6.0).
/// </summary>
public const int XClipPathUnits = 344;
/// <summary>
/// YClipPathUnits (see TIFF Supplement 1: Adobe Pagemaker 6.0).
/// </summary>
public const int YClipPathUnits = 345;
/// <summary>
/// Indexed (see TIFF Supplement 1: Adobe Pagemaker 6.0).
/// </summary>
public const int Indexed = 346;
/// <summary>
/// ImageID (see TIFF Supplement 1: Adobe Pagemaker 6.0).
/// </summary>
public const int ImageID = 32781;
/// <summary>
/// OpiProxy (see TIFF Supplement 1: Adobe Pagemaker 6.0).
/// </summary>
public const int OpiProxy = 351;
/// <summary>
/// ImageSourceData (see TIFF Supplement 2: Adobe Photoshop).
/// </summary>
public const int ImageSourceData = 37724;
/// <summary>
/// JPEGTables (see TIFF/EP Specification: Additional Tags).
/// </summary>
public const int JPEGTables = 0x015B;
/// <summary>
/// CFARepeatPatternDim (see TIFF/EP Specification: Additional Tags).
/// </summary>
public const int CFARepeatPatternDim = 0x828D;
/// <summary>
/// BatteryLevel (see TIFF/EP Specification: Additional Tags).
/// </summary>
public const int BatteryLevel = 0x828F;
/// <summary>
/// Interlace (see TIFF/EP Specification: Additional Tags).
/// </summary>
public const int Interlace = 0x8829;
/// <summary>
/// TimeZoneOffset (see TIFF/EP Specification: Additional Tags).
/// </summary>
public const int TimeZoneOffset = 0x882A;
/// <summary>
/// SelfTimerMode (see TIFF/EP Specification: Additional Tags).
/// </summary>
public const int SelfTimerMode = 0x882B;
/// <summary>
/// Noise (see TIFF/EP Specification: Additional Tags).
/// </summary>
public const int Noise = 0x920D;
/// <summary>
/// ImageNumber (see TIFF/EP Specification: Additional Tags).
/// </summary>
public const int ImageNumber = 0x9211;
/// <summary>
/// SecurityClassification (see TIFF/EP Specification: Additional Tags).
/// </summary>
public const int SecurityClassification = 0x9212;
/// <summary>
/// ImageHistory (see TIFF/EP Specification: Additional Tags).
/// </summary>
public const int ImageHistory = 0x9213;
/// <summary>
/// TiffEPStandardID (see TIFF/EP Specification: Additional Tags).
/// </summary>
public const int TiffEPStandardID = 0x9216;
/// <summary>
/// BadFaxLines (see RFC2301: TIFF-F/FX Specification).
/// </summary>
public const int BadFaxLines = 326;
/// <summary>
/// CleanFaxData (see RFC2301: TIFF-F/FX Specification).
/// </summary>
public const int CleanFaxData = 327;
/// <summary>
/// ConsecutiveBadFaxLines (see RFC2301: TIFF-F/FX Specification).
/// </summary>
public const int ConsecutiveBadFaxLines = 328;
/// <summary>
/// GlobalParametersIFD (see RFC2301: TIFF-F/FX Specification).
/// </summary>
public const int GlobalParametersIFD = 400;
/// <summary>
/// ProfileType (see RFC2301: TIFF-F/FX Specification).
/// </summary>
public const int ProfileType = 401;
/// <summary>
/// FaxProfile (see RFC2301: TIFF-F/FX Specification).
/// </summary>
public const int FaxProfile = 402;
/// <summary>
/// CodingMethod (see RFC2301: TIFF-F/FX Specification).
/// </summary>
public const int CodingMethod = 403;
/// <summary>
/// VersionYear (see RFC2301: TIFF-F/FX Specification).
/// </summary>
public const int VersionYear = 404;
/// <summary>
/// ModeNumber (see RFC2301: TIFF-F/FX Specification).
/// </summary>
public const int ModeNumber = 405;
/// <summary>
/// Decode (see RFC2301: TIFF-F/FX Specification).
/// </summary>
public const int Decode = 433;
/// <summary>
/// DefaultImageColor (see RFC2301: TIFF-F/FX Specification).
/// </summary>
public const int DefaultImageColor = 434;
/// <summary>
/// StripRowCounts (see RFC2301: TIFF-F/FX Specification).
/// </summary>
public const int StripRowCounts = 559;
/// <summary>
/// ImageLayer (see RFC2301: TIFF-F/FX Specification).
/// </summary>
public const int ImageLayer = 34732;
/// <summary>
/// Xmp (Embedded Metadata).
/// </summary>
public const int Xmp = 700;
/// <summary>
/// Iptc (Embedded Metadata).
/// </summary>
public const int Iptc = 33723;
/// <summary>
/// Photoshop (Embedded Metadata).
/// </summary>
public const int Photoshop = 34377;
/// <summary>
/// ExifIFD (Embedded Metadata).
/// </summary>
public const int ExifIFD = 34665;
/// <summary>
/// GpsIFD (Embedded Metadata).
/// </summary>
public const int GpsIFD = 34853;
/// <summary>
/// InteroperabilityIFD (Embedded Metadata).
/// </summary>
public const int InteroperabilityIFD = 40965;
/// <summary>
/// WangAnnotation (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int WangAnnotation = 32932;
/// <summary>
/// MDFileTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int MDFileTag = 33445;
/// <summary>
/// MDScalePixel (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int MDScalePixel = 33446;
/// <summary>
/// MDColorTable (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int MDColorTable = 33447;
/// <summary>
/// MDLabName (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int MDLabName = 33448;
/// <summary>
/// MDSampleInfo (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int MDSampleInfo = 33449;
/// <summary>
/// MDPrepDate (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int MDPrepDate = 33450;
/// <summary>
/// MDPrepTime (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int MDPrepTime = 33451;
/// <summary>
/// MDFileUnits (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int MDFileUnits = 33452;
/// <summary>
/// ModelPixelScaleTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int ModelPixelScaleTag = 33550;
/// <summary>
/// IngrPacketDataTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int IngrPacketDataTag = 33918;
/// <summary>
/// IngrFlagRegisters (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int IngrFlagRegisters = 33919;
/// <summary>
/// IrasBTransformationMatrix (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int IrasBTransformationMatrix = 33920;
/// <summary>
/// ModelTiePointTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int ModelTiePointTag = 33922;
/// <summary>
/// ModelTransformationTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int ModelTransformationTag = 34264;
/// <summary>
/// IccProfile (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int IccProfile = 34675;
/// <summary>
/// GeoKeyDirectoryTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int GeoKeyDirectoryTag = 34735;
/// <summary>
/// GeoDoubleParamsTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int GeoDoubleParamsTag = 34736;
/// <summary>
/// GeoAsciiParamsTag (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int GeoAsciiParamsTag = 34737;
/// <summary>
/// HylaFAXFaxRecvParams (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int HylaFAXFaxRecvParams = 34908;
/// <summary>
/// HylaFAXFaxSubAddress (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int HylaFAXFaxSubAddress = 34909;
/// <summary>
/// HylaFAXFaxRecvTime (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int HylaFAXFaxRecvTime = 34910;
/// <summary>
/// GdalMetadata (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int GdalMetadata = 42112;
/// <summary>
/// GdalNodata (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int GdalNodata = 42113;
/// <summary>
/// OceScanjobDescription (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int OceScanjobDescription = 50215;
/// <summary>
/// OceApplicationSelector (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int OceApplicationSelector = 50216;
/// <summary>
/// OceIdentificationNumber (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int OceIdentificationNumber = 50217;
/// <summary>
/// OceImageLogicCharacteristics (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int OceImageLogicCharacteristics = 50218;
/// <summary>
/// AliasLayerMetadata (Other Private TIFF tags : see http://www.awaresystems.be/imaging/tiff/tifftags/private.html).
/// </summary>
public const int AliasLayerMetadata = 50784;
}
}

26
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
{
/// <summary>
/// Enumeration representing the threshholding applied to image data defined by the Tiff file-format.
/// </summary>
internal enum TiffThreshholding
{
/// <summary>
/// No dithering or halftoning.
/// </summary>
None = 1,
/// <summary>
/// An ordered dither or halftone technique.
/// </summary>
Ordered = 2,
/// <summary>
/// A randomized process such as error diffusion.
/// </summary>
Random = 3
}
}

76
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
{
/// <summary>
/// Enumeration representing the data types understood by the Tiff file-format.
/// </summary>
internal enum TiffType
{
/// <summary>
/// Unsigned 8-bit integer.
/// </summary>
Byte = 1,
/// <summary>
/// ASCII formatted text.
/// </summary>
Ascii = 2,
/// <summary>
/// Unsigned 16-bit integer.
/// </summary>
Short = 3,
/// <summary>
/// Unsigned 32-bit integer.
/// </summary>
Long = 4,
/// <summary>
/// Unsigned rational number.
/// </summary>
Rational = 5,
/// <summary>
/// Signed 8-bit integer.
/// </summary>
SByte = 6,
/// <summary>
/// Undefined data type.
/// </summary>
Undefined = 7,
/// <summary>
/// Signed 16-bit integer.
/// </summary>
SShort = 8,
/// <summary>
/// Signed 32-bit integer.
/// </summary>
SLong = 9,
/// <summary>
/// Signed rational number.
/// </summary>
SRational = 10,
/// <summary>
/// Single precision (4-byte) IEEE format.
/// </summary>
Float = 11,
/// <summary>
/// Double precision (8-byte) IEEE format.
/// </summary>
Double = 12,
/// <summary>
/// Reference to an IFD.
/// </summary>
Ifd = 13
}
}

16
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
{
/// <summary>
/// Encapsulates the options for the <see cref="TiffDecoder"/>.
/// </summary>
public interface ITiffDecoderOptions
{
/// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
bool IgnoreMetadata { get; }
}
}

12
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
{
/// <summary>
/// Encapsulates the options for the <see cref="TiffEncoder"/>.
/// </summary>
public interface ITiffEncoderOptions
{
}
}

51
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
{
/// <summary>
/// Extension methods for the <see cref="Image{TPixel}"/> type.
/// </summary>
public static partial class ImageExtensions
{
/// <summary>
/// Saves the image to the given stream with the tiff format.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>
/// The <see cref="Image{TPixel}"/>.
/// </returns>
public static Image<TPixel> SaveAsTiff<TPixel>(this Image<TPixel> source, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
return SaveAsTiff(source, stream, null);
}
/// <summary>
/// Saves the image to the given stream with the tiff format.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The options for the encoder.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>
/// The <see cref="Image{TPixel}"/>.
/// </returns>
public static Image<TPixel> SaveAsTiff<TPixel>(this Image<TPixel> source, Stream stream, TiffEncoder encoder)
where TPixel : struct, IPixel<TPixel>
{
encoder = encoder ?? new TiffEncoder();
encoder.Encode(source, stream);
return source;
}
}
}

52
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
{
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation (optimised for bilevel images).
/// </summary>
internal static class BlackIsZero1TiffColor
{
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TPixel>(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
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;
}
}
}
}
}
}

60
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
{
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation (optimised for 4-bit grayscale images).
/// </summary>
internal static class BlackIsZero4TiffColor
{
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TPixel>(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
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;
}
}
}
}
}

44
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
{
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation (optimised for 8-bit grayscale images).
/// </summary>
internal static class BlackIsZero8TiffColor
{
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TPixel>(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
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;
}
}
}
}
}

51
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
{
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation (for all bit depths).
/// </summary>
internal static class BlackIsZeroTiffColor
{
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="bitsPerSample">The number of bits per sample for each pixel.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TPixel>(byte[] data, uint[] bitsPerSample, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
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();
}
}
}
}

71
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
{
/// <summary>
/// Implements the 'PaletteTiffColor' photometric interpretation (for all bit depths).
/// </summary>
internal static class PaletteTiffColor
{
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="bitsPerSample">The number of bits per sample for each pixel.</param>
/// <param name="colorMap">The RGB color lookup table to use for decoding the image.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TPixel>(byte[] data, uint[] bitsPerSample, uint[] colorMap, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
int colorCount = (int)Math.Pow(2, bitsPerSample[0]);
TPixel[] palette = GeneratePalette<TPixel>(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<TPixel>(uint[] colorMap, int colorCount)
where TPixel : struct, IPixel<TPixel>
{
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;
}
}
}

46
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
{
/// <summary>
/// Implements the 'RGB' photometric interpretation (optimised for 8-bit full color images).
/// </summary>
internal static class Rgb888TiffColor
{
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TPixel>(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
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;
}
}
}
}
}

58
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
{
/// <summary>
/// Implements the 'RGB' photometric interpretation with 'Planar' layout (for all bit depths).
/// </summary>
internal static class RgbPlanarTiffColor
{
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffers to read image data from.</param>
/// <param name="bitsPerSample">The number of bits per sample for each pixel.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TPixel>(byte[][] data, uint[] bitsPerSample, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
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();
}
}
}
}

54
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
{
/// <summary>
/// Implements the 'RGB' photometric interpretation (for all bit depths).
/// </summary>
internal static class RgbTiffColor
{
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="bitsPerSample">The number of bits per sample for each pixel.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TPixel>(byte[] data, uint[] bitsPerSample, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
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();
}
}
}
}

71
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
{
/// <summary>
/// Provides enumeration of the various TIFF photometric interpretation implementation types.
/// </summary>
internal enum TiffColorType
{
/// <summary>
/// Grayscale: 0 is imaged as black. The maximum value is imaged as white.
/// </summary>
BlackIsZero,
/// <summary>
/// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimised implementation for bilevel images.
/// </summary>
BlackIsZero1,
/// <summary>
/// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimised implementation for 4-bit images.
/// </summary>
BlackIsZero4,
/// <summary>
/// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimised implementation for 8-bit images.
/// </summary>
BlackIsZero8,
/// <summary>
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black.
/// </summary>
WhiteIsZero,
/// <summary>
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimised implementation for bilevel images.
/// </summary>
WhiteIsZero1,
/// <summary>
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimised implementation for 4-bit images.
/// </summary>
WhiteIsZero4,
/// <summary>
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimised implementation for 8-bit images.
/// </summary>
WhiteIsZero8,
/// <summary>
/// Palette-color.
/// </summary>
PaletteColor,
/// <summary>
/// RGB Full Color.
/// </summary>
Rgb,
/// <summary>
/// RGB Full Color. Optimised implementation for 8-bit images.
/// </summary>
Rgb888,
/// <summary>
/// RGB Full Color. Planar configuration of data.
/// </summary>
RgbPlanar,
}
}

52
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
{
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation (optimised for bilevel images).
/// </summary>
internal static class WhiteIsZero1TiffColor
{
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TPixel>(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
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;
}
}
}
}
}
}

60
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
{
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation (optimised for 4-bit grayscale images).
/// </summary>
internal static class WhiteIsZero4TiffColor
{
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TPixel>(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
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;
}
}
}
}
}

44
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
{
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation (optimised for 8-bit grayscale images).
/// </summary>
internal static class WhiteIsZero8TiffColor
{
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TPixel>(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
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;
}
}
}
}
}

51
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
{
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation (for all bit depths).
/// </summary>
internal static class WhiteIsZeroTiffColor
{
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="bitsPerSample">The number of bits per sample for each pixel.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TPixel>(byte[] data, uint[] bitsPerSample, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
where TPixel : struct, IPixel<TPixel>
{
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();
}
}
}
}

250
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 | | | |

19
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
{
/// <summary>
/// Registers the image encoders, decoders and mime type detectors for the TIFF format.
/// </summary>
public sealed class TiffConfigurationModule : IConfigurationModule
{
/// <inheritdoc/>
public void Configure(Configuration configuration)
{
configuration.ImageFormatsManager.SetEncoder(TiffFormat.Instance, new TiffEncoder());
configuration.ImageFormatsManager.SetDecoder(TiffFormat.Instance, new TiffDecoder());
configuration.ImageFormatsManager.AddImageFormatDetector(new TiffImageFormatDetector());
}
}
}

32
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
{
/// <summary>
/// Image decoder for generating an image out of a TIFF stream.
/// </summary>
public class TiffDecoder : IImageDecoder, ITiffDecoderOptions
{
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
public bool IgnoreMetadata { get; set; }
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
Guard.NotNull(stream, "stream");
using (TiffDecoderCore decoder = new TiffDecoderCore(configuration, this))
{
return decoder.Decode<TPixel>(stream);
}
}
}
}

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

File diff suppressed because it is too large

27
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
{
/// <summary>
/// Encoder for writing the data image to a stream in TIFF format.
/// </summary>
public class TiffEncoder : IImageEncoder, ITiffEncoderOptions
{
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
var encode = new TiffEncoderCore(this);
encode.Encode(image, stream);
}
}
}

230
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
{
/// <summary>
/// Performs the TIFF encoding operation.
/// </summary>
internal sealed class TiffEncoderCore
{
/// <summary>
/// Initializes a new instance of the <see cref="TiffEncoderCore"/> class.
/// </summary>
/// <param name="options">The options for the encoder.</param>
public TiffEncoderCore(ITiffEncoderOptions options)
{
options = options ?? new TiffEncoder();
}
/// <summary>
/// Gets or sets the photometric interpretation implementation to use when encoding the image.
/// </summary>
public TiffColorType ColorType { get; set; }
/// <summary>
/// Gets or sets the compression implementation to use when encoding the image.
/// </summary>
public TiffCompressionType CompressionType { get; set; }
/// <summary>
/// Encodes the image to the specified stream from the <see cref="Image{TPixel}"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="stream">The <see cref="Stream"/> to encode the image data to.</param>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : struct, IPixel<TPixel>
{
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);
}
}
/// <summary>
/// Writes the TIFF file header.
/// </summary>
/// <param name="writer">The <see cref="TiffWriter"/> to write data to.</param>
/// <returns>The marker to write the first IFD offset.</returns>
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;
}
/// <summary>
/// Writes a TIFF IFD block.
/// </summary>
/// <param name="writer">The <see cref="BinaryWriter"/> to write data to.</param>
/// <param name="entries">The IFD entries to write to the file.</param>
/// <returns>The marker to write the next IFD offset (if present).</returns>
public long WriteIfd(TiffWriter writer, List<TiffIfdEntry> 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<byte[]> largeDataBlocks = new List<byte[]>();
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;
}
/// <summary>
/// Writes all data required to define an image
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="writer">The <see cref="BinaryWriter"/> to write data to.</param>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="ifdOffset">The marker to write this IFD offset.</param>
/// <returns>The marker to write the next IFD offset (if present).</returns>
public long WriteImage<TPixel>(TiffWriter writer, Image<TPixel> image, long ifdOffset)
where TPixel : struct, IPixel<TPixel>
{
List<TiffIfdEntry> ifdEntries = new List<TiffIfdEntry>();
this.AddImageFormat(image, ifdEntries);
this.AddMetadata(image, ifdEntries);
writer.WriteMarker(ifdOffset, (uint)writer.Position);
long nextIfdMarker = this.WriteIfd(writer, ifdEntries);
return nextIfdMarker;
}
/// <summary>
/// Adds image metadata to the specified IFD.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="ifdEntries">The metadata entries to add to the IFD.</param>
public void AddMetadata<TPixel>(Image<TPixel> image, List<TiffIfdEntry> ifdEntries)
where TPixel : struct, IPixel<TPixel>
{
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;
}
}
}
}
/// <summary>
/// Adds image format information to the specified IFD.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="ifdEntries">The image format entries to add to the IFD.</param>
public void AddImageFormat<TPixel>(Image<TPixel> image, List<TiffIfdEntry> ifdEntries)
where TPixel : struct, IPixel<TPixel>
{
throw new NotImplementedException();
}
}
}

35
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
{
/// <summary>
/// Encapsulates the means to encode and decode Tiff images.
/// </summary>
public class TiffFormat : IImageFormat
{
private TiffFormat()
{
}
/// <summary>
/// Gets the current instance.
/// </summary>
public static TiffFormat Instance { get; } = new TiffFormat();
/// <inheritdoc/>
public string Name => "TIFF";
/// <inheritdoc/>
public string DefaultMimeType => "image/tiff";
/// <inheritdoc/>
public IEnumerable<string> MimeTypes => TiffConstants.MimeTypes;
/// <inheritdoc/>
public IEnumerable<string> FileExtensions => TiffConstants.FileExtensions;
}
}

63
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
{
/// <summary>
/// Data structure for holding details of each TIFF IFD.
/// </summary>
internal struct TiffIfd
{
/// <summary>
/// An array of the entries within this IFD.
/// </summary>
public TiffIfdEntry[] Entries;
/// <summary>
/// Offset (in bytes) to the next IFD, or zero if this is the last IFD.
/// </summary>
public uint NextIfdOffset;
/// <summary>
/// Initializes a new instance of the <see cref="TiffIfd"/> struct.
/// </summary>
/// <param name="entries">An array of the entries within the IFD.</param>
/// <param name="nextIfdOffset">Offset (in bytes) to the next IFD, or zero if this is the last IFD.</param>
public TiffIfd(TiffIfdEntry[] entries, uint nextIfdOffset)
{
this.Entries = entries;
this.NextIfdOffset = nextIfdOffset;
}
/// <summary>
/// Gets the child <see cref="TiffIfdEntry"/> with the specified tag ID.
/// </summary>
/// <param name="tag">The tag ID to search for.</param>
/// <returns>The resulting <see cref="TiffIfdEntry"/>, or null if it does not exists.</returns>
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;
}
/// <summary>
/// Gets the child <see cref="TiffIfdEntry"/> with the specified tag ID.
/// </summary>
/// <param name="tag">The tag ID to search for.</param>
/// <param name="entry">The resulting <see cref="TiffIfdEntry"/>, if it exists.</param>
/// <returns>A flag indicating whether the requested entry exists.</returns>
public bool TryGetIfdEntry(ushort tag, out TiffIfdEntry entry)
{
TiffIfdEntry? nullableEntry = this.GetIfdEntry(tag);
entry = nullableEntry ?? default(TiffIfdEntry);
return nullableEntry.HasValue;
}
}
}

46
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
{
/// <summary>
/// Data structure for holding details of each TIFF IFD entry.
/// </summary>
internal struct TiffIfdEntry
{
/// <summary>
/// The Tag ID for this entry. See <see cref="TiffTags"/> for typical values.
/// </summary>
public ushort Tag;
/// <summary>
/// The data-type of this entry.
/// </summary>
public TiffType Type;
/// <summary>
/// The number of array items in this entry, or one if only a single value.
/// </summary>
public uint Count;
/// <summary>
/// The raw byte data for this entry.
/// </summary>
public byte[] Value;
/// <summary>
/// Initializes a new instance of the <see cref="TiffIfdEntry"/> struct.
/// </summary>
/// <param name="tag">The Tag ID for this entry.</param>
/// <param name="type">The data-type of this entry.</param>
/// <param name="count">The number of array items in this entry.</param>
/// <param name="value">The raw byte data for this entry.</param>
public TiffIfdEntry(ushort tag, TiffType type, uint count, byte[] value)
{
this.Tag = tag;
this.Type = type;
this.Count = count;
this.Value = value;
}
}
}

354
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
{
/// <summary>
/// Utility class for generating TIFF IFD entries.
/// </summary>
internal static class TiffIfdEntryCreator
{
/// <summary>
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Byte' from a unsigned integer.
/// </summary>
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
/// <param name="tag">The tag for the resulting entry.</param>
/// <param name="value">The value for the resulting entry.</param>
public static void AddUnsignedByte(this List<TiffIfdEntry> entries, ushort tag, uint value)
{
TiffIfdEntryCreator.AddUnsignedByte(entries, tag, new[] { value });
}
/// <summary>
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Byte' from an array of unsigned integers.
/// </summary>
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
/// <param name="tag">The tag for the resulting entry.</param>
/// <param name="value">The value for the resulting entry.</param>
public static void AddUnsignedByte(this List<TiffIfdEntry> 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));
}
/// <summary>
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Short' from a unsigned integer.
/// </summary>
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
/// <param name="tag">The tag for the resulting entry.</param>
/// <param name="value">The value for the resulting entry.</param>
public static void AddUnsignedShort(this List<TiffIfdEntry> entries, ushort tag, uint value)
{
TiffIfdEntryCreator.AddUnsignedShort(entries, tag, new[] { value });
}
/// <summary>
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Short' from an array of unsigned integers.
/// </summary>
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
/// <param name="tag">The tag for the resulting entry.</param>
/// <param name="value">The value for the resulting entry.</param>
public static void AddUnsignedShort(this List<TiffIfdEntry> 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));
}
/// <summary>
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Long' from a unsigned integer.
/// </summary>
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
/// <param name="tag">The tag for the resulting entry.</param>
/// <param name="value">The value for the resulting entry.</param>
public static void AddUnsignedLong(this List<TiffIfdEntry> entries, ushort tag, uint value)
{
TiffIfdEntryCreator.AddUnsignedLong(entries, tag, new[] { value });
}
/// <summary>
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Long' from an array of unsigned integers.
/// </summary>
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
/// <param name="tag">The tag for the resulting entry.</param>
/// <param name="value">The value for the resulting entry.</param>
public static void AddUnsignedLong(this List<TiffIfdEntry> 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));
}
/// <summary>
/// Adds a new <see cref="TiffIfdEntry"/> of type 'SByte' from a signed integer.
/// </summary>
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
/// <param name="tag">The tag for the resulting entry.</param>
/// <param name="value">The value for the resulting entry.</param>
public static void AddSignedByte(this List<TiffIfdEntry> entries, ushort tag, int value)
{
TiffIfdEntryCreator.AddSignedByte(entries, tag, new[] { value });
}
/// <summary>
/// Adds a new <see cref="TiffIfdEntry"/> of type 'SByte' from an array of signed integers.
/// </summary>
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
/// <param name="tag">The tag for the resulting entry.</param>
/// <param name="value">The value for the resulting entry.</param>
public static void AddSignedByte(this List<TiffIfdEntry> 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));
}
/// <summary>
/// Adds a new <see cref="TiffIfdEntry"/> of type 'SShort' from a signed integer.
/// </summary>
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
/// <param name="tag">The tag for the resulting entry.</param>
/// <param name="value">The value for the resulting entry.</param>
public static void AddSignedShort(this List<TiffIfdEntry> entries, ushort tag, int value)
{
TiffIfdEntryCreator.AddSignedShort(entries, tag, new[] { value });
}
/// <summary>
/// Adds a new <see cref="TiffIfdEntry"/> of type 'SShort' from an array of signed integers.
/// </summary>
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
/// <param name="tag">The tag for the resulting entry.</param>
/// <param name="value">The value for the resulting entry.</param>
public static void AddSignedShort(this List<TiffIfdEntry> 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));
}
/// <summary>
/// Adds a new <see cref="TiffIfdEntry"/> of type 'SLong' from a signed integer.
/// </summary>
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
/// <param name="tag">The tag for the resulting entry.</param>
/// <param name="value">The value for the resulting entry.</param>
public static void AddSignedLong(this List<TiffIfdEntry> entries, ushort tag, int value)
{
TiffIfdEntryCreator.AddSignedLong(entries, tag, new[] { value });
}
/// <summary>
/// Adds a new <see cref="TiffIfdEntry"/> of type 'SLong' from an array of signed integers.
/// </summary>
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
/// <param name="tag">The tag for the resulting entry.</param>
/// <param name="value">The value for the resulting entry.</param>
public static void AddSignedLong(this List<TiffIfdEntry> 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));
}
/// <summary>
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Ascii' from a string.
/// </summary>
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
/// <param name="tag">The tag for the resulting entry.</param>
/// <param name="value">The value for the resulting entry.</param>
public static void AddAscii(this List<TiffIfdEntry> entries, ushort tag, string value)
{
byte[] bytes = Encoding.UTF8.GetBytes(value + "\0");
entries.Add(new TiffIfdEntry(tag, TiffType.Ascii, (uint)bytes.Length, bytes));
}
/// <summary>
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Rational' from a <see cref="Rational"/>.
/// </summary>
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
/// <param name="tag">The tag for the resulting entry.</param>
/// <param name="value">The value for the resulting entry.</param>
public static void AddUnsignedRational(this List<TiffIfdEntry> entries, ushort tag, Rational value)
{
TiffIfdEntryCreator.AddUnsignedRational(entries, tag, new[] { value });
}
/// <summary>
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Rational' from an array of <see cref="Rational"/> values.
/// </summary>
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
/// <param name="tag">The tag for the resulting entry.</param>
/// <param name="value">The value for the resulting entry.</param>
public static void AddUnsignedRational(this List<TiffIfdEntry> 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));
}
/// <summary>
/// Adds a new <see cref="TiffIfdEntry"/> of type 'SRational' from a <see cref="SignedRational"/>.
/// </summary>
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
/// <param name="tag">The tag for the resulting entry.</param>
/// <param name="value">The value for the resulting entry.</param>
public static void AddSignedRational(this List<TiffIfdEntry> entries, ushort tag, SignedRational value)
{
TiffIfdEntryCreator.AddSignedRational(entries, tag, new[] { value });
}
/// <summary>
/// Adds a new <see cref="TiffIfdEntry"/> of type 'SRational' from an array of <see cref="SignedRational"/> values.
/// </summary>
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
/// <param name="tag">The tag for the resulting entry.</param>
/// <param name="value">The value for the resulting entry.</param>
public static void AddSignedRational(this List<TiffIfdEntry> 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));
}
/// <summary>
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Float' from a floating-point value.
/// </summary>
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
/// <param name="tag">The tag for the resulting entry.</param>
/// <param name="value">The value for the resulting entry.</param>
public static void AddFloat(this List<TiffIfdEntry> entries, ushort tag, float value)
{
TiffIfdEntryCreator.AddFloat(entries, tag, new[] { value });
}
/// <summary>
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Float' from an array of floating-point values.
/// </summary>
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
/// <param name="tag">The tag for the resulting entry.</param>
/// <param name="value">The value for the resulting entry.</param>
public static void AddFloat(this List<TiffIfdEntry> 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));
}
/// <summary>
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Double' from a floating-point value.
/// </summary>
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
/// <param name="tag">The tag for the resulting entry.</param>
/// <param name="value">The value for the resulting entry.</param>
public static void AddDouble(this List<TiffIfdEntry> entries, ushort tag, double value)
{
TiffIfdEntryCreator.AddDouble(entries, tag, new[] { value });
}
/// <summary>
/// Adds a new <see cref="TiffIfdEntry"/> of type 'Double' from an array of floating-point values.
/// </summary>
/// <param name="entries">The list of <see cref="TiffIfdEntry"/> to add the new entry to.</param>
/// <param name="tag">The tag for the resulting entry.</param>
/// <param name="value">The value for the resulting entry.</param>
public static void AddDouble(this List<TiffIfdEntry> 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);
}
}
}

34
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
{
/// <summary>
/// Detects tiff file headers
/// </summary>
public sealed class TiffImageFormatDetector : IImageFormatDetector
{
/// <inheritdoc/>
public int HeaderSize => 4;
/// <inheritdoc/>
public IImageFormat DetectFormat(ReadOnlySpan<byte> header)
{
if (this.IsSupportedFileFormat(header))
{
return TiffFormat.Instance;
}
return null;
}
private bool IsSupportedFileFormat(ReadOnlySpan<byte> 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
}
}
}

51
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
{
/// <summary>
/// Defines constants for each of the supported TIFF metadata types.
/// </summary>
public static class TiffMetadataNames
{
/// <summary>
/// Person who created the image.
/// </summary>
public const string Artist = "Artist";
/// <summary>
/// Copyright notice.
/// </summary>
public const string Copyright = "Copyright";
/// <summary>
/// Date and time of image creation.
/// </summary>
public const string DateTime = "DateTime";
/// <summary>
/// The computer and/or operating system in use at the time of image creation.
/// </summary>
public const string HostComputer = "HostComputer";
/// <summary>
/// A string that describes the subject of the image.
/// </summary>
public const string ImageDescription = "ImageDescription";
/// <summary>
/// The scanner/camera manufacturer.
/// </summary>
public const string Make = "Make";
/// <summary>
/// The scanner/camera model name or number.
/// </summary>
public const string Model = "Model";
/// <summary>
/// Name and version number of the software package(s) used to create the image.
/// </summary>
public const string Software = "Software";
}
}

62
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
{
/// <summary>
/// Utility class to read a sequence of bits from an array
/// </summary>
internal class BitReader
{
private readonly byte[] array;
private int offset;
private int bitOffset;
/// <summary>
/// Initializes a new instance of the <see cref="BitReader" /> class.
/// </summary>
/// <param name="array">The array to read data from.</param>
public BitReader(byte[] array)
{
this.array = array;
}
/// <summary>
/// Reads the specified number of bits from the array.
/// </summary>
/// <param name="bits">The number of bits to read.</param>
/// <returns>The value read from the array.</returns>
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;
}
/// <summary>
/// Moves the reader to the next row of byte-aligned data.
/// </summary>
public void NextRow()
{
if (this.bitOffset > 0)
{
this.bitOffset = 0;
this.offset++;
}
}
}
}

176
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
{
/// <summary>
/// Utility class to encapsulate a sub-portion of another <see cref="Stream"/>.
/// </summary>
/// <remarks>
/// Note that disposing of the <see cref="SubStream"/> does not dispose the underlying
/// <see cref="Stream"/>.
/// </remarks>
internal class SubStream : Stream
{
private Stream innerStream;
private long offset;
private long endOffset;
private long length;
/// <summary>
/// Initializes a new instance of the <see cref="SubStream"/> class.
/// </summary>
/// <param name="innerStream">The underlying <see cref="Stream"/> to wrap.</param>
/// <param name="length">The length of the sub-stream.</param>
/// <remarks>
/// Note that calling the sub-stream with start from the current offset of the
/// underlying <see cref="Stream"/>
/// </remarks>
public SubStream(Stream innerStream, long length)
{
this.innerStream = innerStream;
this.offset = this.innerStream.Position;
this.endOffset = this.offset + length;
this.length = length;
}
/// <summary>
/// Initializes a new instance of the <see cref="SubStream"/> class.
/// </summary>
/// <param name="innerStream">The underlying <see cref="Stream"/> to wrap.</param>
/// <param name="offset">The offset of the sub-stream within the underlying <see cref="Stream"/>.</param>
/// <param name="length">The length of the sub-stream.</param>
/// <remarks>
/// Note that calling the constructor will immediately move the underlying
/// <see cref="Stream"/> to the specified offset.
/// </remarks>
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);
}
/// <inheritdoc/>
public override bool CanRead
{
get
{
return true;
}
}
/// <inheritdoc/>
public override bool CanWrite
{
get
{
return false;
}
}
/// <inheritdoc/>
public override bool CanSeek
{
get
{
return this.innerStream.CanSeek;
}
}
/// <inheritdoc/>
public override long Length
{
get
{
return this.length;
}
}
/// <inheritdoc/>
public override long Position
{
get
{
return this.innerStream.Position - this.offset;
}
set
{
this.Seek(value, SeekOrigin.Begin);
}
}
/// <inheritdoc/>
public override void Flush()
{
throw new NotSupportedException();
}
/// <inheritdoc/>
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);
}
/// <inheritdoc/>
public override int ReadByte()
{
if (this.innerStream.Position < this.endOffset)
{
return this.innerStream.ReadByte();
}
else
{
return -1;
}
}
/// <inheritdoc/>
public override void Write(byte[] array, int offset, int count)
{
throw new NotSupportedException();
}
/// <inheritdoc/>
public override void WriteByte(byte value)
{
throw new NotSupportedException();
}
/// <inheritdoc/>
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.");
}
}
/// <inheritdoc/>
public override void SetLength(long value)
{
throw new NotSupportedException();
}
}
}

271
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
{
/// <summary>
/// Decompresses and decodes data using the dynamic LZW algorithms.
/// </summary>
/// <remarks>
/// This code is based on the <see cref="LzwDecoder"/> 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).
/// </remarks>
internal sealed class TiffLzwDecoder : IDisposable
{
/// <summary>
/// The max decoder pixel stack size.
/// </summary>
private const int MaxStackSize = 4096;
/// <summary>
/// The null code.
/// </summary>
private const int NullCode = -1;
/// <summary>
/// The stream to decode.
/// </summary>
private readonly Stream stream;
/// <summary>
/// The prefix buffer.
/// </summary>
private readonly int[] prefix;
/// <summary>
/// The suffix buffer.
/// </summary>
private readonly int[] suffix;
/// <summary>
/// The pixel stack buffer.
/// </summary>
private readonly int[] pixelStack;
/// <summary>
/// A value indicating whether this instance of the given entity has been disposed.
/// </summary>
/// <value><see langword="true"/> if this instance has been disposed; otherwise, <see langword="false"/>.</value>
/// <remarks>
/// 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.
/// </remarks>
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="TiffLzwDecoder"/> class
/// and sets the stream, where the compressed data should be read from.
/// </summary>
/// <param name="stream">The stream to read from.</param>
/// <exception cref="System.ArgumentNullException"><paramref name="stream"/> is null.</exception>
public TiffLzwDecoder(Stream stream)
{
Guard.NotNull(stream, nameof(stream));
this.stream = stream;
this.prefix = ArrayPool<int>.Shared.Rent(MaxStackSize);
this.suffix = ArrayPool<int>.Shared.Rent(MaxStackSize);
this.pixelStack = ArrayPool<int>.Shared.Rent(MaxStackSize + 1);
Array.Clear(this.prefix, 0, MaxStackSize);
Array.Clear(this.suffix, 0, MaxStackSize);
Array.Clear(this.pixelStack, 0, MaxStackSize + 1);
}
/// <summary>
/// Decodes and decompresses all pixel indices from the stream.
/// </summary>
/// <param name="length">The length of the compressed data.</param>
/// <param name="dataSize">Size of the data.</param>
/// <param name="pixels">The pixel array to decode to.</param>
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];
}
}
/// <inheritdoc />
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
this.Dispose(true);
}
/// <summary>
/// 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
/// </summary>
/// <param name="buffer">The buffer to store the block in.</param>
/// <returns>
/// The <see cref="T:byte[]"/>.
/// </returns>
private int ReadBlock(byte[] buffer)
{
return this.stream.Read(buffer, 0, 255);
}
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
/// <param name="disposing">If true, the object gets disposed.</param>
private void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
if (disposing)
{
ArrayPool<int>.Shared.Return(this.prefix);
ArrayPool<int>.Shared.Return(this.suffix);
ArrayPool<int>.Shared.Return(this.pixelStack);
}
this.isDisposed = true;
}
}
}

495
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
{
/// <summary>
/// Encodes and compresses the image data using dynamic Lempel-Ziv compression.
/// </summary>
/// <remarks>
/// Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. K Weiner 12/00
/// <para>
/// GIFCOMPR.C - GIF Image compression routines
/// </para>
/// <para>
/// Lempel-Ziv compression based on 'compress'. GIF modifications by
/// David Rowley (mgardi@watdcsu.waterloo.edu)
/// </para>
/// GIF Image compression - modified 'compress'
/// <para>
/// 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)
/// </para>
/// <para>
/// This code is based on the <see cref="LzwEncoder"/> 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).
/// </para>
/// </remarks>
internal sealed class TiffLzwEncoder : IDisposable
{
/// <summary>
/// The end-of-file marker
/// </summary>
private const int Eof = -1;
/// <summary>
/// The maximum number of bits.
/// </summary>
private const int Bits = 12;
/// <summary>
/// 80% occupancy
/// </summary>
private const int HashSize = 5003;
/// <summary>
/// Mask used when shifting pixel values
/// </summary>
private static readonly int[] Masks =
{
0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF,
0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF
};
/// <summary>
/// The working pixel array
/// </summary>
private readonly byte[] pixelArray;
/// <summary>
/// The initial code size.
/// </summary>
private readonly int initialCodeSize;
/// <summary>
/// The hash table.
/// </summary>
private readonly int[] hashTable;
/// <summary>
/// The code table.
/// </summary>
private readonly int[] codeTable;
/// <summary>
/// Define the storage for the packet accumulator.
/// </summary>
private readonly byte[] accumulators = new byte[256];
/// <summary>
/// A value indicating whether this instance of the given entity has been disposed.
/// </summary>
/// <value><see langword="true"/> if this instance has been disposed; otherwise, <see langword="false"/>.</value>
/// <remarks>
/// 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.
/// </remarks>
private bool isDisposed;
/// <summary>
/// The current pixel
/// </summary>
private int currentPixel;
/// <summary>
/// Number of bits/code
/// </summary>
private int bitCount;
/// <summary>
/// User settable max # bits/code
/// </summary>
private int maxbits = Bits;
/// <summary>
/// maximum code, given bitCount
/// </summary>
private int maxcode;
/// <summary>
/// should NEVER generate this code
/// </summary>
private int maxmaxcode = 1 << Bits;
/// <summary>
/// For dynamic table sizing
/// </summary>
private int hsize = HashSize;
/// <summary>
/// First unused entry
/// </summary>
private int freeEntry;
/// <summary>
/// Block compression parameters -- after all codes are used up,
/// and compression rate changes, start over.
/// </summary>
private bool clearFlag;
/// <summary>
/// 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.
/// </summary>
private int globalInitialBits;
/// <summary>
/// The clear code.
/// </summary>
private int clearCode;
/// <summary>
/// The end-of-file code.
/// </summary>
private int eofCode;
/// <summary>
/// Output the given code.
/// Inputs:
/// code: A bitCount-bit integer. If == -1, then EOF. This assumes
/// that bitCount =&lt; 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.
/// </summary>
private int currentAccumulator;
/// <summary>
/// The current bits.
/// </summary>
private int currentBits;
/// <summary>
/// Number of characters so far in this 'packet'
/// </summary>
private int accumulatorCount;
/// <summary>
/// Initializes a new instance of the <see cref="TiffLzwEncoder"/> class.
/// </summary>
/// <param name="indexedPixels">The array of indexed pixels.</param>
/// <param name="colorDepth">The color depth in bits.</param>
public TiffLzwEncoder(byte[] indexedPixels, int colorDepth)
{
this.pixelArray = indexedPixels;
this.initialCodeSize = Math.Max(2, colorDepth);
this.hashTable = ArrayPool<int>.Shared.Rent(HashSize);
this.codeTable = ArrayPool<int>.Shared.Rent(HashSize);
Array.Clear(this.hashTable, 0, HashSize);
Array.Clear(this.codeTable, 0, HashSize);
}
/// <summary>
/// Encodes and compresses the indexed pixels to the stream.
/// </summary>
/// <param name="stream">The stream to write to.</param>
public void Encode(Stream stream)
{
this.currentPixel = 0;
// Compress and write the pixel data
this.Compress(this.initialCodeSize + 1, stream);
}
/// <inheritdoc />
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
this.Dispose(true);
}
/// <summary>
/// Gets the maximum code value
/// </summary>
/// <param name="bitCount">The number of bits</param>
/// <returns>See <see cref="int"/></returns>
private static int GetMaxcode(int bitCount)
{
return (1 << bitCount) - 1;
}
/// <summary>
/// Add a character to the end of the current packet, and if it is 254 characters,
/// flush the packet to disk.
/// </summary>
/// <param name="c">The character to add.</param>
/// <param name="stream">The stream to write to.</param>
private void AddCharacter(byte c, Stream stream)
{
this.accumulators[this.accumulatorCount++] = c;
if (this.accumulatorCount >= 254)
{
this.FlushPacket(stream);
}
}
/// <summary>
/// Table clear for block compress
/// </summary>
/// <param name="stream">The output stream.</param>
private void ClearBlock(Stream stream)
{
this.ResetCodeTable(this.hsize);
this.freeEntry = this.clearCode + 2;
this.clearFlag = true;
this.Output(this.clearCode, stream);
}
/// <summary>
/// Reset the code table.
/// </summary>
/// <param name="size">The hash size.</param>
private void ResetCodeTable(int size)
{
for (int i = 0; i < size; ++i)
{
this.hashTable[i] = -1;
}
}
/// <summary>
/// Compress the packets to the stream.
/// </summary>
/// <param name="intialBits">The initial bits.</param>
/// <param name="stream">The stream to write to.</param>
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);
}
/// <summary>
/// Flush the packet to disk, and reset the accumulator.
/// </summary>
/// <param name="outStream">The output stream.</param>
private void FlushPacket(Stream outStream)
{
if (this.accumulatorCount > 0)
{
outStream.Write(this.accumulators, 0, this.accumulatorCount);
this.accumulatorCount = 0;
}
}
/// <summary>
/// Return the next pixel from the image
/// </summary>
/// <returns>
/// The <see cref="int"/>
/// </returns>
private int NextPixel()
{
if (this.currentPixel == this.pixelArray.Length)
{
return Eof;
}
this.currentPixel++;
return this.pixelArray[this.currentPixel - 1] & 0xff;
}
/// <summary>
/// Output the current code to the stream.
/// </summary>
/// <param name="code">The code.</param>
/// <param name="outs">The stream to write to.</param>
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);
}
}
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// </summary>
/// <param name="disposing">If true, the object gets disposed.</param>
private void Dispose(bool disposing)
{
if (this.isDisposed)
{
return;
}
if (disposing)
{
ArrayPool<int>.Shared.Return(this.hashTable);
ArrayPool<int>.Shared.Return(this.codeTable);
}
this.isDisposed = true;
}
}
}

47
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
{
/// <summary>
/// TIFF specific utilities and extension methods.
/// </summary>
internal static class TiffUtils
{
/// <summary>
/// Reads a sequence of bytes from the input stream into a buffer.
/// </summary>
/// <param name="stream">The stream to read from.</param>
/// <param name="buffer">A buffer to store the retrieved data.</param>
/// <param name="count">The number of bytes to read.</param>
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;
}
}
/// <summary>
/// Reads all bytes from the input stream into a buffer until the end of stream or the buffer is full.
/// </summary>
/// <param name="stream">The stream to read from.</param>
/// <param name="buffer">A buffer to store the retrieved data.</param>
public static void ReadFull(this Stream stream, byte[] buffer)
{
ReadFull(stream, buffer, buffer.Length);
}
}
}

108
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
{
/// <summary>
/// Utility class for writing TIFF data to a <see cref="Stream"/>.
/// </summary>
internal class TiffWriter : IDisposable
{
private readonly Stream output;
private readonly byte[] paddingBytes = new byte[4];
private readonly List<long> references = new List<long>();
/// <summary>Initializes a new instance of the <see cref="TiffWriter"/> class.</summary>
/// <param name="output">The output stream.</param>
public TiffWriter(Stream output)
{
this.output = output;
}
/// <summary>
/// Gets a value indicating whether the architecture is little-endian.
/// </summary>
public bool IsLittleEndian => BitConverter.IsLittleEndian;
/// <summary>
/// Gets the current position within the stream.
/// </summary>
public long Position => this.output.Position;
/// <summary>Writes an empty four bytes to the stream, returning the offset to be written later.</summary>
/// <returns>The offset to be written later</returns>
public long PlaceMarker()
{
long offset = this.output.Position;
this.Write(0u);
return offset;
}
/// <summary>Writes an array of bytes to the current stream.</summary>
/// <param name="value">The bytes to write.</param>
public void Write(byte[] value)
{
this.output.Write(value, 0, value.Length);
}
/// <summary>Writes a byte to the current stream.</summary>
/// <param name="value">The byte to write.</param>
public void Write(byte value)
{
this.output.Write(new byte[] { value }, 0, 1);
}
/// <summary>Writes a two-byte unsigned integer to the current stream.</summary>
/// <param name="value">The two-byte unsigned integer to write.</param>
public void Write(ushort value)
{
byte[] bytes = BitConverter.GetBytes(value);
this.output.Write(bytes, 0, 2);
}
/// <summary>Writes a four-byte unsigned integer to the current stream.</summary>
/// <param name="value">The four-byte unsigned integer to write.</param>
public void Write(uint value)
{
byte[] bytes = BitConverter.GetBytes(value);
this.output.Write(bytes, 0, 4);
}
/// <summary>Writes an array of bytes to the current stream, padded to four-bytes.</summary>
/// <param name="value">The bytes to write.</param>
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);
}
}
/// <summary>Writes a four-byte unsigned integer to the specified marker in the stream.</summary>
/// <param name="offset">The offset returned when placing the marker</param>
/// <param name="value">The four-byte unsigned integer to write.</param>
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);
}
/// <summary>
/// Disposes <see cref="TiffWriter"/> instance, ensuring any unwritten data is flushed.
/// </summary>
public void Dispose()
{
this.output.Flush();
}
}
}

7618
src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs

File diff suppressed because it is too large

226
src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt

@ -1,114 +1,114 @@
<#
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
#>
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
// <auto-generated />
using System;
using System.Numerics;
using System.Buffers;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
{
/// <summary>
/// Collection of Porter Duff alpha blending functions applying different composition models.
/// </summary>
/// <remarks>
/// 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
/// </remarks>
internal static class DefaultPixelBlenders<TPixel>
where TPixel : struct, IPixel<TPixel>
{
<#
string[] composers = new []{
"Src",
"SrcAtop",
"SrcOver",
"SrcIn",
"SrcOut",
"Dest",
"DestAtop",
"DestOver",
"DestIn",
"DestOut",
"Clear",
"Xor",
};
string[] blenders = new []{
"Normal",
"Multiply",
"Add",
"Subtract",
"Screen",
"Darken",
"Lighten",
"Overlay",
"HardLight"
};
foreach(var composer in composers) {
foreach(var blender in blenders) {
string blender_composer= $"{blender}{composer}";
#>
internal class <#= blender_composer#> : PixelBlender<TPixel>
{
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
public static <#=blender_composer#> Instance { get; } = new <#=blender_composer#>();
/// <inheritdoc />
public override TPixel Blend(TPixel background, TPixel source, float amount)
{
TPixel dest = default;
dest.FromScaledVector4(PorterDuffFunctions.<#=blender_composer#>(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1)));
return dest;
}
/// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, float amount)
{
amount = amount.Clamp(0, 1);
for (int i = 0; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
}
}
/// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount)
{
for (int i = 0; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount[i].Clamp(0, 1));
}
}
}
<#
}
}
#>
}
<#
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
#>
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
// <auto-generated />
using System;
using System.Numerics;
using System.Buffers;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
{
/// <summary>
/// Collection of Porter Duff alpha blending functions applying different composition models.
/// </summary>
/// <remarks>
/// 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
/// </remarks>
internal static class DefaultPixelBlenders<TPixel>
where TPixel : struct, IPixel<TPixel>
{
<#
string[] composers = new []{
"Src",
"SrcAtop",
"SrcOver",
"SrcIn",
"SrcOut",
"Dest",
"DestAtop",
"DestOver",
"DestIn",
"DestOut",
"Clear",
"Xor",
};
string[] blenders = new []{
"Normal",
"Multiply",
"Add",
"Subtract",
"Screen",
"Darken",
"Lighten",
"Overlay",
"HardLight"
};
foreach(var composer in composers) {
foreach(var blender in blenders) {
string blender_composer= $"{blender}{composer}";
#>
internal class <#= blender_composer#> : PixelBlender<TPixel>
{
/// <summary>
/// Gets the static instance of this blender.
/// </summary>
public static <#=blender_composer#> Instance { get; } = new <#=blender_composer#>();
/// <inheritdoc />
public override TPixel Blend(TPixel background, TPixel source, float amount)
{
TPixel dest = default;
dest.FromScaledVector4(PorterDuffFunctions.<#=blender_composer#>(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1)));
return dest;
}
/// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, float amount)
{
amount = amount.Clamp(0, 1);
for (int i = 0; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
}
}
/// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount)
{
for (int i = 0; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount[i].Clamp(0, 1));
}
}
}
<#
}
}
#>
}
}

362
src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs

@ -1,182 +1,182 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.PixelFormats
{
/// <summary>
/// Abstract base class for calling pixel composition functions
/// </summary>
/// <typeparam name="TPixel">The type of the pixel</typeparam>
internal abstract class PixelBlender<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Blend 2 pixels together.
/// </summary>
/// <param name="background">The background color.</param>
/// <param name="source">The source color.</param>
/// <param name="amount">
/// A value between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
/// </param>
/// <returns>The final pixel value after composition</returns>
public abstract TPixel Blend(TPixel background, TPixel source, float amount);
/// <summary>
/// Blend 2 rows together.
/// </summary>
/// <param name="destination">destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A value between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
/// </param>
protected abstract void BlendFunction(
Span<Vector4> destination,
ReadOnlySpan<Vector4> background,
ReadOnlySpan<Vector4> source,
float amount);
/// <summary>
/// Blend 2 rows together.
/// </summary>
/// <param name="destination">destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A span with values between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
/// </param>
protected abstract void BlendFunction(
Span<Vector4> destination,
ReadOnlySpan<Vector4> background,
ReadOnlySpan<Vector4> source,
ReadOnlySpan<float> amount);
/// <summary>
/// Blends 2 rows together
/// </summary>
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A span with values between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
/// </param>
public void Blend(
Configuration configuration,
Span<TPixel> destination,
ReadOnlySpan<TPixel> background,
ReadOnlySpan<TPixel> source,
ReadOnlySpan<float> amount)
{
this.Blend<TPixel>(configuration, destination, background, source, amount);
}
/// <summary>
/// Blends 2 rows together
/// </summary>
/// <typeparam name="TPixelSrc">the pixel format of the source span</typeparam>
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A span with values between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
/// </param>
public void Blend<TPixelSrc>(
Configuration configuration,
Span<TPixel> destination,
ReadOnlySpan<TPixel> background,
ReadOnlySpan<TPixelSrc> source,
ReadOnlySpan<float> amount)
where TPixelSrc : struct, IPixel<TPixelSrc>
{
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IMemoryOwner<Vector4> buffer =
configuration.MemoryAllocator.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
Span<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
PixelOperations<TPixel>.Instance.ToScaledVector4(
configuration,
background.Slice(0, background.Length),
backgroundSpan);
PixelOperations<TPixelSrc>.Instance.ToScaledVector4(
configuration,
source.Slice(0, background.Length),
sourceSpan);
this.BlendFunction(destinationSpan, backgroundSpan, sourceSpan, amount);
PixelOperations<TPixel>.Instance.FromScaledVector4(
configuration,
destinationSpan.Slice(0, background.Length),
destination);
}
}
/// <summary>
/// Blends 2 rows together
/// </summary>
/// <typeparam name="TPixelSrc">the pixel format of the source span</typeparam>
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A value between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
/// </param>
public void Blend<TPixelSrc>(
Configuration configuration,
Span<TPixel> destination,
ReadOnlySpan<TPixel> background,
ReadOnlySpan<TPixelSrc> source,
float amount)
where TPixelSrc : struct, IPixel<TPixelSrc>
{
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount));
using (IMemoryOwner<Vector4> buffer =
configuration.MemoryAllocator.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
Span<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
PixelOperations<TPixel>.Instance.ToScaledVector4(
configuration,
background.Slice(0, background.Length),
backgroundSpan);
PixelOperations<TPixelSrc>.Instance.ToScaledVector4(
configuration,
source.Slice(0, background.Length),
sourceSpan);
this.BlendFunction(destinationSpan, backgroundSpan, sourceSpan, amount);
PixelOperations<TPixel>.Instance.FromScaledVector4(
configuration,
destinationSpan.Slice(0, background.Length),
destination);
}
}
}
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.PixelFormats
{
/// <summary>
/// Abstract base class for calling pixel composition functions
/// </summary>
/// <typeparam name="TPixel">The type of the pixel</typeparam>
internal abstract class PixelBlender<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Blend 2 pixels together.
/// </summary>
/// <param name="background">The background color.</param>
/// <param name="source">The source color.</param>
/// <param name="amount">
/// A value between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
/// </param>
/// <returns>The final pixel value after composition</returns>
public abstract TPixel Blend(TPixel background, TPixel source, float amount);
/// <summary>
/// Blend 2 rows together.
/// </summary>
/// <param name="destination">destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A value between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
/// </param>
protected abstract void BlendFunction(
Span<Vector4> destination,
ReadOnlySpan<Vector4> background,
ReadOnlySpan<Vector4> source,
float amount);
/// <summary>
/// Blend 2 rows together.
/// </summary>
/// <param name="destination">destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A span with values between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
/// </param>
protected abstract void BlendFunction(
Span<Vector4> destination,
ReadOnlySpan<Vector4> background,
ReadOnlySpan<Vector4> source,
ReadOnlySpan<float> amount);
/// <summary>
/// Blends 2 rows together
/// </summary>
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A span with values between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
/// </param>
public void Blend(
Configuration configuration,
Span<TPixel> destination,
ReadOnlySpan<TPixel> background,
ReadOnlySpan<TPixel> source,
ReadOnlySpan<float> amount)
{
this.Blend<TPixel>(configuration, destination, background, source, amount);
}
/// <summary>
/// Blends 2 rows together
/// </summary>
/// <typeparam name="TPixelSrc">the pixel format of the source span</typeparam>
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A span with values between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
/// </param>
public void Blend<TPixelSrc>(
Configuration configuration,
Span<TPixel> destination,
ReadOnlySpan<TPixel> background,
ReadOnlySpan<TPixelSrc> source,
ReadOnlySpan<float> amount)
where TPixelSrc : struct, IPixel<TPixelSrc>
{
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IMemoryOwner<Vector4> buffer =
configuration.MemoryAllocator.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
Span<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
PixelOperations<TPixel>.Instance.ToScaledVector4(
configuration,
background.Slice(0, background.Length),
backgroundSpan);
PixelOperations<TPixelSrc>.Instance.ToScaledVector4(
configuration,
source.Slice(0, background.Length),
sourceSpan);
this.BlendFunction(destinationSpan, backgroundSpan, sourceSpan, amount);
PixelOperations<TPixel>.Instance.FromScaledVector4(
configuration,
destinationSpan.Slice(0, background.Length),
destination);
}
}
/// <summary>
/// Blends 2 rows together
/// </summary>
/// <typeparam name="TPixelSrc">the pixel format of the source span</typeparam>
/// <param name="configuration"><see cref="Configuration"/> to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A value between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
/// </param>
public void Blend<TPixelSrc>(
Configuration configuration,
Span<TPixel> destination,
ReadOnlySpan<TPixel> background,
ReadOnlySpan<TPixelSrc> source,
float amount)
where TPixelSrc : struct, IPixel<TPixelSrc>
{
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount));
using (IMemoryOwner<Vector4> buffer =
configuration.MemoryAllocator.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
Span<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
PixelOperations<TPixel>.Instance.ToScaledVector4(
configuration,
background.Slice(0, background.Length),
backgroundSpan);
PixelOperations<TPixelSrc>.Instance.ToScaledVector4(
configuration,
source.Slice(0, background.Length),
sourceSpan);
this.BlendFunction(destinationSpan, backgroundSpan, sourceSpan, amount);
PixelOperations<TPixel>.Instance.FromScaledVector4(
configuration,
destinationSpan.Slice(0, background.Length),
destination);
}
}
}
}

53
tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs

@ -0,0 +1,53 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Drawing;
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats;
using CoreImage = SixLabors.ImageSharp.Image;
using CoreSize = SixLabors.Primitives.Size;
namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
public class DecodeTiff : BenchmarkBase
{
private byte[] tiffBytes;
[GlobalSetup]
public void ReadImages()
{
if (this.tiffBytes == null)
{
this.tiffBytes = File.ReadAllBytes("../ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_uncompressed.tiff");
}
}
[Benchmark(Baseline = true, Description = "System.Drawing Tiff")]
public Size TiffSystemDrawing()
{
using (MemoryStream memoryStream = new MemoryStream(this.tiffBytes))
{
using (var image = System.Drawing.Image.FromStream(memoryStream))
{
return image.Size;
}
}
}
[Benchmark(Description = "ImageSharp Tiff")]
public CoreSize TiffCore()
{
using (MemoryStream memoryStream = new MemoryStream(this.tiffBytes))
{
using (Image<Rgba32> image = CoreImage.Load<Rgba32>(memoryStream))
{
return new CoreSize(image.Width, image.Height);
}
}
}
}
}

48
tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs

@ -0,0 +1,48 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests
{
using System.IO;
using Xunit;
using ImageSharp.Formats;
using ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Png.Zlib;
public class DeflateTiffCompressionTests
{
[Theory]
[InlineData(new byte[] { })]
[InlineData(new byte[] { 42 })] // One byte
[InlineData(new byte[] { 42, 16, 128, 53, 96, 218, 7, 64, 3, 4, 97 })] // Random bytes
[InlineData(new byte[] { 1, 2, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 3, 4 })] // Repeated bytes
[InlineData(new byte[] { 1, 2, 42, 53, 42, 53, 42, 53, 42, 53, 42, 53, 3, 4 })] // Repeated sequence
public void Decompress_ReadsData(byte[] data)
{
using (Stream stream = CreateCompressedStream(data))
{
byte[] buffer = new byte[data.Length];
DeflateTiffCompression.Decompress(stream, (int)stream.Length, buffer);
Assert.Equal(data, buffer);
}
}
private static Stream CreateCompressedStream(byte[] data)
{
Stream compressedStream = new MemoryStream();
using (Stream uncompressedStream = new MemoryStream(data),
deflateStream = new ZlibDeflateStream(compressedStream, 6))
{
uncompressedStream.CopyTo(deflateStream);
}
compressedStream.Seek(0, SeekOrigin.Begin);
return compressedStream;
}
}
}

43
tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs

@ -0,0 +1,43 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests
{
using System.IO;
using Xunit;
using ImageSharp.Formats.Tiff;
public class LzwTiffCompressionTests
{
[Theory]
[InlineData(new byte[] { })]
[InlineData(new byte[] { 42 })] // One byte
[InlineData(new byte[] { 42, 16, 128, 53, 96, 218, 7, 64, 3, 4, 97 })] // Random bytes
[InlineData(new byte[] { 1, 2, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 3, 4 })] // Repeated bytes
[InlineData(new byte[] { 1, 2, 42, 53, 42, 53, 42, 53, 42, 53, 42, 53, 3, 4 })] // Repeated sequence
public void Decompress_ReadsData(byte[] data)
{
using (Stream stream = CreateCompressedStream(data))
{
byte[] buffer = new byte[data.Length];
LzwTiffCompression.Decompress(stream, (int)stream.Length, buffer);
Assert.Equal(data, buffer);
}
}
private static Stream CreateCompressedStream(byte[] data)
{
Stream compressedStream = new MemoryStream();
using (var encoder = new TiffLzwEncoder(data, 8))
{
encoder.Encode(compressedStream);
}
compressedStream.Seek(0, SeekOrigin.Begin);
return compressedStream;
}
}
}

26
tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs

@ -0,0 +1,26 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests
{
using System.IO;
using Xunit;
using ImageSharp.Formats.Tiff;
public class NoneTiffCompressionTests
{
[Theory]
[InlineData(new byte[] { 10, 15, 20, 25, 30, 35, 40, 45 }, 8, new byte[] { 10, 15, 20, 25, 30, 35, 40, 45 })]
[InlineData(new byte[] { 10, 15, 20, 25, 30, 35, 40, 45 }, 5, new byte[] { 10, 15, 20, 25, 30 })]
public void Decompress_ReadsData(byte[] inputData, int byteCount, byte[] expectedResult)
{
Stream stream = new MemoryStream(inputData);
byte[] buffer = new byte[expectedResult.Length];
NoneTiffCompression.Decompress(stream, byteCount, buffer);
Assert.Equal(expectedResult, buffer);
}
}
}

33
tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs

@ -0,0 +1,33 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests
{
using System.IO;
using Xunit;
using ImageSharp.Formats.Tiff;
public class PackBitsTiffCompressionTests
{
[Theory]
[InlineData(new byte[] { }, new byte[] { })]
[InlineData(new byte[] { 0x00, 0x2A }, new byte[] { 0x2A })] // Read one byte
[InlineData(new byte[] { 0x01, 0x15, 0x32 }, new byte[] { 0x15, 0x32 })] // Read two bytes
[InlineData(new byte[] { 0xFF, 0x2A }, new byte[] { 0x2A, 0x2A })] // Repeat two bytes
[InlineData(new byte[] { 0xFE, 0x2A }, new byte[] { 0x2A, 0x2A, 0x2A })] // Repeat three bytes
[InlineData(new byte[] { 0x80 }, new byte[] { })] // Read a 'No operation' byte
[InlineData(new byte[] { 0x01, 0x15, 0x32, 0x80, 0xFF, 0xA2 }, new byte[] { 0x15, 0x32, 0xA2, 0xA2 })] // Read two bytes, nop, repeat two bytes
[InlineData(new byte[] { 0xFE, 0xAA, 0x02, 0x80, 0x00, 0x2A, 0xFD, 0xAA, 0x03, 0x80, 0x00, 0x2A, 0x22, 0xF7, 0xAA },
new byte[] { 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA })] // Apple PackBits sample
public void Decompress_ReadsData(byte[] inputData, byte[] expectedResult)
{
Stream stream = new MemoryStream(inputData);
byte[] buffer = new byte[expectedResult.Length];
PackBitsTiffCompression.Decompress(stream, inputData.Length, buffer);
Assert.Equal(expectedResult, buffer);
}
}
}

164
tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs

@ -0,0 +1,164 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests
{
using System.Collections.Generic;
using Xunit;
using ImageSharp.Formats.Tiff;
public class BlackIsZeroTiffColorTests : PhotometricInterpretationTestBase
{
private static Rgba32 Gray000 = new Rgba32(0, 0, 0, 255);
private static Rgba32 Gray128 = new Rgba32(128, 128, 128, 255);
private static Rgba32 Gray255 = new Rgba32(255, 255, 255, 255);
private static Rgba32 Gray0 = new Rgba32(0, 0, 0, 255);
private static Rgba32 Gray8 = new Rgba32(136, 136, 136, 255);
private static Rgba32 GrayF = new Rgba32(255, 255, 255, 255);
private static Rgba32 Bit0 = new Rgba32(0, 0, 0, 255);
private static Rgba32 Bit1 = new Rgba32(255, 255, 255, 255);
private static byte[] Bilevel_Bytes4x4 = new byte[] { 0b01010000,
0b11110000,
0b01110000,
0b10010000 };
private static Rgba32[][] Bilevel_Result4x4 = new[] { new[] { Bit0, Bit1, Bit0, Bit1 },
new[] { Bit1, Bit1, Bit1, Bit1 },
new[] { Bit0, Bit1, Bit1, Bit1 },
new[] { Bit1, Bit0, Bit0, Bit1 }};
private static byte[] Bilevel_Bytes12x4 = new byte[] { 0b01010101, 0b01010000,
0b11111111, 0b11111111,
0b01101001, 0b10100000,
0b10010000, 0b01100000};
private static Rgba32[][] Bilevel_Result12x4 = new[] { new[] { Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1 },
new[] { Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1 },
new[] { Bit0, Bit1, Bit1, Bit0, Bit1, Bit0, Bit0, Bit1, Bit1, Bit0, Bit1, Bit0 },
new[] { Bit1, Bit0, Bit0, Bit1, Bit0, Bit0, Bit0, Bit0, Bit0, Bit1, Bit1, Bit0 }};
private static byte[] Grayscale4_Bytes4x4 = new byte[] { 0x8F, 0x0F,
0xFF, 0xFF,
0x08, 0x8F,
0xF0, 0xF8 };
private static Rgba32[][] Grayscale4_Result4x4 = new[] { new[] { Gray8, GrayF, Gray0, GrayF },
new[] { GrayF, GrayF, GrayF, GrayF },
new[] { Gray0, Gray8, Gray8, GrayF },
new[] { GrayF, Gray0, GrayF, Gray8 }};
private static byte[] Grayscale4_Bytes3x4 = new byte[] { 0x8F, 0x00,
0xFF, 0xF0,
0x08, 0x80,
0xF0, 0xF0 };
private static Rgba32[][] Grayscale4_Result3x4 = new[] { new[] { Gray8, GrayF, Gray0 },
new[] { GrayF, GrayF, GrayF },
new[] { Gray0, Gray8, Gray8 },
new[] { GrayF, Gray0, GrayF }};
private static byte[] Grayscale8_Bytes4x4 = new byte[] { 128, 255, 000, 255,
255, 255, 255, 255,
000, 128, 128, 255,
255, 000, 255, 128 };
private static Rgba32[][] Grayscale8_Result4x4 = new[] { new[] { Gray128, Gray255, Gray000, Gray255 },
new[] { Gray255, Gray255, Gray255, Gray255 },
new[] { Gray000, Gray128, Gray128, Gray255 },
new[] { Gray255, Gray000, Gray255, Gray128 }};
public static IEnumerable<object[]> Bilevel_Data
{
get
{
yield return new object[] { Bilevel_Bytes4x4, 1, 0, 0, 4, 4, Bilevel_Result4x4 };
yield return new object[] { Bilevel_Bytes4x4, 1, 0, 0, 4, 4, Offset(Bilevel_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Bilevel_Bytes4x4, 1, 1, 0, 4, 4, Offset(Bilevel_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Bilevel_Bytes4x4, 1, 0, 1, 4, 4, Offset(Bilevel_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Bilevel_Bytes4x4, 1, 1, 1, 4, 4, Offset(Bilevel_Result4x4, 1, 1, 6, 6) };
yield return new object[] { Bilevel_Bytes12x4, 1, 0, 0, 12, 4, Bilevel_Result12x4 };
yield return new object[] { Bilevel_Bytes12x4, 1, 0, 0, 12, 4, Offset(Bilevel_Result12x4, 0, 0, 18, 6) };
yield return new object[] { Bilevel_Bytes12x4, 1, 1, 0, 12, 4, Offset(Bilevel_Result12x4, 1, 0, 18, 6) };
yield return new object[] { Bilevel_Bytes12x4, 1, 0, 1, 12, 4, Offset(Bilevel_Result12x4, 0, 1, 18, 6) };
yield return new object[] { Bilevel_Bytes12x4, 1, 1, 1, 12, 4, Offset(Bilevel_Result12x4, 1, 1, 18, 6) };
}
}
public static IEnumerable<object[]> Grayscale4_Data
{
get
{
yield return new object[] { Grayscale4_Bytes4x4, 4, 0, 0, 4, 4, Grayscale4_Result4x4 };
yield return new object[] { Grayscale4_Bytes4x4, 4, 0, 0, 4, 4, Offset(Grayscale4_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Grayscale4_Bytes4x4, 4, 1, 0, 4, 4, Offset(Grayscale4_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Grayscale4_Bytes4x4, 4, 0, 1, 4, 4, Offset(Grayscale4_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Grayscale4_Bytes4x4, 4, 1, 1, 4, 4, Offset(Grayscale4_Result4x4, 1, 1, 6, 6) };
yield return new object[] { Grayscale4_Bytes3x4, 4, 0, 0, 3, 4, Grayscale4_Result3x4 };
yield return new object[] { Grayscale4_Bytes3x4, 4, 0, 0, 3, 4, Offset(Grayscale4_Result3x4, 0, 0, 6, 6) };
yield return new object[] { Grayscale4_Bytes3x4, 4, 1, 0, 3, 4, Offset(Grayscale4_Result3x4, 1, 0, 6, 6) };
yield return new object[] { Grayscale4_Bytes3x4, 4, 0, 1, 3, 4, Offset(Grayscale4_Result3x4, 0, 1, 6, 6) };
yield return new object[] { Grayscale4_Bytes3x4, 4, 1, 1, 3, 4, Offset(Grayscale4_Result3x4, 1, 1, 6, 6) };
}
}
public static IEnumerable<object[]> Grayscale8_Data
{
get
{
yield return new object[] { Grayscale8_Bytes4x4, 8, 0, 0, 4, 4, Grayscale8_Result4x4 };
yield return new object[] { Grayscale8_Bytes4x4, 8, 0, 0, 4, 4, Offset(Grayscale8_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Grayscale8_Bytes4x4, 8, 1, 0, 4, 4, Offset(Grayscale8_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Grayscale8_Bytes4x4, 8, 0, 1, 4, 4, Offset(Grayscale8_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Grayscale8_Bytes4x4, 8, 1, 1, 4, 4, Offset(Grayscale8_Result4x4, 1, 1, 6, 6) };
}
}
[Theory]
[MemberData(nameof(Bilevel_Data))]
[MemberData(nameof(Grayscale4_Data))]
[MemberData(nameof(Grayscale8_Data))]
public void Decode_WritesPixelData(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
{
AssertDecode(expectedResult, pixels =>
{
BlackIsZeroTiffColor.Decode(inputData, new[] { (uint)bitsPerSample }, pixels, left, top, width, height);
});
}
[Theory]
[MemberData(nameof(Bilevel_Data))]
public void Decode_WritesPixelData_Bilevel(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
{
AssertDecode(expectedResult, pixels =>
{
BlackIsZero1TiffColor.Decode(inputData, pixels, left, top, width, height);
});
}
[Theory]
[MemberData(nameof(Grayscale4_Data))]
public void Decode_WritesPixelData_4Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
{
AssertDecode(expectedResult, pixels =>
{
BlackIsZero4TiffColor.Decode(inputData, pixels, left, top, width, height);
});
}
[Theory]
[MemberData(nameof(Grayscale8_Data))]
public void Decode_WritesPixelData_8Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
{
AssertDecode(expectedResult, pixels =>
{
BlackIsZero8TiffColor.Decode(inputData, pixels, left, top, width, height);
});
}
}
}

143
tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs

@ -0,0 +1,143 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests
{
using System.Collections.Generic;
using Xunit;
using ImageSharp.Formats.Tiff;
public class PaletteTiffColorTests : PhotometricInterpretationTestBase
{
public static uint[][] Palette4_ColorPalette { get => GeneratePalette(16); }
public static uint[] Palette4_ColorMap { get => GenerateColorMap(Palette4_ColorPalette); }
private static byte[] Palette4_Bytes4x4 = new byte[] { 0x01, 0x23,
0x4A, 0xD2,
0x12, 0x34,
0xAB, 0xEF };
private static Rgba32[][] Palette4_Result4x4 = GenerateResult(Palette4_ColorPalette,
new[] { new[] { 0x00, 0x01, 0x02, 0x03 },
new[] { 0x04, 0x0A, 0x0D, 0x02 },
new[] { 0x01, 0x02, 0x03, 0x04 },
new[] { 0x0A, 0x0B, 0x0E, 0x0F }});
private static byte[] Palette4_Bytes3x4 = new byte[] { 0x01, 0x20,
0x4A, 0xD0,
0x12, 0x30,
0xAB, 0xE0 };
private static Rgba32[][] Palette4_Result3x4 = GenerateResult(Palette4_ColorPalette,
new[] { new[] { 0x00, 0x01, 0x02 },
new[] { 0x04, 0x0A, 0x0D },
new[] { 0x01, 0x02, 0x03 },
new[] { 0x0A, 0x0B, 0x0E }});
public static IEnumerable<object[]> Palette4_Data
{
get
{
yield return new object[] { Palette4_Bytes4x4, 4, Palette4_ColorMap, 0, 0, 4, 4, Palette4_Result4x4 };
yield return new object[] { Palette4_Bytes4x4, 4, Palette4_ColorMap, 0, 0, 4, 4, Offset(Palette4_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Palette4_Bytes4x4, 4, Palette4_ColorMap, 1, 0, 4, 4, Offset(Palette4_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Palette4_Bytes4x4, 4, Palette4_ColorMap, 0, 1, 4, 4, Offset(Palette4_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Palette4_Bytes4x4, 4, Palette4_ColorMap, 1, 1, 4, 4, Offset(Palette4_Result4x4, 1, 1, 6, 6) };
yield return new object[] { Palette4_Bytes3x4, 4, Palette4_ColorMap, 0, 0, 3, 4, Palette4_Result3x4 };
yield return new object[] { Palette4_Bytes3x4, 4, Palette4_ColorMap, 0, 0, 3, 4, Offset(Palette4_Result3x4, 0, 0, 6, 6) };
yield return new object[] { Palette4_Bytes3x4, 4, Palette4_ColorMap, 1, 0, 3, 4, Offset(Palette4_Result3x4, 1, 0, 6, 6) };
yield return new object[] { Palette4_Bytes3x4, 4, Palette4_ColorMap, 0, 1, 3, 4, Offset(Palette4_Result3x4, 0, 1, 6, 6) };
yield return new object[] { Palette4_Bytes3x4, 4, Palette4_ColorMap, 1, 1, 3, 4, Offset(Palette4_Result3x4, 1, 1, 6, 6) };
}
}
public static uint[][] Palette8_ColorPalette { get => GeneratePalette(256); }
public static uint[] Palette8_ColorMap { get => GenerateColorMap(Palette8_ColorPalette); }
private static byte[] Palette8_Bytes4x4 = new byte[] { 000, 001, 002, 003,
100, 110, 120, 130,
000, 255, 128, 255,
050, 100, 150, 200 };
private static Rgba32[][] Palette8_Result4x4 = GenerateResult(Palette8_ColorPalette,
new[] { new[] { 000, 001, 002, 003 },
new[] { 100, 110, 120, 130 },
new[] { 000, 255, 128, 255 },
new[] { 050, 100, 150, 200 }});
public static IEnumerable<object[]> Palette8_Data
{
get
{
yield return new object[] { Palette8_Bytes4x4, 8, Palette8_ColorMap, 0, 0, 4, 4, Palette8_Result4x4 };
yield return new object[] { Palette8_Bytes4x4, 8, Palette8_ColorMap, 0, 0, 4, 4, Offset(Palette8_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Palette8_Bytes4x4, 8, Palette8_ColorMap, 1, 0, 4, 4, Offset(Palette8_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Palette8_Bytes4x4, 8, Palette8_ColorMap, 0, 1, 4, 4, Offset(Palette8_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Palette8_Bytes4x4, 8, Palette8_ColorMap, 1, 1, 4, 4, Offset(Palette8_Result4x4, 1, 1, 6, 6) };
}
}
[Theory]
[MemberData(nameof(Palette4_Data))]
[MemberData(nameof(Palette8_Data))]
public void Decode_WritesPixelData(byte[] inputData, int bitsPerSample, uint[] colorMap, int left, int top, int width, int height, Rgba32[][] expectedResult)
{
AssertDecode(expectedResult, pixels =>
{
PaletteTiffColor.Decode(inputData, new[] { (uint)bitsPerSample }, colorMap, pixels, left, top, width, height);
});
}
private static uint[][] GeneratePalette(int count)
{
uint[][] palette = new uint[count][];
for (uint i = 0; i < count; i++)
{
palette[i] = new uint[] { (i * 2u) % 65536u, (i * 2625u) % 65536u, (i * 29401u) % 65536u };
}
return palette;
}
private static uint[] GenerateColorMap(uint[][] colorPalette)
{
int colorCount = colorPalette.Length;
uint[] colorMap = new uint[colorCount * 3];
for (int i = 0; i < colorCount; i++)
{
colorMap[colorCount * 0 + i] = colorPalette[i][0];
colorMap[colorCount * 1 + i] = colorPalette[i][1];
colorMap[colorCount * 2 + i] = colorPalette[i][2];
}
return colorMap;
}
private static Rgba32[][] GenerateResult(uint[][] colorPalette, int[][] pixelLookup)
{
var result = new Rgba32[pixelLookup.Length][];
for (int y = 0; y < pixelLookup.Length; y++)
{
result[y] = new Rgba32[pixelLookup[y].Length];
for (int x = 0; x < pixelLookup[y].Length; x++)
{
uint[] sourceColor = colorPalette[pixelLookup[y][x]];
result[y][x] = new Rgba32(sourceColor[0] / 65535F, sourceColor[1] / 65535F, sourceColor[2] / 65535F);
}
}
return result;
}
}
}

66
tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs

@ -0,0 +1,66 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
namespace SixLabors.ImageSharp.Tests
{
using System;
using Xunit;
using ImageSharp;
using SixLabors.ImageSharp.Memory;
public abstract class PhotometricInterpretationTestBase
{
public static Rgba32 DefaultColor = new Rgba32(42, 96, 18, 128);
public static Rgba32[][] Offset(Rgba32[][] input, int xOffset, int yOffset, int width, int height)
{
int inputHeight = input.Length;
int inputWidth = input[0].Length;
Rgba32[][] output = new Rgba32[height][];
for (int y = 0; y < output.Length; y++)
{
output[y] = new Rgba32[width];
for (int x = 0; x < width; x++)
{
output[y][x] = DefaultColor;
}
}
for (int y = 0; y < inputHeight; y++)
{
for (int x = 0; x < inputWidth; x++)
{
output[y + yOffset][x + xOffset] = input[y][x];
}
}
return output;
}
internal static void AssertDecode(Rgba32[][] expectedResult, Action<Buffer2D<Rgba32>> decodeAction)
{
int resultWidth = expectedResult[0].Length;
int resultHeight = expectedResult.Length;
Image<Rgba32> image = new Image<Rgba32>(resultWidth, resultHeight);
image.Mutate(x => x.Fill(DefaultColor));
Buffer2D<Rgba32> pixels = image.GetRootFramePixelBuffer();
decodeAction(pixels);
for (int y = 0; y < resultHeight; y++)
{
for (int x = 0; x < resultWidth; x++)
{
Assert.True(expectedResult[y][x] == pixels[x, y],
$"Pixel ({x}, {y}) should be {expectedResult[y][x]} but was {pixels[x, y]}");
}
}
}
}
}

199
tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs

@ -0,0 +1,199 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests
{
using System.Collections.Generic;
using Xunit;
using ImageSharp.Formats.Tiff;
public class RgbPlanarTiffColorTests : PhotometricInterpretationTestBase
{
private static Rgba32 Rgb4_000 = new Rgba32(0, 0, 0, 255);
private static Rgba32 Rgb4_444 = new Rgba32(68, 68, 68, 255);
private static Rgba32 Rgb4_888 = new Rgba32(136, 136, 136, 255);
private static Rgba32 Rgb4_CCC = new Rgba32(204, 204, 204, 255);
private static Rgba32 Rgb4_FFF = new Rgba32(255, 255, 255, 255);
private static Rgba32 Rgb4_F00 = new Rgba32(255, 0, 0, 255);
private static Rgba32 Rgb4_0F0 = new Rgba32(0, 255, 0, 255);
private static Rgba32 Rgb4_00F = new Rgba32(0, 0, 255, 255);
private static Rgba32 Rgb4_F0F = new Rgba32(255, 0, 255, 255);
private static Rgba32 Rgb4_400 = new Rgba32(68, 0, 0, 255);
private static Rgba32 Rgb4_800 = new Rgba32(136, 0, 0, 255);
private static Rgba32 Rgb4_C00 = new Rgba32(204, 0, 0, 255);
private static Rgba32 Rgb4_48C = new Rgba32(68, 136, 204, 255);
private static byte[] Rgb4_Bytes4x4_R = new byte[] { 0x0F, 0x0F,
0xF0, 0x0F,
0x48, 0xC4,
0x04, 0x8C };
private static byte[] Rgb4_Bytes4x4_G = new byte[] { 0x0F, 0x0F,
0x0F, 0x00,
0x00, 0x08,
0x04, 0x8C };
private static byte[] Rgb4_Bytes4x4_B = new byte[] { 0x0F, 0x0F,
0x00, 0xFF,
0x00, 0x0C,
0x04, 0x8C };
private static byte[][] Rgb4_Bytes4x4 = new[] { Rgb4_Bytes4x4_R, Rgb4_Bytes4x4_G, Rgb4_Bytes4x4_B };
private static Rgba32[][] Rgb4_Result4x4 = new[] { new[] { Rgb4_000, Rgb4_FFF, Rgb4_000, Rgb4_FFF },
new[] { Rgb4_F00, Rgb4_0F0, Rgb4_00F, Rgb4_F0F },
new[] { Rgb4_400, Rgb4_800, Rgb4_C00, Rgb4_48C },
new[] { Rgb4_000, Rgb4_444, Rgb4_888, Rgb4_CCC }};
private static byte[] Rgb4_Bytes3x4_R = new byte[] { 0x0F, 0x00,
0xF0, 0x00,
0x48, 0xC0,
0x04, 0x80 };
private static byte[] Rgb4_Bytes3x4_G = new byte[] { 0x0F, 0x00,
0x0F, 0x00,
0x00, 0x00,
0x04, 0x80 };
private static byte[] Rgb4_Bytes3x4_B = new byte[] { 0x0F, 0x00,
0x00, 0xF0,
0x00, 0x00,
0x04, 0x80 };
private static byte[][] Rgb4_Bytes3x4 = new[] { Rgb4_Bytes3x4_R, Rgb4_Bytes3x4_G, Rgb4_Bytes3x4_B };
private static Rgba32[][] Rgb4_Result3x4 = new[] { new[] { Rgb4_000, Rgb4_FFF, Rgb4_000 },
new[] { Rgb4_F00, Rgb4_0F0, Rgb4_00F },
new[] { Rgb4_400, Rgb4_800, Rgb4_C00 },
new[] { Rgb4_000, Rgb4_444, Rgb4_888 }};
public static IEnumerable<object[]> Rgb4_Data
{
get
{
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 0, 0, 4, 4, Rgb4_Result4x4 };
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 0, 0, 4, 4, Offset(Rgb4_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 1, 0, 4, 4, Offset(Rgb4_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 0, 1, 4, 4, Offset(Rgb4_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 1, 1, 4, 4, Offset(Rgb4_Result4x4, 1, 1, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 0, 0, 3, 4, Rgb4_Result3x4 };
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 0, 0, 3, 4, Offset(Rgb4_Result3x4, 0, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 1, 0, 3, 4, Offset(Rgb4_Result3x4, 1, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 0, 1, 3, 4, Offset(Rgb4_Result3x4, 0, 1, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 1, 1, 3, 4, Offset(Rgb4_Result3x4, 1, 1, 6, 6) };
}
}
private static Rgba32 Rgb8_000 = new Rgba32(0, 0, 0, 255);
private static Rgba32 Rgb8_444 = new Rgba32(64, 64, 64, 255);
private static Rgba32 Rgb8_888 = new Rgba32(128, 128, 128, 255);
private static Rgba32 Rgb8_CCC = new Rgba32(192, 192, 192, 255);
private static Rgba32 Rgb8_FFF = new Rgba32(255, 255, 255, 255);
private static Rgba32 Rgb8_F00 = new Rgba32(255, 0, 0, 255);
private static Rgba32 Rgb8_0F0 = new Rgba32(0, 255, 0, 255);
private static Rgba32 Rgb8_00F = new Rgba32(0, 0, 255, 255);
private static Rgba32 Rgb8_F0F = new Rgba32(255, 0, 255, 255);
private static Rgba32 Rgb8_400 = new Rgba32(64, 0, 0, 255);
private static Rgba32 Rgb8_800 = new Rgba32(128, 0, 0, 255);
private static Rgba32 Rgb8_C00 = new Rgba32(192, 0, 0, 255);
private static Rgba32 Rgb8_48C = new Rgba32(64, 128, 192, 255);
private static byte[] Rgb8_Bytes4x4_R = new byte[] { 000, 255, 000, 255,
255, 000, 000, 255,
064, 128, 192, 064,
000, 064, 128, 192 };
private static byte[] Rgb8_Bytes4x4_G = new byte[] { 000, 255, 000, 255,
000, 255, 000, 000,
000, 000, 000, 128,
000, 064, 128, 192 };
private static byte[] Rgb8_Bytes4x4_B = new byte[] { 000, 255, 000, 255,
000, 000, 255, 255,
000, 000, 000, 192,
000, 064, 128, 192 };
private static byte[][] Rgb8_Bytes4x4 = new[] { Rgb8_Bytes4x4_R, Rgb8_Bytes4x4_G, Rgb8_Bytes4x4_B };
private static Rgba32[][] Rgb8_Result4x4 = new[] { new[] { Rgb8_000, Rgb8_FFF, Rgb8_000, Rgb8_FFF },
new[] { Rgb8_F00, Rgb8_0F0, Rgb8_00F, Rgb8_F0F },
new[] { Rgb8_400, Rgb8_800, Rgb8_C00, Rgb8_48C },
new[] { Rgb8_000, Rgb8_444, Rgb8_888, Rgb8_CCC }};
public static IEnumerable<object[]> Rgb8_Data
{
get
{
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 0, 0, 4, 4, Rgb8_Result4x4 };
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 0, 0, 4, 4, Offset(Rgb8_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 1, 0, 4, 4, Offset(Rgb8_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 0, 1, 4, 4, Offset(Rgb8_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 1, 1, 4, 4, Offset(Rgb8_Result4x4, 1, 1, 6, 6) };
}
}
private static Rgba32 Rgb484_000 = new Rgba32(0, 0, 0, 255);
private static Rgba32 Rgb484_444 = new Rgba32(68, 64, 68, 255);
private static Rgba32 Rgb484_888 = new Rgba32(136, 128, 136, 255);
private static Rgba32 Rgb484_CCC = new Rgba32(204, 192, 204, 255);
private static Rgba32 Rgb484_FFF = new Rgba32(255, 255, 255, 255);
private static Rgba32 Rgb484_F00 = new Rgba32(255, 0, 0, 255);
private static Rgba32 Rgb484_0F0 = new Rgba32(0, 255, 0, 255);
private static Rgba32 Rgb484_00F = new Rgba32(0, 0, 255, 255);
private static Rgba32 Rgb484_F0F = new Rgba32(255, 0, 255, 255);
private static Rgba32 Rgb484_400 = new Rgba32(68, 0, 0, 255);
private static Rgba32 Rgb484_800 = new Rgba32(136, 0, 0, 255);
private static Rgba32 Rgb484_C00 = new Rgba32(204, 0, 0, 255);
private static Rgba32 Rgb484_48C = new Rgba32(68, 128, 204, 255);
private static byte[] Rgb484_Bytes4x4_R = new byte[] { 0x0F, 0x0F,
0xF0, 0x0F,
0x48, 0xC4,
0x04, 0x8C };
private static byte[] Rgb484_Bytes4x4_G = new byte[] { 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0x00,
0x00, 0x00, 0x00, 0x80,
0x00, 0x40, 0x80, 0xC0 };
private static byte[] Rgb484_Bytes4x4_B = new byte[] { 0x0F, 0x0F,
0x00, 0xFF,
0x00, 0x0C,
0x04, 0x8C };
private static Rgba32[][] Rgb484_Result4x4 = new[] { new[] { Rgb484_000, Rgb484_FFF, Rgb484_000, Rgb484_FFF },
new[] { Rgb484_F00, Rgb484_0F0, Rgb484_00F, Rgb484_F0F },
new[] { Rgb484_400, Rgb484_800, Rgb484_C00, Rgb484_48C },
new[] { Rgb484_000, Rgb484_444, Rgb484_888, Rgb484_CCC }};
private static byte[][] Rgb484_Bytes4x4 = new[] { Rgb484_Bytes4x4_R, Rgb484_Bytes4x4_G, Rgb484_Bytes4x4_B };
public static IEnumerable<object[]> Rgb484_Data
{
get
{
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 0, 0, 4, 4, Rgb484_Result4x4 };
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 0, 0, 4, 4, Offset(Rgb484_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 1, 0, 4, 4, Offset(Rgb484_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 0, 1, 4, 4, Offset(Rgb484_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 1, 1, 4, 4, Offset(Rgb484_Result4x4, 1, 1, 6, 6) };
}
}
[Theory]
[MemberData(nameof(Rgb4_Data))]
[MemberData(nameof(Rgb8_Data))]
[MemberData(nameof(Rgb484_Data))]
public void Decode_WritesPixelData(byte[][] inputData, uint[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
{
AssertDecode(expectedResult, pixels =>
{
RgbPlanarTiffColor.Decode(inputData, bitsPerSample, pixels, left, top, width, height);
});
}
}
}

161
tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs

@ -0,0 +1,161 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests
{
using System.Collections.Generic;
using Xunit;
using ImageSharp.Formats.Tiff;
public class RgbTiffColorTests : PhotometricInterpretationTestBase
{
private static Rgba32 Rgb4_000 = new Rgba32(0, 0, 0, 255);
private static Rgba32 Rgb4_444 = new Rgba32(68, 68, 68, 255);
private static Rgba32 Rgb4_888 = new Rgba32(136, 136, 136, 255);
private static Rgba32 Rgb4_CCC = new Rgba32(204, 204, 204, 255);
private static Rgba32 Rgb4_FFF = new Rgba32(255, 255, 255, 255);
private static Rgba32 Rgb4_F00 = new Rgba32(255, 0, 0, 255);
private static Rgba32 Rgb4_0F0 = new Rgba32(0, 255, 0, 255);
private static Rgba32 Rgb4_00F = new Rgba32(0, 0, 255, 255);
private static Rgba32 Rgb4_F0F = new Rgba32(255, 0, 255, 255);
private static Rgba32 Rgb4_400 = new Rgba32(68, 0, 0, 255);
private static Rgba32 Rgb4_800 = new Rgba32(136, 0, 0, 255);
private static Rgba32 Rgb4_C00 = new Rgba32(204, 0, 0, 255);
private static Rgba32 Rgb4_48C = new Rgba32(68, 136, 204, 255);
private static byte[] Rgb4_Bytes4x4 = new byte[] { 0x00, 0x0F, 0xFF, 0x00, 0x0F, 0xFF,
0xF0, 0x00, 0xF0, 0x00, 0xFF, 0x0F,
0x40, 0x08, 0x00, 0xC0, 0x04, 0x8C,
0x00, 0x04, 0x44, 0x88, 0x8C, 0xCC };
private static Rgba32[][] Rgb4_Result4x4 = new[] { new[] { Rgb4_000, Rgb4_FFF, Rgb4_000, Rgb4_FFF },
new[] { Rgb4_F00, Rgb4_0F0, Rgb4_00F, Rgb4_F0F },
new[] { Rgb4_400, Rgb4_800, Rgb4_C00, Rgb4_48C },
new[] { Rgb4_000, Rgb4_444, Rgb4_888, Rgb4_CCC }};
private static byte[] Rgb4_Bytes3x4 = new byte[] { 0x00, 0x0F, 0xFF, 0x00, 0x00,
0xF0, 0x00, 0xF0, 0x00, 0xF0,
0x40, 0x08, 0x00, 0xC0, 0x00,
0x00, 0x04, 0x44, 0x88, 0x80 };
private static Rgba32[][] Rgb4_Result3x4 = new[] { new[] { Rgb4_000, Rgb4_FFF, Rgb4_000 },
new[] { Rgb4_F00, Rgb4_0F0, Rgb4_00F },
new[] { Rgb4_400, Rgb4_800, Rgb4_C00 },
new[] { Rgb4_000, Rgb4_444, Rgb4_888 }};
public static IEnumerable<object[]> Rgb4_Data
{
get
{
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 0, 0, 4, 4, Rgb4_Result4x4 };
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 0, 0, 4, 4, Offset(Rgb4_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 1, 0, 4, 4, Offset(Rgb4_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 0, 1, 4, 4, Offset(Rgb4_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 1, 1, 4, 4, Offset(Rgb4_Result4x4, 1, 1, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 0, 0, 3, 4, Rgb4_Result3x4 };
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 0, 0, 3, 4, Offset(Rgb4_Result3x4, 0, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 1, 0, 3, 4, Offset(Rgb4_Result3x4, 1, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 0, 1, 3, 4, Offset(Rgb4_Result3x4, 0, 1, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 1, 1, 3, 4, Offset(Rgb4_Result3x4, 1, 1, 6, 6) };
}
}
private static Rgba32 Rgb8_000 = new Rgba32(0, 0, 0, 255);
private static Rgba32 Rgb8_444 = new Rgba32(64, 64, 64, 255);
private static Rgba32 Rgb8_888 = new Rgba32(128, 128, 128, 255);
private static Rgba32 Rgb8_CCC = new Rgba32(192, 192, 192, 255);
private static Rgba32 Rgb8_FFF = new Rgba32(255, 255, 255, 255);
private static Rgba32 Rgb8_F00 = new Rgba32(255, 0, 0, 255);
private static Rgba32 Rgb8_0F0 = new Rgba32(0, 255, 0, 255);
private static Rgba32 Rgb8_00F = new Rgba32(0, 0, 255, 255);
private static Rgba32 Rgb8_F0F = new Rgba32(255, 0, 255, 255);
private static Rgba32 Rgb8_400 = new Rgba32(64, 0, 0, 255);
private static Rgba32 Rgb8_800 = new Rgba32(128, 0, 0, 255);
private static Rgba32 Rgb8_C00 = new Rgba32(192, 0, 0, 255);
private static Rgba32 Rgb8_48C = new Rgba32(64, 128, 192, 255);
private static byte[] Rgb8_Bytes4x4 = new byte[] { 000, 000, 000, 255, 255, 255, 000, 000, 000, 255, 255, 255,
255, 000, 000, 000, 255, 000, 000, 000, 255, 255, 000, 255,
064, 000, 000, 128, 000, 000, 192, 000, 000, 064, 128, 192,
000, 000, 000, 064, 064, 064, 128, 128, 128, 192, 192, 192 };
private static Rgba32[][] Rgb8_Result4x4 = new[] { new[] { Rgb8_000, Rgb8_FFF, Rgb8_000, Rgb8_FFF },
new[] { Rgb8_F00, Rgb8_0F0, Rgb8_00F, Rgb8_F0F },
new[] { Rgb8_400, Rgb8_800, Rgb8_C00, Rgb8_48C },
new[] { Rgb8_000, Rgb8_444, Rgb8_888, Rgb8_CCC }};
public static IEnumerable<object[]> Rgb8_Data
{
get
{
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 0, 0, 4, 4, Rgb8_Result4x4 };
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 0, 0, 4, 4, Offset(Rgb8_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 1, 0, 4, 4, Offset(Rgb8_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 0, 1, 4, 4, Offset(Rgb8_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 1, 1, 4, 4, Offset(Rgb8_Result4x4, 1, 1, 6, 6) };
}
}
private static Rgba32 Rgb484_000 = new Rgba32(0, 0, 0, 255);
private static Rgba32 Rgb484_444 = new Rgba32(68, 64, 68, 255);
private static Rgba32 Rgb484_888 = new Rgba32(136, 128, 136, 255);
private static Rgba32 Rgb484_CCC = new Rgba32(204, 192, 204, 255);
private static Rgba32 Rgb484_FFF = new Rgba32(255, 255, 255, 255);
private static Rgba32 Rgb484_F00 = new Rgba32(255, 0, 0, 255);
private static Rgba32 Rgb484_0F0 = new Rgba32(0, 255, 0, 255);
private static Rgba32 Rgb484_00F = new Rgba32(0, 0, 255, 255);
private static Rgba32 Rgb484_F0F = new Rgba32(255, 0, 255, 255);
private static Rgba32 Rgb484_400 = new Rgba32(68, 0, 0, 255);
private static Rgba32 Rgb484_800 = new Rgba32(136, 0, 0, 255);
private static Rgba32 Rgb484_C00 = new Rgba32(204, 0, 0, 255);
private static Rgba32 Rgb484_48C = new Rgba32(68, 128, 204, 255);
private static byte[] Rgb484_Bytes4x4 = new byte[] { 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF,
0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x0F, 0xF0, 0x0F,
0x40, 0x00, 0x80, 0x00, 0xC0, 0x00, 0x48, 0x0C,
0x00, 0x00, 0x44, 0x04, 0x88, 0x08, 0xCC, 0x0C };
private static Rgba32[][] Rgb484_Result4x4 = new[] { new[] { Rgb484_000, Rgb484_FFF, Rgb484_000, Rgb484_FFF },
new[] { Rgb484_F00, Rgb484_0F0, Rgb484_00F, Rgb484_F0F },
new[] { Rgb484_400, Rgb484_800, Rgb484_C00, Rgb484_48C },
new[] { Rgb484_000, Rgb484_444, Rgb484_888, Rgb484_CCC }};
public static IEnumerable<object[]> Rgb484_Data
{
get
{
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 0, 0, 4, 4, Rgb484_Result4x4 };
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 0, 0, 4, 4, Offset(Rgb484_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 1, 0, 4, 4, Offset(Rgb484_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 0, 1, 4, 4, Offset(Rgb484_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 1, 1, 4, 4, Offset(Rgb484_Result4x4, 1, 1, 6, 6) };
}
}
[Theory]
[MemberData(nameof(Rgb4_Data))]
[MemberData(nameof(Rgb8_Data))]
[MemberData(nameof(Rgb484_Data))]
public void Decode_WritesPixelData(byte[] inputData, uint[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
{
AssertDecode(expectedResult, pixels =>
{
RgbTiffColor.Decode(inputData, bitsPerSample, pixels, left, top, width, height);
});
}
[Theory]
[MemberData(nameof(Rgb8_Data))]
public void Decode_WritesPixelData_8Bit(byte[] inputData, uint[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
{
AssertDecode(expectedResult, pixels =>
{
Rgb888TiffColor.Decode(inputData, pixels, left, top, width, height);
});
}
}
}

164
tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColorTests.cs

@ -0,0 +1,164 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests
{
using System.Collections.Generic;
using Xunit;
using ImageSharp.Formats.Tiff;
public class WhiteIsZeroTiffColorTests : PhotometricInterpretationTestBase
{
private static Rgba32 Gray000 = new Rgba32(255, 255, 255, 255);
private static Rgba32 Gray128 = new Rgba32(127, 127, 127, 255);
private static Rgba32 Gray255 = new Rgba32(0, 0, 0, 255);
private static Rgba32 Gray0 = new Rgba32(255, 255, 255, 255);
private static Rgba32 Gray8 = new Rgba32(119, 119, 119, 255);
private static Rgba32 GrayF = new Rgba32(0, 0, 0, 255);
private static Rgba32 Bit0 = new Rgba32(255, 255, 255, 255);
private static Rgba32 Bit1 = new Rgba32(0, 0, 0, 255);
private static byte[] Bilevel_Bytes4x4 = new byte[] { 0b01010000,
0b11110000,
0b01110000,
0b10010000 };
private static Rgba32[][] Bilevel_Result4x4 = new[] { new[] { Bit0, Bit1, Bit0, Bit1 },
new[] { Bit1, Bit1, Bit1, Bit1 },
new[] { Bit0, Bit1, Bit1, Bit1 },
new[] { Bit1, Bit0, Bit0, Bit1 }};
private static byte[] Bilevel_Bytes12x4 = new byte[] { 0b01010101, 0b01010000,
0b11111111, 0b11111111,
0b01101001, 0b10100000,
0b10010000, 0b01100000};
private static Rgba32[][] Bilevel_Result12x4 = new[] { new[] { Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1 },
new[] { Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1, Bit1 },
new[] { Bit0, Bit1, Bit1, Bit0, Bit1, Bit0, Bit0, Bit1, Bit1, Bit0, Bit1, Bit0 },
new[] { Bit1, Bit0, Bit0, Bit1, Bit0, Bit0, Bit0, Bit0, Bit0, Bit1, Bit1, Bit0 }};
private static byte[] Grayscale4_Bytes4x4 = new byte[] { 0x8F, 0x0F,
0xFF, 0xFF,
0x08, 0x8F,
0xF0, 0xF8 };
private static Rgba32[][] Grayscale4_Result4x4 = new[] { new[] { Gray8, GrayF, Gray0, GrayF },
new[] { GrayF, GrayF, GrayF, GrayF },
new[] { Gray0, Gray8, Gray8, GrayF },
new[] { GrayF, Gray0, GrayF, Gray8 }};
private static byte[] Grayscale4_Bytes3x4 = new byte[] { 0x8F, 0x00,
0xFF, 0xF0,
0x08, 0x80,
0xF0, 0xF0 };
private static Rgba32[][] Grayscale4_Result3x4 = new[] { new[] { Gray8, GrayF, Gray0 },
new[] { GrayF, GrayF, GrayF },
new[] { Gray0, Gray8, Gray8 },
new[] { GrayF, Gray0, GrayF }};
private static byte[] Grayscale8_Bytes4x4 = new byte[] { 128, 255, 000, 255,
255, 255, 255, 255,
000, 128, 128, 255,
255, 000, 255, 128 };
private static Rgba32[][] Grayscale8_Result4x4 = new[] { new[] { Gray128, Gray255, Gray000, Gray255 },
new[] { Gray255, Gray255, Gray255, Gray255 },
new[] { Gray000, Gray128, Gray128, Gray255 },
new[] { Gray255, Gray000, Gray255, Gray128 }};
public static IEnumerable<object[]> Bilevel_Data
{
get
{
yield return new object[] { Bilevel_Bytes4x4, 1, 0, 0, 4, 4, Bilevel_Result4x4 };
yield return new object[] { Bilevel_Bytes4x4, 1, 0, 0, 4, 4, Offset(Bilevel_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Bilevel_Bytes4x4, 1, 1, 0, 4, 4, Offset(Bilevel_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Bilevel_Bytes4x4, 1, 0, 1, 4, 4, Offset(Bilevel_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Bilevel_Bytes4x4, 1, 1, 1, 4, 4, Offset(Bilevel_Result4x4, 1, 1, 6, 6) };
yield return new object[] { Bilevel_Bytes12x4, 1, 0, 0, 12, 4, Bilevel_Result12x4 };
yield return new object[] { Bilevel_Bytes12x4, 1, 0, 0, 12, 4, Offset(Bilevel_Result12x4, 0, 0, 18, 6) };
yield return new object[] { Bilevel_Bytes12x4, 1, 1, 0, 12, 4, Offset(Bilevel_Result12x4, 1, 0, 18, 6) };
yield return new object[] { Bilevel_Bytes12x4, 1, 0, 1, 12, 4, Offset(Bilevel_Result12x4, 0, 1, 18, 6) };
yield return new object[] { Bilevel_Bytes12x4, 1, 1, 1, 12, 4, Offset(Bilevel_Result12x4, 1, 1, 18, 6) };
}
}
public static IEnumerable<object[]> Grayscale4_Data
{
get
{
yield return new object[] { Grayscale4_Bytes4x4, 4, 0, 0, 4, 4, Grayscale4_Result4x4 };
yield return new object[] { Grayscale4_Bytes4x4, 4, 0, 0, 4, 4, Offset(Grayscale4_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Grayscale4_Bytes4x4, 4, 1, 0, 4, 4, Offset(Grayscale4_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Grayscale4_Bytes4x4, 4, 0, 1, 4, 4, Offset(Grayscale4_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Grayscale4_Bytes4x4, 4, 1, 1, 4, 4, Offset(Grayscale4_Result4x4, 1, 1, 6, 6) };
yield return new object[] { Grayscale4_Bytes3x4, 4, 0, 0, 3, 4, Grayscale4_Result3x4 };
yield return new object[] { Grayscale4_Bytes3x4, 4, 0, 0, 3, 4, Offset(Grayscale4_Result3x4, 0, 0, 6, 6) };
yield return new object[] { Grayscale4_Bytes3x4, 4, 1, 0, 3, 4, Offset(Grayscale4_Result3x4, 1, 0, 6, 6) };
yield return new object[] { Grayscale4_Bytes3x4, 4, 0, 1, 3, 4, Offset(Grayscale4_Result3x4, 0, 1, 6, 6) };
yield return new object[] { Grayscale4_Bytes3x4, 4, 1, 1, 3, 4, Offset(Grayscale4_Result3x4, 1, 1, 6, 6) };
}
}
public static IEnumerable<object[]> Grayscale8_Data
{
get
{
yield return new object[] { Grayscale8_Bytes4x4, 8, 0, 0, 4, 4, Grayscale8_Result4x4 };
yield return new object[] { Grayscale8_Bytes4x4, 8, 0, 0, 4, 4, Offset(Grayscale8_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Grayscale8_Bytes4x4, 8, 1, 0, 4, 4, Offset(Grayscale8_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Grayscale8_Bytes4x4, 8, 0, 1, 4, 4, Offset(Grayscale8_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Grayscale8_Bytes4x4, 8, 1, 1, 4, 4, Offset(Grayscale8_Result4x4, 1, 1, 6, 6) };
}
}
[Theory]
[MemberData(nameof(Bilevel_Data))]
[MemberData(nameof(Grayscale4_Data))]
[MemberData(nameof(Grayscale8_Data))]
public void Decode_WritesPixelData(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
{
AssertDecode(expectedResult, pixels =>
{
WhiteIsZeroTiffColor.Decode(inputData, new[] { (uint)bitsPerSample }, pixels, left, top, width, height);
});
}
[Theory]
[MemberData(nameof(Bilevel_Data))]
public void Decode_WritesPixelData_Bilevel(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
{
AssertDecode(expectedResult, pixels =>
{
WhiteIsZero1TiffColor.Decode(inputData, pixels, left, top, width, height);
});
}
[Theory]
[MemberData(nameof(Grayscale4_Data))]
public void Decode_WritesPixelData_4Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
{
AssertDecode(expectedResult, pixels =>
{
WhiteIsZero4TiffColor.Decode(inputData, pixels, left, top, width, height);
});
}
[Theory]
[MemberData(nameof(Grayscale8_Data))]
public void Decode_WritesPixelData_8Bit(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
{
AssertDecode(expectedResult, pixels =>
{
WhiteIsZero8TiffColor.Decode(inputData, pixels, left, top, width, height);
});
}
}
}

111
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderHeaderTests.cs

@ -0,0 +1,111 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests
{
using System.IO;
using Xunit;
using ImageSharp.Formats;
public class TiffDecoderHeaderTests
{
public static object[][] IsLittleEndianValues = new[] { new object[] { false },
new object[] { true } };
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void ReadHeader_ReadsEndianness(bool isLittleEndian)
{
Stream stream = new TiffGenHeader()
{
FirstIfd = new TiffGenIfd()
}
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, false, null, null);
decoder.ReadHeader();
Assert.Equal(isLittleEndian, decoder.IsLittleEndian);
}
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void ReadHeader_ReadsFirstIfdOffset(bool isLittleEndian)
{
Stream stream = new TiffGenHeader()
{
FirstIfd = new TiffGenIfd()
}
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, false, null, null);
uint firstIfdOffset = decoder.ReadHeader();
Assert.Equal(8u, firstIfdOffset);
}
[Theory]
[InlineData(0x1234)]
[InlineData(0x4912)]
[InlineData(0x1249)]
[InlineData(0x4D12)]
[InlineData(0x124D)]
[InlineData(0x494D)]
[InlineData(0x4D49)]
public void Decode_ThrowsException_WithInvalidByteOrderMarkers(ushort byteOrderMarker)
{
Stream stream = new TiffGenHeader()
{
FirstIfd = new TiffGenIfd(),
ByteOrderMarker = byteOrderMarker
}
.ToStream(true);
TiffDecoder decoder = new TiffDecoder();
ImageFormatException e = Assert.Throws<ImageFormatException>(() => { decoder.Decode<Rgba32>(Configuration.Default, stream); });
Assert.Equal("Invalid TIFF file header.", e.Message);
}
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void Decode_ThrowsException_WithIncorrectMagicNumber(bool isLittleEndian)
{
Stream stream = new TiffGenHeader()
{
FirstIfd = new TiffGenIfd(),
MagicNumber = 32
}
.ToStream(isLittleEndian);
TiffDecoder decoder = new TiffDecoder();
ImageFormatException e = Assert.Throws<ImageFormatException>(() => { decoder.Decode<Rgba32>(Configuration.Default, stream); });
Assert.Equal("Invalid TIFF file header.", e.Message);
}
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void Decode_ThrowsException_WithNoIfdZero(bool isLittleEndian)
{
Stream stream = new TiffGenHeader()
{
FirstIfd = null
}
.ToStream(isLittleEndian);
TiffDecoder decoder = new TiffDecoder();
ImageFormatException e = Assert.Throws<ImageFormatException>(() => { decoder.Decode<Rgba32>(Configuration.Default, stream); });
Assert.Equal("Invalid TIFF file header.", e.Message);
}
}
}

846
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderIfdEntryTests.cs

@ -0,0 +1,846 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests
{
using System;
using System.IO;
using System.Linq;
using Xunit;
using ImageSharp.Formats;
using ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.Primitives;
public class TiffDecoderIfdEntryTests
{
[Theory]
[InlineDataAttribute((ushort)TiffType.Byte, 1u, 1u)]
[InlineDataAttribute((ushort)TiffType.Ascii, 1u, 1u)]
[InlineDataAttribute((ushort)TiffType.Short, 1u, 2u)]
[InlineDataAttribute((ushort)TiffType.Long, 1u, 4u)]
[InlineDataAttribute((ushort)TiffType.Rational, 1u, 8u)]
[InlineDataAttribute((ushort)TiffType.SByte, 1u, 1u)]
[InlineDataAttribute((ushort)TiffType.Undefined, 1u, 1u)]
[InlineDataAttribute((ushort)TiffType.SShort, 1u, 2u)]
[InlineDataAttribute((ushort)TiffType.SLong, 1u, 4u)]
[InlineDataAttribute((ushort)TiffType.SRational, 1u, 8u)]
[InlineDataAttribute((ushort)TiffType.Float, 1u, 4u)]
[InlineDataAttribute((ushort)TiffType.Double, 1u, 8u)]
[InlineDataAttribute((ushort)TiffType.Ifd, 1u, 4u)]
[InlineDataAttribute((ushort)999, 1u, 0u)]
public void GetSizeOfData_SingleItem_ReturnsCorrectSize(ushort type, uint count, uint expectedSize)
{
TiffIfdEntry entry = new TiffIfdEntry(TiffTags.ImageWidth, (TiffType)type, count, new byte[4]);
uint size = TiffDecoderCore.GetSizeOfData(entry);
Assert.Equal(expectedSize, size);
}
[Theory]
[InlineDataAttribute((ushort)TiffType.Byte, 15u, 15u)]
[InlineDataAttribute((ushort)TiffType.Ascii, 20u, 20u)]
[InlineDataAttribute((ushort)TiffType.Short, 18u, 36u)]
[InlineDataAttribute((ushort)TiffType.Long, 4u, 16u)]
[InlineDataAttribute((ushort)TiffType.Rational, 9u, 72u)]
[InlineDataAttribute((ushort)TiffType.SByte, 5u, 5u)]
[InlineDataAttribute((ushort)TiffType.Undefined, 136u, 136u)]
[InlineDataAttribute((ushort)TiffType.SShort, 12u, 24u)]
[InlineDataAttribute((ushort)TiffType.SLong, 15u, 60u)]
[InlineDataAttribute((ushort)TiffType.SRational, 10u, 80u)]
[InlineDataAttribute((ushort)TiffType.Float, 2u, 8u)]
[InlineDataAttribute((ushort)TiffType.Double, 2u, 16u)]
[InlineDataAttribute((ushort)TiffType.Ifd, 10u, 40u)]
[InlineDataAttribute((ushort)999, 1050u, 0u)]
public void GetSizeOfData_Array_ReturnsCorrectSize(ushort type, uint count, uint expectedSize)
{
TiffIfdEntry entry = new TiffIfdEntry(TiffTags.ImageWidth, (TiffType)type, count, new byte[4]);
uint size = TiffDecoderCore.GetSizeOfData(entry);
Assert.Equal(expectedSize, size);
}
[Theory]
[InlineDataAttribute((ushort)TiffType.Byte, 1u, new byte[] { 17 }, false)]
[InlineDataAttribute((ushort)TiffType.Byte, 1u, new byte[] { 17 }, true)]
[InlineDataAttribute((ushort)TiffType.Byte, 2u, new byte[] { 17, 28 }, false)]
[InlineDataAttribute((ushort)TiffType.Byte, 2u, new byte[] { 17, 28 }, true)]
[InlineDataAttribute((ushort)TiffType.Byte, 4u, new byte[] { 17, 28, 2, 9 }, false)]
[InlineDataAttribute((ushort)TiffType.Byte, 4u, new byte[] { 17, 28, 2, 9 }, true)]
[InlineDataAttribute((ushort)TiffType.Byte, 5u, new byte[] { 17, 28, 2, 9, 13 }, false)]
[InlineDataAttribute((ushort)TiffType.Byte, 5u, new byte[] { 17, 28, 2, 9, 13 }, true)]
[InlineDataAttribute((ushort)TiffType.Byte, 10u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2, 127, 86 }, false)]
[InlineDataAttribute((ushort)TiffType.Byte, 10u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2, 127, 86 }, true)]
[InlineDataAttribute((ushort)TiffType.Short, 1u, new byte[] { 17, 28 }, false)]
[InlineDataAttribute((ushort)TiffType.Short, 1u, new byte[] { 17, 28 }, true)]
[InlineDataAttribute((ushort)TiffType.Short, 2u, new byte[] { 17, 28, 2, 9 }, false)]
[InlineDataAttribute((ushort)TiffType.Short, 2u, new byte[] { 17, 28, 2, 9 }, true)]
[InlineDataAttribute((ushort)TiffType.Short, 3u, new byte[] { 17, 28, 2, 9, 13, 37 }, false)]
[InlineDataAttribute((ushort)TiffType.Short, 3u, new byte[] { 17, 28, 2, 9, 13, 37 }, true)]
[InlineDataAttribute((ushort)TiffType.Long, 1u, new byte[] { 17, 28, 2, 9 }, false)]
[InlineDataAttribute((ushort)TiffType.Long, 1u, new byte[] { 17, 28, 2, 9 }, true)]
[InlineDataAttribute((ushort)TiffType.Long, 2u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2 }, false)]
[InlineDataAttribute((ushort)TiffType.Long, 2u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2 }, true)]
[InlineDataAttribute((ushort)TiffType.Rational, 1u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2 }, false)]
[InlineDataAttribute((ushort)TiffType.Rational, 1u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2 }, true)]
public void ReadBytes_ReturnsExpectedData(ushort type, uint count, byte[] bytes, bool isLittleEndian)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, count, bytes), isLittleEndian);
byte[] result = decoder.ReadBytes(ref entry);
if (bytes.Length < 4)
result = result.Take(bytes.Length).ToArray();
Assert.Equal(bytes, result);
}
[Theory]
[InlineDataAttribute((ushort)TiffType.Byte, 5u, new byte[] { 17, 28, 2, 9, 13 }, false)]
[InlineDataAttribute((ushort)TiffType.Byte, 5u, new byte[] { 17, 28, 2, 9, 13 }, true)]
[InlineDataAttribute((ushort)TiffType.Byte, 10u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2, 127, 86 }, false)]
[InlineDataAttribute((ushort)TiffType.Byte, 10u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2, 127, 86 }, true)]
[InlineDataAttribute((ushort)TiffType.Short, 3u, new byte[] { 17, 28, 2, 9, 13, 37 }, false)]
[InlineDataAttribute((ushort)TiffType.Short, 3u, new byte[] { 17, 28, 2, 9, 13, 37 }, true)]
[InlineDataAttribute((ushort)TiffType.Long, 2u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2 }, false)]
[InlineDataAttribute((ushort)TiffType.Long, 2u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2 }, true)]
[InlineDataAttribute((ushort)TiffType.Rational, 1u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2 }, false)]
[InlineDataAttribute((ushort)TiffType.Rational, 1u, new byte[] { 17, 28, 2, 9, 13, 37, 18, 2 }, true)]
public void ReadBytes_CachesDataLongerThanFourBytes(ushort type, uint count, byte[] bytes, bool isLittleEndian)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, count, bytes), isLittleEndian);
Assert.Equal(4, entry.Value.Length);
byte[] result = decoder.ReadBytes(ref entry);
Assert.Equal(bytes.Length, entry.Value.Length);
Assert.Equal(bytes, entry.Value);
}
[Theory]
[InlineDataAttribute((ushort)TiffType.Byte, true, new byte[] { 0, 1, 2, 3 }, 0)]
[InlineDataAttribute((ushort)TiffType.Byte, true, new byte[] { 1, 2, 3, 4 }, 1)]
[InlineDataAttribute((ushort)TiffType.Byte, true, new byte[] { 255, 2, 3, 4 }, 255)]
[InlineDataAttribute((ushort)TiffType.Byte, false, new byte[] { 0, 1, 2, 3 }, 0)]
[InlineDataAttribute((ushort)TiffType.Byte, false, new byte[] { 1, 2, 3, 4 }, 1)]
[InlineDataAttribute((ushort)TiffType.Byte, false, new byte[] { 255, 2, 3, 4 }, 255)]
[InlineDataAttribute((ushort)TiffType.Short, true, new byte[] { 0, 0, 2, 3 }, 0)]
[InlineDataAttribute((ushort)TiffType.Short, true, new byte[] { 1, 0, 2, 3 }, 1)]
[InlineDataAttribute((ushort)TiffType.Short, true, new byte[] { 0, 1, 2, 3 }, 256)]
[InlineDataAttribute((ushort)TiffType.Short, true, new byte[] { 2, 1, 2, 3 }, 258)]
[InlineDataAttribute((ushort)TiffType.Short, true, new byte[] { 255, 255, 2, 3 }, UInt16.MaxValue)]
[InlineDataAttribute((ushort)TiffType.Short, false, new byte[] { 0, 0, 2, 3 }, 0)]
[InlineDataAttribute((ushort)TiffType.Short, false, new byte[] { 0, 1, 2, 3 }, 1)]
[InlineDataAttribute((ushort)TiffType.Short, false, new byte[] { 1, 0, 2, 3 }, 256)]
[InlineDataAttribute((ushort)TiffType.Short, false, new byte[] { 1, 2, 2, 3 }, 258)]
[InlineDataAttribute((ushort)TiffType.Short, false, new byte[] { 255, 255, 2, 3 }, UInt16.MaxValue)]
[InlineDataAttribute((ushort)TiffType.Long, true, new byte[] { 0, 0, 0, 0 }, 0)]
[InlineDataAttribute((ushort)TiffType.Long, true, new byte[] { 1, 0, 0, 0 }, 1)]
[InlineDataAttribute((ushort)TiffType.Long, true, new byte[] { 0, 1, 0, 0 }, 256)]
[InlineDataAttribute((ushort)TiffType.Long, true, new byte[] { 0, 0, 1, 0 }, 256 * 256)]
[InlineDataAttribute((ushort)TiffType.Long, true, new byte[] { 0, 0, 0, 1 }, 256 * 256 * 256)]
[InlineDataAttribute((ushort)TiffType.Long, true, new byte[] { 1, 2, 3, 4 }, 67305985)]
[InlineDataAttribute((ushort)TiffType.Long, true, new byte[] { 255, 255, 255, 255 }, UInt32.MaxValue)]
[InlineDataAttribute((ushort)TiffType.Long, false, new byte[] { 0, 0, 0, 0 }, 0)]
[InlineDataAttribute((ushort)TiffType.Long, false, new byte[] { 0, 0, 0, 1 }, 1)]
[InlineDataAttribute((ushort)TiffType.Long, false, new byte[] { 0, 0, 1, 0 }, 256)]
[InlineDataAttribute((ushort)TiffType.Long, false, new byte[] { 0, 1, 0, 0 }, 256 * 256)]
[InlineDataAttribute((ushort)TiffType.Long, false, new byte[] { 1, 0, 0, 0 }, 256 * 256 * 256)]
[InlineDataAttribute((ushort)TiffType.Long, false, new byte[] { 4, 3, 2, 1 }, 67305985)]
[InlineDataAttribute((ushort)TiffType.Long, false, new byte[] { 255, 255, 255, 255 }, UInt32.MaxValue)]
public void ReadUnsignedInteger_ReturnsValue(ushort type, bool isLittleEndian, byte[] bytes, uint expectedValue)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, bytes), isLittleEndian);
uint result = decoder.ReadUnsignedInteger(ref entry);
Assert.Equal(expectedValue, result);
}
[Theory]
[InlineDataAttribute((ushort)TiffType.Ascii)]
[InlineDataAttribute((ushort)TiffType.Rational)]
[InlineDataAttribute((ushort)TiffType.SByte)]
[InlineDataAttribute((ushort)TiffType.Undefined)]
[InlineDataAttribute((ushort)TiffType.SShort)]
[InlineDataAttribute((ushort)TiffType.SLong)]
[InlineDataAttribute((ushort)TiffType.SRational)]
[InlineDataAttribute((ushort)TiffType.Float)]
[InlineDataAttribute((ushort)TiffType.Double)]
[InlineDataAttribute((ushort)TiffType.Ifd)]
[InlineDataAttribute((ushort)99)]
public void ReadUnsignedInteger_ThrowsExceptionIfInvalidType(ushort type)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true);
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadUnsignedInteger(ref entry));
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to an unsigned integer.", e.Message);
}
[Theory]
[InlineDataAttribute((ushort)TiffType.Byte, true)]
[InlineDataAttribute((ushort)TiffType.Short, true)]
[InlineDataAttribute((ushort)TiffType.Long, true)]
[InlineDataAttribute((ushort)TiffType.Byte, false)]
[InlineDataAttribute((ushort)TiffType.Short, false)]
[InlineDataAttribute((ushort)TiffType.Long, false)]
public void ReadUnsignedInteger_ThrowsExceptionIfCountIsNotOne(ushort type, bool isLittleEndian)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 2, new byte[4]), isLittleEndian);
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadUnsignedInteger(ref entry));
Assert.Equal($"Cannot read a single value from an array of multiple items.", e.Message);
}
[Theory]
[InlineDataAttribute((ushort)TiffType.SByte, true, new byte[] { 0, 1, 2, 3 }, 0)]
[InlineDataAttribute((ushort)TiffType.SByte, true, new byte[] { 1, 2, 3, 4 }, 1)]
[InlineDataAttribute((ushort)TiffType.SByte, true, new byte[] { 255, 2, 3, 4 }, -1)]
[InlineDataAttribute((ushort)TiffType.SByte, false, new byte[] { 0, 1, 2, 3 }, 0)]
[InlineDataAttribute((ushort)TiffType.SByte, false, new byte[] { 1, 2, 3, 4 }, 1)]
[InlineDataAttribute((ushort)TiffType.SByte, false, new byte[] { 255, 2, 3, 4 }, -1)]
[InlineDataAttribute((ushort)TiffType.SShort, true, new byte[] { 0, 0, 2, 3 }, 0)]
[InlineDataAttribute((ushort)TiffType.SShort, true, new byte[] { 1, 0, 2, 3 }, 1)]
[InlineDataAttribute((ushort)TiffType.SShort, true, new byte[] { 0, 1, 2, 3 }, 256)]
[InlineDataAttribute((ushort)TiffType.SShort, true, new byte[] { 2, 1, 2, 3 }, 258)]
[InlineDataAttribute((ushort)TiffType.SShort, true, new byte[] { 255, 255, 2, 3 }, -1)]
[InlineDataAttribute((ushort)TiffType.SShort, false, new byte[] { 0, 0, 2, 3 }, 0)]
[InlineDataAttribute((ushort)TiffType.SShort, false, new byte[] { 0, 1, 2, 3 }, 1)]
[InlineDataAttribute((ushort)TiffType.SShort, false, new byte[] { 1, 0, 2, 3 }, 256)]
[InlineDataAttribute((ushort)TiffType.SShort, false, new byte[] { 1, 2, 2, 3 }, 258)]
[InlineDataAttribute((ushort)TiffType.SShort, false, new byte[] { 255, 255, 2, 3 }, -1)]
[InlineDataAttribute((ushort)TiffType.SLong, true, new byte[] { 0, 0, 0, 0 }, 0)]
[InlineDataAttribute((ushort)TiffType.SLong, true, new byte[] { 1, 0, 0, 0 }, 1)]
[InlineDataAttribute((ushort)TiffType.SLong, true, new byte[] { 0, 1, 0, 0 }, 256)]
[InlineDataAttribute((ushort)TiffType.SLong, true, new byte[] { 0, 0, 1, 0 }, 256 * 256)]
[InlineDataAttribute((ushort)TiffType.SLong, true, new byte[] { 0, 0, 0, 1 }, 256 * 256 * 256)]
[InlineDataAttribute((ushort)TiffType.SLong, true, new byte[] { 1, 2, 3, 4 }, 67305985)]
[InlineDataAttribute((ushort)TiffType.SLong, true, new byte[] { 255, 255, 255, 255 }, -1)]
[InlineDataAttribute((ushort)TiffType.SLong, false, new byte[] { 0, 0, 0, 0 }, 0)]
[InlineDataAttribute((ushort)TiffType.SLong, false, new byte[] { 0, 0, 0, 1 }, 1)]
[InlineDataAttribute((ushort)TiffType.SLong, false, new byte[] { 0, 0, 1, 0 }, 256)]
[InlineDataAttribute((ushort)TiffType.SLong, false, new byte[] { 0, 1, 0, 0 }, 256 * 256)]
[InlineDataAttribute((ushort)TiffType.SLong, false, new byte[] { 1, 0, 0, 0 }, 256 * 256 * 256)]
[InlineDataAttribute((ushort)TiffType.SLong, false, new byte[] { 4, 3, 2, 1 }, 67305985)]
[InlineDataAttribute((ushort)TiffType.SLong, false, new byte[] { 255, 255, 255, 255 }, -1)]
public void ReadSignedInteger_ReturnsValue(ushort type, bool isLittleEndian, byte[] bytes, int expectedValue)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, bytes), isLittleEndian);
int result = decoder.ReadSignedInteger(ref entry);
Assert.Equal(expectedValue, result);
}
[Theory]
[InlineDataAttribute((ushort)TiffType.Byte)]
[InlineDataAttribute((ushort)TiffType.Ascii)]
[InlineDataAttribute((ushort)TiffType.Short)]
[InlineDataAttribute((ushort)TiffType.Long)]
[InlineDataAttribute((ushort)TiffType.Rational)]
[InlineDataAttribute((ushort)TiffType.Undefined)]
[InlineDataAttribute((ushort)TiffType.SRational)]
[InlineDataAttribute((ushort)TiffType.Float)]
[InlineDataAttribute((ushort)TiffType.Double)]
[InlineDataAttribute((ushort)TiffType.Ifd)]
[InlineDataAttribute((ushort)99)]
public void ReadSignedInteger_ThrowsExceptionIfInvalidType(ushort type)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true);
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadSignedInteger(ref entry));
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to a signed integer.", e.Message);
}
[Theory]
[InlineDataAttribute((ushort)TiffType.SByte, true)]
[InlineDataAttribute((ushort)TiffType.SShort, true)]
[InlineDataAttribute((ushort)TiffType.SLong, true)]
[InlineDataAttribute((ushort)TiffType.SByte, false)]
[InlineDataAttribute((ushort)TiffType.SShort, false)]
[InlineDataAttribute((ushort)TiffType.SLong, false)]
public void ReadSignedInteger_ThrowsExceptionIfCountIsNotOne(ushort type, bool isLittleEndian)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 2, new byte[4]), isLittleEndian);
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadSignedInteger(ref entry));
Assert.Equal($"Cannot read a single value from an array of multiple items.", e.Message);
}
[Theory]
[InlineDataAttribute((ushort)TiffType.Byte, 1, true, new byte[] { 0, 1, 2, 3 }, new uint[] { 0 })]
[InlineDataAttribute((ushort)TiffType.Byte, 3, true, new byte[] { 0, 1, 2, 3 }, new uint[] { 0, 1, 2 })]
[InlineDataAttribute((ushort)TiffType.Byte, 7, true, new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 }, new uint[] { 0, 1, 2, 3, 4, 5, 6 })]
[InlineDataAttribute((ushort)TiffType.Byte, 1, false, new byte[] { 0, 1, 2, 3 }, new uint[] { 0 })]
[InlineDataAttribute((ushort)TiffType.Byte, 3, false, new byte[] { 0, 1, 2, 3 }, new uint[] { 0, 1, 2 })]
[InlineDataAttribute((ushort)TiffType.Byte, 7, false, new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 }, new uint[] { 0, 1, 2, 3, 4, 5, 6 })]
[InlineDataAttribute((ushort)TiffType.Short, 1, true, new byte[] { 1, 0, 3, 2 }, new uint[] { 1 })]
[InlineDataAttribute((ushort)TiffType.Short, 2, true, new byte[] { 1, 0, 3, 2 }, new uint[] { 1, 515 })]
[InlineDataAttribute((ushort)TiffType.Short, 3, true, new byte[] { 1, 0, 3, 2, 5, 4, 6, 7, 8 }, new uint[] { 1, 515, 1029 })]
[InlineDataAttribute((ushort)TiffType.Short, 1, false, new byte[] { 0, 1, 2, 3 }, new uint[] { 1 })]
[InlineDataAttribute((ushort)TiffType.Short, 2, false, new byte[] { 0, 1, 2, 3 }, new uint[] { 1, 515 })]
[InlineDataAttribute((ushort)TiffType.Short, 3, false, new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8 }, new uint[] { 1, 515, 1029 })]
[InlineDataAttribute((ushort)TiffType.Long, 1, true, new byte[] { 4, 3, 2, 1 }, new uint[] { 0x01020304 })]
[InlineDataAttribute((ushort)TiffType.Long, 2, true, new byte[] { 4, 3, 2, 1, 6, 5, 4, 3, 99, 99 }, new uint[] { 0x01020304, 0x03040506 })]
[InlineDataAttribute((ushort)TiffType.Long, 1, false, new byte[] { 1, 2, 3, 4 }, new uint[] { 0x01020304 })]
[InlineDataAttribute((ushort)TiffType.Long, 2, false, new byte[] { 1, 2, 3, 4, 3, 4, 5, 6, 99, 99 }, new uint[] { 0x01020304, 0x03040506 })]
public void ReadUnsignedIntegerArray_ReturnsValue(ushort type, int count, bool isLittleEndian, byte[] bytes, uint[] expectedValue)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, (uint)expectedValue.Length, bytes), isLittleEndian);
uint[] result = decoder.ReadUnsignedIntegerArray(ref entry);
Assert.Equal(expectedValue, result);
}
[Theory]
[InlineDataAttribute((ushort)TiffType.Ascii)]
[InlineDataAttribute((ushort)TiffType.Rational)]
[InlineDataAttribute((ushort)TiffType.SByte)]
[InlineDataAttribute((ushort)TiffType.Undefined)]
[InlineDataAttribute((ushort)TiffType.SShort)]
[InlineDataAttribute((ushort)TiffType.SLong)]
[InlineDataAttribute((ushort)TiffType.SRational)]
[InlineDataAttribute((ushort)TiffType.Float)]
[InlineDataAttribute((ushort)TiffType.Double)]
[InlineDataAttribute((ushort)TiffType.Ifd)]
[InlineDataAttribute((ushort)99)]
public void ReadUnsignedIntegerArray_ThrowsExceptionIfInvalidType(ushort type)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true);
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadUnsignedIntegerArray(ref entry));
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to an unsigned integer.", e.Message);
}
[Theory]
[InlineDataAttribute((ushort)TiffType.SByte, 1, true, new byte[] { 0, 1, 2, 3 }, new int[] { 0 })]
[InlineDataAttribute((ushort)TiffType.SByte, 3, true, new byte[] { 0, 255, 2, 3 }, new int[] { 0, -1, 2 })]
[InlineDataAttribute((ushort)TiffType.SByte, 7, true, new byte[] { 0, 255, 2, 3, 4, 5, 6, 7, 8 }, new int[] { 0, -1, 2, 3, 4, 5, 6 })]
[InlineDataAttribute((ushort)TiffType.SByte, 1, false, new byte[] { 0, 1, 2, 3 }, new int[] { 0 })]
[InlineDataAttribute((ushort)TiffType.SByte, 3, false, new byte[] { 0, 255, 2, 3 }, new int[] { 0, -1, 2 })]
[InlineDataAttribute((ushort)TiffType.SByte, 7, false, new byte[] { 0, 255, 2, 3, 4, 5, 6, 7, 8 }, new int[] { 0, -1, 2, 3, 4, 5, 6 })]
[InlineDataAttribute((ushort)TiffType.SShort, 1, true, new byte[] { 1, 0, 3, 2 }, new int[] { 1 })]
[InlineDataAttribute((ushort)TiffType.SShort, 2, true, new byte[] { 1, 0, 255, 255 }, new int[] { 1, -1 })]
[InlineDataAttribute((ushort)TiffType.SShort, 3, true, new byte[] { 1, 0, 255, 255, 5, 4, 6, 7, 8 }, new int[] { 1, -1, 1029 })]
[InlineDataAttribute((ushort)TiffType.SShort, 1, false, new byte[] { 0, 1, 2, 3 }, new int[] { 1 })]
[InlineDataAttribute((ushort)TiffType.SShort, 2, false, new byte[] { 0, 1, 255, 255 }, new int[] { 1, -1 })]
[InlineDataAttribute((ushort)TiffType.SShort, 3, false, new byte[] { 0, 1, 255, 255, 4, 5, 6, 7, 8 }, new int[] { 1, -1, 1029 })]
[InlineDataAttribute((ushort)TiffType.SLong, 1, true, new byte[] { 4, 3, 2, 1 }, new int[] { 0x01020304 })]
[InlineDataAttribute((ushort)TiffType.SLong, 2, true, new byte[] { 4, 3, 2, 1, 255, 255, 255, 255, 99, 99 }, new int[] { 0x01020304, -1 })]
[InlineDataAttribute((ushort)TiffType.SLong, 1, false, new byte[] { 1, 2, 3, 4 }, new int[] { 0x01020304 })]
[InlineDataAttribute((ushort)TiffType.SLong, 2, false, new byte[] { 1, 2, 3, 4, 255, 255, 255, 255, 99, 99 }, new int[] { 0x01020304, -1 })]
public void ReadSignedIntegerArray_ReturnsValue(ushort type, int count, bool isLittleEndian, byte[] bytes, int[] expectedValue)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, (uint)expectedValue.Length, bytes), isLittleEndian);
int[] result = decoder.ReadSignedIntegerArray(ref entry);
Assert.Equal(expectedValue, result);
}
[Theory]
[InlineDataAttribute((ushort)TiffType.Byte)]
[InlineDataAttribute((ushort)TiffType.Ascii)]
[InlineDataAttribute((ushort)TiffType.Short)]
[InlineDataAttribute((ushort)TiffType.Long)]
[InlineDataAttribute((ushort)TiffType.Rational)]
[InlineDataAttribute((ushort)TiffType.Undefined)]
[InlineDataAttribute((ushort)TiffType.SRational)]
[InlineDataAttribute((ushort)TiffType.Float)]
[InlineDataAttribute((ushort)TiffType.Double)]
[InlineDataAttribute((ushort)TiffType.Ifd)]
[InlineDataAttribute((ushort)99)]
public void ReadSignedIntegerArray_ThrowsExceptionIfInvalidType(ushort type)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true);
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadSignedIntegerArray(ref entry));
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to a signed integer.", e.Message);
}
[Theory]
[InlineDataAttribute(true, new byte[] { 0 }, "")]
[InlineDataAttribute(true, new byte[] { (byte)'A', (byte)'B', (byte)'C', 0 }, "ABC")]
[InlineDataAttribute(true, new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', 0 }, "ABCDEF")]
[InlineDataAttribute(true, new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', 0, (byte)'E', (byte)'F', (byte)'G', (byte)'H', 0 }, "ABCD\0EFGH")]
[InlineDataAttribute(false, new byte[] { 0 }, "")]
[InlineDataAttribute(false, new byte[] { (byte)'A', (byte)'B', (byte)'C', 0 }, "ABC")]
[InlineDataAttribute(false, new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', 0 }, "ABCDEF")]
[InlineDataAttribute(false, new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', 0, (byte)'E', (byte)'F', (byte)'G', (byte)'H', 0 }, "ABCD\0EFGH")]
public void ReadString_ReturnsValue(bool isLittleEndian, byte[] bytes, string expectedValue)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.Ascii, (uint)bytes.Length, bytes), isLittleEndian);
string result = decoder.ReadString(ref entry);
Assert.Equal(expectedValue, result);
}
[Theory]
[InlineDataAttribute((ushort)TiffType.Byte)]
[InlineDataAttribute((ushort)TiffType.Short)]
[InlineDataAttribute((ushort)TiffType.Long)]
[InlineDataAttribute((ushort)TiffType.Rational)]
[InlineDataAttribute((ushort)TiffType.SByte)]
[InlineDataAttribute((ushort)TiffType.Undefined)]
[InlineDataAttribute((ushort)TiffType.SShort)]
[InlineDataAttribute((ushort)TiffType.SLong)]
[InlineDataAttribute((ushort)TiffType.SRational)]
[InlineDataAttribute((ushort)TiffType.Float)]
[InlineDataAttribute((ushort)TiffType.Double)]
[InlineDataAttribute((ushort)TiffType.Ifd)]
[InlineDataAttribute((ushort)99)]
public void ReadString_ThrowsExceptionIfInvalidType(ushort type)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true);
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadString(ref entry));
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to a string.", e.Message);
}
[Theory]
[InlineDataAttribute(true, new byte[] { (byte)'A' })]
[InlineDataAttribute(true, new byte[] { (byte)'A', (byte)'B', (byte)'C' })]
[InlineDataAttribute(true, new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F' })]
[InlineDataAttribute(true, new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', 0, (byte)'E', (byte)'F', (byte)'G', (byte)'H' })]
[InlineDataAttribute(false, new byte[] { (byte)'A' })]
[InlineDataAttribute(false, new byte[] { (byte)'A', (byte)'B', (byte)'C' })]
[InlineDataAttribute(false, new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F' })]
[InlineDataAttribute(false, new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', 0, (byte)'E', (byte)'F', (byte)'G', (byte)'H' })]
public void ReadString_ThrowsExceptionIfStringIsNotNullTerminated(bool isLittleEndian, byte[] bytes)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.Ascii, (uint)bytes.Length, bytes), isLittleEndian);
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadString(ref entry));
Assert.Equal($"The retrieved string is not null terminated.", e.Message);
}
[Theory]
[InlineDataAttribute(true, new byte[] { 0, 0, 0, 0, 2, 0, 0, 0 }, 0, 2)]
[InlineDataAttribute(true, new byte[] { 1, 0, 0, 0, 2, 0, 0, 0 }, 1, 2)]
[InlineDataAttribute(false, new byte[] { 0, 0, 0, 0, 0, 0, 0, 2 }, 0, 2)]
[InlineDataAttribute(false, new byte[] { 0, 0, 0, 1, 0, 0, 0, 2 }, 1, 2)]
public void ReadUnsignedRational_ReturnsValue(bool isLittleEndian, byte[] bytes, uint expectedNumerator, uint expectedDenominator)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.Rational, 1, bytes), isLittleEndian);
Rational result = decoder.ReadUnsignedRational(ref entry);
Rational expectedValue = new Rational(expectedNumerator, expectedDenominator);
Assert.Equal(expectedValue, result);
}
[Theory]
[InlineDataAttribute(true, new byte[] { 0, 0, 0, 0, 2, 0, 0, 0 }, 0, 2)]
[InlineDataAttribute(true, new byte[] { 1, 0, 0, 0, 2, 0, 0, 0 }, 1, 2)]
[InlineDataAttribute(true, new byte[] { 255, 255, 255, 255, 2, 0, 0, 0 }, -1, 2)]
[InlineDataAttribute(false, new byte[] { 0, 0, 0, 0, 0, 0, 0, 2 }, 0, 2)]
[InlineDataAttribute(false, new byte[] { 0, 0, 0, 1, 0, 0, 0, 2 }, 1, 2)]
[InlineDataAttribute(false, new byte[] { 255, 255, 255, 255, 0, 0, 0, 2 }, -1, 2)]
public void ReadSignedRational_ReturnsValue(bool isLittleEndian, byte[] bytes, int expectedNumerator, int expectedDenominator)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.SRational, 1, bytes), isLittleEndian);
SignedRational result = decoder.ReadSignedRational(ref entry);
SignedRational expectedValue = new SignedRational(expectedNumerator, expectedDenominator);
Assert.Equal(expectedValue, result);
}
[Theory]
[InlineDataAttribute(true, new byte[] { 0, 0, 0, 0, 2, 0, 0, 0 }, new uint[] { 0 }, new uint[] { 2 })]
[InlineDataAttribute(true, new byte[] { 1, 0, 0, 0, 2, 0, 0, 0 }, new uint[] { 1 }, new uint[] { 2 })]
[InlineDataAttribute(true, new byte[] { 1, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0 }, new uint[] { 1, 2 }, new uint[] { 2, 3 })]
[InlineDataAttribute(false, new byte[] { 0, 0, 0, 0, 0, 0, 0, 2 }, new uint[] { 0 }, new uint[] { 2 })]
[InlineDataAttribute(false, new byte[] { 0, 0, 0, 1, 0, 0, 0, 2 }, new uint[] { 1 }, new uint[] { 2 })]
[InlineDataAttribute(false, new byte[] { 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 3 }, new uint[] { 1, 2 }, new uint[] { 2, 3 })]
public void ReadUnsignedRationalArray_ReturnsValue(bool isLittleEndian, byte[] bytes, uint[] expectedNumerators, uint[] expectedDenominators)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.Rational, (uint)expectedNumerators.Length, bytes), isLittleEndian);
Rational[] result = decoder.ReadUnsignedRationalArray(ref entry);
Rational[] expectedValue = Enumerable.Range(0, expectedNumerators.Length).Select(i => new Rational(expectedNumerators[i], expectedDenominators[i])).ToArray();
Assert.Equal(expectedValue, result);
}
[Theory]
[InlineDataAttribute(true, new byte[] { 0, 0, 0, 0, 2, 0, 0, 0 }, new int[] { 0 }, new int[] { 2 })]
[InlineDataAttribute(true, new byte[] { 1, 0, 0, 0, 2, 0, 0, 0 }, new int[] { 1 }, new int[] { 2 })]
[InlineDataAttribute(true, new byte[] { 255, 255, 255, 255, 2, 0, 0, 0 }, new int[] { -1 }, new int[] { 2 })]
[InlineDataAttribute(true, new byte[] { 255, 255, 255, 255, 2, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0 }, new int[] { -1, 2 }, new int[] { 2, 3 })]
[InlineDataAttribute(false, new byte[] { 0, 0, 0, 0, 0, 0, 0, 2 }, new int[] { 0 }, new int[] { 2 })]
[InlineDataAttribute(false, new byte[] { 0, 0, 0, 1, 0, 0, 0, 2 }, new int[] { 1 }, new int[] { 2 })]
[InlineDataAttribute(false, new byte[] { 255, 255, 255, 255, 0, 0, 0, 2 }, new int[] { -1 }, new int[] { 2 })]
[InlineDataAttribute(false, new byte[] { 255, 255, 255, 255, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 3 }, new int[] { -1, 2 }, new int[] { 2, 3 })]
public void ReadSignedRationalArray_ReturnsValue(bool isLittleEndian, byte[] bytes, int[] expectedNumerators, int[] expectedDenominators)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.SRational, (uint)expectedNumerators.Length, bytes), isLittleEndian);
SignedRational[] result = decoder.ReadSignedRationalArray(ref entry);
SignedRational[] expectedValue = Enumerable.Range(0, expectedNumerators.Length).Select(i => new SignedRational(expectedNumerators[i], expectedDenominators[i])).ToArray();
Assert.Equal(expectedValue, result);
}
[Theory]
[InlineDataAttribute((ushort)TiffType.Byte)]
[InlineDataAttribute((ushort)TiffType.Ascii)]
[InlineDataAttribute((ushort)TiffType.Short)]
[InlineDataAttribute((ushort)TiffType.Long)]
[InlineDataAttribute((ushort)TiffType.SByte)]
[InlineDataAttribute((ushort)TiffType.Undefined)]
[InlineDataAttribute((ushort)TiffType.SShort)]
[InlineDataAttribute((ushort)TiffType.SLong)]
[InlineDataAttribute((ushort)TiffType.SRational)]
[InlineDataAttribute((ushort)TiffType.Float)]
[InlineDataAttribute((ushort)TiffType.Double)]
[InlineDataAttribute((ushort)TiffType.Ifd)]
[InlineDataAttribute((ushort)99)]
public void ReadUnsignedRational_ThrowsExceptionIfInvalidType(ushort type)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true);
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadUnsignedRational(ref entry));
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to a Rational.", e.Message);
}
[Theory]
[InlineDataAttribute((ushort)TiffType.Byte)]
[InlineDataAttribute((ushort)TiffType.Ascii)]
[InlineDataAttribute((ushort)TiffType.Short)]
[InlineDataAttribute((ushort)TiffType.Long)]
[InlineDataAttribute((ushort)TiffType.SByte)]
[InlineDataAttribute((ushort)TiffType.Rational)]
[InlineDataAttribute((ushort)TiffType.Undefined)]
[InlineDataAttribute((ushort)TiffType.SShort)]
[InlineDataAttribute((ushort)TiffType.SLong)]
[InlineDataAttribute((ushort)TiffType.Float)]
[InlineDataAttribute((ushort)TiffType.Double)]
[InlineDataAttribute((ushort)TiffType.Ifd)]
[InlineDataAttribute((ushort)99)]
public void ReadSignedRational_ThrowsExceptionIfInvalidType(ushort type)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true);
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadSignedRational(ref entry));
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to a SignedRational.", e.Message);
}
[Theory]
[InlineDataAttribute((ushort)TiffType.Byte)]
[InlineDataAttribute((ushort)TiffType.Ascii)]
[InlineDataAttribute((ushort)TiffType.Short)]
[InlineDataAttribute((ushort)TiffType.Long)]
[InlineDataAttribute((ushort)TiffType.SByte)]
[InlineDataAttribute((ushort)TiffType.Undefined)]
[InlineDataAttribute((ushort)TiffType.SShort)]
[InlineDataAttribute((ushort)TiffType.SLong)]
[InlineDataAttribute((ushort)TiffType.SRational)]
[InlineDataAttribute((ushort)TiffType.Float)]
[InlineDataAttribute((ushort)TiffType.Double)]
[InlineDataAttribute((ushort)TiffType.Ifd)]
[InlineDataAttribute((ushort)99)]
public void ReadUnsignedRationalArray_ThrowsExceptionIfInvalidType(ushort type)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true);
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadUnsignedRationalArray(ref entry));
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to a Rational.", e.Message);
}
[Theory]
[InlineDataAttribute((ushort)TiffType.Byte)]
[InlineDataAttribute((ushort)TiffType.Ascii)]
[InlineDataAttribute((ushort)TiffType.Short)]
[InlineDataAttribute((ushort)TiffType.Long)]
[InlineDataAttribute((ushort)TiffType.Rational)]
[InlineDataAttribute((ushort)TiffType.SByte)]
[InlineDataAttribute((ushort)TiffType.Undefined)]
[InlineDataAttribute((ushort)TiffType.SShort)]
[InlineDataAttribute((ushort)TiffType.SLong)]
[InlineDataAttribute((ushort)TiffType.Float)]
[InlineDataAttribute((ushort)TiffType.Double)]
[InlineDataAttribute((ushort)TiffType.Ifd)]
[InlineDataAttribute((ushort)99)]
public void ReadSignedRationalArray_ThrowsExceptionIfInvalidType(ushort type)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true);
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadSignedRationalArray(ref entry));
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to a SignedRational.", e.Message);
}
[Theory]
[InlineDataAttribute(false)]
[InlineDataAttribute(true)]
public void ReadUnsignedRational_ThrowsExceptionIfCountIsNotOne(bool isLittleEndian)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.Rational, 2, new byte[4]), isLittleEndian);
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadUnsignedRational(ref entry));
Assert.Equal($"Cannot read a single value from an array of multiple items.", e.Message);
}
[Theory]
[InlineDataAttribute(false)]
[InlineDataAttribute(true)]
public void ReadSignedRational_ThrowsExceptionIfCountIsNotOne(bool isLittleEndian)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.SRational, 2, new byte[4]), isLittleEndian);
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadSignedRational(ref entry));
Assert.Equal($"Cannot read a single value from an array of multiple items.", e.Message);
}
[Theory]
[InlineDataAttribute(false, new byte[] { 0x00, 0x00, 0x00, 0x00 }, 0.0F)]
[InlineDataAttribute(false, new byte[] { 0x3F, 0x80, 0x00, 0x00 }, 1.0F)]
[InlineDataAttribute(false, new byte[] { 0xC0, 0x00, 0x00, 0x00 }, -2.0F)]
[InlineDataAttribute(false, new byte[] { 0x7F, 0x7F, 0xFF, 0xFF }, float.MaxValue)]
[InlineDataAttribute(false, new byte[] { 0x7F, 0x80, 0x00, 0x00 }, float.PositiveInfinity)]
[InlineDataAttribute(false, new byte[] { 0xFF, 0x80, 0x00, 0x00 }, float.NegativeInfinity)]
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00 }, 0.0F)]
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x80, 0x3F }, 1.0F)]
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0xC0 }, -2.0F)]
[InlineDataAttribute(true, new byte[] { 0xFF, 0xFF, 0x7F, 0x7F }, float.MaxValue)]
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x80, 0x7F }, float.PositiveInfinity)]
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x80, 0xFF }, float.NegativeInfinity)]
public void ReadFloat_ReturnsValue(bool isLittleEndian, byte[] bytes, float expectedValue)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.Float, 1, bytes), isLittleEndian);
float result = decoder.ReadFloat(ref entry);
Assert.Equal(expectedValue, result);
}
[Theory]
[InlineDataAttribute((ushort)TiffType.Byte)]
[InlineDataAttribute((ushort)TiffType.Ascii)]
[InlineDataAttribute((ushort)TiffType.Short)]
[InlineDataAttribute((ushort)TiffType.Long)]
[InlineDataAttribute((ushort)TiffType.Rational)]
[InlineDataAttribute((ushort)TiffType.SByte)]
[InlineDataAttribute((ushort)TiffType.Undefined)]
[InlineDataAttribute((ushort)TiffType.SShort)]
[InlineDataAttribute((ushort)TiffType.SLong)]
[InlineDataAttribute((ushort)TiffType.SRational)]
[InlineDataAttribute((ushort)TiffType.Double)]
[InlineDataAttribute((ushort)TiffType.Ifd)]
[InlineDataAttribute((ushort)99)]
public void ReadFloat_ThrowsExceptionIfInvalidType(ushort type)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true);
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadFloat(ref entry));
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to a float.", e.Message);
}
[Theory]
[InlineDataAttribute(false)]
[InlineDataAttribute(true)]
public void ReadFloat_ThrowsExceptionIfCountIsNotOne(bool isLittleEndian)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.Float, 2, new byte[4]), isLittleEndian);
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadFloat(ref entry));
Assert.Equal($"Cannot read a single value from an array of multiple items.", e.Message);
}
[Theory]
[InlineDataAttribute(false, new byte[] { 0x00, 0x00, 0x00, 0x00 }, new float[] { 0.0F })]
[InlineDataAttribute(false, new byte[] { 0x3F, 0x80, 0x00, 0x00 }, new float[] { 1.0F })]
[InlineDataAttribute(false, new byte[] { 0xC0, 0x00, 0x00, 0x00 }, new float[] { -2.0F })]
[InlineDataAttribute(false, new byte[] { 0x7F, 0x7F, 0xFF, 0xFF }, new float[] { float.MaxValue })]
[InlineDataAttribute(false, new byte[] { 0x7F, 0x80, 0x00, 0x00 }, new float[] { float.PositiveInfinity })]
[InlineDataAttribute(false, new byte[] { 0xFF, 0x80, 0x00, 0x00 }, new float[] { float.NegativeInfinity })]
[InlineDataAttribute(false, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x3F, 0x80, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00 }, new float[] { 0.0F, 1.0F, -2.0F })]
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00 }, new float[] { 0.0F })]
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x80, 0x3F }, new float[] { 1.0F })]
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0xC0 }, new float[] { -2.0F })]
[InlineDataAttribute(true, new byte[] { 0xFF, 0xFF, 0x7F, 0x7F }, new float[] { float.MaxValue })]
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x80, 0x7F }, new float[] { float.PositiveInfinity })]
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x80, 0xFF }, new float[] { float.NegativeInfinity })]
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0xC0 }, new float[] { 0.0F, 1.0F, -2.0F })]
public void ReadFloatArray_ReturnsValue(bool isLittleEndian, byte[] bytes, float[] expectedValue)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.Float, (uint)expectedValue.Length, bytes), isLittleEndian);
float[] result = decoder.ReadFloatArray(ref entry);
Assert.Equal(expectedValue, result);
}
[Theory]
[InlineDataAttribute((ushort)TiffType.Byte)]
[InlineDataAttribute((ushort)TiffType.Ascii)]
[InlineDataAttribute((ushort)TiffType.Short)]
[InlineDataAttribute((ushort)TiffType.Long)]
[InlineDataAttribute((ushort)TiffType.Rational)]
[InlineDataAttribute((ushort)TiffType.SByte)]
[InlineDataAttribute((ushort)TiffType.Undefined)]
[InlineDataAttribute((ushort)TiffType.SShort)]
[InlineDataAttribute((ushort)TiffType.SLong)]
[InlineDataAttribute((ushort)TiffType.SRational)]
[InlineDataAttribute((ushort)TiffType.Double)]
[InlineDataAttribute((ushort)TiffType.Ifd)]
[InlineDataAttribute((ushort)99)]
public void ReadFloatArray_ThrowsExceptionIfInvalidType(ushort type)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true);
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadFloatArray(ref entry));
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to a float.", e.Message);
}
[Theory]
[InlineDataAttribute(false, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 0.0)]
[InlineDataAttribute(false, new byte[] { 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 1.0)]
[InlineDataAttribute(false, new byte[] { 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 2.0)]
[InlineDataAttribute(false, new byte[] { 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, -2.0)]
[InlineDataAttribute(false, new byte[] { 0x7F, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, double.MaxValue)]
[InlineDataAttribute(false, new byte[] { 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, double.PositiveInfinity)]
[InlineDataAttribute(false, new byte[] { 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, double.NegativeInfinity)]
[InlineDataAttribute(false, new byte[] { 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, double.NaN)]
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 0.0)]
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F }, 1.0)]
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40 }, 2.0)]
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0 }, -2.0)]
[InlineDataAttribute(true, new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x7F }, double.MaxValue)]
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x7F }, double.PositiveInfinity)]
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF }, double.NegativeInfinity)]
[InlineDataAttribute(true, new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F }, double.NaN)]
public void ReadDouble_ReturnsValue(bool isLittleEndian, byte[] bytes, double expectedValue)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.Double, 1, bytes), isLittleEndian);
double result = decoder.ReadDouble(ref entry);
Assert.Equal(expectedValue, result);
}
[Theory]
[InlineDataAttribute((ushort)TiffType.Byte)]
[InlineDataAttribute((ushort)TiffType.Ascii)]
[InlineDataAttribute((ushort)TiffType.Short)]
[InlineDataAttribute((ushort)TiffType.Long)]
[InlineDataAttribute((ushort)TiffType.Rational)]
[InlineDataAttribute((ushort)TiffType.SByte)]
[InlineDataAttribute((ushort)TiffType.Undefined)]
[InlineDataAttribute((ushort)TiffType.SShort)]
[InlineDataAttribute((ushort)TiffType.SLong)]
[InlineDataAttribute((ushort)TiffType.SRational)]
[InlineDataAttribute((ushort)TiffType.Float)]
[InlineDataAttribute((ushort)TiffType.Ifd)]
[InlineDataAttribute((ushort)99)]
public void ReadDouble_ThrowsExceptionIfInvalidType(ushort type)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true);
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadDouble(ref entry));
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to a double.", e.Message);
}
[Theory]
[InlineDataAttribute(false)]
[InlineDataAttribute(true)]
public void ReadDouble_ThrowsExceptionIfCountIsNotOne(bool isLittleEndian)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.Double, 2, new byte[4]), isLittleEndian);
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadDouble(ref entry));
Assert.Equal($"Cannot read a single value from an array of multiple items.", e.Message);
}
[Theory]
[InlineDataAttribute(false, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { 0.0 })]
[InlineDataAttribute(false, new byte[] { 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { 1.0 })]
[InlineDataAttribute(false, new byte[] { 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { 2.0 })]
[InlineDataAttribute(false, new byte[] { 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { -2.0 })]
[InlineDataAttribute(false, new byte[] { 0x7F, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, new double[] { double.MaxValue })]
[InlineDataAttribute(false, new byte[] { 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { double.PositiveInfinity })]
[InlineDataAttribute(false, new byte[] { 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { double.NegativeInfinity })]
[InlineDataAttribute(false, new byte[] { 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, new double[] { double.NaN })]
[InlineDataAttribute(false, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { 0.0, 1.0, -2.0 })]
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { 0.0 })]
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F }, new double[] { 1.0 })]
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40 }, new double[] { 2.0 })]
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0 }, new double[] { -2.0 })]
[InlineDataAttribute(true, new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x7F }, new double[] { double.MaxValue })]
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x7F }, new double[] { double.PositiveInfinity })]
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF }, new double[] { double.NegativeInfinity })]
[InlineDataAttribute(true, new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F }, new double[] { double.NaN })]
[InlineDataAttribute(true, new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0 }, new double[] { 0.0, 1.0, -2.0 })]
public void ReadDoubleArray_ReturnsValue(bool isLittleEndian, byte[] bytes, double[] expectedValue)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.Double, (uint)expectedValue.Length, bytes), isLittleEndian);
double[] result = decoder.ReadDoubleArray(ref entry);
Assert.Equal(expectedValue, result);
}
[Theory]
[InlineDataAttribute((ushort)TiffType.Byte)]
[InlineDataAttribute((ushort)TiffType.Ascii)]
[InlineDataAttribute((ushort)TiffType.Short)]
[InlineDataAttribute((ushort)TiffType.Long)]
[InlineDataAttribute((ushort)TiffType.Rational)]
[InlineDataAttribute((ushort)TiffType.SByte)]
[InlineDataAttribute((ushort)TiffType.Undefined)]
[InlineDataAttribute((ushort)TiffType.SShort)]
[InlineDataAttribute((ushort)TiffType.SLong)]
[InlineDataAttribute((ushort)TiffType.SRational)]
[InlineDataAttribute((ushort)TiffType.Float)]
[InlineDataAttribute((ushort)TiffType.Ifd)]
[InlineDataAttribute((ushort)99)]
public void ReadDoubleArray_ThrowsExceptionIfInvalidType(ushort type)
{
(TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true);
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadDoubleArray(ref entry));
Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to a double.", e.Message);
}
private (TiffDecoderCore, TiffIfdEntry) GenerateTestIfdEntry(TiffGenEntry entry, bool isLittleEndian)
{
Stream stream = new TiffGenIfd()
{
Entries =
{
entry
}
}
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfdEntry ifdEntry = decoder.ReadIfd(0).Entries[0];
return (decoder, ifdEntry);
}
}
}

109
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderIfdTests.cs

@ -0,0 +1,109 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests
{
using System.IO;
using Xunit;
using ImageSharp.Formats;
using ImageSharp.Formats.Tiff;
public class TiffDecoderIfdTests
{
public static object[][] IsLittleEndianValues = new[] { new object[] { false },
new object[] { true } };
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void ReadIfd_ReadsNextIfdOffset_IfPresent(bool isLittleEndian)
{
Stream stream = new TiffGenIfd()
{
Entries =
{
TiffGenEntry.Integer(TiffTags.ImageWidth, TiffType.Long, 150)
},
NextIfd = new TiffGenIfd()
}
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
Assert.Equal(18u, ifd.NextIfdOffset);
}
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void ReadIfd_ReadsNextIfdOffset_ZeroIfLastIfd(bool isLittleEndian)
{
Stream stream = new TiffGenIfd()
{
Entries =
{
TiffGenEntry.Integer(TiffTags.ImageWidth, TiffType.Long, 150)
}
}
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
Assert.Equal(0u, ifd.NextIfdOffset);
}
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void ReadIfd_ReturnsCorrectNumberOfEntries(bool isLittleEndian)
{
Stream stream = new TiffGenIfd()
{
Entries =
{
TiffGenEntry.Integer(TiffTags.ImageWidth, TiffType.Long, 150),
TiffGenEntry.Integer(TiffTags.ImageLength, TiffType.Long, 210),
TiffGenEntry.Integer(TiffTags.Orientation, TiffType.Short, 1),
TiffGenEntry.Ascii(TiffTags.Artist, "Image Artist Name"),
TiffGenEntry.Ascii(TiffTags.HostComputer, "Host Computer Name")
},
NextIfd = new TiffGenIfd()
}
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
Assert.NotNull(ifd.Entries);
Assert.Equal(5, ifd.Entries.Length);
}
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void ReadIfd_ReadsRawTiffEntryData(bool isLittleEndian)
{
Stream stream = new TiffGenIfd()
{
Entries =
{
TiffGenEntry.Integer(TiffTags.ImageWidth, TiffType.Long, 150),
TiffGenEntry.Integer(TiffTags.ImageLength, TiffType.Long, 210),
TiffGenEntry.Integer(TiffTags.Orientation, TiffType.Short, 1)
},
NextIfd = new TiffGenIfd()
}
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
TiffIfdEntry entry = ifd.Entries[1];
byte[] expectedData = isLittleEndian ? new byte[] { 210, 0, 0, 0 } : new byte[] { 0, 0, 0, 210 };
Assert.Equal(TiffTags.ImageLength, entry.Tag);
Assert.Equal(TiffType.Long, entry.Type);
Assert.Equal(1u, entry.Count);
Assert.Equal(expectedData, entry.Value);
}
}
}

512
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderImageTests.cs

@ -0,0 +1,512 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests
{
using System;
using System.IO;
using Xunit;
using ImageSharp.Formats;
using ImageSharp.Formats.Tiff;
public class TiffDecoderImageTests
{
public const int ImageWidth = 200;
public const int ImageHeight = 150;
public static object[][] IsLittleEndianValues = new[] { new object[] { false },
new object[] { true } };
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void DecodeImage_SetsImageDimensions(bool isLittleEndian)
{
Stream stream = CreateTiffGenIfd()
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
Image<Rgba32> image = decoder.DecodeImage<Rgba32>(ifd);
Assert.Equal(ImageWidth, image.Width);
Assert.Equal(ImageHeight, image.Height);
}
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void DecodeImage_ThrowsException_WithMissingImageWidth(bool isLittleEndian)
{
Stream stream = CreateTiffGenIfd()
.WithoutEntry(TiffTags.ImageWidth)
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
var e = Assert.Throws<ImageFormatException>(() => decoder.DecodeImage<Rgba32>(ifd));
Assert.Equal("The TIFF IFD does not specify the image dimensions.", e.Message);
}
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void DecodeImage_ThrowsException_WithMissingImageLength(bool isLittleEndian)
{
Stream stream = CreateTiffGenIfd()
.WithoutEntry(TiffTags.ImageLength)
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
var e = Assert.Throws<ImageFormatException>(() => decoder.DecodeImage<Rgba32>(ifd));
Assert.Equal("The TIFF IFD does not specify the image dimensions.", e.Message);
}
[Theory]
[InlineData(false, (ushort)TiffCompression.None, (int)TiffCompressionType.None)]
[InlineData(true, (ushort)TiffCompression.None, (int)TiffCompressionType.None)]
[InlineData(false, (ushort)TiffCompression.PackBits, (int)TiffCompressionType.PackBits)]
[InlineData(true, (ushort)TiffCompression.PackBits, (int)TiffCompressionType.PackBits)]
[InlineData(false, (ushort)TiffCompression.Deflate, (int)TiffCompressionType.Deflate)]
[InlineData(true, (ushort)TiffCompression.Deflate, (int)TiffCompressionType.Deflate)]
[InlineData(false, (ushort)TiffCompression.OldDeflate, (int)TiffCompressionType.Deflate)]
[InlineData(true, (ushort)TiffCompression.OldDeflate, (int)TiffCompressionType.Deflate)]
[InlineData(false, (ushort)TiffCompression.Lzw, (int)TiffCompressionType.Lzw)]
[InlineData(true, (ushort)TiffCompression.Lzw, (int)TiffCompressionType.Lzw)]
public void ReadImageFormat_DeterminesCorrectCompressionImplementation(bool isLittleEndian, ushort compression, int compressionType)
{
Stream stream = CreateTiffGenIfd()
.WithEntry(TiffGenEntry.Integer(TiffTags.Compression, TiffType.Short, compression))
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
decoder.ReadImageFormat(ifd);
Assert.Equal((TiffCompressionType)compressionType, decoder.CompressionType);
}
[Theory]
[InlineData(false, (ushort)TiffCompression.Ccitt1D)]
[InlineData(false, (ushort)TiffCompression.CcittGroup3Fax)]
[InlineData(false, (ushort)TiffCompression.CcittGroup4Fax)]
[InlineData(false, (ushort)TiffCompression.ItuTRecT43)]
[InlineData(false, (ushort)TiffCompression.ItuTRecT82)]
[InlineData(false, (ushort)TiffCompression.Jpeg)]
[InlineData(false, (ushort)TiffCompression.OldJpeg)]
[InlineData(false, 999)]
[InlineData(true, (ushort)TiffCompression.Ccitt1D)]
[InlineData(true, (ushort)TiffCompression.CcittGroup3Fax)]
[InlineData(true, (ushort)TiffCompression.CcittGroup4Fax)]
[InlineData(true, (ushort)TiffCompression.ItuTRecT43)]
[InlineData(true, (ushort)TiffCompression.ItuTRecT82)]
[InlineData(true, (ushort)TiffCompression.Jpeg)]
[InlineData(true, (ushort)TiffCompression.OldJpeg)]
[InlineData(true, 999)]
public void ReadImageFormat_ThrowsExceptionForUnsupportedCompression(bool isLittleEndian, ushort compression)
{
Stream stream = CreateTiffGenIfd()
.WithEntry(TiffGenEntry.Integer(TiffTags.Compression, TiffType.Short, compression))
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
var e = Assert.Throws<NotSupportedException>(() => decoder.ReadImageFormat(ifd));
Assert.Equal("The specified TIFF compression format is not supported.", e.Message);
}
[Theory]
[InlineData(false, (ushort)TiffPhotometricInterpretation.WhiteIsZero, new[] { 3 }, (int)TiffColorType.WhiteIsZero)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.WhiteIsZero, new[] { 3 }, (int)TiffColorType.WhiteIsZero)]
[InlineData(false, (ushort)TiffPhotometricInterpretation.WhiteIsZero, new[] { 8 }, (int)TiffColorType.WhiteIsZero8)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.WhiteIsZero, new[] { 8 }, (int)TiffColorType.WhiteIsZero8)]
[InlineData(false, (ushort)TiffPhotometricInterpretation.WhiteIsZero, new[] { 4 }, (int)TiffColorType.WhiteIsZero4)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.WhiteIsZero, new[] { 4 }, (int)TiffColorType.WhiteIsZero4)]
[InlineData(false, (ushort)TiffPhotometricInterpretation.WhiteIsZero, new[] { 1 }, (int)TiffColorType.WhiteIsZero1)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.WhiteIsZero, new[] { 1 }, (int)TiffColorType.WhiteIsZero1)]
[InlineData(false, (ushort)TiffPhotometricInterpretation.BlackIsZero, new[] { 3 }, (int)TiffColorType.BlackIsZero)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.BlackIsZero, new[] { 3 }, (int)TiffColorType.BlackIsZero)]
[InlineData(false, (ushort)TiffPhotometricInterpretation.BlackIsZero, new[] { 8 }, (int)TiffColorType.BlackIsZero8)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.BlackIsZero, new[] { 8 }, (int)TiffColorType.BlackIsZero8)]
[InlineData(false, (ushort)TiffPhotometricInterpretation.BlackIsZero, new[] { 4 }, (int)TiffColorType.BlackIsZero4)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.BlackIsZero, new[] { 4 }, (int)TiffColorType.BlackIsZero4)]
[InlineData(false, (ushort)TiffPhotometricInterpretation.BlackIsZero, new[] { 1 }, (int)TiffColorType.BlackIsZero1)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.BlackIsZero, new[] { 1 }, (int)TiffColorType.BlackIsZero1)]
[InlineData(false, (ushort)TiffPhotometricInterpretation.PaletteColor, new[] { 3 }, (int)TiffColorType.PaletteColor)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.PaletteColor, new[] { 3 }, (int)TiffColorType.PaletteColor)]
[InlineData(false, (ushort)TiffPhotometricInterpretation.PaletteColor, new[] { 8 }, (int)TiffColorType.PaletteColor)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.PaletteColor, new[] { 8 }, (int)TiffColorType.PaletteColor)]
[InlineData(false, (ushort)TiffPhotometricInterpretation.PaletteColor, new[] { 4 }, (int)TiffColorType.PaletteColor)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.PaletteColor, new[] { 4 }, (int)TiffColorType.PaletteColor)]
[InlineData(false, (ushort)TiffPhotometricInterpretation.PaletteColor, new[] { 1 }, (int)TiffColorType.PaletteColor)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.PaletteColor, new[] { 1 }, (int)TiffColorType.PaletteColor)]
[InlineData(false, (ushort)TiffPhotometricInterpretation.Rgb, new[] { 4, 4, 4 }, (int)TiffColorType.Rgb)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.Rgb, new[] { 4, 4, 4 }, (int)TiffColorType.Rgb)]
[InlineData(false, (ushort)TiffPhotometricInterpretation.Rgb, new[] { 8, 8, 8 }, (int)TiffColorType.Rgb888)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.Rgb, new[] { 8, 8, 8 }, (int)TiffColorType.Rgb888)]
public void ReadImageFormat_DeterminesCorrectColorImplementation_Chunky(bool isLittleEndian, ushort photometricInterpretation, int[] bitsPerSample, int colorType)
{
Stream stream = CreateTiffGenIfd()
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, photometricInterpretation))
.WithEntry(TiffGenEntry.Integer(TiffTags.PlanarConfiguration, TiffType.Short, (int)TiffPlanarConfiguration.Chunky))
.WithEntry(TiffGenEntry.Integer(TiffTags.BitsPerSample, TiffType.Short, bitsPerSample))
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
decoder.ReadImageFormat(ifd);
Assert.Equal((TiffColorType)colorType, decoder.ColorType);
}
[Theory]
[InlineData(false, (ushort)TiffPhotometricInterpretation.Rgb, new[] { 4, 4, 4 }, (int)TiffColorType.RgbPlanar)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.Rgb, new[] { 4, 4, 4 }, (int)TiffColorType.RgbPlanar)]
[InlineData(false, (ushort)TiffPhotometricInterpretation.Rgb, new[] { 8, 8, 8 }, (int)TiffColorType.RgbPlanar)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.Rgb, new[] { 8, 8, 8 }, (int)TiffColorType.RgbPlanar)]
public void ReadImageFormat_DeterminesCorrectColorImplementation_Planar(bool isLittleEndian, ushort photometricInterpretation, int[] bitsPerSample, int colorType)
{
Stream stream = CreateTiffGenIfd()
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, photometricInterpretation))
.WithEntry(TiffGenEntry.Integer(TiffTags.PlanarConfiguration, TiffType.Short, (int)TiffPlanarConfiguration.Planar))
.WithEntry(TiffGenEntry.Integer(TiffTags.BitsPerSample, TiffType.Short, bitsPerSample))
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
decoder.ReadImageFormat(ifd);
Assert.Equal((TiffColorType)colorType, decoder.ColorType);
}
[Theory]
[InlineData(false, (ushort)TiffPhotometricInterpretation.WhiteIsZero, (int)TiffColorType.WhiteIsZero1)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.WhiteIsZero, (int)TiffColorType.WhiteIsZero1)]
[InlineData(false, (ushort)TiffPhotometricInterpretation.BlackIsZero, (int)TiffColorType.BlackIsZero1)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.BlackIsZero, (int)TiffColorType.BlackIsZero1)]
public void ReadImageFormat_DeterminesCorrectColorImplementation_DefaultsToBilevel(bool isLittleEndian, ushort photometricInterpretation, int colorType)
{
Stream stream = CreateTiffGenIfd()
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, photometricInterpretation))
.WithoutEntry(TiffTags.BitsPerSample)
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
decoder.ReadImageFormat(ifd);
Assert.Equal((TiffColorType)colorType, decoder.ColorType);
}
// [Theory]
// [InlineData(false, new[] { 8 }, (int)TiffColorType.WhiteIsZero8)]
// [InlineData(true, new[] { 8 }, (int)TiffColorType.WhiteIsZero8)]
// public void ReadImageFormat_UsesDefaultColorImplementationForCcitt1D(bool isLittleEndian, int[] bitsPerSample, int colorType)
// {
// Stream stream = CreateTiffGenIfd()
// .WithEntry(TiffGenEntry.Integer(TiffTags.Compression, TiffType.Short, (int)TiffCompression.Ccitt1D))
// .WithEntry(TiffGenEntry.Integer(TiffTags.BitsPerSample, TiffType.Short, bitsPerSample))
// .WithoutEntry(TiffTags.PhotometricInterpretation)
// .ToStream(isLittleEndian);
// TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
// TiffIfd ifd = decoder.ReadIfd(0);
// decoder.ReadImageFormat(ifd);
// Assert.Equal((TiffColorType)colorType, decoder.ColorType);
// }
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void ReadImageFormat_ThrowsExceptionForMissingPhotometricInterpretation(bool isLittleEndian)
{
Stream stream = CreateTiffGenIfd()
.WithoutEntry(TiffTags.PhotometricInterpretation)
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadImageFormat(ifd));
Assert.Equal("The TIFF photometric interpretation entry is missing.", e.Message);
}
[Theory]
[InlineData(false, (ushort)TiffPhotometricInterpretation.CieLab)]
[InlineData(false, (ushort)TiffPhotometricInterpretation.ColorFilterArray)]
[InlineData(false, (ushort)TiffPhotometricInterpretation.IccLab)]
[InlineData(false, (ushort)TiffPhotometricInterpretation.ItuLab)]
[InlineData(false, (ushort)TiffPhotometricInterpretation.LinearRaw)]
[InlineData(false, (ushort)TiffPhotometricInterpretation.Separated)]
[InlineData(false, (ushort)TiffPhotometricInterpretation.TransparencyMask)]
[InlineData(false, (ushort)TiffPhotometricInterpretation.YCbCr)]
[InlineData(false, 999)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.CieLab)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.ColorFilterArray)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.IccLab)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.ItuLab)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.LinearRaw)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.Separated)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.TransparencyMask)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.YCbCr)]
[InlineData(true, 999)]
public void ReadImageFormat_ThrowsExceptionForUnsupportedPhotometricInterpretation(bool isLittleEndian, ushort photometricInterpretation)
{
Stream stream = CreateTiffGenIfd()
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, photometricInterpretation))
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
var e = Assert.Throws<NotSupportedException>(() => decoder.ReadImageFormat(ifd));
Assert.Equal("The specified TIFF photometric interpretation is not supported.", e.Message);
}
[Theory]
[InlineData(false, new[] { 8u })]
[InlineData(true, new[] { 8u })]
[InlineData(false, new[] { 4u })]
[InlineData(true, new[] { 4u })]
[InlineData(false, new[] { 1u })]
[InlineData(true, new[] { 1u })]
// [InlineData(false, new[] { 1u, 2u, 3u })]
// [InlineData(true, new[] { 1u, 2u, 3u })]
// [InlineData(false, new[] { 8u, 8u, 8u })]
// [InlineData(true, new[] { 8u, 8u, 8u })]
public void ReadImageFormat_ReadsBitsPerSample(bool isLittleEndian, uint[] bitsPerSample)
{
Stream stream = CreateTiffGenIfd()
.WithEntry(TiffGenEntry.Integer(TiffTags.BitsPerSample, TiffType.Short, bitsPerSample))
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
decoder.ReadImageFormat(ifd);
Assert.Equal(bitsPerSample, decoder.BitsPerSample);
}
[Theory]
[InlineData(false, (ushort)TiffPhotometricInterpretation.WhiteIsZero)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.WhiteIsZero)]
[InlineData(false, (ushort)TiffPhotometricInterpretation.BlackIsZero)]
[InlineData(true, (ushort)TiffPhotometricInterpretation.BlackIsZero)]
public void ReadImageFormat_ReadsBitsPerSample_DefaultsToBilevel(bool isLittleEndian, ushort photometricInterpretation)
{
Stream stream = CreateTiffGenIfd()
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, photometricInterpretation))
.WithoutEntry(TiffTags.BitsPerSample)
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
decoder.ReadImageFormat(ifd);
Assert.Equal(new[] { 1u }, decoder.BitsPerSample);
}
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void ReadImageFormat_ThrowsExceptionForMissingBitsPerSample(bool isLittleEndian)
{
Stream stream = CreateTiffGenIfd()
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, (int)TiffPhotometricInterpretation.PaletteColor))
.WithoutEntry(TiffTags.BitsPerSample)
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadImageFormat(ifd));
Assert.Equal("The TIFF BitsPerSample entry is missing.", e.Message);
}
[Theory]
[InlineData(false, (ushort)TiffPhotometricInterpretation.WhiteIsZero, new int[] { })]
[InlineData(true, (ushort)TiffPhotometricInterpretation.WhiteIsZero, new int[] { })]
[InlineData(false, (ushort)TiffPhotometricInterpretation.BlackIsZero, new int[] { })]
[InlineData(true, (ushort)TiffPhotometricInterpretation.BlackIsZero, new int[] { })]
[InlineData(false, (ushort)TiffPhotometricInterpretation.PaletteColor, new int[] { })]
[InlineData(true, (ushort)TiffPhotometricInterpretation.PaletteColor, new int[] { })]
[InlineData(false, (ushort)TiffPhotometricInterpretation.Rgb, new int[] { })]
[InlineData(true, (ushort)TiffPhotometricInterpretation.Rgb, new int[] { })]
[InlineData(false, (ushort)TiffPhotometricInterpretation.WhiteIsZero, new[] { 8, 8 })]
[InlineData(true, (ushort)TiffPhotometricInterpretation.WhiteIsZero, new[] { 8, 8 })]
[InlineData(false, (ushort)TiffPhotometricInterpretation.BlackIsZero, new[] { 8, 8 })]
[InlineData(true, (ushort)TiffPhotometricInterpretation.BlackIsZero, new[] { 8, 8 })]
[InlineData(false, (ushort)TiffPhotometricInterpretation.PaletteColor, new[] { 8, 8 })]
[InlineData(true, (ushort)TiffPhotometricInterpretation.PaletteColor, new[] { 8, 8 })]
[InlineData(false, (ushort)TiffPhotometricInterpretation.Rgb, new[] { 8 })]
[InlineData(true, (ushort)TiffPhotometricInterpretation.Rgb, new[] { 8 })]
[InlineData(false, (ushort)TiffPhotometricInterpretation.Rgb, new[] { 8, 8 })]
[InlineData(true, (ushort)TiffPhotometricInterpretation.Rgb, new[] { 8, 8 })]
public void ReadImageFormat_ThrowsExceptionForUnsupportedNumberOfSamples(bool isLittleEndian, ushort photometricInterpretation, int[] bitsPerSample)
{
Stream stream = CreateTiffGenIfd()
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, photometricInterpretation))
.WithEntry(TiffGenEntry.Integer(TiffTags.BitsPerSample, TiffType.Short, bitsPerSample))
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
var e = Assert.Throws<NotSupportedException>(() => decoder.ReadImageFormat(ifd));
Assert.Equal("The number of samples in the TIFF BitsPerSample entry is not supported.", e.Message);
}
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void ReadImageFormat_ReadsColorMap(bool isLittleEndian)
{
Stream stream = CreateTiffGenIfd()
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, (int)TiffPhotometricInterpretation.PaletteColor))
.WithEntry(TiffGenEntry.Integer(TiffTags.ColorMap, TiffType.Short, new int[] { 10, 20, 30, 40, 50, 60 }))
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
decoder.ReadImageFormat(ifd);
Assert.Equal(new uint[] { 10, 20, 30, 40, 50, 60 }, decoder.ColorMap);
}
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void ReadImageFormat_ThrowsExceptionForMissingColorMap(bool isLittleEndian)
{
Stream stream = CreateTiffGenIfd()
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, (int)TiffPhotometricInterpretation.PaletteColor))
.WithoutEntry(TiffTags.ColorMap)
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
var e = Assert.Throws<ImageFormatException>(() => decoder.ReadImageFormat(ifd));
Assert.Equal("The TIFF ColorMap entry is missing for a pallete color image.", e.Message);
}
[Theory]
[InlineData(false, (ushort)TiffPlanarConfiguration.Chunky)]
[InlineData(true, (ushort)TiffPlanarConfiguration.Chunky)]
[InlineData(false, (ushort)TiffPlanarConfiguration.Planar)]
[InlineData(true, (ushort)TiffPlanarConfiguration.Planar)]
public void ReadImageFormat_ReadsPlanarConfiguration(bool isLittleEndian, int planarConfiguration)
{
Stream stream = CreateTiffGenIfd()
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, (int)TiffPhotometricInterpretation.Rgb))
.WithEntry(TiffGenEntry.Integer(TiffTags.BitsPerSample, TiffType.Short, new int[] { 8, 8, 8 }))
.WithEntry(TiffGenEntry.Integer(TiffTags.PlanarConfiguration, TiffType.Short, (int)planarConfiguration))
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
decoder.ReadImageFormat(ifd);
Assert.Equal((TiffPlanarConfiguration)planarConfiguration, decoder.PlanarConfiguration);
}
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void ReadImageFormat_DefaultsPlanarConfigurationToChunky(bool isLittleEndian)
{
Stream stream = CreateTiffGenIfd()
.WithEntry(TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, (int)TiffPhotometricInterpretation.Rgb))
.WithEntry(TiffGenEntry.Integer(TiffTags.BitsPerSample, TiffType.Short, new int[] { 8, 8, 8 }))
.WithoutEntry(TiffTags.PlanarConfiguration)
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
decoder.ReadImageFormat(ifd);
Assert.Equal(TiffPlanarConfiguration.Chunky, decoder.PlanarConfiguration);
}
[Theory]
[InlineData((ushort)TiffColorType.WhiteIsZero, new uint[] { 1 }, 160, 80, 20 * 80)]
[InlineData((ushort)TiffColorType.WhiteIsZero, new uint[] { 1 }, 153, 80, 20 * 80)]
[InlineData((ushort)TiffColorType.WhiteIsZero, new uint[] { 3 }, 100, 80, 38 * 80)]
[InlineData((ushort)TiffColorType.WhiteIsZero, new uint[] { 4 }, 100, 80, 50 * 80)]
[InlineData((ushort)TiffColorType.WhiteIsZero, new uint[] { 4 }, 99, 80, 50 * 80)]
[InlineData((ushort)TiffColorType.WhiteIsZero, new uint[] { 8 }, 100, 80, 100 * 80)]
[InlineData((ushort)TiffColorType.PaletteColor, new uint[] { 1 }, 160, 80, 20 * 80)]
[InlineData((ushort)TiffColorType.PaletteColor, new uint[] { 1 }, 153, 80, 20 * 80)]
[InlineData((ushort)TiffColorType.PaletteColor, new uint[] { 3 }, 100, 80, 38 * 80)]
[InlineData((ushort)TiffColorType.PaletteColor, new uint[] { 4 }, 100, 80, 50 * 80)]
[InlineData((ushort)TiffColorType.PaletteColor, new uint[] { 4 }, 99, 80, 50 * 80)]
[InlineData((ushort)TiffColorType.PaletteColor, new uint[] { 8 }, 100, 80, 100 * 80)]
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 8, 8, 8 }, 100, 80, 300 * 80)]
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 4, 4, 4 }, 100, 80, 150 * 80)]
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 4, 8, 4 }, 100, 80, 200 * 80)]
public void CalculateImageBufferSize_ReturnsCorrectSize_Chunky(ushort colorType, uint[] bitsPerSample, int width, int height, int expectedResult)
{
TiffDecoderCore decoder = new TiffDecoderCore(null, null);
decoder.ColorType = (TiffColorType)colorType;
decoder.PlanarConfiguration = TiffPlanarConfiguration.Chunky;
decoder.BitsPerSample = bitsPerSample;
int bufferSize = decoder.CalculateImageBufferSize(width, height, 0);
Assert.Equal(expectedResult, bufferSize);
}
[Theory]
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 8, 8, 8 }, 100, 80, 0, 100 * 80)]
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 8, 8, 8 }, 100, 80, 1, 100 * 80)]
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 8, 8, 8 }, 100, 80, 2, 100 * 80)]
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 4, 4, 4 }, 100, 80, 0, 50 * 80)]
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 4, 4, 4 }, 100, 80, 1, 50 * 80)]
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 4, 4, 4 }, 100, 80, 2, 50 * 80)]
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 4, 8, 4 }, 100, 80, 0, 50 * 80)]
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 4, 8, 4 }, 100, 80, 1, 100 * 80)]
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 4, 8, 4 }, 100, 80, 2, 50 * 80)]
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 4, 8, 4 }, 99, 80, 0, 50 * 80)]
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 4, 8, 4 }, 99, 80, 1, 99 * 80)]
[InlineData((ushort)TiffColorType.Rgb, new uint[] { 4, 8, 4 }, 99, 80, 2, 50 * 80)]
public void CalculateImageBufferSize_ReturnsCorrectSize_Planar(ushort colorType, uint[] bitsPerSample, int width, int height, int plane, int expectedResult)
{
TiffDecoderCore decoder = new TiffDecoderCore(null, null);
decoder.ColorType = (TiffColorType)colorType;
decoder.PlanarConfiguration = TiffPlanarConfiguration.Planar;
decoder.BitsPerSample = bitsPerSample;
int bufferSize = decoder.CalculateImageBufferSize(width, height, plane);
Assert.Equal(expectedResult, bufferSize);
}
private TiffGenIfd CreateTiffGenIfd()
{
return new TiffGenIfd()
{
Entries =
{
TiffGenEntry.Integer(TiffTags.ImageWidth, TiffType.Long, ImageWidth),
TiffGenEntry.Integer(TiffTags.ImageLength, TiffType.Long, ImageHeight),
TiffGenEntry.Rational(TiffTags.XResolution, 100, 1),
TiffGenEntry.Rational(TiffTags.YResolution, 200, 1),
TiffGenEntry.Integer(TiffTags.ResolutionUnit, TiffType.Short, 2),
TiffGenEntry.Integer(TiffTags.PhotometricInterpretation, TiffType.Short, (int)TiffPhotometricInterpretation.WhiteIsZero),
TiffGenEntry.Integer(TiffTags.BitsPerSample, TiffType.Short, new int[] { 8 }),
TiffGenEntry.Integer(TiffTags.Compression, TiffType.Short, (int)TiffCompression.None),
TiffGenEntry.Integer(TiffTags.ColorMap, TiffType.Short, new int[256])
}
};
}
}
}

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

@ -0,0 +1,137 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests
{
using System.IO;
using System.Linq;
using Xunit;
using ImageSharp.Formats;
using ImageSharp.Formats.Tiff;
public class TiffDecoderMetadataTests
{
public static object[][] BaselineMetadataValues = new[] { new object[] { false, TiffTags.Artist, TiffMetadataNames.Artist, "My Artist Name" },
new object[] { false, TiffTags.Copyright, TiffMetadataNames.Copyright, "My Copyright Statement" },
new object[] { false, TiffTags.DateTime, TiffMetadataNames.DateTime, "My DateTime Value" },
new object[] { false, TiffTags.HostComputer, TiffMetadataNames.HostComputer, "My Host Computer Name" },
new object[] { false, TiffTags.ImageDescription, TiffMetadataNames.ImageDescription, "My Image Description" },
new object[] { false, TiffTags.Make, TiffMetadataNames.Make, "My Camera Make" },
new object[] { false, TiffTags.Model, TiffMetadataNames.Model, "My Camera Model" },
new object[] { false, TiffTags.Software, TiffMetadataNames.Software, "My Imaging Software" },
new object[] { true, TiffTags.Artist, TiffMetadataNames.Artist, "My Artist Name" },
new object[] { true, TiffTags.Copyright, TiffMetadataNames.Copyright, "My Copyright Statement" },
new object[] { true, TiffTags.DateTime, TiffMetadataNames.DateTime, "My DateTime Value" },
new object[] { true, TiffTags.HostComputer, TiffMetadataNames.HostComputer, "My Host Computer Name" },
new object[] { true, TiffTags.ImageDescription, TiffMetadataNames.ImageDescription, "My Image Description" },
new object[] { true, TiffTags.Make, TiffMetadataNames.Make, "My Camera Make" },
new object[] { true, TiffTags.Model, TiffMetadataNames.Model, "My Camera Model" },
new object[] { true, TiffTags.Software, TiffMetadataNames.Software, "My Imaging Software" }};
[Theory]
[InlineData(false, 150u, 1u, 200u, 1u, 2u /* Inch */, 150.0, 200.0)]
[InlineData(false, 150u, 1u, 200u, 1u, 3u /* Cm */, 150.0 * 2.54, 200.0 * 2.54)]
[InlineData(false, 150u, 1u, 200u, 1u, 1u /* None */, 96.0, 96.0)]
[InlineData(false, 150u, 1u, 200u, 1u, null /* Inch */, 150.0, 200.0)]
[InlineData(false, 5u, 2u, 9u, 4u, 2u /* Inch */, 2.5, 2.25)]
[InlineData(false, null, null, null, null, null /* Inch */, 96.0, 96.0)]
[InlineData(false, 150u, 1u, null, null, 2u /* Inch */, 150.0, 96.0)]
[InlineData(false, null, null, 200u, 1u, 2u /* Inch */, 96.0, 200.0)]
[InlineData(true, 150u, 1u, 200u, 1u, 2u /* Inch */, 150.0, 200.0)]
[InlineData(true, 150u, 1u, 200u, 1u, 3u /* Cm */, 150.0 * 2.54, 200.0 * 2.54)]
[InlineData(true, 150u, 1u, 200u, 1u, 1u /* None */, 96.0, 96.0)]
[InlineData(true, 5u, 2u, 9u, 4u, 2u /* Inch */, 2.5, 2.25)]
[InlineData(true, 150u, 1u, 200u, 1u, null /* Inch */, 150.0, 200.0)]
[InlineData(true, null, null, null, null, null /* Inch */, 96.0, 96.0)]
[InlineData(true, 150u, 1u, null, null, 2u /* Inch */, 150.0, 96.0)]
[InlineData(true, null, null, 200u, 1u, 2u /* Inch */, 96.0, 200.0)]
public void ReadMetadata_SetsImageResolution(bool isLittleEndian, uint? xResolutionNumerator, uint? xResolutionDenominator,
uint? yResolutionNumerator, uint? yResolutionDenominator, uint? resolutionUnit,
double expectedHorizonalResolution, double expectedVerticalResolution)
{
TiffGenIfd ifdGen = new TiffGenIfd();
if (xResolutionNumerator != null)
{
ifdGen.WithEntry(TiffGenEntry.Rational(TiffTags.XResolution, xResolutionNumerator.Value, xResolutionDenominator.Value));
}
if (yResolutionNumerator != null)
{
ifdGen.WithEntry(TiffGenEntry.Rational(TiffTags.YResolution, yResolutionNumerator.Value, yResolutionDenominator.Value));
}
if (resolutionUnit != null)
{
ifdGen.WithEntry(TiffGenEntry.Integer(TiffTags.ResolutionUnit, TiffType.Short, resolutionUnit.Value));
}
Stream stream = ifdGen.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
Image<Rgba32> image = new Image<Rgba32>(null, 20, 20);
decoder.ReadMetadata<Rgba32>(ifd, image);
Assert.Equal(expectedHorizonalResolution, image.MetaData.HorizontalResolution, 10);
Assert.Equal(expectedVerticalResolution, image.MetaData.VerticalResolution, 10);
}
[Theory]
[MemberData(nameof(BaselineMetadataValues))]
public void ReadMetadata_SetsAsciiMetadata(bool isLittleEndian, ushort tag, string metadataName, string metadataValue)
{
Stream stream = new TiffGenIfd()
{
Entries =
{
TiffGenEntry.Integer(TiffTags.ImageWidth, TiffType.Long, 150),
TiffGenEntry.Integer(TiffTags.ImageLength, TiffType.Long, 210),
TiffGenEntry.Ascii(tag, metadataValue),
TiffGenEntry.Integer(TiffTags.Orientation, TiffType.Short, 1)
}
}
.ToStream(isLittleEndian);
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, null);
TiffIfd ifd = decoder.ReadIfd(0);
Image<Rgba32> image = new Image<Rgba32>(null, 20, 20);
decoder.ReadMetadata<Rgba32>(ifd, image);
var metadata = image.MetaData.Properties.FirstOrDefault(m => m.Name == metadataName).Value;
Assert.Equal(metadataValue, metadata);
}
[Theory]
[MemberData(nameof(BaselineMetadataValues))]
public void ReadMetadata_DoesntSetMetadataIfIgnoring(bool isLittleEndian, ushort tag, string metadataName, string metadataValue)
{
Stream stream = new TiffGenIfd()
{
Entries =
{
TiffGenEntry.Integer(TiffTags.ImageWidth, TiffType.Long, 150),
TiffGenEntry.Integer(TiffTags.ImageLength, TiffType.Long, 210),
TiffGenEntry.Ascii(tag, metadataValue),
TiffGenEntry.Integer(TiffTags.Orientation, TiffType.Short, 1)
}
}
.ToStream(isLittleEndian);
TiffDecoder options = new TiffDecoder() { IgnoreMetadata = true };
TiffDecoderCore decoder = new TiffDecoderCore(stream, isLittleEndian, null, options);
TiffIfd ifd = decoder.ReadIfd(0);
Image<Rgba32> image = new Image<Rgba32>(null, 20, 20);
decoder.ReadMetadata<Rgba32>(ifd, image);
var metadata = image.MetaData.Properties.FirstOrDefault(m => m.Name == metadataName).Value;
Assert.Null(metadata);
}
}
}

42
tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs

@ -0,0 +1,42 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests
{
using System.IO;
using Xunit;
using ImageSharp.Formats;
using ImageSharp.Formats.Tiff;
public class TiffEncoderHeaderTests
{
[Fact]
public void WriteHeader_WritesValidHeader()
{
MemoryStream stream = new MemoryStream();
TiffEncoderCore encoder = new TiffEncoderCore(null);
using (TiffWriter writer = new TiffWriter(stream))
{
long firstIfdMarker = encoder.WriteHeader(writer);
}
Assert.Equal(new byte[] { 0x49, 0x49, 42, 0, 0x00, 0x00, 0x00, 0x00 }, stream.ToArray());
}
[Fact]
public void WriteHeader_ReturnsFirstIfdMarker()
{
MemoryStream stream = new MemoryStream();
TiffEncoderCore encoder = new TiffEncoderCore(null);
using (TiffWriter writer = new TiffWriter(stream))
{
long firstIfdMarker = encoder.WriteHeader(writer);
Assert.Equal(4, firstIfdMarker);
}
}
}
}

297
tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderIfdTests.cs

@ -0,0 +1,297 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests
{
using System;
using System.IO;
using System.Linq;
using Xunit;
using ImageSharp.Formats;
using ImageSharp.Formats.Tiff;
using System.Collections.Generic;
public class TiffEncoderIfdTests
{
[Fact]
public void WriteIfd_DataIsCorrectLength()
{
MemoryStream stream = new MemoryStream();
TiffEncoderCore encoder = new TiffEncoderCore(null);
List<TiffIfdEntry> entries = new List<TiffIfdEntry>()
{
new TiffIfdEntry(TiffTags.ImageWidth, TiffType.Long, 1, new byte[] { 1, 2, 3, 4 }),
new TiffIfdEntry(TiffTags.ImageLength, TiffType.Long, 1, new byte[] { 5, 6, 7, 8 }),
new TiffIfdEntry(TiffTags.Compression, TiffType.Long, 1, new byte[] { 9, 10, 11, 12 })
};
using (TiffWriter writer = new TiffWriter(stream))
{
long nextIfdMarker = encoder.WriteIfd(writer, entries);
}
Assert.Equal(2 + 12 * 3 + 4, stream.Length);
}
[Fact]
public void WriteIfd_WritesNumberOfEntries()
{
MemoryStream stream = new MemoryStream();
TiffEncoderCore encoder = new TiffEncoderCore(null);
List<TiffIfdEntry> entries = new List<TiffIfdEntry>()
{
new TiffIfdEntry(TiffTags.ImageWidth, TiffType.Long, 1, new byte[] { 1, 2, 3, 4 }),
new TiffIfdEntry(TiffTags.ImageLength, TiffType.Long, 1, new byte[] { 5, 6, 7, 8 }),
new TiffIfdEntry(TiffTags.Compression, TiffType.Long, 1, new byte[] { 9, 10, 11, 12 })
};
using (TiffWriter writer = new TiffWriter(stream))
{
long nextIfdMarker = encoder.WriteIfd(writer, entries);
}
var ifdEntryBytes = stream.ToArray().Take(2).ToArray();
Assert.Equal(new byte[] { 3, 0 }, ifdEntryBytes);
}
[Fact]
public void WriteIfd_ReturnsNextIfdMarker()
{
MemoryStream stream = new MemoryStream();
TiffEncoderCore encoder = new TiffEncoderCore(null);
List<TiffIfdEntry> entries = new List<TiffIfdEntry>()
{
new TiffIfdEntry(TiffTags.ImageWidth, TiffType.Long, 1, new byte[] { 1, 2, 3, 4 }),
new TiffIfdEntry(TiffTags.ImageLength, TiffType.Long, 1, new byte[] { 5, 6, 7, 8 }),
new TiffIfdEntry(TiffTags.Compression, TiffType.Long, 1, new byte[] { 9, 10, 11, 12 })
};
using (TiffWriter writer = new TiffWriter(stream))
{
long nextIfdMarker = encoder.WriteIfd(writer, entries);
Assert.Equal(2 + 12 * 3, nextIfdMarker);
}
}
[Fact]
public void WriteIfd_WritesTagIdForEachEntry()
{
MemoryStream stream = new MemoryStream();
TiffEncoderCore encoder = new TiffEncoderCore(null);
List<TiffIfdEntry> entries = new List<TiffIfdEntry>()
{
new TiffIfdEntry(10, TiffType.Long, 1, new byte[] { 1, 2, 3, 4 }),
new TiffIfdEntry(20, TiffType.Long, 1, new byte[] { 5, 6, 7, 8 }),
new TiffIfdEntry(30, TiffType.Long, 1, new byte[] { 9, 10, 11, 12 })
};
using (TiffWriter writer = new TiffWriter(stream))
{
long nextIfdMarker = encoder.WriteIfd(writer, entries);
}
var ifdEntry1Bytes = stream.ToArray().Skip(2 + 12 * 0).Take(2).ToArray();
var ifdEntry2Bytes = stream.ToArray().Skip(2 + 12 * 1).Take(2).ToArray();
var ifdEntry3Bytes = stream.ToArray().Skip(2 + 12 * 2).Take(2).ToArray();
Assert.Equal(new byte[] { 10, 0 }, ifdEntry1Bytes);
Assert.Equal(new byte[] { 20, 0 }, ifdEntry2Bytes);
Assert.Equal(new byte[] { 30, 0 }, ifdEntry3Bytes);
}
[Fact]
public void WriteIfd_WritesTypeForEachEntry()
{
MemoryStream stream = new MemoryStream();
TiffEncoderCore encoder = new TiffEncoderCore(null);
List<TiffIfdEntry> entries = new List<TiffIfdEntry>()
{
new TiffIfdEntry(TiffTags.ImageWidth, TiffType.Long, 1, new byte[] { 1, 2, 3, 4 }),
new TiffIfdEntry(TiffTags.ImageLength, TiffType.Short, 2, new byte[] { 5, 6, 7, 8 }),
new TiffIfdEntry(TiffTags.Compression, TiffType.Ascii, 4, new byte[] { (byte)'A', (byte)'B', (byte)'C', 0 })
};
using (TiffWriter writer = new TiffWriter(stream))
{
long nextIfdMarker = encoder.WriteIfd(writer, entries);
}
var ifdEntry1Bytes = stream.ToArray().Skip(4 + 12 * 0).Take(2).ToArray();
var ifdEntry2Bytes = stream.ToArray().Skip(4 + 12 * 1).Take(2).ToArray();
var ifdEntry3Bytes = stream.ToArray().Skip(4 + 12 * 2).Take(2).ToArray();
Assert.Equal(new byte[] { 4, 0 }, ifdEntry1Bytes);
Assert.Equal(new byte[] { 3, 0 }, ifdEntry2Bytes);
Assert.Equal(new byte[] { 2, 0 }, ifdEntry3Bytes);
}
[Fact]
public void WriteIfd_WritesCountForEachEntry()
{
MemoryStream stream = new MemoryStream();
TiffEncoderCore encoder = new TiffEncoderCore(null);
List<TiffIfdEntry> entries = new List<TiffIfdEntry>()
{
new TiffIfdEntry(TiffTags.ImageWidth, TiffType.Long, 1, new byte[] { 1, 2, 3, 4 }),
new TiffIfdEntry(TiffTags.ImageLength, TiffType.Short, 2, new byte[] { 5, 6, 7, 8 }),
new TiffIfdEntry(TiffTags.Compression, TiffType.Ascii, 4, new byte[] { (byte)'A', (byte)'B', (byte)'C', 0 })
};
using (TiffWriter writer = new TiffWriter(stream))
{
long nextIfdMarker = encoder.WriteIfd(writer, entries);
}
var ifdEntry1Bytes = stream.ToArray().Skip(6 + 12 * 0).Take(4).ToArray();
var ifdEntry2Bytes = stream.ToArray().Skip(6 + 12 * 1).Take(4).ToArray();
var ifdEntry3Bytes = stream.ToArray().Skip(6 + 12 * 2).Take(4).ToArray();
Assert.Equal(new byte[] { 1, 0, 0, 0 }, ifdEntry1Bytes);
Assert.Equal(new byte[] { 2, 0, 0, 0 }, ifdEntry2Bytes);
Assert.Equal(new byte[] { 4, 0, 0, 0 }, ifdEntry3Bytes);
}
[Fact]
public void WriteIfd_WritesDataInline()
{
MemoryStream stream = new MemoryStream();
TiffEncoderCore encoder = new TiffEncoderCore(null);
List<TiffIfdEntry> entries = new List<TiffIfdEntry>()
{
new TiffIfdEntry(TiffTags.ImageWidth, TiffType.Long, 1, new byte[] { 1, 2, 3, 4 }),
new TiffIfdEntry(TiffTags.ImageLength, TiffType.Short, 2, new byte[] { 5, 6, 7, 8 }),
new TiffIfdEntry(TiffTags.Compression, TiffType.Ascii, 3, new byte[] { (byte)'A', (byte)'B', 0 })
};
using (TiffWriter writer = new TiffWriter(stream))
{
long nextIfdMarker = encoder.WriteIfd(writer, entries);
}
var ifdEntry1Bytes = stream.ToArray().Skip(10 + 12 * 0).Take(4).ToArray();
var ifdEntry2Bytes = stream.ToArray().Skip(10 + 12 * 1).Take(4).ToArray();
var ifdEntry3Bytes = stream.ToArray().Skip(10 + 12 * 2).Take(4).ToArray();
Assert.Equal(new byte[] { 1, 2, 3, 4 }, ifdEntry1Bytes);
Assert.Equal(new byte[] { 5, 6, 7, 8 }, ifdEntry2Bytes);
Assert.Equal(new byte[] { (byte)'A', (byte)'B', 0, 0 }, ifdEntry3Bytes);
}
[Fact]
public void WriteIfd_WritesDataByReference()
{
MemoryStream stream = new MemoryStream();
TiffEncoderCore encoder = new TiffEncoderCore(null);
List<TiffIfdEntry> entries = new List<TiffIfdEntry>()
{
new TiffIfdEntry(TiffTags.ImageWidth, TiffType.Byte, 8, new byte[] { 1, 2, 3, 4, 4, 3, 2, 1 }),
new TiffIfdEntry(TiffTags.ImageLength, TiffType.Short, 4, new byte[] { 5, 6, 7, 8, 9, 10, 11, 12 }),
new TiffIfdEntry(TiffTags.Compression, TiffType.Ascii, 3, new byte[] { (byte)'A', (byte)'B', 0 })
};
using (TiffWriter writer = new TiffWriter(stream))
{
writer.Write(new byte[] { 1, 2, 3, 4 });
long nextIfdMarker = encoder.WriteIfd(writer, entries);
}
var ifdEntry1Bytes = stream.ToArray().Skip(14 + 12 * 0).Take(4).ToArray();
var ifdEntry1Data = stream.ToArray().Skip(46).Take(8).ToArray();
var ifdEntry2Bytes = stream.ToArray().Skip(14 + 12 * 1).Take(4).ToArray();
var ifdEntry2Data = stream.ToArray().Skip(54).Take(8).ToArray();
var ifdEntry3Bytes = stream.ToArray().Skip(14 + 12 * 2).Take(4).ToArray();
Assert.Equal(new byte[] { 46, 0, 0, 0 }, ifdEntry1Bytes);
Assert.Equal(new byte[] { 1, 2, 3, 4, 4, 3, 2, 1 }, ifdEntry1Data);
Assert.Equal(new byte[] { 54, 0, 0, 0 }, ifdEntry2Bytes);
Assert.Equal(new byte[] { 5, 6, 7, 8, 9, 10, 11, 12 }, ifdEntry2Data);
Assert.Equal(new byte[] { (byte)'A', (byte)'B', 0, 0 }, ifdEntry3Bytes);
}
[Fact]
public void WriteIfd_WritesDataByReferenceOnWordBoundary()
{
MemoryStream stream = new MemoryStream();
TiffEncoderCore encoder = new TiffEncoderCore(null);
List<TiffIfdEntry> entries = new List<TiffIfdEntry>()
{
new TiffIfdEntry(TiffTags.ImageWidth, TiffType.Byte, 8, new byte[] { 1, 2, 3, 4, 5 }),
new TiffIfdEntry(TiffTags.ImageLength, TiffType.Short, 4, new byte[] { 5, 6, 7, 8, 9, 10, 11, 12 }),
new TiffIfdEntry(TiffTags.Compression, TiffType.Ascii, 3, new byte[] { (byte)'A', (byte)'B', 0 })
};
using (TiffWriter writer = new TiffWriter(stream))
{
writer.Write(new byte[] { 1, 2, 3, 4 });
long nextIfdMarker = encoder.WriteIfd(writer, entries);
}
var ifdEntry1Bytes = stream.ToArray().Skip(14 + 12 * 0).Take(4).ToArray();
var ifdEntry1Data = stream.ToArray().Skip(46).Take(5).ToArray();
var ifdEntry2Bytes = stream.ToArray().Skip(14 + 12 * 1).Take(4).ToArray();
var ifdEntry2Data = stream.ToArray().Skip(52).Take(8).ToArray();
var ifdEntry3Bytes = stream.ToArray().Skip(14 + 12 * 2).Take(4).ToArray();
Assert.Equal(new byte[] { 46, 0, 0, 0 }, ifdEntry1Bytes);
Assert.Equal(new byte[] { 1, 2, 3, 4, 5 }, ifdEntry1Data);
Assert.Equal(new byte[] { 52, 0, 0, 0 }, ifdEntry2Bytes);
Assert.Equal(new byte[] { 5, 6, 7, 8, 9, 10, 11, 12 }, ifdEntry2Data);
Assert.Equal(new byte[] { (byte)'A', (byte)'B', 0, 0 }, ifdEntry3Bytes);
}
[Fact]
public void WriteIfd_WritesEntriesInCorrectOrder()
{
MemoryStream stream = new MemoryStream();
TiffEncoderCore encoder = new TiffEncoderCore(null);
List<TiffIfdEntry> entries = new List<TiffIfdEntry>()
{
new TiffIfdEntry(10, TiffType.Long, 1, new byte[] { 1, 2, 3, 4 }),
new TiffIfdEntry(30, TiffType.Long, 1, new byte[] { 5, 6, 7, 8 }),
new TiffIfdEntry(20, TiffType.Long, 1, new byte[] { 9, 10, 11, 12 })
};
using (TiffWriter writer = new TiffWriter(stream))
{
long nextIfdMarker = encoder.WriteIfd(writer, entries);
}
var ifdEntry1Bytes = stream.ToArray().Skip(2 + 12 * 0).Take(2).ToArray();
var ifdEntry2Bytes = stream.ToArray().Skip(2 + 12 * 1).Take(2).ToArray();
var ifdEntry3Bytes = stream.ToArray().Skip(2 + 12 * 2).Take(2).ToArray();
Assert.Equal(new byte[] { 10, 0 }, ifdEntry1Bytes);
Assert.Equal(new byte[] { 20, 0 }, ifdEntry2Bytes);
Assert.Equal(new byte[] { 30, 0 }, ifdEntry3Bytes);
}
[Fact]
public void WriteIfd_ThrowsException_IfNoEntriesArePresent()
{
MemoryStream stream = new MemoryStream();
TiffEncoderCore encoder = new TiffEncoderCore(null);
List<TiffIfdEntry> entries = new List<TiffIfdEntry>();
using (TiffWriter writer = new TiffWriter(stream))
{
ArgumentException e = Assert.Throws<ArgumentException>(() => { encoder.WriteIfd(writer, entries); });
Assert.Equal($"There must be at least one entry per IFD.{Environment.NewLine}Parameter name: entries", e.Message);
Assert.Equal("entries", e.ParamName);
}
}
}
}

59
tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMetadataTests.cs

@ -0,0 +1,59 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests
{
using Xunit;
using ImageSharp.Formats;
using ImageSharp.Formats.Tiff;
using System.Collections.Generic;
using SixLabors.ImageSharp.MetaData;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.Primitives;
public class TiffEncoderMetadataTests
{
public static object[][] BaselineMetadataValues = new[] { new object[] { TiffTags.Artist, TiffMetadataNames.Artist, "My Artist Name" },
new object[] { TiffTags.Copyright, TiffMetadataNames.Copyright, "My Copyright Statement" },
new object[] { TiffTags.DateTime, TiffMetadataNames.DateTime, "My DateTime Value" },
new object[] { TiffTags.HostComputer, TiffMetadataNames.HostComputer, "My Host Computer Name" },
new object[] { TiffTags.ImageDescription, TiffMetadataNames.ImageDescription, "My Image Description" },
new object[] { TiffTags.Make, TiffMetadataNames.Make, "My Camera Make" },
new object[] { TiffTags.Model, TiffMetadataNames.Model, "My Camera Model" },
new object[] { TiffTags.Software, TiffMetadataNames.Software, "My Imaging Software" }};
[Fact]
public void AddMetadata_SetsImageResolution()
{
Image<Rgba32> image = new Image<Rgba32>(100, 100);
image.MetaData.HorizontalResolution = 40.0;
image.MetaData.VerticalResolution = 50.5;
TiffEncoderCore encoder = new TiffEncoderCore(null);
List<TiffIfdEntry> ifdEntries = new List<TiffIfdEntry>();
encoder.AddMetadata(image, ifdEntries);
Assert.Equal(new Rational(40, 1), ifdEntries.GetUnsignedRational(TiffTags.XResolution));
Assert.Equal(new Rational(101, 2), ifdEntries.GetUnsignedRational(TiffTags.YResolution));
Assert.Equal(TiffResolutionUnit.Inch, (TiffResolutionUnit?)ifdEntries.GetInteger(TiffTags.ResolutionUnit));
}
[Theory]
[MemberData(nameof(BaselineMetadataValues))]
public void AddMetadata_SetsAsciiMetadata(ushort tag, string metadataName, string metadataValue)
{
Image<Rgba32> image = new Image<Rgba32>(100, 100);
image.MetaData.Properties.Add(new ImageProperty(metadataName, metadataValue));
TiffEncoderCore encoder = new TiffEncoderCore(null);
List<TiffIfdEntry> ifdEntries = new List<TiffIfdEntry>();
encoder.AddMetadata(image, ifdEntries);
Assert.Equal(metadataValue + "\0", ifdEntries.GetAscii(tag));
}
}
}

24
tests/ImageSharp.Tests/Formats/Tiff/TiffFormatTests.cs

@ -0,0 +1,24 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests
{
using Xunit;
using ImageSharp.Formats;
public class TiffFormatTests
{
[Fact]
public void FormatProperties_AreAsExpected()
{
TiffFormat tiffFormat = TiffFormat.Instance;
Assert.Equal("TIFF", tiffFormat.Name);
Assert.Equal("image/tiff", tiffFormat.DefaultMimeType);
Assert.Contains("image/tiff", tiffFormat.MimeTypes);
Assert.Contains("tif", tiffFormat.FileExtensions);
Assert.Contains("tiff", tiffFormat.FileExtensions);
}
}
}

409
tests/ImageSharp.Tests/Formats/Tiff/TiffIfd/TiffIfdEntryCreatorTests.cs

@ -0,0 +1,409 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests
{
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
using ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.MetaData.Profiles.Exif;
using SixLabors.ImageSharp.Primitives;
public class TiffIfdEntryCreatorTests
{
[Theory]
[InlineDataAttribute(new byte[] { 0 }, 0)]
[InlineDataAttribute(new byte[] { 1 }, 1)]
[InlineDataAttribute(new byte[] { 255 }, 255)]
public void AddUnsignedByte_AddsSingleValue(byte[] bytes, uint value)
{
var entries = new List<TiffIfdEntry>();
entries.AddUnsignedByte(TiffTags.ImageWidth, value);
var entry = entries[0];
Assert.Equal(TiffTags.ImageWidth, entry.Tag);
Assert.Equal(TiffType.Byte, entry.Type);
Assert.Equal(1u, entry.Count);
Assert.Equal(bytes, entry.Value);
}
[Theory]
[InlineDataAttribute(new byte[] { 0 }, new uint[] { 0 })]
[InlineDataAttribute(new byte[] { 0, 1, 2 }, new uint[] { 0, 1, 2 })]
[InlineDataAttribute(new byte[] { 0, 1, 2, 3, 4, 5, 6 }, new uint[] { 0, 1, 2, 3, 4, 5, 6 })]
public void AddUnsignedByte_AddsArray(byte[] bytes, uint[] value)
{
var entries = new List<TiffIfdEntry>();
entries.AddUnsignedByte(TiffTags.ImageWidth, value);
var entry = entries[0];
Assert.Equal(TiffTags.ImageWidth, entry.Tag);
Assert.Equal(TiffType.Byte, entry.Type);
Assert.Equal((uint)value.Length, entry.Count);
Assert.Equal(bytes, entry.Value);
}
[Theory]
[InlineDataAttribute(new byte[] { 0, 0 }, 0)]
[InlineDataAttribute(new byte[] { 1, 0 }, 1)]
[InlineDataAttribute(new byte[] { 0, 1 }, 256)]
[InlineDataAttribute(new byte[] { 2, 1 }, 258)]
[InlineDataAttribute(new byte[] { 255, 255 }, UInt16.MaxValue)]
public void AddUnsignedShort_AddsSingleValue(byte[] bytes, uint value)
{
var entries = new List<TiffIfdEntry>();
entries.AddUnsignedShort(TiffTags.ImageWidth, value);
var entry = entries[0];
Assert.Equal(TiffTags.ImageWidth, entry.Tag);
Assert.Equal(TiffType.Short, entry.Type);
Assert.Equal(1u, entry.Count);
Assert.Equal(bytes, entry.Value);
}
[Theory]
[InlineDataAttribute(new byte[] { 1, 0 }, new uint[] { 1 })]
[InlineDataAttribute(new byte[] { 1, 0, 3, 2 }, new uint[] { 1, 515 })]
[InlineDataAttribute(new byte[] { 1, 0, 3, 2, 5, 4 }, new uint[] { 1, 515, 1029 })]
public void AddUnsignedShort_AddsArray(byte[] bytes, uint[] value)
{
var entries = new List<TiffIfdEntry>();
entries.AddUnsignedShort(TiffTags.ImageWidth, value);
var entry = entries[0];
Assert.Equal(TiffTags.ImageWidth, entry.Tag);
Assert.Equal(TiffType.Short, entry.Type);
Assert.Equal((uint)value.Length, entry.Count);
Assert.Equal(bytes, entry.Value);
}
[Theory]
[InlineDataAttribute(new byte[] { 0, 0, 0, 0 }, 0)]
[InlineDataAttribute(new byte[] { 1, 0, 0, 0 }, 1)]
[InlineDataAttribute(new byte[] { 0, 1, 0, 0 }, 256)]
[InlineDataAttribute(new byte[] { 0, 0, 1, 0 }, 256 * 256)]
[InlineDataAttribute(new byte[] { 0, 0, 0, 1 }, 256 * 256 * 256)]
[InlineDataAttribute(new byte[] { 1, 2, 3, 4 }, 67305985)]
[InlineDataAttribute(new byte[] { 255, 255, 255, 255 }, UInt32.MaxValue)]
public void AddUnsignedLong_AddsSingleValue(byte[] bytes, uint value)
{
var entries = new List<TiffIfdEntry>();
entries.AddUnsignedLong(TiffTags.ImageWidth, value);
var entry = entries[0];
Assert.Equal(TiffTags.ImageWidth, entry.Tag);
Assert.Equal(TiffType.Long, entry.Type);
Assert.Equal(1u, entry.Count);
Assert.Equal(bytes, entry.Value);
}
[Theory]
[InlineDataAttribute(new byte[] { 4, 3, 2, 1 }, new uint[] { 0x01020304 })]
[InlineDataAttribute(new byte[] { 4, 3, 2, 1, 6, 5, 4, 3 }, new uint[] { 0x01020304, 0x03040506 })]
public void AddUnsignedLong_AddsArray(byte[] bytes, uint[] value)
{
var entries = new List<TiffIfdEntry>();
entries.AddUnsignedLong(TiffTags.ImageWidth, value);
var entry = entries[0];
Assert.Equal(TiffTags.ImageWidth, entry.Tag);
Assert.Equal(TiffType.Long, entry.Type);
Assert.Equal((uint)value.Length, entry.Count);
Assert.Equal(bytes, entry.Value);
}
[Theory]
[InlineDataAttribute(new byte[] { 0 }, 0)]
[InlineDataAttribute(new byte[] { 1 }, 1)]
[InlineDataAttribute(new byte[] { 255 }, -1)]
public void AddSignedByte_AddsSingleValue(byte[] bytes, int value)
{
var entries = new List<TiffIfdEntry>();
entries.AddSignedByte(TiffTags.ImageWidth, value);
var entry = entries[0];
Assert.Equal(TiffTags.ImageWidth, entry.Tag);
Assert.Equal(TiffType.SByte, entry.Type);
Assert.Equal(1u, entry.Count);
Assert.Equal(bytes, entry.Value);
}
[Theory]
[InlineDataAttribute(new byte[] { 0 }, new int[] { 0 })]
[InlineDataAttribute(new byte[] { 0, 255, 2 }, new int[] { 0, -1, 2 })]
[InlineDataAttribute(new byte[] { 0, 255, 2, 3, 4, 5, 6 }, new int[] { 0, -1, 2, 3, 4, 5, 6 })]
public void AddSignedByte_AddsArray(byte[] bytes, int[] value)
{
var entries = new List<TiffIfdEntry>();
entries.AddSignedByte(TiffTags.ImageWidth, value);
var entry = entries[0];
Assert.Equal(TiffTags.ImageWidth, entry.Tag);
Assert.Equal(TiffType.SByte, entry.Type);
Assert.Equal((uint)value.Length, entry.Count);
Assert.Equal(bytes, entry.Value);
}
[Theory]
[InlineDataAttribute(new byte[] { 0, 0 }, 0)]
[InlineDataAttribute(new byte[] { 1, 0 }, 1)]
[InlineDataAttribute(new byte[] { 0, 1 }, 256)]
[InlineDataAttribute(new byte[] { 2, 1 }, 258)]
[InlineDataAttribute(new byte[] { 255, 255 }, -1)]
public void AddSignedShort_AddsSingleValue(byte[] bytes, int value)
{
var entries = new List<TiffIfdEntry>();
entries.AddSignedShort(TiffTags.ImageWidth, value);
var entry = entries[0];
Assert.Equal(TiffTags.ImageWidth, entry.Tag);
Assert.Equal(TiffType.SShort, entry.Type);
Assert.Equal(1u, entry.Count);
Assert.Equal(bytes, entry.Value);
}
[Theory]
[InlineDataAttribute(new byte[] { 1, 0 }, new int[] { 1 })]
[InlineDataAttribute(new byte[] { 1, 0, 255, 255 }, new int[] { 1, -1 })]
[InlineDataAttribute(new byte[] { 1, 0, 255, 255, 5, 4 }, new int[] { 1, -1, 1029 })]
public void AddSignedShort_AddsArray(byte[] bytes, int[] value)
{
var entries = new List<TiffIfdEntry>();
entries.AddSignedShort(TiffTags.ImageWidth, value);
var entry = entries[0];
Assert.Equal(TiffTags.ImageWidth, entry.Tag);
Assert.Equal(TiffType.SShort, entry.Type);
Assert.Equal((uint)value.Length, entry.Count);
Assert.Equal(bytes, entry.Value);
}
[Theory]
[InlineDataAttribute(new byte[] { 0, 0, 0, 0 }, 0)]
[InlineDataAttribute(new byte[] { 1, 0, 0, 0 }, 1)]
[InlineDataAttribute(new byte[] { 0, 1, 0, 0 }, 256)]
[InlineDataAttribute(new byte[] { 0, 0, 1, 0 }, 256 * 256)]
[InlineDataAttribute(new byte[] { 0, 0, 0, 1 }, 256 * 256 * 256)]
[InlineDataAttribute(new byte[] { 1, 2, 3, 4 }, 67305985)]
[InlineDataAttribute(new byte[] { 255, 255, 255, 255 }, -1)]
public void AddSignedLong_AddsSingleValue(byte[] bytes, int value)
{
var entries = new List<TiffIfdEntry>();
entries.AddSignedLong(TiffTags.ImageWidth, value);
var entry = entries[0];
Assert.Equal(TiffTags.ImageWidth, entry.Tag);
Assert.Equal(TiffType.SLong, entry.Type);
Assert.Equal(1u, entry.Count);
Assert.Equal(bytes, entry.Value);
}
[Theory]
[InlineDataAttribute(new byte[] { 4, 3, 2, 1 }, new int[] { 0x01020304 })]
[InlineDataAttribute(new byte[] { 4, 3, 2, 1, 255, 255, 255, 255 }, new int[] { 0x01020304, -1 })]
public void AddSignedLong_AddsArray(byte[] bytes, int[] value)
{
var entries = new List<TiffIfdEntry>();
entries.AddSignedLong(TiffTags.ImageWidth, value);
var entry = entries[0];
Assert.Equal(TiffTags.ImageWidth, entry.Tag);
Assert.Equal(TiffType.SLong, entry.Type);
Assert.Equal((uint)value.Length, entry.Count);
Assert.Equal(bytes, entry.Value);
}
[Theory]
[InlineDataAttribute(new byte[] { 0 }, "")]
[InlineDataAttribute(new byte[] { (byte)'A', (byte)'B', (byte)'C', 0 }, "ABC")]
[InlineDataAttribute(new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', 0 }, "ABCDEF")]
[InlineDataAttribute(new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', 0, (byte)'E', (byte)'F', (byte)'G', (byte)'H', 0 }, "ABCD\0EFGH")]
public void AddAscii_AddsEntry(byte[] bytes, string value)
{
var entries = new List<TiffIfdEntry>();
entries.AddAscii(TiffTags.ImageWidth, value);
var entry = entries[0];
Assert.Equal(TiffTags.ImageWidth, entry.Tag);
Assert.Equal(TiffType.Ascii, entry.Type);
Assert.Equal((uint)bytes.Length, entry.Count);
Assert.Equal(bytes, entry.Value);
}
[Theory]
[InlineDataAttribute(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, 0)]
[InlineDataAttribute(new byte[] { 2, 0, 0, 0, 1, 0, 0, 0 }, 2, 1)]
[InlineDataAttribute(new byte[] { 1, 0, 0, 0, 2, 0, 0, 0 }, 1, 2)]
public void AddUnsignedRational_AddsSingleValue(byte[] bytes, uint numerator, uint denominator)
{
var entries = new List<TiffIfdEntry>();
entries.AddUnsignedRational(TiffTags.ImageWidth, new Rational(numerator, denominator));
var entry = entries[0];
Assert.Equal(TiffTags.ImageWidth, entry.Tag);
Assert.Equal(TiffType.Rational, entry.Type);
Assert.Equal(1u, entry.Count);
Assert.Equal(bytes, entry.Value);
}
[Theory]
[InlineDataAttribute(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, new uint[] { 0 }, new uint[] { 0 })]
[InlineDataAttribute(new byte[] { 1, 0, 0, 0, 2, 0, 0, 0 }, new uint[] { 1 }, new uint[] { 2 })]
[InlineDataAttribute(new byte[] { 1, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0 }, new uint[] { 1, 2 }, new uint[] { 2, 3 })]
public void AddUnsignedRational_AddsArray(byte[] bytes, uint[] numerators, uint[] denominators)
{
var entries = new List<TiffIfdEntry>();
Rational[] value = Enumerable.Range(0, numerators.Length).Select(i => new Rational(numerators[i], denominators[i])).ToArray();
entries.AddUnsignedRational(TiffTags.ImageWidth, value);
var entry = entries[0];
Assert.Equal(TiffTags.ImageWidth, entry.Tag);
Assert.Equal(TiffType.Rational, entry.Type);
Assert.Equal((uint)numerators.Length, entry.Count);
Assert.Equal(bytes, entry.Value);
}
[Theory]
[InlineDataAttribute(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, 0, 0)]
[InlineDataAttribute(new byte[] { 2, 0, 0, 0, 1, 0, 0, 0 }, 2, 1)]
[InlineDataAttribute(new byte[] { 1, 0, 0, 0, 2, 0, 0, 0 }, 1, 2)]
[InlineDataAttribute(new byte[] { 255, 255, 255, 255, 2, 0, 0, 0 }, -1, 2)]
public void AddSignedRational_AddsSingleValue(byte[] bytes, int numerator, int denominator)
{
var entries = new List<TiffIfdEntry>();
entries.AddSignedRational(TiffTags.ImageWidth, new SignedRational(numerator, denominator));
var entry = entries[0];
Assert.Equal(TiffTags.ImageWidth, entry.Tag);
Assert.Equal(TiffType.SRational, entry.Type);
Assert.Equal(1u, entry.Count);
Assert.Equal(bytes, entry.Value);
}
[Theory]
[InlineDataAttribute(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, new int[] { 0 }, new int[] { 0 })]
[InlineDataAttribute(new byte[] { 2, 0, 0, 0, 1, 0, 0, 0 }, new int[] { 2 }, new int[] { 1 })]
[InlineDataAttribute(new byte[] { 1, 0, 0, 0, 2, 0, 0, 0 }, new int[] { 1 }, new int[] { 2 })]
[InlineDataAttribute(new byte[] { 255, 255, 255, 255, 2, 0, 0, 0 }, new int[] { -1 }, new int[] { 2 })]
[InlineDataAttribute(new byte[] { 255, 255, 255, 255, 2, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0 }, new int[] { -1, 2 }, new int[] { 2, 3 })]
public void AddSignedRational_AddsArray(byte[] bytes, int[] numerators, int[] denominators)
{
var entries = new List<TiffIfdEntry>();
SignedRational[] value = Enumerable.Range(0, numerators.Length).Select(i => new SignedRational(numerators[i], denominators[i])).ToArray();
entries.AddSignedRational(TiffTags.ImageWidth, value);
var entry = entries[0];
Assert.Equal(TiffTags.ImageWidth, entry.Tag);
Assert.Equal(TiffType.SRational, entry.Type);
Assert.Equal((uint)numerators.Length, entry.Count);
Assert.Equal(bytes, entry.Value);
}
[Theory]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00 }, 0.0F)]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x80, 0x3F }, 1.0F)]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0xC0 }, -2.0F)]
[InlineDataAttribute(new byte[] { 0xFF, 0xFF, 0x7F, 0x7F }, float.MaxValue)]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x80, 0x7F }, float.PositiveInfinity)]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x80, 0xFF }, float.NegativeInfinity)]
public void AddFloat_AddsSingleValue(byte[] bytes, float value)
{
var entries = new List<TiffIfdEntry>();
entries.AddFloat(TiffTags.ImageWidth, value);
var entry = entries[0];
Assert.Equal(TiffTags.ImageWidth, entry.Tag);
Assert.Equal(TiffType.Float, entry.Type);
Assert.Equal(1u, entry.Count);
Assert.Equal(bytes, entry.Value);
}
[Theory]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00 }, new float[] { 0.0F })]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x80, 0x3F }, new float[] { 1.0F })]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0xC0 }, new float[] { -2.0F })]
[InlineDataAttribute(new byte[] { 0xFF, 0xFF, 0x7F, 0x7F }, new float[] { float.MaxValue })]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x80, 0x7F }, new float[] { float.PositiveInfinity })]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x80, 0xFF }, new float[] { float.NegativeInfinity })]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0xC0 }, new float[] { 0.0F, 1.0F, -2.0F })]
public void AddFloat_AddsArray(byte[] bytes, float[] value)
{
var entries = new List<TiffIfdEntry>();
entries.AddFloat(TiffTags.ImageWidth, value);
var entry = entries[0];
Assert.Equal(TiffTags.ImageWidth, entry.Tag);
Assert.Equal(TiffType.Float, entry.Type);
Assert.Equal((uint)value.Length, entry.Count);
Assert.Equal(bytes, entry.Value);
}
[Theory]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 0.0)]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F }, 1.0)]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40 }, 2.0)]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0 }, -2.0)]
[InlineDataAttribute(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x7F }, double.MaxValue)]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x7F }, double.PositiveInfinity)]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF }, double.NegativeInfinity)]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF }, double.NaN)]
public void AddDouble_AddsSingleValue(byte[] bytes, double value)
{
var entries = new List<TiffIfdEntry>();
entries.AddDouble(TiffTags.ImageWidth, value);
var entry = entries[0];
Assert.Equal(TiffTags.ImageWidth, entry.Tag);
Assert.Equal(TiffType.Double, entry.Type);
Assert.Equal(1u, entry.Count);
Assert.Equal(bytes, entry.Value);
}
[Theory]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, new double[] { 0.0 })]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F }, new double[] { 1.0 })]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40 }, new double[] { 2.0 })]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0 }, new double[] { -2.0 })]
[InlineDataAttribute(new byte[] { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEF, 0x7F }, new double[] { double.MaxValue })]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x7F }, new double[] { double.PositiveInfinity })]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF }, new double[] { double.NegativeInfinity })]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF }, new double[] { double.NaN })]
[InlineDataAttribute(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0 }, new double[] { 0.0, 1.0, -2.0 })]
public void AddDouble_AddsArray(byte[] bytes, double[] value)
{
var entries = new List<TiffIfdEntry>();
entries.AddDouble(TiffTags.ImageWidth, value);
var entry = entries[0];
Assert.Equal(TiffTags.ImageWidth, entry.Tag);
Assert.Equal(TiffType.Double, entry.Type);
Assert.Equal((uint)value.Length, entry.Count);
Assert.Equal(bytes, entry.Value);
}
}
}

23
tests/ImageSharp.Tests/Formats/Tiff/TiffIfd/TiffIfdEntryTests.cs

@ -0,0 +1,23 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests
{
using Xunit;
using ImageSharp.Formats.Tiff;
public class TiffIfdEntryTests
{
[Fact]
public void Constructor_SetsProperties()
{
var entry = new TiffIfdEntry((ushort)10u, TiffType.Short, 20u, new byte[] { 2, 4, 6, 8 });
Assert.Equal(10u, entry.Tag);
Assert.Equal(TiffType.Short, entry.Type);
Assert.Equal(20u, entry.Count);
Assert.Equal(new byte[] { 2, 4, 6, 8 }, entry.Value);
}
}
}

93
tests/ImageSharp.Tests/Formats/Tiff/TiffIfd/TiffIfdTests.cs

@ -0,0 +1,93 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests
{
using Xunit;
using ImageSharp.Formats.Tiff;
public class TiffIfdTests
{
[Fact]
public void Constructor_SetsProperties()
{
var entries = new TiffIfdEntry[10];
var ifd = new TiffIfd(entries, 1234u);
Assert.Equal(entries, ifd.Entries);
Assert.Equal(1234u, ifd.NextIfdOffset);
}
[Fact]
public void GetIfdEntry_ReturnsIfdIfExists()
{
var entries = new[]
{
new TiffIfdEntry(10, TiffType.Short, 20, new byte[4]),
new TiffIfdEntry(20, TiffType.Short, 20, new byte[4]),
new TiffIfdEntry(30, TiffType.Short, 20, new byte[4]),
new TiffIfdEntry(40, TiffType.Short, 20, new byte[4])
};
var ifd = new TiffIfd(entries, 1234u);
TiffIfdEntry? entry = ifd.GetIfdEntry(30);
Assert.True(entry.HasValue);
Assert.Equal(30, entry.Value.Tag);
}
[Fact]
public void GetIfdEntry_ReturnsNullOtherwise()
{
var entries = new[]
{
new TiffIfdEntry(10, TiffType.Short, 20, new byte[4]),
new TiffIfdEntry(20, TiffType.Short, 20, new byte[4]),
new TiffIfdEntry(30, TiffType.Short, 20, new byte[4]),
new TiffIfdEntry(40, TiffType.Short, 20, new byte[4])
};
var ifd = new TiffIfd(entries, 1234u);
TiffIfdEntry? entry = ifd.GetIfdEntry(25);
Assert.False(entry.HasValue);
}
[Fact]
public void TryGetIfdEntry_ReturnsIfdIfExists()
{
var entries = new[]
{
new TiffIfdEntry(10, TiffType.Short, 20, new byte[4]),
new TiffIfdEntry(20, TiffType.Short, 20, new byte[4]),
new TiffIfdEntry(30, TiffType.Short, 20, new byte[4]),
new TiffIfdEntry(40, TiffType.Short, 20, new byte[4])
};
var ifd = new TiffIfd(entries, 1234u);
bool success = ifd.TryGetIfdEntry(30, out var entry);
Assert.True(success);
Assert.Equal(30, entry.Tag);
}
[Fact]
public void TryGetIfdEntry_ReturnsFalseOtherwise()
{
var entries = new[]
{
new TiffIfdEntry(10, TiffType.Short, 20, new byte[4]),
new TiffIfdEntry(20, TiffType.Short, 20, new byte[4]),
new TiffIfdEntry(30, TiffType.Short, 20, new byte[4]),
new TiffIfdEntry(40, TiffType.Short, 20, new byte[4])
};
var ifd = new TiffIfd(entries, 1234u);
bool success = ifd.TryGetIfdEntry(25, out var entry);
Assert.False(success);
Assert.Equal(0, entry.Tag);
}
}
}

87
tests/ImageSharp.Tests/Formats/Tiff/TiffImageFormatDetectorTests.cs

@ -0,0 +1,87 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests
{
using System.Linq;
using Xunit;
using ImageSharp.Formats;
public class TiffImageFormatDetectorTests
{
public static object[][] IsLittleEndianValues = new[] { new object[] { false },
new object[] { true } };
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void DetectFormat_ReturnsTiffFormat_ForValidFile(bool isLittleEndian)
{
byte[] bytes = new TiffGenHeader()
{
FirstIfd = new TiffGenIfd()
}
.ToBytes(isLittleEndian);
TiffImageFormatDetector formatDetector = new TiffImageFormatDetector();
byte[] headerBytes = bytes.Take(formatDetector.HeaderSize).ToArray();
var format = formatDetector.DetectFormat(headerBytes);
Assert.NotNull(format);
Assert.IsType<TiffFormat>(format);
}
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void DetectFormat_ReturnsNull_WithInvalidByteOrderMarkers(bool isLittleEndian)
{
byte[] bytes = new TiffGenHeader()
{
FirstIfd = new TiffGenIfd(),
ByteOrderMarker = 0x1234
}
.ToBytes(isLittleEndian);
TiffImageFormatDetector formatDetector = new TiffImageFormatDetector();
byte[] headerBytes = bytes.Take(formatDetector.HeaderSize).ToArray();
var format = formatDetector.DetectFormat(headerBytes);
Assert.Null(format);
}
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void DetectFormat_ReturnsNull_WithIncorrectMagicNumber(bool isLittleEndian)
{
byte[] bytes = new TiffGenHeader()
{
FirstIfd = new TiffGenIfd(),
MagicNumber = 32
}
.ToBytes(isLittleEndian);
TiffImageFormatDetector formatDetector = new TiffImageFormatDetector();
byte[] headerBytes = bytes.Take(formatDetector.HeaderSize).ToArray();
var format = formatDetector.DetectFormat(headerBytes);
Assert.Null(format);
}
[Theory]
[MemberData(nameof(IsLittleEndianValues))]
public void DetectFormat_ReturnsNull_WithShortHeader(bool isLittleEndian)
{
byte[] bytes = new TiffGenHeader()
{
FirstIfd = new TiffGenIfd()
}
.ToBytes(isLittleEndian);
TiffImageFormatDetector formatDetector = new TiffImageFormatDetector();
byte[] headerBytes = bytes.Take(formatDetector.HeaderSize - 1).ToArray();
var format = formatDetector.DetectFormat(headerBytes);
Assert.Null(format);
}
}
}

326
tests/ImageSharp.Tests/Formats/Tiff/Utils/SubStreamTests.cs

@ -0,0 +1,326 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests
{
using System;
using System.IO;
using Xunit;
using ImageSharp.Formats.Tiff;
public class SubStreamTests
{
[Fact]
public void Constructor_PositionsStreamCorrectly_WithSpecifiedOffset()
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
innerStream.Position = 2;
SubStream stream = new SubStream(innerStream, 4, 6);
Assert.Equal(0, stream.Position);
Assert.Equal(6, stream.Length);
Assert.Equal(4, innerStream.Position);
}
[Fact]
public void Constructor_PositionsStreamCorrectly_WithCurrentOffset()
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
innerStream.Position = 2;
SubStream stream = new SubStream(innerStream, 6);
Assert.Equal(0, stream.Position);
Assert.Equal(6, stream.Length);
Assert.Equal(2, innerStream.Position);
}
[Fact]
public void CanRead_ReturnsTrue()
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
SubStream stream = new SubStream(innerStream, 2, 6);
Assert.True(stream.CanRead);
}
[Fact]
public void CanWrite_ReturnsFalse()
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
SubStream stream = new SubStream(innerStream, 2, 6);
Assert.False(stream.CanWrite);
}
[Fact]
public void CanSeek_ReturnsTrue()
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
SubStream stream = new SubStream(innerStream, 2, 6);
Assert.True(stream.CanSeek);
}
[Fact]
public void Length_ReturnsTheConstrainedLength()
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
SubStream stream = new SubStream(innerStream, 2, 6);
Assert.Equal(6, stream.Length);
}
[Fact]
public void Position_ReturnsZeroBeforeReading()
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
SubStream stream = new SubStream(innerStream, 2, 6);
Assert.Equal(0, stream.Position);
Assert.Equal(2, innerStream.Position);
}
[Fact]
public void Position_ReturnsPositionAfterReading()
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
SubStream stream = new SubStream(innerStream, 2, 6);
stream.Read(new byte[2], 0, 2);
Assert.Equal(2, stream.Position);
Assert.Equal(4, innerStream.Position);
}
[Fact]
public void Position_ReturnsPositionAfterReadingTwice()
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
SubStream stream = new SubStream(innerStream, 2, 6);
stream.Read(new byte[2], 0, 2);
stream.Read(new byte[2], 0, 2);
Assert.Equal(4, stream.Position);
Assert.Equal(6, innerStream.Position);
}
[Fact]
public void Position_SettingPropertySeeksToNewPosition()
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
SubStream stream = new SubStream(innerStream, 2, 6);
stream.Position = 3;
Assert.Equal(3, stream.Position);
Assert.Equal(5, innerStream.Position);
}
[Fact]
public void Flush_ThrowsNotSupportedException()
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
SubStream stream = new SubStream(innerStream, 2, 6);
Assert.Throws<NotSupportedException>(() => stream.Flush());
}
[Fact]
public void Read_Reads_FromStartOfSubStream()
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
SubStream stream = new SubStream(innerStream, 2, 6);
byte[] buffer = new byte[3];
var result = stream.Read(buffer, 0, 3);
Assert.Equal(new byte[] { 3, 4, 5 }, buffer);
Assert.Equal(3, result);
}
[Theory]
[InlineData(2, SeekOrigin.Begin)]
[InlineData(1, SeekOrigin.Current)]
[InlineData(4, SeekOrigin.End)]
public void Read_Reads_FromMiddleOfSubStream(long offset, SeekOrigin origin)
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
SubStream stream = new SubStream(innerStream, 2, 6);
stream.Position = 1;
stream.Seek(offset, origin);
byte[] buffer = new byte[3];
var result = stream.Read(buffer, 0, 3);
Assert.Equal(new byte[] { 5, 6, 7 }, buffer);
Assert.Equal(3, result);
}
[Theory]
[InlineData(3, SeekOrigin.Begin)]
[InlineData(2, SeekOrigin.Current)]
[InlineData(3, SeekOrigin.End)]
public void Read_Reads_FromEndOfSubStream(long offset, SeekOrigin origin)
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
SubStream stream = new SubStream(innerStream, 2, 6);
stream.Position = 1;
stream.Seek(offset, origin);
byte[] buffer = new byte[3];
var result = stream.Read(buffer, 0, 3);
Assert.Equal(new byte[] { 6, 7, 8 }, buffer);
Assert.Equal(3, result);
}
[Theory]
[InlineData(4, SeekOrigin.Begin)]
[InlineData(3, SeekOrigin.Current)]
[InlineData(2, SeekOrigin.End)]
public void Read_Reads_FromBeyondEndOfSubStream(long offset, SeekOrigin origin)
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
SubStream stream = new SubStream(innerStream, 2, 6);
stream.Position = 1;
stream.Seek(offset, origin);
byte[] buffer = new byte[3];
var result = stream.Read(buffer, 0, 3);
Assert.Equal(new byte[] { 7, 8, 0 }, buffer);
Assert.Equal(2, result);
}
[Fact]
public void ReadByte_Reads_FromStartOfSubStream()
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
SubStream stream = new SubStream(innerStream, 2, 6);
var result = stream.ReadByte();
Assert.Equal(3, result);
}
[Fact]
public void ReadByte_Reads_FromMiddleOfSubStream()
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
SubStream stream = new SubStream(innerStream, 2, 6);
stream.Position = 3;
var result = stream.ReadByte();
Assert.Equal(6, result);
}
[Fact]
public void ReadByte_Reads_FromEndOfSubStream()
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
SubStream stream = new SubStream(innerStream, 2, 6);
stream.Position = 5;
var result = stream.ReadByte();
Assert.Equal(8, result);
}
[Fact]
public void ReadByte_Reads_FromBeyondEndOfSubStream()
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
SubStream stream = new SubStream(innerStream, 2, 6);
stream.Position = 5;
stream.ReadByte();
var result = stream.ReadByte();
Assert.Equal(-1, result);
}
[Fact]
public void Write_ThrowsNotSupportedException()
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
SubStream stream = new SubStream(innerStream, 2, 6);
Assert.Throws<NotSupportedException>(() => stream.Write(new byte[] { 1, 2 }, 0, 2));
}
[Fact]
public void WriteByte_ThrowsNotSupportedException()
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
SubStream stream = new SubStream(innerStream, 2, 6);
Assert.Throws<NotSupportedException>(() => stream.WriteByte(42));
}
[Fact]
public void Seek_MovesToNewPosition_FromBegin()
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
SubStream stream = new SubStream(innerStream, 2, 6);
stream.Position = 1;
long result = stream.Seek(2, SeekOrigin.Begin);
Assert.Equal(2, result);
Assert.Equal(2, stream.Position);
Assert.Equal(4, innerStream.Position);
}
[Fact]
public void Seek_MovesToNewPosition_FromCurrent()
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
SubStream stream = new SubStream(innerStream, 2, 6);
stream.Position = 1;
long result = stream.Seek(2, SeekOrigin.Current);
Assert.Equal(3, result);
Assert.Equal(3, stream.Position);
Assert.Equal(5, innerStream.Position);
}
[Fact]
public void Seek_MovesToNewPosition_FromEnd()
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
SubStream stream = new SubStream(innerStream, 2, 6);
stream.Position = 1;
long result = stream.Seek(2, SeekOrigin.End);
Assert.Equal(4, result);
Assert.Equal(4, stream.Position);
Assert.Equal(6, innerStream.Position);
}
[Fact]
public void Seek_ThrowsException_WithInvalidOrigin()
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
SubStream stream = new SubStream(innerStream, 2, 6);
var e = Assert.Throws<ArgumentException>(() => stream.Seek(2, (SeekOrigin)99));
Assert.Equal("Invalid seek origin.", e.Message);
}
[Fact]
public void SetLength_ThrowsNotSupportedException()
{
Stream innerStream = new MemoryStream(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
SubStream stream = new SubStream(innerStream, 2, 6);
Assert.Throws<NotSupportedException>(() => stream.SetLength(5));
}
}
}

132
tests/ImageSharp.Tests/Formats/Tiff/Utils/TiffWriterTests.cs

@ -0,0 +1,132 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests
{
using System.IO;
using Xunit;
using ImageSharp.Formats.Tiff;
public class TiffWriterTests
{
[Fact]
public void IsLittleEndian_IsTrueOnWindows()
{
MemoryStream stream = new MemoryStream();
using (TiffWriter writer = new TiffWriter(stream))
{
Assert.True(writer.IsLittleEndian);
}
}
[Theory]
[InlineData(new byte[] {}, 0)]
[InlineData(new byte[] { 42 }, 1)]
[InlineData(new byte[] { 1, 2, 3, 4, 5 }, 5)]
public void Position_EqualsTheStreamPosition(byte[] data, long expectedResult)
{
MemoryStream stream = new MemoryStream();
using (TiffWriter writer = new TiffWriter(stream))
{
writer.Write(data);
Assert.Equal(writer.Position, expectedResult);
}
}
[Fact]
public void Write_WritesByte()
{
MemoryStream stream = new MemoryStream();
using (TiffWriter writer = new TiffWriter(stream))
{
writer.Write((byte)42);
}
Assert.Equal(new byte[] { 42 }, stream.ToArray());
}
[Fact]
public void Write_WritesByteArray()
{
MemoryStream stream = new MemoryStream();
using (TiffWriter writer = new TiffWriter(stream))
{
writer.Write(new byte[] { 2, 4, 6, 8 });
}
Assert.Equal(new byte[] { 2, 4, 6, 8 }, stream.ToArray());
}
[Fact]
public void Write_WritesUInt16()
{
MemoryStream stream = new MemoryStream();
using (TiffWriter writer = new TiffWriter(stream))
{
writer.Write((ushort)1234);
}
Assert.Equal(new byte[] { 0xD2, 0x04 }, stream.ToArray());
}
[Fact]
public void Write_WritesUInt32()
{
MemoryStream stream = new MemoryStream();
using (TiffWriter writer = new TiffWriter(stream))
{
writer.Write((uint)12345678);
}
Assert.Equal(new byte[] { 0x4E, 0x61, 0xBC, 0x00 }, stream.ToArray());
}
[Theory]
[InlineData(new byte[] { }, new byte[] { 0, 0, 0, 0 })]
[InlineData(new byte[] { 2 }, new byte[] { 2, 0, 0, 0 })]
[InlineData(new byte[] { 2, 4 }, new byte[] { 2, 4, 0, 0 })]
[InlineData(new byte[] { 2, 4, 6 }, new byte[] { 2, 4, 6, 0 })]
[InlineData(new byte[] { 2, 4, 6, 8 }, new byte[] { 2, 4, 6, 8 })]
[InlineData(new byte[] { 2, 4, 6, 8, 10, 12 }, new byte[] { 2, 4, 6, 8, 10, 12 })]
public void WritePadded_WritesByteArray(byte[] bytes, byte[] expectedResult)
{
MemoryStream stream = new MemoryStream();
using (TiffWriter writer = new TiffWriter(stream))
{
writer.WritePadded(bytes);
}
Assert.Equal(expectedResult, stream.ToArray());
}
[Fact]
public void WriteMarker_WritesToPlacedPosition()
{
MemoryStream stream = new MemoryStream();
using (TiffWriter writer = new TiffWriter(stream))
{
writer.Write((uint)0x11111111);
long marker = writer.PlaceMarker();
writer.Write((uint)0x33333333);
writer.WriteMarker(marker, 0x12345678);
writer.Write((uint)0x44444444);
}
Assert.Equal(new byte[] { 0x11, 0x11, 0x11, 0x11,
0x78, 0x56, 0x34, 0x12,
0x33, 0x33, 0x33, 0x33,
0x44, 0x44, 0x44, 0x44 }, stream.ToArray());
}
}
}

BIN
tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_grayscale_uncompressed.tiff

Binary file not shown.

BIN
tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_palette_uncompressed.tiff

Binary file not shown.

BIN
tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_deflate.tiff

Binary file not shown.

BIN
tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_jpeg.tiff

Binary file not shown.

BIN
tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_lzw.tiff

Binary file not shown.

BIN
tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_packbits.tiff

Binary file not shown.

BIN
tests/ImageSharp.Tests/TestImages/Formats/Tiff/Calliphora_rgb_uncompressed.tiff

Binary file not shown.

12
tests/ImageSharp.Tests/TestImages/Formats/Tiff/genimages.ps1

@ -0,0 +1,12 @@
$Gm_Exe = "C:\Program Files\GraphicsMagick-1.3.25-Q16\gm.exe"
$Source_Image = "..\Jpg\baseline\Calliphora.jpg"
$Output_Prefix = ".\Calliphora"
& $Gm_Exe convert $Source_Image -compress None -type TrueColor $Output_Prefix"_rgb_uncompressed.tiff"
& $Gm_Exe convert $Source_Image -compress LZW -type TrueColor $Output_Prefix"_rgb_lzw.tiff"
& $Gm_Exe convert $Source_Image -compress RLE -type TrueColor $Output_Prefix"_rgb_packbits.tiff"
& $Gm_Exe convert $Source_Image -compress JPEG -type TrueColor $Output_Prefix"_rgb_jpeg.tiff"
& $Gm_Exe convert $Source_Image -compress Zip -type TrueColor $Output_Prefix"_rgb_deflate.tiff"
& $Gm_Exe convert $Source_Image -compress None -type Grayscale $Output_Prefix"_grayscale_uncompressed.tiff"
& $Gm_Exe convert $Source_Image -compress None -colors 256 $Output_Prefix"_palette_uncompressed.tiff"

25
tests/ImageSharp.Tests/TestUtilities/ByteArrayUtility.cs

@ -0,0 +1,25 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests
{
using System;
public static class ByteArrayUtility
{
public static byte[] WithByteOrder(this byte[] bytes, bool isLittleEndian)
{
if (BitConverter.IsLittleEndian != isLittleEndian)
{
byte[] reversedBytes = new byte[bytes.Length];
Array.Copy(bytes, reversedBytes, bytes.Length);
Array.Reverse(reversedBytes);
return reversedBytes;
}
else
{
return bytes;
}
}
}
}

39
tests/ImageSharp.Tests/TestUtilities/ByteBuffer.cs

@ -0,0 +1,39 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests
{
using System;
using System.Collections.Generic;
public class ByteBuffer
{
List<byte> bytes = new List<byte>();
bool isLittleEndian;
public ByteBuffer(bool isLittleEndian)
{
this.isLittleEndian = isLittleEndian;
}
public void AddByte(byte value)
{
bytes.Add(value);
}
public void AddUInt16(ushort value)
{
bytes.AddRange(BitConverter.GetBytes(value).WithByteOrder(isLittleEndian));
}
public void AddUInt32(uint value)
{
bytes.AddRange(BitConverter.GetBytes(value).WithByteOrder(isLittleEndian));
}
public byte[] ToArray()
{
return bytes.ToArray();
}
}
}

15
tests/ImageSharp.Tests/TestUtilities/Tiff/ITiffGenDataSource.cs

@ -0,0 +1,15 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests
{
using System.Collections.Generic;
/// <summary>
/// An interface for any class within the Tiff generator that produces data to be included in the file.
/// </summary>
internal interface ITiffGenDataSource
{
IEnumerable<TiffGenDataBlock> GetData(bool isLittleEndian);
}
}

28
tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenDataBlock.cs

@ -0,0 +1,28 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests
{
using System.Collections.Generic;
/// <summary>
/// A utility data structure to represent an independent block of data in a Tiff file.
/// These may be located in any order within a Tiff file.
/// </summary>
internal class TiffGenDataBlock
{
public TiffGenDataBlock(byte[] bytes)
{
this.Bytes = bytes;
this.References = new List<TiffGenDataReference>();
}
public byte[] Bytes { get; }
public IList<TiffGenDataReference> References { get; }
public void AddReference(byte[] bytes, int offset)
{
References.Add(new TiffGenDataReference(bytes, offset));
}
}
}

20
tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenDataReference.cs

@ -0,0 +1,20 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests
{
/// <summary>
/// A utility data structure to represent a reference from one block of data to another in a Tiff file.
/// </summary>
internal class TiffGenDataReference
{
public TiffGenDataReference(byte[] bytes, int offset)
{
this.Bytes = bytes;
this.Offset = offset;
}
public byte[] Bytes { get; }
public int Offset { get; }
}
}

204
tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenEntry.cs

@ -0,0 +1,204 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ImageSharp.Formats.Tiff;
/// <summary>
/// A utility data structure to represent Tiff IFD entries in unit tests.
/// </summary>
internal abstract class TiffGenEntry : ITiffGenDataSource
{
private TiffGenEntry(ushort tag, TiffType type, uint count)
{
this.Tag = tag;
this.Type = type;
this.Count = count;
}
public uint Count { get; }
public ushort Tag { get; }
public TiffType Type { get; }
public abstract IEnumerable<TiffGenDataBlock> GetData(bool isLittleEndian);
public static TiffGenEntry Ascii(ushort tag, string value)
{
return new TiffGenEntryAscii(tag, value);
}
public static TiffGenEntry Bytes(ushort tag, TiffType type, uint count, byte[] value)
{
return new TiffGenEntryBytes(tag, type, count, value);
}
public static TiffGenEntry Integer(ushort tag, TiffType type, int value)
{
return TiffGenEntry.Integer(tag, type, new int[] { value });
}
public static TiffGenEntry Integer(ushort tag, TiffType type, int[] value)
{
if (type != TiffType.Byte && type != TiffType.Short && type != TiffType.Long &&
type != TiffType.SByte && type != TiffType.SShort && type != TiffType.SLong)
throw new ArgumentException(nameof(type), "The specified type is not an integer type.");
return new TiffGenEntryInteger(tag, type, value);
}
public static TiffGenEntry Integer(ushort tag, TiffType type, uint value)
{
return TiffGenEntry.Integer(tag, type, new uint[] { value });
}
public static TiffGenEntry Integer(ushort tag, TiffType type, uint[] value)
{
if (type != TiffType.Byte && type != TiffType.Short && type != TiffType.Long &&
type != TiffType.SByte && type != TiffType.SShort && type != TiffType.SLong)
throw new ArgumentException(nameof(type), "The specified type is not an integer type.");
return new TiffGenEntryUnsignedInteger(tag, type, value);
}
public static TiffGenEntry Rational(ushort tag, uint numerator, uint denominator)
{
return new TiffGenEntryRational(tag, numerator, denominator);
}
private class TiffGenEntryAscii : TiffGenEntry
{
public TiffGenEntryAscii(ushort tag, string value) : base(tag, TiffType.Ascii, (uint)GetBytes(value).Length)
{
this.Value = value;
}
public string Value { get; }
public override IEnumerable<TiffGenDataBlock> GetData(bool isLittleEndian)
{
byte[] bytes = GetBytes(Value);
return new[] { new TiffGenDataBlock(bytes) };
}
private static byte[] GetBytes(string value)
{
return Encoding.ASCII.GetBytes($"{value}\0");
}
}
private class TiffGenEntryBytes : TiffGenEntry
{
public TiffGenEntryBytes(ushort tag, TiffType type, uint count, byte[] value) : base(tag, type, count)
{
this.Value = value;
}
public byte[] Value { get; }
public override IEnumerable<TiffGenDataBlock> GetData(bool isLittleEndian)
{
return new[] { new TiffGenDataBlock(Value) };
}
}
private class TiffGenEntryInteger : TiffGenEntry
{
public TiffGenEntryInteger(ushort tag, TiffType type, int[] value) : base(tag, type, (uint)value.Length)
{
this.Value = value;
}
public int[] Value { get; }
public override IEnumerable<TiffGenDataBlock> GetData(bool isLittleEndian)
{
byte[] bytes = GetBytes().SelectMany(b => b.WithByteOrder(isLittleEndian)).ToArray();
return new[] { new TiffGenDataBlock(bytes) };
}
private IEnumerable<byte[]> GetBytes()
{
switch (Type)
{
case TiffType.Byte:
return Value.Select(i => new byte[] { (byte)i });
case TiffType.Short:
return Value.Select(i => BitConverter.GetBytes((ushort)i));
case TiffType.Long:
return Value.Select(i => BitConverter.GetBytes((uint)i));
case TiffType.SByte:
return Value.Select(i => BitConverter.GetBytes((sbyte)i));
case TiffType.SShort:
return Value.Select(i => BitConverter.GetBytes((short)i));
case TiffType.SLong:
return Value.Select(i => BitConverter.GetBytes((int)i));
default:
throw new InvalidOperationException();
}
}
}
private class TiffGenEntryUnsignedInteger : TiffGenEntry
{
public TiffGenEntryUnsignedInteger(ushort tag, TiffType type, uint[] value) : base(tag, type, (uint)value.Length)
{
this.Value = value;
}
public uint[] Value { get; }
public override IEnumerable<TiffGenDataBlock> GetData(bool isLittleEndian)
{
byte[] bytes = GetBytes().SelectMany(b => b.WithByteOrder(isLittleEndian)).ToArray();
return new[] { new TiffGenDataBlock(bytes) };
}
private IEnumerable<byte[]> GetBytes()
{
switch (Type)
{
case TiffType.Byte:
return Value.Select(i => new byte[] { (byte)i });
case TiffType.Short:
return Value.Select(i => BitConverter.GetBytes((ushort)i));
case TiffType.Long:
return Value.Select(i => BitConverter.GetBytes((uint)i));
case TiffType.SByte:
return Value.Select(i => BitConverter.GetBytes((sbyte)i));
case TiffType.SShort:
return Value.Select(i => BitConverter.GetBytes((short)i));
case TiffType.SLong:
return Value.Select(i => BitConverter.GetBytes((int)i));
default:
throw new InvalidOperationException();
}
}
}
private class TiffGenEntryRational : TiffGenEntry
{
public TiffGenEntryRational(ushort tag, uint numerator, uint denominator) : base(tag, TiffType.Rational, 1u)
{
this.Numerator = numerator;
this.Denominator = denominator;
}
public uint Numerator { get; }
public uint Denominator { get; }
public override IEnumerable<TiffGenDataBlock> GetData(bool isLittleEndian)
{
byte[] numeratorBytes = BitConverter.GetBytes(Numerator).WithByteOrder(isLittleEndian);
byte[] denominatorBytes = BitConverter.GetBytes(Denominator).WithByteOrder(isLittleEndian);
byte[] bytes = Enumerable.Concat(numeratorBytes, denominatorBytes).ToArray();
return new[] { new TiffGenDataBlock(bytes) };
}
}
}
}

45
tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenExtensions.cs

@ -0,0 +1,45 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests
{
using System;
using System.IO;
using System.Linq;
/// <summary>
/// A utility class for generating in-memory Tiff files for use in unit tests.
/// </summary>
internal static class TiffGenExtensions
{
public static byte[] ToBytes(this ITiffGenDataSource dataSource, bool isLittleEndian)
{
var dataBlocks = dataSource.GetData(isLittleEndian);
int offset = 0;
foreach (var dataBlock in dataBlocks)
{
byte[] offsetBytes = BitConverter.GetBytes(offset).WithByteOrder(isLittleEndian);
foreach (var reference in dataBlock.References)
{
reference.Bytes[reference.Offset + 0] = offsetBytes[0];
reference.Bytes[reference.Offset + 1] = offsetBytes[1];
reference.Bytes[reference.Offset + 2] = offsetBytes[2];
reference.Bytes[reference.Offset + 3] = offsetBytes[3];
}
offset += dataBlock.Bytes.Length;
}
return dataBlocks.SelectMany(b => b.Bytes).ToArray();
}
public static Stream ToStream(this ITiffGenDataSource dataSource, bool isLittleEndian)
{
var bytes = dataSource.ToBytes(isLittleEndian);
return new MemoryStream(bytes);
}
}
}

45
tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenHeader.cs

@ -0,0 +1,45 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests
{
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// A utility data structure to represent a Tiff file-header.
/// </summary>
internal class TiffGenHeader : ITiffGenDataSource
{
public TiffGenHeader()
{
this.MagicNumber = 42;
}
public ushort? ByteOrderMarker { get; set; }
public ushort MagicNumber { get; set; }
public TiffGenIfd FirstIfd { get; set; }
public IEnumerable<TiffGenDataBlock> GetData(bool isLittleEndian)
{
ByteBuffer bytes = new ByteBuffer(isLittleEndian);
bytes.AddUInt16(ByteOrderMarker ?? (isLittleEndian ? (ushort)0x4949 : (ushort)0x4D4D));
bytes.AddUInt16(MagicNumber);
bytes.AddUInt32(0);
var headerData = new TiffGenDataBlock(bytes.ToArray());
if (FirstIfd != null)
{
var firstIfdData = FirstIfd.GetData(isLittleEndian);
firstIfdData.First().AddReference(headerData.Bytes, 4);
return new[] { headerData }.Concat(firstIfdData);
}
else
{
return new[] { headerData };
}
}
}
}

88
tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenIfd.cs

@ -0,0 +1,88 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests
{
using System;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// A utility data structure to represent Tiff IFDs in unit tests.
/// </summary>
internal class TiffGenIfd : ITiffGenDataSource
{
public TiffGenIfd()
{
this.Entries = new List<TiffGenEntry>();
}
public List<TiffGenEntry> Entries { get; }
public TiffGenIfd NextIfd { get; set; }
public IEnumerable<TiffGenDataBlock> GetData(bool isLittleEndian)
{
ByteBuffer bytes = new ByteBuffer(isLittleEndian);
List<TiffGenDataBlock> dataBlocks = new List<TiffGenDataBlock>();
List<Tuple<TiffGenDataBlock, int>> entryReferences = new List<Tuple<TiffGenDataBlock, int>>();
// Add the entry count
bytes.AddUInt16((ushort)Entries.Count);
// Add all IFD entries
int entryOffset = 2;
foreach (var entry in Entries)
{
var entryData = entry.GetData(isLittleEndian);
var entryBytes = entryData.First().Bytes;
bytes.AddUInt16(entry.Tag);
bytes.AddUInt16((ushort)entry.Type);
bytes.AddUInt32(entry.Count);
if (entryBytes.Length <=4)
{
bytes.AddByte(entryBytes.Length > 0 ? entryBytes[0] : (byte)0);
bytes.AddByte(entryBytes.Length > 1 ? entryBytes[1] : (byte)0);
bytes.AddByte(entryBytes.Length > 2 ? entryBytes[2] : (byte)0);
bytes.AddByte(entryBytes.Length > 3 ? entryBytes[3] : (byte)0);
dataBlocks.AddRange(entryData.Skip(1));
}
else
{
bytes.AddUInt32(0);
dataBlocks.AddRange(entryData);
entryReferences.Add(Tuple.Create(entryData.First(), entryOffset + 8));
}
entryOffset += 12;
}
// Add reference to next IFD
bytes.AddUInt32(0);
// Build the data
var ifdData = new TiffGenDataBlock(bytes.ToArray());
foreach (var entryReference in entryReferences)
{
entryReference.Item1.AddReference(ifdData.Bytes, entryReference.Item2);
}
IEnumerable<TiffGenDataBlock> nextIfdData = new TiffGenDataBlock[0];
if (NextIfd != null)
{
nextIfdData = NextIfd.GetData(isLittleEndian);
nextIfdData.First().AddReference(ifdData.Bytes, ifdData.Bytes.Length - 4);
}
return new [] { ifdData }.Concat(dataBlocks).Concat(nextIfdData);
}
}
}

31
tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenIfdExtensions.cs

@ -0,0 +1,31 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Tests
{
using System.Linq;
/// <summary>
/// A utility class for manipulating in-memory Tiff files for use in unit tests.
/// </summary>
internal static class TiffGenIfdExtensions
{
public static TiffGenIfd WithoutEntry(this TiffGenIfd ifd, ushort tag)
{
TiffGenEntry entry = ifd.Entries.FirstOrDefault(e => e.Tag == tag);
if (entry != null)
{
ifd.Entries.Remove(entry);
}
return ifd;
}
public static TiffGenIfd WithEntry(this TiffGenIfd ifd, TiffGenEntry entry)
{
ifd.WithoutEntry(entry.Tag);
ifd.Entries.Add(entry);
return ifd;
}
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save