Browse Source

Merge pull request #1330 from IldarKhayrutdinov/tiff-codec_rebased

#12 Tiff codec
pull/1570/head
James Jackson-South 6 years ago
committed by GitHub
parent
commit
6fe387264e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .gitattributes
  2. 1
      ImageSharp.sln
  3. 2
      src/ImageSharp/Advanced/AotCompilerTools.cs
  4. 5
      src/ImageSharp/Configuration.cs
  5. 106
      src/ImageSharp/Formats/ImageExtensions.Save.cs
  6. 3
      src/ImageSharp/Formats/ImageExtensions.Save.tt
  7. 56
      src/ImageSharp/Formats/Tiff/Compression/DeflateTiffCompression.cs
  8. 30
      src/ImageSharp/Formats/Tiff/Compression/LzwTiffCompression.cs
  9. 26
      src/ImageSharp/Formats/Tiff/Compression/NoneTiffCompression.cs
  10. 71
      src/ImageSharp/Formats/Tiff/Compression/PackBitsTiffCompression.cs
  11. 29
      src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs
  12. 28
      src/ImageSharp/Formats/Tiff/Compression/TiffCompressionFactory.cs
  13. 31
      src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs
  14. 21
      src/ImageSharp/Formats/Tiff/Constants/TiffByteOrder.cs
  15. 71
      src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs
  16. 83
      src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs
  17. 26
      src/ImageSharp/Formats/Tiff/Constants/TiffExtraSamples.cs
  18. 21
      src/ImageSharp/Formats/Tiff/Constants/TiffFillOrder.cs
  19. 44
      src/ImageSharp/Formats/Tiff/Constants/TiffNewSubfileType.cs
  20. 51
      src/ImageSharp/Formats/Tiff/Constants/TiffOrientation.cs
  21. 71
      src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs
  22. 21
      src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs
  23. 26
      src/ImageSharp/Formats/Tiff/Constants/TiffPredictor.cs
  24. 26
      src/ImageSharp/Formats/Tiff/Constants/TiffResolutionUnit.cs
  25. 41
      src/ImageSharp/Formats/Tiff/Constants/TiffSampleFormat.cs
  26. 26
      src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs
  27. 26
      src/ImageSharp/Formats/Tiff/Constants/TiffThreshholding.cs
  28. 16
      src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs
  29. 12
      src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
  30. 104
      src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs
  31. 311
      src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs
  32. 51
      src/ImageSharp/Formats/Tiff/ImageExtensions.cs
  33. 28
      src/ImageSharp/Formats/Tiff/MetadataExtensions.cs
  34. 47
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor.cs
  35. 54
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor.cs
  36. 39
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor.cs
  37. 49
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor.cs
  38. 66
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor.cs
  39. 43
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor.cs
  40. 75
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor.cs
  41. 63
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor.cs
  42. 42
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBaseColorDecoder.cs
  43. 95
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory.cs
  44. 71
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs
  45. 46
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor.cs
  46. 54
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor.cs
  47. 39
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor.cs
  48. 49
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor.cs
  49. 250
      src/ImageSharp/Formats/Tiff/README.md
  50. 88
      src/ImageSharp/Formats/Tiff/Streams/TiffBigEndianStream.cs
  51. 88
      src/ImageSharp/Formats/Tiff/Streams/TiffLittleEndianStream.cs
  52. 94
      src/ImageSharp/Formats/Tiff/Streams/TiffStream.cs
  53. 53
      src/ImageSharp/Formats/Tiff/Streams/TiffStreamFactory.cs
  54. 19
      src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs
  55. 68
      src/ImageSharp/Formats/Tiff/TiffDecoder.cs
  56. 326
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  57. 354
      src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs
  58. 31
      src/ImageSharp/Formats/Tiff/TiffEncoder.cs
  59. 165
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  60. 40
      src/ImageSharp/Formats/Tiff/TiffFormat.cs
  61. 331
      src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
  62. 34
      src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs
  63. 44
      src/ImageSharp/Formats/Tiff/TiffMetadata.cs
  64. 66
      src/ImageSharp/Formats/Tiff/Utils/BitReader.cs
  65. 176
      src/ImageSharp/Formats/Tiff/Utils/SubStream.cs
  66. 271
      src/ImageSharp/Formats/Tiff/Utils/TiffLzwDecoder.cs
  67. 495
      src/ImageSharp/Formats/Tiff/Utils/TiffLzwEncoder.cs
  68. 48
      src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs
  69. 108
      src/ImageSharp/Formats/Tiff/Utils/TiffWriter.cs
  70. 352
      src/ImageSharp/Formats/Tiff/__obsolete/TiffIfdEntryCreator.cs
  71. 51
      src/ImageSharp/Formats/Tiff/__obsolete/TiffMetadataNames.cs
  72. 716
      src/ImageSharp/Formats/Tiff/__obsolete/TiffTagId.cs
  73. 76
      src/ImageSharp/Formats/Tiff/__obsolete/TiffTagType.cs
  74. 6
      src/ImageSharp/ImageSharp.csproj
  75. 7
      src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs
  76. 22
      src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs
  77. 10
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs
  78. 5
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Long.cs
  79. 5
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs
  80. 287
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTagValue.cs
  81. 6
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs
  82. 52
      tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs
  83. 76
      tests/ImageSharp.Benchmarks/Codecs/DecodeTiffBig.cs
  84. 10
      tests/ImageSharp.Tests/FileTestBase.cs
  85. 47
      tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
  86. 44
      tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs
  87. 26
      tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs
  88. 34
      tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs
  89. 172
      tests/ImageSharp.Tests/Formats/Tiff/ImageExtensionsTest.cs
  90. 162
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs
  91. 141
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs
  92. 69
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs
  93. 206
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs
  94. 159
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs
  95. 162
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColorTests.cs
  96. 85
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  97. 40
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderHeaderTests.cs
  98. 24
      tests/ImageSharp.Tests/Formats/Tiff/TiffFormatTests.cs
  99. 113
      tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs
  100. 326
      tests/ImageSharp.Tests/Formats/Tiff/Utils/SubStreamTests.cs

2
.gitattributes

@ -82,6 +82,8 @@
*.tga binary
*.ttc binary
*.ttf binary
*.tif binary
*.tiff binary
*.webp binary
*.woff binary
*.woff2 binary

1
ImageSharp.sln

@ -1,4 +1,3 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28902.138

2
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -94,6 +94,8 @@ namespace SixLabors.ImageSharp.Advanced
AotCodec<TPixel>(new Formats.Bmp.BmpDecoder(), new Formats.Bmp.BmpEncoder());
AotCodec<TPixel>(new Formats.Gif.GifDecoder(), new Formats.Gif.GifEncoder());
AotCodec<TPixel>(new Formats.Jpeg.JpegDecoder(), new Formats.Jpeg.JpegEncoder());
AotCodec<TPixel>(new Formats.Tga.TgaDecoder(), new Formats.Tga.TgaEncoder());
AotCodec<TPixel>(new Formats.Tiff.TiffDecoder(), new Formats.Tiff.TiffEncoder());
// TODO: Do the discovery work to figure out what works and what doesn't.
}

5
src/ImageSharp/Configuration.cs

@ -10,6 +10,7 @@ using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Processing;
@ -180,6 +181,7 @@ namespace SixLabors.ImageSharp
/// <see cref="GifConfigurationModule"/>
/// <see cref="BmpConfigurationModule"/>.
/// <see cref="TgaConfigurationModule"/>.
/// <see cref="TiffConfigurationModule"/>
/// </summary>
/// <returns>The default configuration of <see cref="Configuration"/>.</returns>
internal static Configuration CreateDefaultInstance()
@ -189,7 +191,8 @@ namespace SixLabors.ImageSharp
new JpegConfigurationModule(),
new GifConfigurationModule(),
new BmpConfigurationModule(),
new TgaConfigurationModule());
new TgaConfigurationModule(),
new TiffConfigurationModule());
}
}
}

106
src/ImageSharp/Formats/ImageExtensions.Save.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// <auto-generated />
@ -12,6 +12,7 @@ using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Tiff;
namespace SixLabors.ImageSharp
{
@ -535,5 +536,108 @@ namespace SixLabors.ImageSharp
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance),
cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsTiff(this Image source, string path) => SaveAsTiff(source, path, null);
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsTiffAsync(this Image source, string path) => SaveAsTiffAsync(source, path, null);
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsTiffAsync(this Image source, string path, CancellationToken cancellationToken)
=> SaveAsTiffAsync(source, path, null, cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsTiff(this Image source, string path, TiffEncoder encoder) =>
source.Save(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsTiffAsync(this Image source, string path, TiffEncoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance),
cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <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>
public static void SaveAsTiff(this Image source, Stream stream)
=> SaveAsTiff(source, stream, null);
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsTiffAsync(this Image source, Stream stream, CancellationToken cancellationToken = default)
=> SaveAsTiffAsync(source, stream, null, cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static void SaveAsTiff(this Image source, Stream stream, TiffEncoder encoder)
=> source.Save(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsTiffAsync(this Image source, Stream stream, TiffEncoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance),
cancellationToken);
}
}

3
src/ImageSharp/Formats/ImageExtensions.Save.tt

@ -1,4 +1,4 @@
<#@ template language="C#" #>
<#@ template language="C#" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
// Copyright (c) Six Labors.
@ -17,6 +17,7 @@ using SixLabors.ImageSharp.Advanced;
"Jpeg",
"Png",
"Tga",
"Tiff",
};
foreach (string fmt in formats)

56
src/ImageSharp/Formats/Tiff/Compression/DeflateTiffCompression.cs

@ -0,0 +1,56 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using System.IO.Compression;
using SixLabors.ImageSharp.Memory;
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 class DeflateTiffCompression : TiffBaseCompression
{
public DeflateTiffCompression(MemoryAllocator allocator)
: base(allocator)
{
}
/// <inheritdoc/>
public override void Decompress(Stream stream, int byteCount, Span<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;
var subStream = new SubStream(stream, byteCount - headerLength);
using (var deflateStream = new DeflateStream(subStream, CompressionMode.Decompress, true))
{
deflateStream.ReadFull(buffer);
}
}
}
}

30
src/ImageSharp/Formats/Tiff/Compression/LzwTiffCompression.cs

@ -0,0 +1,30 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Class to handle cases where TIFF image data is compressed using LZW compression.
/// </summary>
internal class LzwTiffCompression : TiffBaseCompression
{
public LzwTiffCompression(MemoryAllocator allocator)
: base(allocator)
{
}
/// <inheritdoc/>
public override void Decompress(Stream stream, int byteCount, Span<byte> buffer)
{
var 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.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Class to handle cases where TIFF image data is not compressed.
/// </summary>
internal class NoneTiffCompression : TiffBaseCompression
{
public NoneTiffCompression(MemoryAllocator allocator)
: base(allocator)
{
}
/// <inheritdoc/>
public override void Decompress(Stream stream, int byteCount, Span<byte> buffer)
{
stream.ReadFull(buffer, byteCount);
}
}
}

71
src/ImageSharp/Formats/Tiff/Compression/PackBitsTiffCompression.cs

@ -0,0 +1,71 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Class to handle cases where TIFF image data is compressed using PackBits compression.
/// </summary>
internal class PackBitsTiffCompression : TiffBaseCompression
{
public PackBitsTiffCompression(MemoryAllocator allocator)
: base(allocator)
{
}
/// <inheritdoc/>
public override void Decompress(Stream stream, int byteCount, Span<byte> buffer)
{
using IMemoryOwner<byte> compressedDataMemory = this.Allocator.Allocate<byte>(byteCount);
Span<byte> compressedData = compressedDataMemory.GetSpan();
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;
compressedData.Slice(literalOffset, literalLength).CopyTo(buffer.Slice(decompressedOffset));
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;
}
}
}
private static void ArrayCopyRepeat(byte value, Span<byte> destinationArray, int destinationIndex, int length)
{
for (int i = 0; i < length; i++)
{
destinationArray[i + destinationIndex] = value;
}
}
}
}

29
src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs

@ -0,0 +1,29 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Base tiff decompressor class.
/// </summary>
internal abstract class TiffBaseCompression
{
private readonly MemoryAllocator allocator;
public TiffBaseCompression(MemoryAllocator allocator) => this.allocator = allocator;
protected MemoryAllocator Allocator => this.allocator;
/// <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>
public abstract void Decompress(Stream stream, int byteCount, Span<byte> buffer);
}
}

28
src/ImageSharp/Formats/Tiff/Compression/TiffCompressionFactory.cs

@ -0,0 +1,28 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff
{
internal static class TiffCompressionFactory
{
public static TiffBaseCompression Create(TiffCompressionType compressionType, MemoryAllocator allocator)
{
switch (compressionType)
{
case TiffCompressionType.None:
return new NoneTiffCompression(allocator);
case TiffCompressionType.PackBits:
return new PackBitsTiffCompression(allocator);
case TiffCompressionType.Deflate:
return new DeflateTiffCompression(allocator);
case TiffCompressionType.Lzw:
return new LzwTiffCompression(allocator);
default:
throw new InvalidOperationException();
}
}
}
}

31
src/ImageSharp/Formats/Tiff/Compression/TiffCompressionType.cs

@ -0,0 +1,31 @@
// Copyright (c) Six Labors.
// 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,
}
}

21
src/ImageSharp/Formats/Tiff/Constants/TiffByteOrder.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// The tiff data stream byte order enum.
/// </summary>
public enum TiffByteOrder
{
/// <summary>
/// The big-endian byte order (Motorola).
/// </summary>
BigEndian,
/// <summary>
/// The little-endian byte order (Intel).
/// </summary>
LittleEndian
}
}

71
src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs

@ -0,0 +1,71 @@
// Copyright (c) Six Labors.
// 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>
public enum TiffCompression : ushort
{
/// <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.
// 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.
// 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.
// 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 : ushort
{
/// <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.
// 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]
public enum TiffNewSubfileType : uint
{
/// <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.
// 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.
// 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>
public enum TiffPhotometricInterpretation : ushort
{
/// <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.
// 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>
public enum TiffPlanarConfiguration : ushort
{
/// <summary>
/// Chunky format.
/// </summary>
Chunky = 1,
/// <summary>
/// Planar format.
/// </summary>
Planar = 2
}
}

26
src/ImageSharp/Formats/Tiff/Constants/TiffPredictor.cs

@ -0,0 +1,26 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// A mathematical operator that is applied to the image data before an encoding scheme is applied.
/// </summary>
public enum TiffPredictor : ushort
{
/// <summary>
/// No prediction scheme used before coding
/// </summary>
None = 1,
/// <summary>
/// Horizontal differencing.
/// </summary>
Horizontal = 2,
/// <summary>
/// Floating point horizontal differencing.
/// </summary>
FloatingPoint = 3
}
}

26
src/ImageSharp/Formats/Tiff/Constants/TiffResolutionUnit.cs

@ -0,0 +1,26 @@
// Copyright (c) Six Labors.
// 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>
public enum TiffResolutionUnit : ushort
{
/// <summary>
/// No absolute unit of measurement.
/// </summary>
None = 1,
/// <summary>
/// Inch.
/// </summary>
Inch = 2,
/// <summary>
/// Centimeter.
/// </summary>
Centimeter = 3
}
}

41
src/ImageSharp/Formats/Tiff/Constants/TiffSampleFormat.cs

@ -0,0 +1,41 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Specifies how to interpret each data sample in a pixel.
/// </summary>
public enum TiffSampleFormat : ushort
{
/// <summary>
/// Unsigned integer data. Default value.
/// </summary>
UnsignedInteger = 1,
/// <summary>
/// Signed integer data.
/// </summary>
SignedInteger = 2,
/// <summary>
/// IEEE floating point data.
/// </summary>
Float = 3,
/// <summary>
/// Undefined data format.
/// </summary>
Undefined = 4,
/// <summary>
/// The complex int.
/// </summary>
ComplexInt = 5,
/// <summary>
/// The complex float.
/// </summary>
ComplexFloat = 6
}
}

26
src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs

@ -0,0 +1,26 @@
// Copyright (c) Six Labors.
// 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>
public enum TiffSubfileType : uint
{
/// <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
}
}

26
src/ImageSharp/Formats/Tiff/Constants/TiffThreshholding.cs

@ -0,0 +1,26 @@
// Copyright (c) Six Labors.
// 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
}
}

16
src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs

@ -0,0 +1,16 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <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.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Encapsulates the options for the <see cref="TiffEncoder"/>.
/// </summary>
public interface ITiffEncoderOptions
{
}
}

104
src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs

@ -0,0 +1,104 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.IO;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// The TIFF IFD reader class.
/// </summary>
internal class DirectoryReader
{
private readonly TiffStream stream;
private readonly EntryReader tagReader;
private uint nextIfdOffset;
public DirectoryReader(TiffStream stream)
{
this.stream = stream;
this.tagReader = new EntryReader(stream);
}
public IEnumerable<IExifValue[]> Read()
{
if (this.ReadHeader())
{
return this.ReadIfds();
}
return null;
}
private bool ReadHeader()
{
ushort magic = this.stream.ReadUInt16();
if (magic != TiffConstants.HeaderMagicNumber)
{
throw new ImageFormatException("Invalid TIFF header magic number: " + magic);
}
uint firstIfdOffset = this.stream.ReadUInt32();
if (firstIfdOffset == 0)
{
throw new ImageFormatException("Invalid TIFF file header.");
}
this.nextIfdOffset = firstIfdOffset;
return true;
}
private IEnumerable<IExifValue[]> ReadIfds()
{
var list = new List<IExifValue[]>();
while (this.nextIfdOffset != 0)
{
this.stream.Seek(this.nextIfdOffset);
IExifValue[] ifd = this.ReadIfd();
list.Add(ifd);
}
this.tagReader.LoadExtendedData();
return list;
}
private IExifValue[] ReadIfd()
{
long pos = this.stream.Position;
ushort entryCount = this.stream.ReadUInt16();
var entries = new List<IExifValue>(entryCount);
for (int i = 0; i < entryCount; i++)
{
IExifValue tag = this.tagReader.ReadNext();
if (tag != null)
{
entries.Add(tag);
}
}
this.nextIfdOffset = this.stream.ReadUInt32();
int ifdSize = 2 + (entryCount * TiffConstants.SizeOfIfdEntry) + 4;
int readedBytes = (int)(this.stream.Position - pos);
int leftBytes = ifdSize - readedBytes;
if (leftBytes > 0)
{
this.stream.Skip(leftBytes);
}
else if (leftBytes < 0)
{
throw new InvalidDataException("Out of range of IFD structure.");
}
return entries.ToArray();
}
}
}

311
src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs

@ -0,0 +1,311 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
namespace SixLabors.ImageSharp.Formats.Tiff
{
internal class EntryReader
{
private readonly TiffStream stream;
private readonly SortedDictionary<uint, Action> extValueLoaders = new SortedDictionary<uint, Action>();
/// <summary>
/// Initializes a new instance of the <see cref="EntryReader" /> class.
/// </summary>
/// <param name="stream">The stream.</param>
public EntryReader(TiffStream stream)
{
this.stream = stream;
}
public IExifValue ReadNext()
{
var tagId = (ExifTagValue)this.stream.ReadUInt16();
var dataType = (ExifDataType)EnumUtils.Parse(this.stream.ReadUInt16(), ExifDataType.Unknown);
uint count = this.stream.ReadUInt32();
ExifDataType rawDataType = dataType;
dataType = LongOrShortFiltering(tagId, dataType);
bool isArray = GetIsArray(tagId, count);
ExifValue entry = ExifValues.Create(tagId, dataType, isArray);
if (rawDataType == ExifDataType.Undefined && count == 0)
{
// todo: investgate
count = 4;
}
if (this.ReadValueOrOffset(entry, rawDataType, count))
{
return entry;
}
return null; // new UnkownExifTag(tagId);
}
public void LoadExtendedData()
{
foreach (Action action in this.extValueLoaders.Values)
{
action();
}
}
private static bool HasExtData(ExifValue tag, uint count) => ExifDataTypes.GetSize(tag.DataType) * count > 4;
private static bool SetValue(ExifValue entry, object value)
{
if (!entry.IsArray && entry.DataType != ExifDataType.Ascii)
{
DebugGuard.IsTrue(((Array)value).Length == 1, "Expected a length is 1");
var single = ((Array)value).GetValue(0);
return entry.TrySetValue(single);
}
return entry.TrySetValue(value);
}
private static ExifDataType LongOrShortFiltering(ExifTagValue tagId, ExifDataType dataType)
{
switch (tagId)
{
case ExifTagValue.ImageWidth:
case ExifTagValue.ImageLength:
case ExifTagValue.StripOffsets:
case ExifTagValue.RowsPerStrip:
case ExifTagValue.StripByteCounts:
case ExifTagValue.TileWidth:
case ExifTagValue.TileLength:
case ExifTagValue.TileOffsets:
case ExifTagValue.TileByteCounts:
case ExifTagValue.OldSubfileType: // by spec SHORT, but can be LONG
return ExifDataType.Long;
default:
return dataType;
}
}
private static bool GetIsArray(ExifTagValue tagId, uint count)
{
switch (tagId)
{
case ExifTagValue.BitsPerSample:
case ExifTagValue.StripOffsets:
case ExifTagValue.StripByteCounts:
case ExifTagValue.TileOffsets:
case ExifTagValue.TileByteCounts:
case ExifTagValue.ColorMap:
case ExifTagValue.ExtraSamples:
case ExifTagValue.SampleFormat:
return true;
default:
return count > 1;
}
}
private bool ReadValueOrOffset(ExifValue entry, ExifDataType rawDataType, uint count)
{
if (HasExtData(entry, count))
{
uint offset = this.stream.ReadUInt32();
this.extValueLoaders.Add(offset, () =>
{
this.ReadExtValue(entry, rawDataType, offset, count);
});
return true;
}
long pos = this.stream.Position;
object value = this.ReadData(entry.DataType, rawDataType, count);
if (value == null)
{
// read unknown type value
value = this.stream.ReadBytes(4);
}
else
{
int leftBytes = 4 - (int)(this.stream.Position - pos);
if (leftBytes > 0)
{
this.stream.Skip(leftBytes);
}
else if (leftBytes < 0)
{
throw new InvalidDataException("Out of range of IFD entry structure.");
}
}
return SetValue(entry, value);
}
private void ReadExtValue(ExifValue entry, ExifDataType rawDataType, uint offset, uint count)
{
DebugGuard.IsTrue(HasExtData(entry, count), "Excepted extended data");
DebugGuard.MustBeGreaterThanOrEqualTo(offset, (uint)TiffConstants.SizeOfTiffHeader, nameof(offset));
this.stream.Seek(offset);
var value = this.ReadData(entry.DataType, rawDataType, count);
SetValue(entry, value);
DebugGuard.IsTrue(entry.DataType == ExifDataType.Ascii || count > 1 ^ !entry.IsArray, "Invalid tag");
DebugGuard.IsTrue(entry.GetValue() != null, "Invalid tag");
}
private object ReadData(ExifDataType entryDataType, ExifDataType rawDataType, uint count)
{
switch (rawDataType)
{
case ExifDataType.Byte:
case ExifDataType.Undefined:
{
return this.stream.ReadBytes(count);
}
case ExifDataType.SignedByte:
{
sbyte[] res = new sbyte[count];
byte[] buf = this.stream.ReadBytes(count);
Array.Copy(buf, res, buf.Length);
return res;
}
case ExifDataType.Short:
{
if (entryDataType == ExifDataType.Long)
{
uint[] buf = new uint[count];
for (int i = 0; i < buf.Length; i++)
{
buf[i] = this.stream.ReadUInt16();
}
return buf;
}
else
{
ushort[] buf = new ushort[count];
for (int i = 0; i < buf.Length; i++)
{
buf[i] = this.stream.ReadUInt16();
}
return buf;
}
}
case ExifDataType.SignedShort:
{
short[] buf = new short[count];
for (int i = 0; i < buf.Length; i++)
{
buf[i] = this.stream.ReadInt16();
}
return buf;
}
case ExifDataType.Long:
{
uint[] buf = new uint[count];
for (int i = 0; i < buf.Length; i++)
{
buf[i] = this.stream.ReadUInt32();
}
return buf;
}
case ExifDataType.SignedLong:
{
int[] buf = new int[count];
for (int i = 0; i < buf.Length; i++)
{
buf[i] = this.stream.ReadInt32();
}
return buf;
}
case ExifDataType.Ascii:
{
byte[] buf = this.stream.ReadBytes(count);
if (buf[buf.Length - 1] != 0)
{
throw new ImageFormatException("The retrieved string is not null terminated.");
}
return Encoding.UTF8.GetString(buf, 0, buf.Length - 1);
}
case ExifDataType.SingleFloat:
{
float[] buf = new float[count];
for (int i = 0; i < buf.Length; i++)
{
buf[i] = this.stream.ReadSingle();
}
return buf;
}
case ExifDataType.DoubleFloat:
{
double[] buf = new double[count];
for (int i = 0; i < buf.Length; i++)
{
buf[i] = this.stream.ReadDouble();
}
return buf;
}
case ExifDataType.Rational:
{
var buf = new Rational[count];
for (int i = 0; i < buf.Length; i++)
{
uint numerator = this.stream.ReadUInt32();
uint denominator = this.stream.ReadUInt32();
buf[i] = new Rational(numerator, denominator);
}
return buf;
}
case ExifDataType.SignedRational:
{
var buf = new SignedRational[count];
for (int i = 0; i < buf.Length; i++)
{
int numerator = this.stream.ReadInt32();
int denominator = this.stream.ReadInt32();
buf[i] = new SignedRational(numerator, denominator);
}
return buf;
}
case ExifDataType.Ifd:
{
return this.stream.ReadUInt32();
}
default:
return null;
}
}
}
}

51
src/ImageSharp/Formats/Tiff/ImageExtensions.cs

@ -0,0 +1,51 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.Formats.Tiff;
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 : unmanaged, 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 : unmanaged, IPixel<TPixel>
{
encoder = encoder ?? new TiffEncoder();
encoder.Encode(source, stream);
return source;
}
}
}

28
src/ImageSharp/Formats/Tiff/MetadataExtensions.cs

@ -0,0 +1,28 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Metadata;
namespace SixLabors.ImageSharp
{
/// <summary>
/// Extension methods for the <see cref="ImageMetadata"/> type.
/// </summary>
public static partial class MetadataExtensions
{
/// <summary>
/// Gets the tiff format specific metadata for the image.
/// </summary>
/// <param name="metadata">The metadata this method extends.</param>
/// <returns>The <see cref="TiffMetadata"/>.</returns>
public static TiffMetadata GetTiffMetadata(this ImageMetadata metadata) => metadata.GetFormatMetadata(TiffFormat.Instance);
/// <summary>
/// Gets the tiff format specific metadata for the image frame.
/// </summary>
/// <param name="metadata">The metadata this method extends.</param>
/// <returns>The <see cref="TiffFrameMetadata"/>.</returns>
public static TiffFrameMetadata GetTiffMetadata(this ImageFrameMetadata metadata) => metadata.GetFormatMetadata(TiffFormat.Instance);
}
}

47
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor.cs

@ -0,0 +1,47 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation (optimised for bilevel images).
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class BlackIsZero1TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public BlackIsZero1TiffColor()
{
}
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
int 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;
}
}
}
}
}
}

54
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor.cs

@ -0,0 +1,54 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
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 class BlackIsZero4TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public BlackIsZero4TiffColor()
{
}
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
int 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;
}
}
}
}
}

39
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor.cs

@ -0,0 +1,39 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
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 class BlackIsZero8TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public BlackIsZero8TiffColor()
{
}
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
int 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;
}
}
}
}
}

49
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor.cs

@ -0,0 +1,49 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
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 class BlackIsZeroTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly ushort bitsPerSample0;
private readonly float factor;
public BlackIsZeroTiffColor(ushort[] bitsPerSample)
{
this.bitsPerSample0 = bitsPerSample[0];
this.factor = (float)(1 << this.bitsPerSample0) - 1.0f;
}
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
var bitReader = new BitReader(data);
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
{
int value = bitReader.ReadBits(this.bitsPerSample0);
float intensity = ((float)value) / this.factor;
color.FromVector4(new Vector4(intensity, intensity, intensity, 1.0f));
pixels[x, y] = color;
}
bitReader.NextRow();
}
}
}
}

66
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor.cs

@ -0,0 +1,66 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
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 class PaletteTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly ushort bitsPerSample0;
private readonly TPixel[] palette;
/// <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>
public PaletteTiffColor(ushort[] bitsPerSample, ushort[] colorMap)
{
this.bitsPerSample0 = bitsPerSample[0];
int colorCount = 1 << this.bitsPerSample0;
this.palette = GeneratePalette(colorMap, colorCount);
}
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var bitReader = new BitReader(data);
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
{
int index = bitReader.ReadBits(this.bitsPerSample0);
pixels[x, y] = this.palette[index];
}
bitReader.NextRow();
}
}
private static TPixel[] GeneratePalette(ushort[] colorMap, int colorCount)
{
var 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;
}
}
}

43
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor.cs

@ -0,0 +1,43 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
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 class Rgb888TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public Rgb888TiffColor()
{
}
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(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));
pixelRow[x] = color;
}
}
}
}
}

75
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor.cs

@ -0,0 +1,75 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
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 class RgbPlanarTiffColor<TPixel> /* : TiffColorDecoder<TPixel> */
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly float rFactor;
private readonly float gFactor;
private readonly float bFactor;
private readonly ushort bitsPerSampleR;
private readonly ushort bitsPerSampleG;
private readonly ushort bitsPerSampleB;
public RgbPlanarTiffColor(ushort[] bitsPerSample)
/* : base(bitsPerSample, null) */
{
this.bitsPerSampleR = bitsPerSample[0];
this.bitsPerSampleG = bitsPerSample[1];
this.bitsPerSampleB = bitsPerSample[2];
this.rFactor = (float)(1 << this.bitsPerSampleR) - 1.0f;
this.gFactor = (float)(1 << this.bitsPerSampleG) - 1.0f;
this.bFactor = (float)(1 << this.bitsPerSampleB) - 1.0f;
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <param name="data">The buffers 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>
public void Decode(IManagedByteBuffer[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
var rBitReader = new BitReader(data[0].GetSpan());
var gBitReader = new BitReader(data[1].GetSpan());
var bBitReader = new BitReader(data[2].GetSpan());
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
{
float r = ((float)rBitReader.ReadBits(this.bitsPerSampleR)) / this.rFactor;
float g = ((float)gBitReader.ReadBits(this.bitsPerSampleG)) / this.gFactor;
float b = ((float)bBitReader.ReadBits(this.bitsPerSampleB)) / this.bFactor;
color.FromVector4(new Vector4(r, g, b, 1.0f));
pixels[x, y] = color;
}
rBitReader.NextRow();
gBitReader.NextRow();
bBitReader.NextRow();
}
}
}
}

63
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor.cs

@ -0,0 +1,63 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
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 class RgbTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly float rFactor;
private readonly float gFactor;
private readonly float bFactor;
private readonly ushort bitsPerSampleR;
private readonly ushort bitsPerSampleG;
private readonly ushort bitsPerSampleB;
public RgbTiffColor(ushort[] bitsPerSample)
{
this.bitsPerSampleR = bitsPerSample[0];
this.bitsPerSampleG = bitsPerSample[1];
this.bitsPerSampleB = bitsPerSample[2];
this.rFactor = (float)(1 << this.bitsPerSampleR) - 1.0f;
this.gFactor = (float)(1 << this.bitsPerSampleG) - 1.0f;
this.bFactor = (float)(1 << this.bitsPerSampleB) - 1.0f;
}
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
var bitReader = new BitReader(data);
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
{
float r = ((float)bitReader.ReadBits(this.bitsPerSampleR)) / this.rFactor;
float g = ((float)bitReader.ReadBits(this.bitsPerSampleG)) / this.gFactor;
float b = ((float)bitReader.ReadBits(this.bitsPerSampleB)) / this.bFactor;
color.FromVector4(new Vector4(r, g, b, 1.0f));
pixels[x, y] = color;
}
bitReader.NextRow();
}
}
}
}

42
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBaseColorDecoder.cs

@ -0,0 +1,42 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// The base class for photometric interpretation decoders.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal abstract class TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
protected TiffBaseColorDecoder()
{
}
/*
/// <summary>
/// Gets the photometric interpretation value.
/// </summary>
/// <value>
/// The photometric interpretation value.
/// </value>
public TiffColorType ColorType { get; }
*/
/// <summary>
/// Decodes source raw pixel data using the current photometric interpretation.
/// </summary>
/// <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>
public abstract void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height);
}
}

95
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory.cs

@ -0,0 +1,95 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff
{
internal static class TiffColorDecoderFactory<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public static TiffBaseColorDecoder<TPixel> Create(TiffColorType colorType, ushort[] bitsPerSample, ushort[] colorMap)
{
switch (colorType)
{
case TiffColorType.WhiteIsZero:
DebugGuard.IsTrue(bitsPerSample.Length == 1, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new WhiteIsZeroTiffColor<TPixel>(bitsPerSample);
case TiffColorType.WhiteIsZero1:
DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 1, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new WhiteIsZero1TiffColor<TPixel>();
case TiffColorType.WhiteIsZero4:
DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 4, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new WhiteIsZero4TiffColor<TPixel>();
case TiffColorType.WhiteIsZero8:
DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 8, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new WhiteIsZero8TiffColor<TPixel>();
case TiffColorType.BlackIsZero:
DebugGuard.IsTrue(bitsPerSample.Length == 1, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new BlackIsZeroTiffColor<TPixel>(bitsPerSample);
case TiffColorType.BlackIsZero1:
DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 1, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new BlackIsZero1TiffColor<TPixel>();
case TiffColorType.BlackIsZero4:
DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 4, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new BlackIsZero4TiffColor<TPixel>();
case TiffColorType.BlackIsZero8:
DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 8, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new BlackIsZero8TiffColor<TPixel>();
case TiffColorType.Rgb:
DebugGuard.NotNull(bitsPerSample, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbTiffColor<TPixel>(bitsPerSample);
case TiffColorType.Rgb888:
DebugGuard.IsTrue(
bitsPerSample.Length == 3
&& bitsPerSample[0] == 8
&& bitsPerSample[1] == 8
&& bitsPerSample[2] == 8,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgb888TiffColor<TPixel>();
case TiffColorType.PaletteColor:
DebugGuard.NotNull(bitsPerSample, "bitsPerSample");
DebugGuard.NotNull(colorMap, "colorMap");
return new PaletteTiffColor<TPixel>(bitsPerSample, colorMap);
default:
throw new InvalidOperationException();
}
}
public static RgbPlanarTiffColor<TPixel> CreatePlanar(TiffColorType colorType, ushort[] bitsPerSample, ushort[] colorMap)
{
switch (colorType)
{
case TiffColorType.RgbPlanar:
DebugGuard.NotNull(bitsPerSample, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbPlanarTiffColor<TPixel>(bitsPerSample);
default:
throw new InvalidOperationException();
}
}
}
}

71
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs

@ -0,0 +1,71 @@
// Copyright (c) Six Labors.
// 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,
}
}

46
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor.cs

@ -0,0 +1,46 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
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 class WhiteIsZero1TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public WhiteIsZero1TiffColor()
{
}
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
int 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;
}
}
}
}
}
}

54
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor.cs

@ -0,0 +1,54 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
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 class WhiteIsZero4TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public WhiteIsZero4TiffColor()
{
}
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
int 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;
}
}
}
}
}

39
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor.cs

@ -0,0 +1,39 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
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 class WhiteIsZero8TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public WhiteIsZero8TiffColor()
{
}
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
int 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;
}
}
}
}
}

49
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor.cs

@ -0,0 +1,49 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
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 class WhiteIsZeroTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly ushort bitsPerSample0;
private readonly float factor;
public WhiteIsZeroTiffColor(ushort[] bitsPerSample)
{
this.bitsPerSample0 = bitsPerSample[0];
this.factor = (float)Math.Pow(2, this.bitsPerSample0) - 1.0f;
}
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
var bitReader = new BitReader(data);
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
{
int value = bitReader.ReadBits(this.bitsPerSample0);
float intensity = 1.0f - (((float)value) / this.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 Center](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 | | - | Ignore. In practice is very uncommon, and is not recommended. |
|ImageDescription | | Y | |
|Make | | Y | |
|Model | | Y | |
|StripOffsets | | Y | |
|Orientation | | - | Ignore. Many readers ignore this tag. |
|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 | | - | priority |
|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 | | Y | |
|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 | | Y | |
|INGR Packet Data Tag | | | |
|INGR Flag Registers | | | |
|IrasB Transformation Matrix| | | |
|ModelTiepointTag | | | |
|ModelTransformationTag | | | |
|Photoshop | | | |
|Exif IFD | | - | 0x8769 SubExif |
|ICC Profile | | Y | |
|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 | | | |

88
src/ImageSharp/Formats/Tiff/Streams/TiffBigEndianStream.cs

@ -0,0 +1,88 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
namespace SixLabors.ImageSharp.Formats.Tiff
{
internal class TiffBigEndianStream : TiffStream
{
public TiffBigEndianStream(Stream stream)
: base(stream)
{
}
public override TiffByteOrder ByteOrder => TiffByteOrder.BigEndian;
/// <summary>
/// Converts buffer data into an <see cref="short"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public override short ReadInt16()
{
byte[] bytes = this.ReadBytes(2);
return (short)((bytes[0] << 8) | bytes[1]);
}
/// <summary>
/// Converts buffer data into an <see cref="int"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public override int ReadInt32()
{
byte[] bytes = this.ReadBytes(4);
return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];
}
/// <summary>
/// Converts buffer data into a <see cref="uint"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public override uint ReadUInt32()
{
return (uint)this.ReadInt32();
}
/// <summary>
/// Converts buffer data into a <see cref="ushort"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public override ushort ReadUInt16()
{
return (ushort)this.ReadInt16();
}
/// <summary>
/// Converts buffer data into a <see cref="float"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public override float ReadSingle()
{
byte[] bytes = this.ReadBytes(4);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(bytes);
}
return BitConverter.ToSingle(bytes, 0);
}
/// <summary>
/// Converts buffer data into a <see cref="double"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public override double ReadDouble()
{
byte[] bytes = this.ReadBytes(8);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(bytes);
}
return BitConverter.ToDouble(bytes, 0);
}
}
}

88
src/ImageSharp/Formats/Tiff/Streams/TiffLittleEndianStream.cs

@ -0,0 +1,88 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
namespace SixLabors.ImageSharp.Formats.Tiff
{
internal class TiffLittleEndianStream : TiffStream
{
public TiffLittleEndianStream(Stream stream)
: base(stream)
{
}
public override TiffByteOrder ByteOrder => TiffByteOrder.LittleEndian;
/// <summary>
/// Converts buffer data into an <see cref="short"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public override short ReadInt16()
{
byte[] bytes = this.ReadBytes(2);
return (short)(bytes[0] | (bytes[1] << 8));
}
/// <summary>
/// Converts buffer data into an <see cref="int"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public override int ReadInt32()
{
byte[] bytes = this.ReadBytes(4);
return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24);
}
/// <summary>
/// Converts buffer data into a <see cref="uint"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public override uint ReadUInt32()
{
return (uint)this.ReadInt32();
}
/// <summary>
/// Converts buffer data into a <see cref="ushort"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public override ushort ReadUInt16()
{
return (ushort)this.ReadInt16();
}
/// <summary>
/// Converts buffer data into a <see cref="float"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public override float ReadSingle()
{
byte[] bytes = this.ReadBytes(4);
if (!BitConverter.IsLittleEndian)
{
Array.Reverse(bytes);
}
return BitConverter.ToSingle(bytes, 0);
}
/// <summary>
/// Converts buffer data into a <see cref="double"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public override double ReadDouble()
{
byte[] bytes = this.ReadBytes(8);
if (!BitConverter.IsLittleEndian)
{
Array.Reverse(bytes);
}
return BitConverter.ToDouble(bytes, 0);
}
}
}

94
src/ImageSharp/Formats/Tiff/Streams/TiffStream.cs

@ -0,0 +1,94 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// The tiff data stream base class.
/// </summary>
internal abstract class TiffStream
{
/// <summary>
/// The input stream.
/// </summary>
private readonly Stream stream;
/// <summary>
/// Initializes a new instance of the <see cref="TiffStream"/> class.
/// </summary>
/// <param name="stream">The stream.</param>
protected TiffStream(Stream stream)
{
this.stream = stream;
}
/// <summary>
/// Gets a value indicating whether the file is encoded in little-endian or big-endian format.
/// </summary>
public abstract TiffByteOrder ByteOrder { get; }
/// <summary>
/// Gets the input stream.
/// </summary>
public Stream InputStream => this.stream;
/// <summary>
/// Gets the stream position.
/// </summary>
public long Position => this.stream.Position;
public void Seek(uint offset)
{
this.stream.Seek(offset, SeekOrigin.Begin);
}
public void Skip(uint offset)
{
this.stream.Seek(offset, SeekOrigin.Current);
}
public void Skip(int offset)
{
this.stream.Seek(offset, SeekOrigin.Current);
}
/// <summary>
/// Converts buffer data into a <see cref="byte"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public byte ReadByte()
{
return (byte)this.stream.ReadByte();
}
/// <summary>
/// Converts buffer data into an <see cref="sbyte"/> using the correct endianness.
/// </summary>
/// <returns>The converted value.</returns>
public sbyte ReadSByte()
{
return (sbyte)this.stream.ReadByte();
}
public byte[] ReadBytes(uint count)
{
byte[] buf = new byte[count];
this.stream.Read(buf, 0, buf.Length);
return buf;
}
public abstract short ReadInt16();
public abstract int ReadInt32();
public abstract uint ReadUInt32();
public abstract ushort ReadUInt16();
public abstract float ReadSingle();
public abstract double ReadDouble();
}
}

53
src/ImageSharp/Formats/Tiff/Streams/TiffStreamFactory.cs

@ -0,0 +1,53 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// The tiff data stream factory class.
/// </summary>
internal static class TiffStreamFactory
{
/// <summary>
/// Creates the specified byte order.
/// </summary>
/// <param name="byteOrder">The byte order.</param>
/// <param name="stream">The stream.</param>
public static TiffStream Create(TiffByteOrder byteOrder, Stream stream)
{
if (byteOrder == TiffByteOrder.BigEndian)
{
return new TiffBigEndianStream(stream);
}
else if (byteOrder == TiffByteOrder.LittleEndian)
{
return new TiffLittleEndianStream(stream);
}
throw new ArgumentOutOfRangeException(nameof(byteOrder));
}
/// <summary>
/// Reads the byte order of stream.
/// </summary>
/// <param name="stream">The stream.</param>
public static TiffByteOrder ReadByteOrder(Stream stream)
{
byte[] headerBytes = new byte[2];
stream.Read(headerBytes, 0, 2);
if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian)
{
return TiffByteOrder.LittleEndian;
}
else if (headerBytes[0] == TiffConstants.ByteOrderBigEndian && headerBytes[1] == TiffConstants.ByteOrderBigEndian)
{
return TiffByteOrder.BigEndian;
}
throw new ImageFormatException("Invalid TIFF file header.");
}
}
}

19
src/ImageSharp/Formats/Tiff/TiffConfigurationModule.cs

@ -0,0 +1,19 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <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());
}
}
}

68
src/ImageSharp/Formats/Tiff/TiffDecoder.cs

@ -0,0 +1,68 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Image decoder for generating an image out of a TIFF stream.
/// </summary>
public class TiffDecoder : IImageDecoder, ITiffDecoderOptions, IImageInfoDetector
{
/// <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 : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, "stream");
using var decoder = new TiffDecoderCore(stream, configuration, this);
return decoder.Decode<TPixel>(configuration, stream);
}
/// <inheritdoc/>
public Image Decode(Configuration configuration, Stream stream) => this.Decode<Rgba32>(configuration, stream);
/// <inheritdoc/>
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
Guard.NotNull(stream, nameof(stream));
var decoder = new TiffDecoderCore(stream, configuration, this);
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc/>
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
var decoder = new TiffDecoderCore(stream, configuration, this);
return decoder.Identify(configuration, stream);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
Guard.NotNull(stream, nameof(stream));
var decoder = new TiffDecoderCore(stream, configuration, this);
return decoder.IdentifyAsync(configuration, stream, cancellationToken);
}
}
}

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

@ -0,0 +1,326 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Performs the tiff decoding operation.
/// </summary>
internal class TiffDecoderCore : IImageDecoderInternals, IDisposable
{
/// <summary>
/// The global configuration
/// </summary>
private readonly Configuration configuration;
/// <summary>
/// Used for allocating memory during processing operations.
/// </summary>
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
private readonly bool ignoreMetadata;
/// <summary>
/// Initializes a new instance of the <see cref="TiffDecoderCore" /> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The decoder options.</param>
private TiffDecoderCore(Configuration configuration, ITiffDecoderOptions options)
{
options = options ?? new TiffDecoder();
this.configuration = configuration ?? Configuration.Default;
this.ignoreMetadata = options.IgnoreMetadata;
this.memoryAllocator = this.configuration.MemoryAllocator;
}
/// <summary>
/// Initializes a new instance of the <see cref="TiffDecoderCore" /> class.
/// </summary>
/// <param name="stream">The stream.</param>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The decoder options.</param>
public TiffDecoderCore(Stream stream, Configuration configuration, ITiffDecoderOptions options)
: this(configuration, options)
{
this.ByteOrder = TiffStreamFactory.ReadByteOrder(stream);
}
/// <summary>
/// Gets the byte order.
/// </summary>
public TiffByteOrder ByteOrder { get; }
/// <summary>
/// Gets the input stream.
/// </summary>
public TiffStream Stream { get; private set; }
/// <summary>
/// Gets or sets the number of bits for each sample of the pixel format used to encode the image.
/// </summary>
public ushort[] BitsPerSample { get; set; }
/// <summary>
/// Gets or sets the lookup table for RGB palette colored images.
/// </summary>
public ushort[] ColorMap { get; set; }
/// <summary>
/// Gets or sets the photometric interpretation implementation to use when decoding the image.
/// </summary>
public TiffColorType ColorType { get; set; }
/// <summary>
/// Gets or sets the compression implementation to use when decoding the image.
/// </summary>
public TiffCompressionType CompressionType { get; set; }
/// <summary>
/// Gets or sets the planar configuration type to use when decoding the image.
/// </summary>
public TiffPlanarConfiguration PlanarConfiguration { get; set; }
/// <summary>
/// Gets or sets the photometric interpretation.
/// </summary>
public TiffPhotometricInterpretation PhotometricInterpretation { get; set; }
/// <inheritdoc/>
public Configuration Configuration => this.configuration;
/// <inheritdoc/>
public Size Dimensions { get; private set; }
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
this.Stream = TiffStreamFactory.Create(this.ByteOrder, stream);
var reader = new DirectoryReader(this.Stream);
IEnumerable<IExifValue[]> directories = reader.Read();
var frames = new List<ImageFrame<TPixel>>();
var framesMetadata = new List<TiffFrameMetadata>();
foreach (IExifValue[] ifd in directories)
{
ImageFrame<TPixel> frame = this.DecodeFrame<TPixel>(ifd, out TiffFrameMetadata frameMetadata);
frames.Add(frame);
framesMetadata.Add(frameMetadata);
}
ImageMetadata metadata = framesMetadata.CreateMetadata(this.ignoreMetadata, this.Stream.ByteOrder);
// todo: tiff frames can have different sizes
{
ImageFrame<TPixel> root = frames.First();
this.Dimensions = root.Size();
foreach (ImageFrame<TPixel> frame in frames)
{
if (frame.Size() != root.Size())
{
throw new NotSupportedException("Images with different sizes are not supported");
}
}
}
var image = new Image<TPixel>(this.configuration, metadata, frames);
return image;
}
/// <inheritdoc/>
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.Stream = TiffStreamFactory.Create(this.ByteOrder, stream);
var reader = new DirectoryReader(this.Stream);
IEnumerable<IExifValue[]> directories = reader.Read();
var framesMetadata = new List<TiffFrameMetadata>();
foreach (IExifValue[] ifd in directories)
{
framesMetadata.Add(new TiffFrameMetadata() { Tags = ifd });
}
ImageMetadata metadata = framesMetadata.CreateMetadata(this.ignoreMetadata, this.Stream.ByteOrder);
TiffFrameMetadata root = framesMetadata.First();
int bitsPerPixel = 0;
foreach (var bits in root.BitsPerSample)
{
bitsPerPixel += bits;
}
return new ImageInfo(new PixelTypeInfo(bitsPerPixel), (int)root.Width, (int)root.Height, metadata);
}
/// <inheritdoc/>
public void Dispose()
{
// nothing
}
/// <summary>
/// Decodes the image data from a specified IFD.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="tags">The IFD tags.</param>
/// <param name="metadata">The frame metadata.</param>
/// <returns>
/// The tiff frame.
/// </returns>
private ImageFrame<TPixel> DecodeFrame<TPixel>(IExifValue[] tags, out TiffFrameMetadata metadata)
where TPixel : unmanaged, IPixel<TPixel>
{
var coreMetadata = new ImageFrameMetadata();
metadata = coreMetadata.GetTiffMetadata();
metadata.Tags = tags;
this.VerifyAndParseOptions(metadata);
int width = (int)metadata.Width;
int height = (int)metadata.Height;
var frame = new ImageFrame<TPixel>(this.configuration, width, height, coreMetadata);
int rowsPerStrip = (int)metadata.RowsPerStrip;
uint[] stripOffsets = metadata.StripOffsets;
uint[] stripByteCounts = metadata.StripByteCounts;
if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar)
{
this.DecodeStripsPlanar(frame, rowsPerStrip, stripOffsets, stripByteCounts);
}
else
{
this.DecodeStripsChunky(frame, rowsPerStrip, stripOffsets, stripByteCounts);
}
return frame;
}
/// <summary>
/// Calculates the size (in bytes) for a pixel buffer using the determined color format.
/// </summary>
/// <param name="width">The width for the desired pixel buffer.</param>
/// <param name="height">The height for the desired pixel buffer.</param>
/// <param name="plane">The index of the plane for planar image configuration (or zero for chunky).</param>
/// <returns>The size (in bytes) of the required pixel buffer.</returns>
private int CalculateStripBufferSize(int width, int height, int plane = -1)
{
uint bitsPerPixel = 0;
if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky)
{
DebugGuard.IsTrue(plane == -1, "Excepted Chunky planar.");
for (int i = 0; i < this.BitsPerSample.Length; i++)
{
bitsPerPixel += this.BitsPerSample[i];
}
}
else
{
bitsPerPixel = this.BitsPerSample[plane];
}
int bytesPerRow = ((width * (int)bitsPerPixel) + 7) / 8;
int stripBytes = bytesPerRow * height;
return stripBytes;
}
/// <summary>
/// Decodes the image data for strip encoded data.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="frame">The image frame to decode data into.</param>
/// <param name="rowsPerStrip">The number of rows per strip of data.</param>
/// <param name="stripOffsets">An array of byte offsets to each strip in the image.</param>
/// <param name="stripByteCounts">An array of the size of each strip (in bytes).</param>
private void DecodeStripsPlanar<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts)
where TPixel : unmanaged, IPixel<TPixel>
{
int stripsPerPixel = this.BitsPerSample.Length;
int stripsPerPlane = stripOffsets.Length / stripsPerPixel;
Buffer2D<TPixel> pixels = frame.PixelBuffer;
var stripBuffers = new IManagedByteBuffer[stripsPerPixel];
try
{
for (int stripIndex = 0; stripIndex < stripBuffers.Length; stripIndex++)
{
int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip, stripIndex);
stripBuffers[stripIndex] = this.memoryAllocator.AllocateManagedByteBuffer(uncompressedStripSize);
}
TiffBaseCompression decompressor = TiffCompressionFactory.Create(this.CompressionType, this.memoryAllocator);
RgbPlanarTiffColor<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap);
for (int i = 0; i < stripsPerPlane; i++)
{
int stripHeight = i < stripsPerPlane - 1 || frame.Height % rowsPerStrip == 0 ? rowsPerStrip : frame.Height % rowsPerStrip;
for (int planeIndex = 0; planeIndex < stripsPerPixel; planeIndex++)
{
int stripIndex = (i * stripsPerPixel) + planeIndex;
this.Stream.Seek(stripOffsets[stripIndex]);
decompressor.Decompress(this.Stream.InputStream, (int)stripByteCounts[stripIndex], stripBuffers[planeIndex].GetSpan());
}
colorDecoder.Decode(stripBuffers, pixels, 0, rowsPerStrip * i, frame.Width, stripHeight);
}
}
finally
{
foreach (IManagedByteBuffer buf in stripBuffers)
{
buf?.Dispose();
}
}
}
private void DecodeStripsChunky<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts)
where TPixel : unmanaged, IPixel<TPixel>
{
int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip);
using IManagedByteBuffer stripBuffer = this.memoryAllocator.AllocateManagedByteBuffer(uncompressedStripSize);
Buffer2D<TPixel> pixels = frame.PixelBuffer;
TiffBaseCompression decompressor = TiffCompressionFactory.Create(this.CompressionType, this.memoryAllocator);
TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(this.ColorType, this.BitsPerSample, this.ColorMap);
for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++)
{
int stripHeight = stripIndex < stripOffsets.Length - 1 || frame.Height % rowsPerStrip == 0 ? rowsPerStrip : frame.Height % rowsPerStrip;
this.Stream.Seek(stripOffsets[stripIndex]);
decompressor.Decompress(this.Stream.InputStream, (int)stripByteCounts[stripIndex], stripBuffer.GetSpan());
colorDecoder.Decode(stripBuffer.GetSpan(), pixels, 0, rowsPerStrip * stripIndex, frame.Width, stripHeight);
}
}
}
}

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

@ -0,0 +1,354 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
using SixLabors.ImageSharp.Metadata.Profiles.Iptc;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// The decoder helper methods.
/// </summary>
internal static class TiffDecoderHelpers
{
public static ImageMetadata CreateMetadata(this IList<TiffFrameMetadata> frames, bool ignoreMetadata, TiffByteOrder byteOrder)
{
var coreMetadata = new ImageMetadata();
TiffMetadata tiffMetadata = coreMetadata.GetTiffMetadata();
tiffMetadata.ByteOrder = byteOrder;
TiffFrameMetadata rootFrameMetadata = frames.First();
switch (rootFrameMetadata.ResolutionUnit)
{
case TiffResolutionUnit.None:
coreMetadata.ResolutionUnits = PixelResolutionUnit.AspectRatio;
break;
case TiffResolutionUnit.Inch:
coreMetadata.ResolutionUnits = PixelResolutionUnit.PixelsPerInch;
break;
case TiffResolutionUnit.Centimeter:
coreMetadata.ResolutionUnits = PixelResolutionUnit.PixelsPerCentimeter;
break;
}
if (rootFrameMetadata.HorizontalResolution != null)
{
coreMetadata.HorizontalResolution = rootFrameMetadata.HorizontalResolution.Value;
}
if (rootFrameMetadata.VerticalResolution != null)
{
coreMetadata.VerticalResolution = rootFrameMetadata.VerticalResolution.Value;
}
if (!ignoreMetadata)
{
foreach (TiffFrameMetadata frame in frames)
{
if (tiffMetadata.XmpProfile == null)
{
byte[] buf = frame.GetArray<byte>(ExifTag.XMP, true);
if (buf != null)
{
tiffMetadata.XmpProfile = buf;
}
}
if (coreMetadata.IptcProfile == null)
{
byte[] buf = frame.GetArray<byte>(ExifTag.IPTC, true);
if (buf != null)
{
coreMetadata.IptcProfile = new IptcProfile(buf);
}
}
if (coreMetadata.IccProfile == null)
{
byte[] buf = frame.GetArray<byte>(ExifTag.IccProfile, true);
if (buf != null)
{
coreMetadata.IccProfile = new IccProfile(buf);
}
}
}
}
return coreMetadata;
}
/// <summary>
/// Determines the TIFF compression and color types, and reads any associated parameters.
/// </summary>
/// <param name="options">The options.</param>
/// <param name="entries">The IFD entries container to read the image format information for.</param>
public static void VerifyAndParseOptions(this TiffDecoderCore options, TiffFrameMetadata entries)
{
if (entries.ExtraSamples != null)
{
throw new NotSupportedException("ExtraSamples is not supported.");
}
if (entries.FillOrder != TiffFillOrder.MostSignificantBitFirst)
{
throw new NotSupportedException("The lower-order bits of the byte FillOrder is not supported.");
}
if (entries.GetArray<uint>(ExifTag.TileOffsets, true) != null)
{
throw new NotSupportedException("The Tile images is not supported.");
}
if (entries.Predictor != TiffPredictor.None)
{
throw new NotSupportedException("At the moment support only None Predictor.");
}
if (entries.SampleFormat != null)
{
foreach (TiffSampleFormat format in entries.SampleFormat)
{
if (format != TiffSampleFormat.UnsignedInteger)
{
throw new NotSupportedException("At the moment support only UnsignedInteger SampleFormat.");
}
}
}
ParseCompression(options, entries.Compression);
options.PlanarConfiguration = entries.PlanarConfiguration;
ParsePhotometric(options, entries);
ParseBitsPerSample(options, entries);
ParseColorType(options, entries);
}
private static void ParseColorType(this TiffDecoderCore options, TiffFrameMetadata entries)
{
switch (options.PhotometricInterpretation)
{
case TiffPhotometricInterpretation.WhiteIsZero:
{
if (options.BitsPerSample.Length == 1)
{
switch (options.BitsPerSample[0])
{
case 8:
{
options.ColorType = TiffColorType.WhiteIsZero8;
break;
}
case 4:
{
options.ColorType = TiffColorType.WhiteIsZero4;
break;
}
case 1:
{
options.ColorType = TiffColorType.WhiteIsZero1;
break;
}
default:
{
options.ColorType = TiffColorType.WhiteIsZero;
break;
}
}
}
else
{
throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported.");
}
break;
}
case TiffPhotometricInterpretation.BlackIsZero:
{
if (options.BitsPerSample.Length == 1)
{
switch (options.BitsPerSample[0])
{
case 8:
{
options.ColorType = TiffColorType.BlackIsZero8;
break;
}
case 4:
{
options.ColorType = TiffColorType.BlackIsZero4;
break;
}
case 1:
{
options.ColorType = TiffColorType.BlackIsZero1;
break;
}
default:
{
options.ColorType = TiffColorType.BlackIsZero;
break;
}
}
}
else
{
throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported.");
}
break;
}
case TiffPhotometricInterpretation.Rgb:
{
if (options.BitsPerSample.Length == 3)
{
if (options.PlanarConfiguration == TiffPlanarConfiguration.Chunky)
{
if (options.BitsPerSample[0] == 8 && options.BitsPerSample[1] == 8 && options.BitsPerSample[2] == 8)
{
options.ColorType = TiffColorType.Rgb888;
}
else
{
options.ColorType = TiffColorType.Rgb;
}
}
else
{
options.ColorType = TiffColorType.RgbPlanar;
}
}
else
{
throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported.");
}
break;
}
case TiffPhotometricInterpretation.PaletteColor:
{
options.ColorMap = entries.ColorMap;
if (options.ColorMap != null)
{
if (options.BitsPerSample.Length == 1)
{
switch (options.BitsPerSample[0])
{
default:
{
options.ColorType = TiffColorType.PaletteColor;
break;
}
}
}
else
{
throw new NotSupportedException("The number of samples in the TIFF BitsPerSample entry is not supported.");
}
}
else
{
throw new ImageFormatException("The TIFF ColorMap entry is missing for a palette color image.");
}
break;
}
default:
throw new NotSupportedException("The specified TIFF photometric interpretation is not supported: " + options.PhotometricInterpretation);
}
}
private static void ParseBitsPerSample(this TiffDecoderCore options, TiffFrameMetadata entries)
{
options.BitsPerSample = entries.BitsPerSample;
if (options.BitsPerSample == null)
{
if (options.PhotometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero
|| options.PhotometricInterpretation == TiffPhotometricInterpretation.BlackIsZero)
{
options.BitsPerSample = new[] { (ushort)1 };
}
else
{
throw new ImageFormatException("The TIFF BitsPerSample entry is missing.");
}
}
}
private static void ParsePhotometric(this TiffDecoderCore options, TiffFrameMetadata entries)
{
/*
if (!entries.TryGetSingleNumber(ExifTag.PhotometricInterpretation, out uint photometricInterpretation))
{
if (entries.Compression == TiffCompression.Ccitt1D)
{
photometricInterpretation = (uint)TiffPhotometricInterpretation.WhiteIsZero;
}
else
{
throw new ImageFormatException("The TIFF photometric interpretation entry is missing.");
}
}
options.PhotometricInterpretation = (TiffPhotometricInterpretation)photometricInterpretation;
/* */
// There is no default for PhotometricInterpretation, and it is required.
options.PhotometricInterpretation = entries.PhotometricInterpretation;
}
private static void ParseCompression(this TiffDecoderCore options, TiffCompression compression)
{
switch (compression)
{
case TiffCompression.None:
{
options.CompressionType = TiffCompressionType.None;
break;
}
case TiffCompression.PackBits:
{
options.CompressionType = TiffCompressionType.PackBits;
break;
}
case TiffCompression.Deflate:
case TiffCompression.OldDeflate:
{
options.CompressionType = TiffCompressionType.Deflate;
break;
}
case TiffCompression.Lzw:
{
options.CompressionType = TiffCompressionType.Lzw;
break;
}
default:
{
throw new NotSupportedException("The specified TIFF compression format is not supported: " + compression);
}
}
}
}
}

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

@ -0,0 +1,31 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Encoder for writing the data image to a stream in TIFF format.
/// </summary>
public class TiffEncoder : IImageEncoder, ITiffEncoderOptions
{
/// <inheritdoc/>
public void Encode<TPixel>(Image<TPixel> image, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
var encode = new TiffEncoderCore(this);
encode.Encode(image, stream);
}
/// <inheritdoc/>
public Task EncodeAsync<TPixel>(Image<TPixel> image, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
throw new System.NotImplementedException();
}
}
}

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

@ -0,0 +1,165 @@
// Copyright (c) Six Labors.
// 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.Profiles.Exif;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <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 : unmanaged, IPixel<TPixel>
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
using (var writer = new TiffWriter(stream))
{
long firstIfdMarker = this.WriteHeader(writer);
//// todo: multiframing is not support
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<IExifValue> 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));
var largeDataBlocks = new List<byte[]>();
entries.Sort((a, b) => (ushort)a.Tag - (ushort)b.Tag);
writer.Write((ushort)entries.Count);
foreach (ExifValue entry in entries)
{
writer.Write((ushort)entry.Tag);
writer.Write((ushort)entry.DataType);
writer.Write(ExifWriter.GetNumberOfComponents(entry));
uint lenght = ExifWriter.GetLength(entry);
var raw = new byte[lenght];
int sz = ExifWriter.WriteValue(entry, raw, 0);
DebugGuard.IsTrue(sz == raw.Length, "Incorrect number of bytes written");
if (raw.Length <= 4)
{
writer.WritePadded(raw);
}
else
{
largeDataBlocks.Add(raw);
writer.Write(dataOffset);
dataOffset += (uint)(raw.Length + (raw.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 : unmanaged, IPixel<TPixel>
{
var ifdEntries = new List<IExifValue>();
this.AddImageFormat(image, ifdEntries);
writer.WriteMarker(ifdOffset, (uint)writer.Position);
long nextIfdMarker = this.WriteIfd(writer, ifdEntries);
return nextIfdMarker;
}
/// <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<IExifValue> ifdEntries)
where TPixel : unmanaged, IPixel<TPixel>
{
throw new NotImplementedException();
}
}
}

40
src/ImageSharp/Formats/Tiff/TiffFormat.cs

@ -0,0 +1,40 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Encapsulates the means to encode and decode Tiff images.
/// </summary>
public class TiffFormat : IImageFormat<TiffMetadata, TiffFrameMetadata>
{
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;
/// <inheritdoc/>
public TiffMetadata CreateDefaultFormatMetadata() => new TiffMetadata();
/// <inheritdoc/>
public TiffFrameMetadata CreateDefaultFormatFrameMetadata() => new TiffFrameMetadata();
}
}

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

@ -0,0 +1,331 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Provides Tiff specific metadata information for the frame.
/// </summary>
public class TiffFrameMetadata : IDeepCloneable
{
private const TiffResolutionUnit DefaultResolutionUnit = TiffResolutionUnit.Inch;
private const TiffPlanarConfiguration DefaultPlanarConfiguration = TiffPlanarConfiguration.Chunky;
private const TiffPredictor DefaultPredictor = TiffPredictor.None;
/// <summary>
/// Initializes a new instance of the <see cref="TiffFrameMetadata"/> class.
/// </summary>
public TiffFrameMetadata()
{
}
/// <summary>
/// Gets or sets the Tiff directory tags list.
/// </summary>
public IList<IExifValue> Tags { get; set; }
/// <summary>Gets a general indication of the kind of data contained in this subfile.</summary>
/// <value>A general indication of the kind of data contained in this subfile.</value>
public TiffNewSubfileType NewSubfileType => this.GetSingleEnum<TiffNewSubfileType, uint>(ExifTag.SubfileType, TiffNewSubfileType.FullImage);
/// <summary>Gets a general indication of the kind of data contained in this subfile.</summary>
/// <value>A general indication of the kind of data contained in this subfile.</value>
public TiffSubfileType? SubfileType => this.GetSingleEnumNullable<TiffSubfileType, uint>(ExifTag.OldSubfileType);
/// <summary>
/// Gets the number of columns in the image, i.e., the number of pixels per row.
/// </summary>
public uint Width => this.GetSingle<uint>(ExifTag.ImageWidth);
/// <summary>
/// Gets the number of rows of pixels in the image.
/// </summary>
public uint Height => this.GetSingle<uint>(ExifTag.ImageLength);
/// <summary>
/// Gets the number of bits per component.
/// </summary>
public ushort[] BitsPerSample => this.GetArray<ushort>(ExifTag.BitsPerSample, true);
/// <summary>Gets the compression scheme used on the image data.</summary>
/// <value>The compression scheme used on the image data.</value>
public TiffCompression Compression => this.GetSingleEnum<TiffCompression, ushort>(ExifTag.Compression);
/// <summary>
/// Gets the color space of the image data.
/// </summary>
public TiffPhotometricInterpretation PhotometricInterpretation => this.GetSingleEnum<TiffPhotometricInterpretation, ushort>(ExifTag.PhotometricInterpretation);
/// <summary>
/// Gets the logical order of bits within a byte.
/// </summary>
internal TiffFillOrder FillOrder => this.GetSingleEnum<TiffFillOrder, ushort>(ExifTag.FillOrder, TiffFillOrder.MostSignificantBitFirst);
/// <summary>
/// Gets the a string that describes the subject of the image.
/// </summary>
public string ImageDescription => this.GetString(ExifTag.ImageDescription);
/// <summary>
/// Gets the scanner manufacturer.
/// </summary>
public string Make => this.GetString(ExifTag.Make);
/// <summary>
/// Gets the scanner model name or number.
/// </summary>
public string Model => this.GetString(ExifTag.Model);
/// <summary>Gets for each strip, the byte offset of that strip..</summary>
public uint[] StripOffsets => this.GetArray<uint>(ExifTag.StripOffsets);
/// <summary>
/// Gets the number of components per pixel.
/// </summary>
public ushort SamplesPerPixel => this.GetSingle<ushort>(ExifTag.SamplesPerPixel);
/// <summary>
/// Gets the number of rows per strip.
/// </summary>
public uint RowsPerStrip => this.GetSingle<uint>(ExifTag.RowsPerStrip);
/// <summary>
/// Gets for each strip, the number of bytes in the strip after compression.
/// </summary>
public uint[] StripByteCounts => this.GetArray<uint>(ExifTag.StripByteCounts);
/// <summary>Gets the resolution of the image in x- direction.</summary>
/// <value>The density of the image in x- direction.</value>
public double? HorizontalResolution
{
get
{
if (this.ResolutionUnit != TiffResolutionUnit.None)
{
double resolutionUnitFactor = this.ResolutionUnit == TiffResolutionUnit.Centimeter ? 2.54 : 1.0;
if (this.TryGetSingle(ExifTag.XResolution, out Rational xResolution))
{
return xResolution.ToDouble() * resolutionUnitFactor;
}
}
return null;
}
}
/// <summary>
/// Gets the resolution of the image in y- direction.
/// </summary>
/// <value>The density of the image in y- direction.</value>
public double? VerticalResolution
{
get
{
if (this.ResolutionUnit != TiffResolutionUnit.None)
{
double resolutionUnitFactor = this.ResolutionUnit == TiffResolutionUnit.Centimeter ? 2.54 : 1.0;
if (this.TryGetSingle(ExifTag.YResolution, out Rational yResolution))
{
return yResolution.ToDouble() * resolutionUnitFactor;
}
}
return null;
}
}
/// <summary>
/// Gets how the components of each pixel are stored.
/// </summary>
public TiffPlanarConfiguration PlanarConfiguration => this.GetSingleEnum<TiffPlanarConfiguration, ushort>(ExifTag.PlanarConfiguration, DefaultPlanarConfiguration);
/// <summary>
/// Gets the unit of measurement for XResolution and YResolution.
/// </summary>
public TiffResolutionUnit ResolutionUnit => this.GetSingleEnum<TiffResolutionUnit, ushort>(ExifTag.ResolutionUnit, DefaultResolutionUnit);
/// <summary>
/// Gets the name and version number of the software package(s) used to create the image.
/// </summary>
public string Software => this.GetString(ExifTag.Software);
/// <summary>
/// Gets the date and time of image creation.
/// </summary>
public string DateTime => this.GetString(ExifTag.DateTime);
/// <summary>
/// Gets the person who created the image.
/// </summary>
public string Artist => this.GetString(ExifTag.Artist);
/// <summary>
/// Gets the computer and/or operating system in use at the time of image creation.
/// </summary>
public string HostComputer => this.GetString(ExifTag.HostComputer);
/// <summary>
/// Gets a color map for palette color images.
/// </summary>
public ushort[] ColorMap => this.GetArray<ushort>(ExifTag.ColorMap, true);
/// <summary>
/// Gets the description of extra components.
/// </summary>
public ushort[] ExtraSamples => this.GetArray<ushort>(ExifTag.ExtraSamples, true);
/// <summary>
/// Gets the copyright notice.
/// </summary>
public string Copyright => this.GetString(ExifTag.Copyright);
/// <summary>
/// Gets a mathematical operator that is applied to the image data before an encoding scheme is applied.
/// </summary>
public TiffPredictor Predictor => this.GetSingleEnum<TiffPredictor, ushort>(ExifTag.Predictor, DefaultPredictor);
/// <summary>
/// Gets the specifies how to interpret each data sample in a pixel.
/// <see cref="SamplesPerPixel"/>
/// </summary>
public TiffSampleFormat[] SampleFormat => this.GetEnumArray<TiffSampleFormat, ushort>(ExifTag.SampleFormat, true);
internal T[] GetArray<T>(ExifTag tag, bool optional = false)
where T : struct
{
if (this.TryGetArray(tag, out T[] result))
{
return result;
}
if (!optional)
{
throw new ArgumentException("Required tag is not founded: " + tag, nameof(tag));
}
return null;
}
private bool TryGetArray<T>(ExifTag tag, out T[] result)
where T : struct
{
foreach (IExifValue entry in this.Tags)
{
if (entry.Tag == tag)
{
DebugGuard.IsTrue(entry.IsArray, "Expected array entry");
result = (T[])entry.GetValue();
return true;
}
}
result = null;
return false;
}
private TEnum[] GetEnumArray<TEnum, TTagValue>(ExifTag tag, bool optional = false)
where TEnum : struct
where TTagValue : struct
{
if (this.TryGetArray(tag, out TTagValue[] result))
{
// todo: improve
return result.Select(a => (TEnum)(object)a).ToArray();
}
if (!optional)
{
throw new ArgumentException("Required tag is not founded: " + tag, nameof(tag));
}
return null;
}
private string GetString(ExifTag tag)
{
foreach (IExifValue entry in this.Tags)
{
if (entry.Tag == tag)
{
DebugGuard.IsTrue(entry.DataType == ExifDataType.Ascii, "Expected string entry");
object value = entry.GetValue();
DebugGuard.IsTrue(value is string, "Expected string entry");
return (string)value;
}
}
return null;
}
private TEnum? GetSingleEnumNullable<TEnum, TTagValue>(ExifTag tag)
where TEnum : struct
where TTagValue : struct
{
if (!this.TryGetSingle(tag, out TTagValue value))
{
return null;
}
return (TEnum)(object)value;
}
private TEnum GetSingleEnum<TEnum, TTagValue>(ExifTag tag, TEnum? defaultValue = null)
where TEnum : struct
where TTagValue : struct
=> this.GetSingleEnumNullable<TEnum, TTagValue>(tag) ?? (defaultValue != null ? defaultValue.Value : throw new ArgumentException("Required tag is not founded: " + tag, nameof(tag)));
private T GetSingle<T>(ExifTag tag)
where T : struct
{
if (this.TryGetSingle(tag, out T result))
{
return result;
}
throw new ArgumentException("Required tag is not founded: " + tag, nameof(tag));
}
private bool TryGetSingle<T>(ExifTag tag, out T result)
where T : struct
{
foreach (IExifValue entry in this.Tags)
{
if (entry.Tag == tag)
{
DebugGuard.IsTrue(!entry.IsArray, "Expected non array entry");
object value = entry.GetValue();
result = (T)value;
return true;
}
}
result = default;
return false;
}
/// <inheritdoc/>
public IDeepCloneable DeepClone()
{
var tags = new List<IExifValue>();
foreach (IExifValue entry in this.Tags)
{
tags.Add(entry.DeepClone());
}
return new TiffFrameMetadata() { Tags = tags };
}
}
}

34
src/ImageSharp/Formats/Tiff/TiffImageFormatDetector.cs

@ -0,0 +1,34 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <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
}
}
}

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

@ -0,0 +1,44 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections;
using System.Collections.Generic;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Provides Tiff specific metadata information for the image.
/// </summary>
public class TiffMetadata : IDeepCloneable
{
/// <summary>
/// Initializes a new instance of the <see cref="TiffMetadata"/> class.
/// </summary>
public TiffMetadata()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="TiffMetadata"/> class.
/// </summary>
/// <param name="other">The metadata to create an instance from.</param>
private TiffMetadata(TiffMetadata other)
{
this.ByteOrder = other.ByteOrder;
this.XmpProfile = other.XmpProfile;
}
/// <summary>
/// Gets or sets the byte order.
/// </summary>
public TiffByteOrder ByteOrder { get; set; }
/// <summary>
/// Gets or sets the XMP profile.
/// </summary>
public byte[] XmpProfile { get; set; }
/// <inheritdoc/>
public IDeepCloneable DeepClone() => new TiffMetadata(this);
}
}

66
src/ImageSharp/Formats/Tiff/Utils/BitReader.cs

@ -0,0 +1,66 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Utility class to read a sequence of bits from an array
/// </summary>
internal ref struct BitReader
{
private readonly ReadOnlySpan<byte> array;
private int offset;
private int bitOffset;
/// <summary>
/// Initializes a new instance of the <see cref="BitReader" /> struct.
/// </summary>
/// <param name="array">The array to read data from.</param>
public BitReader(ReadOnlySpan<byte> array)
{
this.array = array;
this.offset = 0;
this.bitOffset = 0;
}
/// <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.
// 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.
// 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, Span<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.
// 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;
}
}
}

48
src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs

@ -0,0 +1,48 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
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, Span<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, Span<byte> buffer)
{
ReadFull(stream, buffer, buffer.Length);
}
}
}

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

@ -0,0 +1,108 @@
// Copyright (c) Six Labors.
// 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();
}
}
}

352
src/ImageSharp/Formats/Tiff/__obsolete/TiffIfdEntryCreator.cs

@ -0,0 +1,352 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.Text;
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);
}
}
}

51
src/ImageSharp/Formats/Tiff/__obsolete/TiffMetadataNames.cs

@ -0,0 +1,51 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <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";
}
}

716
src/ImageSharp/Formats/Tiff/__obsolete/TiffTagId.cs

@ -0,0 +1,716 @@
// Copyright (c) Six Labors.
// 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;
}
}

76
src/ImageSharp/Formats/Tiff/__obsolete/TiffTagType.cs

@ -0,0 +1,76 @@
// Copyright (c) Six Labors.
// 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
}
}

6
src/ImageSharp/ImageSharp.csproj

@ -19,6 +19,12 @@
<RootNamespace>SixLabors.ImageSharp</RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Formats\Tiff\__obsolete\**" />
<EmbeddedResource Remove="Formats\Tiff\__obsolete\**" />
<None Remove="Formats\Tiff\__obsolete\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" />
<PackageReference Include="MinVer" PrivateAssets="All" />

7
src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs

@ -75,6 +75,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// <summary>
/// A 64-bit double precision floating point value.
/// </summary>
DoubleFloat = 12
DoubleFloat = 12,
/// <summary>
/// Reference to an IFD (32-bit (4-byte) unsigned integer).
/// </summary>
Ifd = 13
}
}

22
src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs

@ -260,9 +260,9 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
return length;
}
private static uint GetLength(IExifValue value) => GetNumberOfComponents(value) * ExifDataTypes.GetSize(value.DataType);
internal static uint GetLength(IExifValue value) => GetNumberOfComponents(value) * ExifDataTypes.GetSize(value.DataType);
private static uint GetNumberOfComponents(IExifValue exifValue)
internal static uint GetNumberOfComponents(IExifValue exifValue)
{
object value = exifValue.GetValue();
@ -279,17 +279,17 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
return 1;
}
private int WriteArray(IExifValue value, Span<byte> destination, int offset)
private static int WriteArray(IExifValue value, Span<byte> destination, int offset)
{
if (value.DataType == ExifDataType.Ascii)
{
return this.WriteValue(ExifDataType.Ascii, value.GetValue(), destination, offset);
return WriteValue(ExifDataType.Ascii, value.GetValue(), destination, offset);
}
int newOffset = offset;
foreach (object obj in (Array)value.GetValue())
{
newOffset = this.WriteValue(value.DataType, obj, destination, newOffset);
newOffset = WriteValue(value.DataType, obj, destination, newOffset);
}
return newOffset;
@ -310,7 +310,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
if (GetLength(value) > 4)
{
WriteUInt32((uint)(newOffset - startIndex), destination, this.dataOffsets[i++]);
newOffset = this.WriteValue(value, destination, newOffset);
newOffset = WriteValue(value, destination, newOffset);
}
}
@ -341,7 +341,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
}
else
{
this.WriteValue(value, destination, newOffset);
WriteValue(value, destination, newOffset);
}
newOffset += 4;
@ -362,7 +362,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
BinaryPrimitives.WriteInt32LittleEndian(destination.Slice(4, 4), value.Denominator);
}
private int WriteValue(ExifDataType dataType, object value, Span<byte> destination, int offset)
private static int WriteValue(ExifDataType dataType, object value, Span<byte> destination, int offset)
{
switch (dataType)
{
@ -410,14 +410,14 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
}
}
private int WriteValue(IExifValue value, Span<byte> destination, int offset)
internal static int WriteValue(IExifValue value, Span<byte> destination, int offset)
{
if (value.IsArray && value.DataType != ExifDataType.Ascii)
{
return this.WriteArray(value, destination, offset);
return WriteArray(value, destination, offset);
}
return this.WriteValue(value.DataType, value.GetValue(), destination, offset);
return WriteValue(value.DataType, value.GetValue(), destination, offset);
}
}
}

10
src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs

@ -21,6 +21,16 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// </summary>
public static ExifTag<byte[]> XMP => new ExifTag<byte[]>(ExifTagValue.XMP);
/// <summary>
/// Gets the IPTC exif tag.
/// </summary>
public static ExifTag<byte[]> IPTC => new ExifTag<byte[]>(ExifTagValue.IPTC);
/// <summary>
/// Gets the IccProfile exif tag.
/// </summary>
public static ExifTag<byte[]> IccProfile => new ExifTag<byte[]>(ExifTagValue.IccProfile);
/// <summary>
/// Gets the CFAPattern2 exif tag.
/// </summary>

5
src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Long.cs

@ -11,6 +11,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// </summary>
public static ExifTag<uint> SubfileType { get; } = new ExifTag<uint>(ExifTagValue.SubfileType);
/// <summary>
/// Gets the RowsPerStrip exif tag.
/// </summary>
public static ExifTag<uint> RowsPerStrip { get; } = new ExifTag<uint>(ExifTagValue.RowsPerStrip);
/// <summary>
/// Gets the SubIFDOffset exif tag.
/// </summary>

5
src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs

@ -56,6 +56,11 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// </summary>
public static ExifTag<uint[]> StripRowCounts { get; } = new ExifTag<uint[]>(ExifTagValue.StripRowCounts);
/// <summary>
/// Gets the StripByteCounts exif tag.
/// </summary>
public static ExifTag<uint[]> StripByteCounts { get; } = new ExifTag<uint[]>(ExifTagValue.StripByteCounts);
/// <summary>
/// Gets the IntergraphRegisters exif tag.
/// </summary>

287
src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTagValue.cs

@ -24,7 +24,16 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
GPSIFDOffset = 0x8825,
/// <summary>
/// SubfileType
/// Indicates the identification of the Interoperability rule.
/// See https://www.awaresystems.be/imaging/tiff/tifftags/privateifd/interoperability/interoperabilityindex.html
/// </summary>
[ExifTagDescription("R98", "Indicates a file conforming to R98 file specification of Recommended Exif Interoperability Rules (ExifR98) or to DCF basic file stipulated by Design Rule for Camera File System.")]
[ExifTagDescription("THM", "Indicates a file conforming to DCF thumbnail file stipulated by Design rule for Camera File System.")]
InteroperabilityIndex = 0x0001,
/// <summary>
/// A general indication of the kind of data contained in this subfile.
/// See Section 8: Baseline Fields.
/// </summary>
[ExifTagDescription(0U, "Full-resolution Image")]
[ExifTagDescription(1U, "Reduced-resolution image")]
@ -38,7 +47,8 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
SubfileType = 0x00FE,
/// <summary>
/// OldSubfileType
/// A general indication of the kind of data contained in this subfile.
/// See Section 8: Baseline Fields.
/// </summary>
[ExifTagDescription((ushort)1, "Full-resolution Image")]
[ExifTagDescription((ushort)2, "Reduced-resolution image")]
@ -46,22 +56,26 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
OldSubfileType = 0x00FF,
/// <summary>
/// ImageWidth
/// The number of columns in the image, i.e., the number of pixels per row.
/// See Section 8: Baseline Fields.
/// </summary>
ImageWidth = 0x0100,
/// <summary>
/// ImageLength
/// The number of rows of pixels in the image.
/// See Section 8: Baseline Fields.
/// </summary>
ImageLength = 0x0101,
/// <summary>
/// BitsPerSample
/// Number of bits per component.
/// See Section 8: Baseline Fields.
/// </summary>
BitsPerSample = 0x0102,
/// <summary>
/// Compression
/// Compression scheme used on the image data.
/// See Section 8: Baseline Fields.
/// </summary>
[ExifTagDescription((ushort)1, "Uncompressed")]
[ExifTagDescription((ushort)2, "CCITT 1D")]
@ -107,7 +121,8 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
Compression = 0x0103,
/// <summary>
/// PhotometricInterpretation
/// The color space of the image data.
/// See Section 8: Baseline Fields.
/// </summary>
[ExifTagDescription((ushort)0, "WhiteIsZero")]
[ExifTagDescription((ushort)1, "BlackIsZero")]
@ -126,7 +141,8 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
PhotometricInterpretation = 0x0106,
/// <summary>
/// Thresholding
/// For black and white TIFF files that represent shades of gray, the technique used to convert from gray to black and white pixels.
/// See Section 8: Baseline Fields.
/// </summary>
[ExifTagDescription((ushort)1, "No dithering or halftoning")]
[ExifTagDescription((ushort)2, "Ordered dither or halftone")]
@ -134,49 +150,58 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
Thresholding = 0x0107,
/// <summary>
/// CellWidth
/// The width of the dithering or halftoning matrix used to create a dithered or halftoned bilevel file.
/// See Section 8: Baseline Fields.
/// </summary>
CellWidth = 0x0108,
/// <summary>
/// CellLength
/// The length of the dithering or halftoning matrix used to create a dithered or halftoned bilevel file.
/// See Section 8: Baseline Fields.
/// </summary>
CellLength = 0x0109,
/// <summary>
/// FillOrder
/// The logical order of bits within a byte.
/// See Section 8: Baseline Fields.
/// </summary>
[ExifTagDescription((ushort)1, "Normal")]
[ExifTagDescription((ushort)2, "Reversed")]
FillOrder = 0x010A,
/// <summary>
/// DocumentName
/// The name of the document from which this image was scanned.
/// See Section 12: Document Storage and Retrieval.
/// </summary>
DocumentName = 0x010D,
/// <summary>
/// ImageDescription
/// A string that describes the subject of the image.
/// See Section 8: Baseline Fields.
/// </summary>
ImageDescription = 0x010E,
/// <summary>
/// Make
/// The scanner manufacturer.
/// See Section 8: Baseline Fields.
/// </summary>
Make = 0x010F,
/// <summary>
/// Model
/// The scanner model name or number.
/// See Section 8: Baseline Fields.
/// </summary>
Model = 0x0110,
/// <summary>
/// StripOffsets
/// For each strip, the byte offset of that strip.
/// See Section 8: Baseline Fields.
/// </summary>
StripOffsets = 0x0111,
/// <summary>
/// Orientation
/// The orientation of the image with respect to the rows and columns.
/// See Section 8: Baseline Fields.
/// </summary>
[ExifTagDescription((ushort)1, "Horizontal (normal)")]
[ExifTagDescription((ushort)2, "Mirror horizontal")]
@ -189,74 +214,88 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
Orientation = 0x0112,
/// <summary>
/// SamplesPerPixel
/// The number of components per pixel.
/// See Section 8: Baseline Fields.
/// </summary>
SamplesPerPixel = 0x0115,
/// <summary>
/// RowsPerStrip
/// The number of rows per strip.
/// See Section 8: Baseline Fields.
/// </summary>
RowsPerStrip = 0x0116,
/// <summary>
/// StripByteCounts
/// For each strip, the number of bytes in the strip after compression.
/// See Section 8: Baseline Fields.
/// </summary>
StripByteCounts = 0x0117,
/// <summary>
/// MinSampleValue
/// The minimum component value used.
/// See Section 8: Baseline Fields.
/// </summary>
MinSampleValue = 0x0118,
/// <summary>
/// MaxSampleValue
/// The maximum component value used.
/// See Section 8: Baseline Fields.
/// </summary>
MaxSampleValue = 0x0119,
/// <summary>
/// XResolution
/// The number of pixels per ResolutionUnit in the ImageWidth direction.
/// See Section 8: Baseline Fields.
/// </summary>
XResolution = 0x011A,
/// <summary>
/// YResolution
/// The number of pixels per ResolutionUnit in the <see cref="ImageLength"/> direction.
/// See Section 8: Baseline Fields.
/// </summary>
YResolution = 0x011B,
/// <summary>
/// PlanarConfiguration
/// How the components of each pixel are stored.
/// See Section 8: Baseline Fields.
/// </summary>
[ExifTagDescription((ushort)1, "Chunky")]
[ExifTagDescription((ushort)2, "Planar")]
PlanarConfiguration = 0x011C,
/// <summary>
/// PageName
/// The name of the page from which this image was scanned.
/// See Section 12: Document Storage and Retrieval.
/// </summary>
PageName = 0x011D,
/// <summary>
/// XPosition
/// X position of the image.
/// See Section 12: Document Storage and Retrieval.
/// </summary>
XPosition = 0x011E,
/// <summary>
/// YPosition
/// Y position of the image.
/// See Section 12: Document Storage and Retrieval.
/// </summary>
YPosition = 0x011F,
/// <summary>
/// FreeOffsets
/// For each string of contiguous unused bytes in a TIFF file, the byte offset of the string.
/// See Section 8: Baseline Fields.
/// </summary>
FreeOffsets = 0x0120,
/// <summary>
/// FreeByteCounts
/// For each string of contiguous unused bytes in a TIFF file, the number of bytes in the string.
/// See Section 8: Baseline Fields.
/// </summary>
FreeByteCounts = 0x0121,
/// <summary>
/// GrayResponseUnit
/// The precision of the information contained in the GrayResponseCurve.
/// See Section 8: Baseline Fields.
/// </summary>
[ExifTagDescription((ushort)1, "0.1")]
[ExifTagDescription((ushort)2, "0.001")]
@ -266,12 +305,13 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
GrayResponseUnit = 0x0122,
/// <summary>
/// GrayResponseCurve
/// For grayscale data, the optical density of each possible pixel value.
/// See Section 8: Baseline Fields.
/// </summary>
GrayResponseCurve = 0x0123,
/// <summary>
/// T4Options
/// Options for Group 3 Fax compression.
/// </summary>
[ExifTagDescription(0U, "2-Dimensional encoding")]
[ExifTagDescription(1U, "Uncompressed")]
@ -279,13 +319,14 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
T4Options = 0x0124,
/// <summary>
/// T6Options
/// Options for Group 4 Fax compression.
/// </summary>
[ExifTagDescription(1U, "Uncompressed")]
T6Options = 0x0125,
/// <summary>
/// ResolutionUnit
/// The unit of measurement for XResolution and YResolution.
/// See Section 8: Baseline Fields.
/// </summary>
[ExifTagDescription((ushort)1, "None")]
[ExifTagDescription((ushort)2, "Inches")]
@ -293,7 +334,8 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
ResolutionUnit = 0x0128,
/// <summary>
/// PageNumber
/// The page number of the page from which this image was scanned.
/// See Section 12: Document Storage and Retrieval.
/// </summary>
PageNumber = 0x0129,
@ -308,22 +350,26 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
TransferFunction = 0x012D,
/// <summary>
/// Software
/// Name and version number of the software package(s) used to create the image.
/// See Section 8: Baseline Fields.
/// </summary>
Software = 0x0131,
/// <summary>
/// DateTime
/// Date and time of image creation.
/// See Section 8: Baseline Fields.
/// </summary>
DateTime = 0x0132,
/// <summary>
/// Artist
/// Person who created the image.
/// See Section 8: Baseline Fields.
/// </summary>
Artist = 0x013B,
/// <summary>
/// HostComputer
/// The computer and/or operating system in use at the time of image creation.
/// See Section 8: Baseline Fields.
/// </summary>
HostComputer = 0x013C,
@ -343,7 +389,8 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
PrimaryChromaticities = 0x013F,
/// <summary>
/// ColorMap
/// A color map for palette color images.
/// See Section 8: Baseline Fields.
/// </summary>
ColorMap = 0x0140,
@ -390,6 +437,14 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// </summary>
ConsecutiveBadFaxLines = 0x0148,
/// <summary>
/// Offset to child IFDs.
/// See TIFF Supplement 1: Adobe Pagemaker 6.0.
/// Each value is an offset (from the beginning of the TIFF file, as always) to a child IFD. Child images provide extra information for the parent image - such as a subsampled version of the parent image.
/// TIFF data type is Long or 13, IFD. The IFD type is identical to LONG, except that it is only used to point to other valid IFDs.
/// </summary>
SubIFDs = 0x014A,
/// <summary>
/// InkSet
/// </summary>
@ -418,7 +473,8 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
TargetPrinter = 0x0151,
/// <summary>
/// ExtraSamples
/// Description of extra components.
/// See Section 8: Baseline Fields.
/// </summary>
[ExifTagDescription((ushort)0, "Unspecified")]
[ExifTagDescription((ushort)1, "Associated Alpha")]
@ -485,6 +541,14 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
[ExifTagDescription((ushort)1, "Higher resolution image exists")]
OPIProxy = 0x015F,
/// <summary>
/// Used in the TIFF-FX standard to point to an IFD containing tags that are globally applicable to the complete TIFF file.
/// See RFC2301: TIFF-F/FX Specification.
/// It is recommended that a TIFF writer place this field in the first IFD, where a TIFF reader would find it quickly.
/// Each field in the GlobalParametersIFD is a TIFF field that is legal in any IFD. Required baseline fields should not be located in the GlobalParametersIFD, but should be in each image IFD. If a conflict exists between fields in the GlobalParametersIFD and in the image IFDs, then the data in the image IFD shall prevail.
/// </summary>
GlobalParametersIFD = 0x0190,
/// <summary>
/// ProfileType
/// </summary>
@ -637,6 +701,12 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// </summary>
ImageID = 0x800D,
/// <summary>
/// Annotation data, as used in 'Imaging for Windows'.
/// See Other Private TIFF tags: http://www.awaresystems.be/imaging/tiff/tifftags/private.html
/// </summary>
WangAnnotation = 0x80A4,
/// <summary>
/// CFARepeatPatternDim
/// </summary>
@ -653,7 +723,8 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
BatteryLevel = 0x828F,
/// <summary>
/// Copyright
/// Copyright notice.
/// See Section 8: Baseline Fields.
/// </summary>
Copyright = 0x8298,
@ -668,38 +739,70 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
FNumber = 0x829D,
/// <summary>
/// MDFileTag
/// Specifies the pixel data format encoding in the Molecular Dynamics GEL file format.
/// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html
/// </summary>
[ExifTagDescription((ushort)2, "Squary root data format")]
[ExifTagDescription((ushort)128, "Linear data format")]
MDFileTag = 0x82A5,
/// <summary>
/// MDScalePixel
/// Specifies a scale factor in the Molecular Dynamics GEL file format.
/// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html
/// The scale factor is to be applies to each pixel before presenting it to the user.
/// </summary>
MDScalePixel = 0x82A6,
/// <summary>
/// MDLabName
/// Used to specify the conversion from 16bit to 8bit in the Molecular Dynamics GEL file format.
/// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html
/// Since the display is only 9bit, the 16bit data must be converted before display.
/// 8bit value = (16bit value - low range ) * 255 / (high range - low range)
/// Count: n.
/// </summary>
[ExifTagDescription((ushort)0, "lowest possible")]
[ExifTagDescription((ushort)1, "low range")]
[ExifTagDescription("n-2", "high range")]
[ExifTagDescription("n-1", "highest possible")]
MDColorTable = 0x82A7,
/// <summary>
/// Name of the lab that scanned this file, as used in the Molecular Dynamics GEL file format.
/// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html
/// </summary>
MDLabName = 0x82A8,
/// <summary>
/// MDSampleInfo
/// Information about the sample, as used in the Molecular Dynamics GEL file format.
/// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html
/// This information is entered by the person that scanned the file.
/// Note that the word 'sample' as used here, refers to the scanned sample, not an image channel.
/// </summary>
MDSampleInfo = 0x82A9,
/// <summary>
/// MDPrepDate
/// Date the sample was prepared, as used in the Molecular Dynamics GEL file format.
/// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html
/// The format of this data is YY/MM/DD.
/// Note that the word 'sample' as used here, refers to the scanned sample, not an image channel.
/// </summary>
MDPrepDate = 0x82AA,
/// <summary>
/// MDPrepTime
/// Time the sample was prepared, as used in the Molecular Dynamics GEL file format.
/// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html
/// Format of this data is HH:MM using the 24-hour clock.
/// Note that the word 'sample' as used here, refers to the scanned sample, not an image channel.
/// </summary>
MDPrepTime = 0x82AB,
/// <summary>
/// MDFileUnits
/// Units for data in this file, as used in the Molecular Dynamics GEL file format.
/// See Molecular Dynamics GEL File Format and Private Tags: https://www.awaresystems.be/imaging/tiff/tifftags/docs/gel.html
/// </summary>
[ExifTagDescription("O.D.", "Densitometer")]
[ExifTagDescription("Counts", "PhosphorImager")]
[ExifTagDescription("RFU", "FluorImager")]
MDFileUnits = 0x82AC,
/// <summary>
@ -707,6 +810,12 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// </summary>
PixelScale = 0x830E,
/// <summary>
/// IPTC (International Press Telecommunications Council) metadata.
/// See IPTC 4.1 specification.
/// </summary>
IPTC = 0x83BB,
/// <summary>
/// IntergraphPacketData
/// </summary>
@ -737,6 +846,40 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// </summary>
ModelTransform = 0x85D8,
/// <summary>
/// Collection of Photoshop 'Image Resource Blocks' (Embedded Metadata).
/// See Extracting the Thumbnail from the PhotoShop private TIFF Tag: https://www.awaresystems.be/imaging/tiff/tifftags/docs/photoshopthumbnail.html
/// </summary>
Photoshop = 0x8649,
/// <summary>
/// ICC profile data.
/// See https://www.awaresystems.be/imaging/tiff/tifftags/iccprofile.html
/// </summary>
IccProfile = 0x8773,
/// <summary>
/// Used in interchangeable GeoTIFF files.
/// See https://www.awaresystems.be/imaging/tiff/tifftags/geokeydirectorytag.html
/// This tag is also know as 'ProjectionInfoTag' and 'CoordSystemInfoTag'
/// This tag may be used to store the GeoKey Directory, which defines and references the "GeoKeys".
/// </summary>
GeoKeyDirectoryTag = 0x87AF,
/// <summary>
/// Used in interchangeable GeoTIFF files.
/// See https://www.awaresystems.be/imaging/tiff/tifftags/geodoubleparamstag.html
/// This tag is used to store all of the DOUBLE valued GeoKeys, referenced by the GeoKeyDirectoryTag. The meaning of any value of this double array is determined from the GeoKeyDirectoryTag reference pointing to it. FLOAT values should first be converted to DOUBLE and stored here.
/// </summary>
GeoDoubleParamsTag = 0x87B0,
/// <summary>
/// Used in interchangeable GeoTIFF files.
/// See https://www.awaresystems.be/imaging/tiff/tifftags/geoasciiparamstag.html
/// This tag is used to store all of the ASCII valued GeoKeys, referenced by the GeoKeyDirectoryTag. Since keys use offsets into tags, any special comments may be placed at the beginning of this tag. For the most part, the only keys that are ASCII valued are "Citation" keys, giving documentation and references for obscure projections, datums, etc.
/// </summary>
GeoAsciiParamsTag = 0x87B1,
/// <summary>
/// ImageLayer
/// </summary>
@ -1184,6 +1327,14 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// </summary>
RelatedSoundFile = 0xA004,
/// <summary>
/// A pointer to the Exif-related Interoperability IFD.
/// See https://www.awaresystems.be/imaging/tiff/tifftags/privateifd/interoperability.html
/// Interoperability IFD is composed of tags which stores the information to ensure the Interoperability.
/// The Interoperability structure of Interoperability IFD is same as TIFF defined IFD structure but does not contain the image data characteristically compared with normal TIFF IFD.
/// </summary>
InteroperabilityIFD = 0xA005,
/// <summary>
/// FlashEnergy
/// </summary>
@ -1539,5 +1690,41 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
/// GPSDifferential
/// </summary>
GPSDifferential = 0x001E,
/// <summary>
/// Used in the Oce scanning process.
/// Identifies the scanticket used in the scanning process.
/// Includes a trailing zero.
/// See https://www.awaresystems.be/imaging/tiff/tifftags/docs/oce.html
/// </summary>
OceScanjobDescription = 0xC427,
/// <summary>
/// Used in the Oce scanning process.
/// Identifies the application to process the TIFF file that results from scanning.
/// Includes a trailing zero.
/// See https://www.awaresystems.be/imaging/tiff/tifftags/docs/oce.html
/// </summary>
OceApplicationSelector = 0xC428,
/// <summary>
/// Used in the Oce scanning process.
/// This is the user's answer to an optional question embedded in the Oce scanticket, and presented to that user before scanning. It can serve in further determination of the workflow.
/// See https://www.awaresystems.be/imaging/tiff/tifftags/docs/oce.html
/// </summary>
OceIdentificationNumber = 0xC429,
/// <summary>
/// Used in the Oce scanning process.
/// This tag encodes the imageprocessing done by the Oce ImageLogic module in the scanner to ensure optimal quality for certain workflows.
/// See https://www.awaresystems.be/imaging/tiff/tifftags/docs/oce.html
/// </summary>
OceImageLogicCharacteristics = 0xC42A,
/// <summary>
/// Alias Sketchbook Pro layer usage description.
/// See https://www.awaresystems.be/imaging/tiff/tifftags/docs/alias.html
/// </summary>
AliasLayerMetadata = 0xC660,
}
}

6
src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs

@ -9,10 +9,10 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
public static ExifValue Create(ExifTag tag) => (ExifValue)CreateValue((ExifTagValue)(ushort)tag);
public static ExifValue Create(ExifTagValue tag, ExifDataType dataType, uint numberOfComponents)
{
bool isArray = numberOfComponents != 1;
public static ExifValue Create(ExifTagValue tag, ExifDataType dataType, uint numberOfComponents) => Create(tag, dataType, numberOfComponents != 1);
public static ExifValue Create(ExifTagValue tag, ExifDataType dataType, bool isArray)
{
switch (dataType)
{
case ExifDataType.Byte: return isArray ? (ExifValue)new ExifByteArray(tag, dataType) : new ExifByte(tag, dataType);

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

@ -0,0 +1,52 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
using SDImage = System.Drawing.Image;
using SDSize = System.Drawing.Size;
namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
[Config(typeof(Config.ShortClr))]
public class DecodeTiff : BenchmarkBase
{
private byte[] tiffBytes;
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
[Params(TestImages.Tiff.RgbPackbits)]
public string TestImage { get; set; }
[GlobalSetup]
public void ReadImages()
{
if (this.tiffBytes == null)
{
this.tiffBytes = File.ReadAllBytes(this.TestImageFullPath);
}
}
[Benchmark(Baseline = true, Description = "System.Drawing Tiff")]
public SDSize TiffSystemDrawing()
{
using (var memoryStream = new MemoryStream(this.tiffBytes))
using (var image = SDImage.FromStream(memoryStream))
{
return image.Size;
}
}
[Benchmark(Description = "ImageSharp Tiff")]
public Size TiffCore()
{
using (var memoryStream = new MemoryStream(this.tiffBytes))
using (var image = Image.Load<Rgba32>(memoryStream))
{
return image.Size();
}
}
}
}

76
tests/ImageSharp.Benchmarks/Codecs/DecodeTiffBig.cs

@ -0,0 +1,76 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Reports;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
using SDImage = System.Drawing.Image;
using SDSize = System.Drawing.Size;
namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
[Config(typeof(DecodeTiffBig.Config.LongClr))]
public class DecodeTiffBig : BenchmarkBase
{
private class Config : SixLabors.ImageSharp.Benchmarks.Config
{
public class LongClr : Config
{
public LongClr()
{
this.Add(
Job.Default.With(ClrRuntime.Net472).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(5),
Job.Default.With(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(5),
Job.Default.With(CoreRuntime.Core21).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(5));
this.SummaryStyle = SummaryStyle.Default.WithMaxParameterColumnWidth(60);
}
}
}
private string prevImage = null;
private byte[] data;
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
[Params(TestImages.Tiff.Benchmark_GrayscaleUncompressed, TestImages.Tiff.Benchmark_PaletteUncompressed, TestImages.Tiff.Benchmark_RgbDeflate, TestImages.Tiff.Benchmark_RgbLzw, TestImages.Tiff.Benchmark_RgbPackbits, TestImages.Tiff.Benchmark_RgbUncompressed)]
// [Params(TestImages.Tiff.GrayscaleUncompressed, TestImages.Tiff.PaletteUncompressed, TestImages.Tiff.RgbDeflate, TestImages.Tiff.RgbLzw, TestImages.Tiff.RgbPackbits, TestImages.Tiff.RgbUncompressed)]
public string TestImage { get; set; }
[IterationSetup]
public void ReadImages()
{
if (this.prevImage != this.TestImage)
{
this.data = File.ReadAllBytes(this.TestImageFullPath);
this.prevImage = this.TestImage;
}
}
[Benchmark(Baseline = true, Description = "System.Drawing Tiff")]
public SDSize TiffSystemDrawing()
{
using (var memoryStream = new MemoryStream(this.data))
using (var image = SDImage.FromStream(memoryStream))
{
return image.Size;
}
}
[Benchmark(Description = "ImageSharp Tiff")]
public Size TiffCore()
{
using (var ms = new MemoryStream(this.data))
using (var image = Image.Load<Rgba32>(ms))
{
return image.Size();
}
}
}
}

10
tests/ImageSharp.Tests/FileTestBase.cs

@ -22,7 +22,9 @@ namespace SixLabors.ImageSharp.Tests
TestImages.Bmp.Car,
TestImages.Jpeg.Baseline.Calliphora,
TestImages.Png.Splash,
TestImages.Gif.Trans
TestImages.Gif.Trans,
TestImages.Tga.Bit24PalRleTopRight,
TestImages.Tiff.RgbPackbits,
};
/// <summary>
@ -64,6 +66,10 @@ namespace SixLabors.ImageSharp.Tests
public const string Png = "png";
public const string Gif = "gif";
public const string Tga = "tga";
public const string Tiff = "tiff";
}
/// <summary>
@ -109,6 +115,8 @@ namespace SixLabors.ImageSharp.Tests
// TestFile.Create(TestImages.Gif.Trans), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Gif.Cheers), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Gif.Giphy) // Perf: Enable for local testing only
TestFile.Create(TestImages.Tga.Bit24PalRleTopRight),
TestFile.Create(TestImages.Tiff.RgbPackbits),
};
#pragma warning restore SA1515 // Single-line comment should be preceded by blank line
}

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

@ -0,0 +1,47 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.Formats.Png.Zlib;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Memory;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
[Trait("Category", "Tiff")]
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];
new DeflateTiffCompression(null).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(Configuration.Default.MemoryAllocator, compressedStream, ImageSharp.Formats.Png.PngCompressionLevel.Level6))
{
uncompressedStream.CopyTo(deflateStream);
}
compressedStream.Seek(0, SeekOrigin.Begin);
return compressedStream;
}
}
}

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

@ -0,0 +1,44 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.Formats.Tiff;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
[Trait("Category", "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];
new LzwTiffCompression(null).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.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.Formats.Tiff;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
[Trait("Category", "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];
new NoneTiffCompression(null).Decompress(stream, byteCount, buffer);
Assert.Equal(expectedResult, buffer);
}
}
}

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

@ -0,0 +1,34 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Memory;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Formats.Tiff
{
[Trait("Category", "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];
new PackBitsTiffCompression(new ArrayPoolMemoryAllocator()).Decompress(stream, inputData.Length, buffer);
Assert.Equal(expectedResult, buffer);
}
}
}

172
tests/ImageSharp.Tests/Formats/Tiff/ImageExtensionsTest.cs

@ -0,0 +1,172 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
[Trait("Category", "Tiff.BlackBox.Encoder")]
[Trait("Category", "Tiff")]
public class ImageExtensionsTest
{
[Theory]
[WithFile(TestImages.Tiff.RgbUncompressed, PixelTypes.Rgba32)]
public void ThrowsSavingNotImplemented<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
Assert.Throws<NotImplementedException>(() =>
{
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest));
string file = Path.Combine(dir, "SaveAsTiff_Path.tiff");
using var image = provider.GetImage(new TiffDecoder());
image.SaveAsTiff(file);
});
}
[Fact(Skip = "Saving not implemented")]
public void SaveAsTiff_Path()
{
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest));
string file = Path.Combine(dir, "SaveAsTiff_Path.tiff");
using (var image = new Image<Rgba32>(10, 10))
{
image.SaveAsTiff(file);
}
using (Image.Load(file, out IImageFormat mime))
{
Assert.Equal("image/tiff", mime.DefaultMimeType);
}
}
[Fact(Skip = "Saving not implemented")]
public async Task SaveAsTiffAsync_Path()
{
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensionsTest));
string file = Path.Combine(dir, "SaveAsTiffAsync_Path.tiff");
using (var image = new Image<Rgba32>(10, 10))
{
await image.SaveAsTiffAsync(file);
}
using (Image.Load(file, out IImageFormat mime))
{
Assert.Equal("image/tiff", mime.DefaultMimeType);
}
}
[Fact(Skip = "Saving not implemented")]
public void SaveAsTiff_Path_Encoder()
{
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions));
string file = Path.Combine(dir, "SaveAsTiff_Path_Encoder.tiff");
using (var image = new Image<Rgba32>(10, 10))
{
image.SaveAsTiff(file, new TiffEncoder());
}
using (Image.Load(file, out IImageFormat mime))
{
Assert.Equal("image/tiff", mime.DefaultMimeType);
}
}
[Fact(Skip = "Saving not implemented")]
public async Task SaveAsTiffAsync_Path_Encoder()
{
string dir = TestEnvironment.CreateOutputDirectory(nameof(ImageExtensions));
string file = Path.Combine(dir, "SaveAsTiffAsync_Path_Encoder.tiff");
using (var image = new Image<Rgba32>(10, 10))
{
await image.SaveAsTiffAsync(file, new TiffEncoder());
}
using (Image.Load(file, out IImageFormat mime))
{
Assert.Equal("image/tiff", mime.DefaultMimeType);
}
}
[Fact(Skip = "Saving not implemented")]
public void SaveAsTiff_Stream()
{
using var memoryStream = new MemoryStream();
using (var image = new Image<Rgba32>(10, 10))
{
image.SaveAsTiff(memoryStream);
}
memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime))
{
Assert.Equal("image/tiff", mime.DefaultMimeType);
}
}
[Fact(Skip = "Saving not implemented")]
public async Task SaveAsTiffAsync_StreamAsync()
{
using var memoryStream = new MemoryStream();
using (var image = new Image<Rgba32>(10, 10))
{
await image.SaveAsTiffAsync(memoryStream);
}
memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime))
{
Assert.Equal("image/tiff", mime.DefaultMimeType);
}
}
[Fact(Skip = "Saving not implemented")]
public void SaveAsTiff_Stream_Encoder()
{
using var memoryStream = new MemoryStream();
using (var image = new Image<Rgba32>(10, 10))
{
image.SaveAsTiff(memoryStream, new TiffEncoder());
}
memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime))
{
Assert.Equal("image/tiff", mime.DefaultMimeType);
}
}
[Fact(Skip = "Saving not implemented")]
public async Task SaveAsTiffAsync_Stream_Encoder()
{
using var memoryStream = new MemoryStream();
using (var image = new Image<Rgba32>(10, 10))
{
await image.SaveAsTiffAsync(memoryStream, new TiffEncoder());
}
memoryStream.Position = 0;
using (Image.Load(memoryStream, out IImageFormat mime))
{
Assert.Equal("image/tiff", mime.DefaultMimeType);
}
}
}
}

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

@ -0,0 +1,162 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
namespace SixLabors.ImageSharp.Tests.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 readonly byte[] Bilevel_Bytes4x4 = new byte[] { 0b01010000,
0b11110000,
0b01110000,
0b10010000 };
private static readonly 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 readonly byte[] Bilevel_Bytes12x4 = new byte[] { 0b01010101, 0b01010000,
0b11111111, 0b11111111,
0b01101001, 0b10100000,
0b10010000, 0b01100000};
private static readonly 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 readonly byte[] Grayscale4_Bytes4x4 = new byte[] { 0x8F, 0x0F,
0xFF, 0xFF,
0x08, 0x8F,
0xF0, 0xF8 };
private static readonly 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 readonly byte[] Grayscale4_Bytes3x4 = new byte[] { 0x8F, 0x00,
0xFF, 0xF0,
0x08, 0x80,
0xF0, 0xF0 };
private static readonly Rgba32[][] Grayscale4_Result3x4 = new[] { new[] { Gray8, GrayF, Gray0 },
new[] { GrayF, GrayF, GrayF },
new[] { Gray0, Gray8, Gray8 },
new[] { GrayF, Gray0, GrayF }};
private static readonly byte[] Grayscale8_Bytes4x4 = new byte[] { 128, 255, 000, 255,
255, 255, 255, 255,
000, 128, 128, 255,
255, 000, 255, 128 };
private static readonly 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 =>
{
new BlackIsZeroTiffColor<Rgba32>(new[] { (ushort)bitsPerSample }).Decode(inputData, 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 =>
{
new BlackIsZero1TiffColor<Rgba32>().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 =>
{
new BlackIsZero4TiffColor<Rgba32>().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 =>
{
new BlackIsZero8TiffColor<Rgba32>().Decode(inputData, pixels, left, top, width, height);
});
}
}
}

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

@ -0,0 +1,141 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
public class PaletteTiffColorTests : PhotometricInterpretationTestBase
{
public static uint[][] Palette4_ColorPalette { get => GeneratePalette(16); }
public static ushort[] Palette4_ColorMap { get => GenerateColorMap(Palette4_ColorPalette); }
private static readonly byte[] Palette4_Bytes4x4 = new byte[] { 0x01, 0x23,
0x4A, 0xD2,
0x12, 0x34,
0xAB, 0xEF };
private static readonly 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 readonly byte[] Palette4_Bytes3x4 = new byte[] { 0x01, 0x20,
0x4A, 0xD0,
0x12, 0x30,
0xAB, 0xE0 };
private static readonly 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 ushort[] Palette8_ColorMap { get => GenerateColorMap(Palette8_ColorPalette); }
private static readonly byte[] Palette8_Bytes4x4 = new byte[] { 000, 001, 002, 003,
100, 110, 120, 130,
000, 255, 128, 255,
050, 100, 150, 200 };
private static readonly 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, ushort bitsPerSample, ushort[] colorMap, int left, int top, int width, int height, Rgba32[][] expectedResult)
{
AssertDecode(expectedResult, pixels =>
{
new PaletteTiffColor<Rgba32>(new[] { (ushort)bitsPerSample }, colorMap).Decode(inputData, 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 ushort[] GenerateColorMap(uint[][] colorPalette)
{
int colorCount = colorPalette.Length;
ushort[] colorMap = new ushort[colorCount * 3];
for (int i = 0; i < colorCount; i++)
{
colorMap[colorCount * 0 + i] = (ushort)colorPalette[i][0];
colorMap[colorCount * 1 + i] = (ushort)colorPalette[i][1];
colorMap[colorCount * 2 + i] = (ushort)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;
}
}
}

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

@ -0,0 +1,69 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
[Trait("Category", "Tiff")]
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;
using (Image<Rgba32> image = new Image<Rgba32>(resultWidth, resultHeight))
{
image.Mutate(x => x.BackgroundColor(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]}");
}
}
}
}
}
}

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

@ -0,0 +1,206 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
namespace SixLabors.ImageSharp.Tests.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 ushort[] { 4, 4, 4 }, 0, 0, 4, 4, Rgb4_Result4x4 };
yield return new object[] { Rgb4_Bytes4x4, new ushort[] { 4, 4, 4 }, 0, 0, 4, 4, Offset(Rgb4_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes4x4, new ushort[] { 4, 4, 4 }, 1, 0, 4, 4, Offset(Rgb4_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes4x4, new ushort[] { 4, 4, 4 }, 0, 1, 4, 4, Offset(Rgb4_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Rgb4_Bytes4x4, new ushort[] { 4, 4, 4 }, 1, 1, 4, 4, Offset(Rgb4_Result4x4, 1, 1, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new ushort[] { 4, 4, 4 }, 0, 0, 3, 4, Rgb4_Result3x4 };
yield return new object[] { Rgb4_Bytes3x4, new ushort[] { 4, 4, 4 }, 0, 0, 3, 4, Offset(Rgb4_Result3x4, 0, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new ushort[] { 4, 4, 4 }, 1, 0, 3, 4, Offset(Rgb4_Result3x4, 1, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new ushort[] { 4, 4, 4 }, 0, 1, 3, 4, Offset(Rgb4_Result3x4, 0, 1, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new ushort[] { 4, 4, 4 }, 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 ushort[] { 8, 8, 8 }, 0, 0, 4, 4, Rgb8_Result4x4 };
yield return new object[] { Rgb8_Bytes4x4, new ushort[] { 8, 8, 8 }, 0, 0, 4, 4, Offset(Rgb8_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Rgb8_Bytes4x4, new ushort[] { 8, 8, 8 }, 1, 0, 4, 4, Offset(Rgb8_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Rgb8_Bytes4x4, new ushort[] { 8, 8, 8 }, 0, 1, 4, 4, Offset(Rgb8_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Rgb8_Bytes4x4, new ushort[] { 8, 8, 8 }, 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 ushort[] { 4, 8, 4 }, 0, 0, 4, 4, Rgb484_Result4x4 };
yield return new object[] { Rgb484_Bytes4x4, new ushort[] { 4, 8, 4 }, 0, 0, 4, 4, Offset(Rgb484_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Rgb484_Bytes4x4, new ushort[] { 4, 8, 4 }, 1, 0, 4, 4, Offset(Rgb484_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Rgb484_Bytes4x4, new ushort[] { 4, 8, 4 }, 0, 1, 4, 4, Offset(Rgb484_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Rgb484_Bytes4x4, new ushort[] { 4, 8, 4 }, 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, ushort[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
{
AssertDecode(expectedResult, pixels =>
{
var buffers = new IManagedByteBuffer[inputData.Length];
for (int i = 0; i < buffers.Length; i++)
{
buffers[i] = Configuration.Default.MemoryAllocator.AllocateManagedByteBuffer(inputData[i].Length);
((Span<byte>)inputData[i]).CopyTo(buffers[i].GetSpan());
}
new RgbPlanarTiffColor<Rgba32>(bitsPerSample).Decode(buffers, pixels, left, top, width, height);
});
}
}
}

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

@ -0,0 +1,159 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
namespace SixLabors.ImageSharp.Tests.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 ushort[] { 4, 4, 4 }, 0, 0, 4, 4, Rgb4_Result4x4 };
yield return new object[] { Rgb4_Bytes4x4, new ushort[] { 4, 4, 4 }, 0, 0, 4, 4, Offset(Rgb4_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes4x4, new ushort[] { 4, 4, 4 }, 1, 0, 4, 4, Offset(Rgb4_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes4x4, new ushort[] { 4, 4, 4 }, 0, 1, 4, 4, Offset(Rgb4_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Rgb4_Bytes4x4, new ushort[] { 4, 4, 4 }, 1, 1, 4, 4, Offset(Rgb4_Result4x4, 1, 1, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new ushort[] { 4, 4, 4 }, 0, 0, 3, 4, Rgb4_Result3x4 };
yield return new object[] { Rgb4_Bytes3x4, new ushort[] { 4, 4, 4 }, 0, 0, 3, 4, Offset(Rgb4_Result3x4, 0, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new ushort[] { 4, 4, 4 }, 1, 0, 3, 4, Offset(Rgb4_Result3x4, 1, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new ushort[] { 4, 4, 4 }, 0, 1, 3, 4, Offset(Rgb4_Result3x4, 0, 1, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new ushort[] { 4, 4, 4 }, 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 ushort[] { 8, 8, 8 }, 0, 0, 4, 4, Rgb8_Result4x4 };
yield return new object[] { Rgb8_Bytes4x4, new ushort[] { 8, 8, 8 }, 0, 0, 4, 4, Offset(Rgb8_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Rgb8_Bytes4x4, new ushort[] { 8, 8, 8 }, 1, 0, 4, 4, Offset(Rgb8_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Rgb8_Bytes4x4, new ushort[] { 8, 8, 8 }, 0, 1, 4, 4, Offset(Rgb8_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Rgb8_Bytes4x4, new ushort[] { 8, 8, 8 }, 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 ushort[] { 4, 8, 4 }, 0, 0, 4, 4, Rgb484_Result4x4 };
yield return new object[] { Rgb484_Bytes4x4, new ushort[] { 4, 8, 4 }, 0, 0, 4, 4, Offset(Rgb484_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Rgb484_Bytes4x4, new ushort[] { 4, 8, 4 }, 1, 0, 4, 4, Offset(Rgb484_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Rgb484_Bytes4x4, new ushort[] { 4, 8, 4 }, 0, 1, 4, 4, Offset(Rgb484_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Rgb484_Bytes4x4, new ushort[] { 4, 8, 4 }, 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, ushort[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
{
AssertDecode(expectedResult, pixels =>
{
new RgbTiffColor<Rgba32>(bitsPerSample).Decode(inputData, pixels, left, top, width, height);
});
}
[Theory]
[MemberData(nameof(Rgb8_Data))]
public void Decode_WritesPixelData_8Bit(byte[] inputData, ushort[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
{
AssertDecode(expectedResult, pixels =>
{
new Rgb888TiffColor<Rgba32>().Decode(inputData, pixels, left, top, width, height);
});
}
}
}

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

@ -0,0 +1,162 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
namespace SixLabors.ImageSharp.Tests.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 readonly byte[] Bilevel_Bytes4x4 = new byte[] { 0b01010000,
0b11110000,
0b01110000,
0b10010000 };
private static readonly 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 readonly byte[] Bilevel_Bytes12x4 = new byte[] { 0b01010101, 0b01010000,
0b11111111, 0b11111111,
0b01101001, 0b10100000,
0b10010000, 0b01100000};
private static readonly 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 readonly byte[] Grayscale4_Bytes4x4 = new byte[] { 0x8F, 0x0F,
0xFF, 0xFF,
0x08, 0x8F,
0xF0, 0xF8 };
private static readonly 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 readonly byte[] Grayscale4_Bytes3x4 = new byte[] { 0x8F, 0x00,
0xFF, 0xF0,
0x08, 0x80,
0xF0, 0xF0 };
private static readonly Rgba32[][] Grayscale4_Result3x4 = new[] { new[] { Gray8, GrayF, Gray0 },
new[] { GrayF, GrayF, GrayF },
new[] { Gray0, Gray8, Gray8 },
new[] { GrayF, Gray0, GrayF }};
private static readonly byte[] Grayscale8_Bytes4x4 = new byte[] { 128, 255, 000, 255,
255, 255, 255, 255,
000, 128, 128, 255,
255, 000, 255, 128 };
private static readonly 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 =>
{
new WhiteIsZeroTiffColor<Rgba32>(new[] { (ushort)bitsPerSample }).Decode(inputData, 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 =>
{
new WhiteIsZero1TiffColor<Rgba32>().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 =>
{
new WhiteIsZero4TiffColor<Rgba32>().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 =>
{
new WhiteIsZero8TiffColor<Rgba32>().Decode(inputData, pixels, left, top, width, height);
});
}
}
}

85
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs

@ -0,0 +1,85 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// ReSharper disable InconsistentNaming
using System;
using System.IO;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
[Trait("Category", "Tiff.BlackBox.Decoder")]
[Trait("Category", "Tiff")]
public class TiffDecoderTests
{
public static readonly string[] SingleTestImages = TestImages.Tiff.All;
public static readonly string[] MultiframeTestImages = TestImages.Tiff.Multiframes;
public static readonly string[] NotSupportedImages = TestImages.Tiff.NotSupported;
[Theory]
[WithFileCollection(nameof(NotSupportedImages), PixelTypes.Rgba32)]
public void ThrowsNotSupported<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
Assert.Throws<NotSupportedException>(() => provider.GetImage(new TiffDecoder()));
}
[Theory]
[InlineData(TestImages.Tiff.RgbUncompressed, 24, 256, 256, 300, 300, PixelResolutionUnit.PixelsPerInch)]
[InlineData(TestImages.Tiff.SmallRgbDeflate, 24, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)]
[InlineData(TestImages.Tiff.Calliphora_GrayscaleUncompressed, 8, 804, 1198, 96, 96, PixelResolutionUnit.PixelsPerInch)]
public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
IImageInfo info = Image.Identify(stream);
Assert.Equal(expectedPixelSize, info.PixelType?.BitsPerPixel);
Assert.Equal(expectedWidth, info.Width);
Assert.Equal(expectedHeight, info.Height);
Assert.NotNull(info.Metadata);
Assert.Equal(expectedHResolution, info.Metadata.HorizontalResolution);
Assert.Equal(expectedVResolution, info.Metadata.VerticalResolution);
Assert.Equal(expectedResolutionUnit, info.Metadata.ResolutionUnits);
}
}
[Theory]
[WithFileCollection(nameof(SingleTestImages), PixelTypes.Rgba32)]
public void Decode<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new TiffDecoder()))
{
image.DebugSave(provider);
image.CompareToOriginal(provider, ImageComparer.Exact, new MagickReferenceDecoder());
}
}
[Theory]
[WithFileCollection(nameof(MultiframeTestImages), PixelTypes.Rgba32)]
public void DecodeMultiframe<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new TiffDecoder()))
{
Assert.True(image.Frames.Count > 1);
image.DebugSave(provider);
image.CompareToOriginal(provider, ImageComparer.Exact, new MagickReferenceDecoder());
image.DebugSaveMultiFrame(provider);
image.CompareToOriginalMultiFrame(provider, ImageComparer.Exact, new MagickReferenceDecoder());
}
}
}
}

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

@ -0,0 +1,40 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.Formats.Tiff;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
[Trait("Category", "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);
}
}
}
}

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

@ -0,0 +1,24 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Formats.Tiff;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
[Trait("Category", "Tiff")]
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);
}
}
}

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

@ -0,0 +1,113 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
[Trait("Category", "Tiff")]
public class TiffMetadataTests
{
public static readonly string[] MetadataImages = TestImages.Tiff.Metadata;
[Theory]
[WithFile(TestImages.Tiff.SampleMetadata, PixelTypes.Rgba32, false)]
[WithFile(TestImages.Tiff.SampleMetadata, PixelTypes.Rgba32, true)]
public void MetadataProfiles<TPixel>(TestImageProvider<TPixel> provider, bool ignoreMetadata)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new TiffDecoder() { IgnoreMetadata = ignoreMetadata }))
{
TiffMetadata meta = image.Metadata.GetTiffMetadata();
Assert.NotNull(meta);
if (ignoreMetadata)
{
Assert.Null(meta.XmpProfile);
}
else
{
Assert.NotNull(meta.XmpProfile);
Assert.Equal(2599, meta.XmpProfile.Length);
}
}
}
[Theory]
[WithFile(TestImages.Tiff.SampleMetadata, PixelTypes.Rgba32)]
public void BaselineTags<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new TiffDecoder()))
{
TiffMetadata meta = image.Metadata.GetTiffMetadata();
Assert.NotNull(meta);
Assert.Equal(TiffByteOrder.LittleEndian, meta.ByteOrder);
Assert.Equal(PixelResolutionUnit.PixelsPerInch, image.Metadata.ResolutionUnits);
Assert.Equal(10, image.Metadata.HorizontalResolution);
Assert.Equal(10, image.Metadata.VerticalResolution);
TiffFrameMetadata frame = image.Frames.RootFrame.Metadata.GetTiffMetadata();
Assert.Equal(32u, frame.Width);
Assert.Equal(32u, frame.Height);
Assert.Equal(new ushort[] { 4 }, frame.BitsPerSample);
Assert.Equal(TiffCompression.Lzw, frame.Compression);
Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frame.PhotometricInterpretation);
Assert.Equal("This is Название", frame.ImageDescription);
Assert.Equal("This is Изготовитель камеры", frame.Make);
Assert.Equal("This is Модель камеры", frame.Model);
Assert.Equal(new uint[] { 8 }, frame.StripOffsets);
Assert.Equal(1, frame.SamplesPerPixel);
Assert.Equal(32u, frame.RowsPerStrip);
Assert.Equal(new uint[] { 297 }, frame.StripByteCounts);
Assert.Equal(10, frame.HorizontalResolution);
Assert.Equal(10, frame.VerticalResolution);
Assert.Equal(TiffPlanarConfiguration.Chunky, frame.PlanarConfiguration);
Assert.Equal(TiffResolutionUnit.Inch, frame.ResolutionUnit);
Assert.Equal("IrfanView", frame.Software);
Assert.Equal(null, frame.DateTime);
Assert.Equal("This is author1;Author2", frame.Artist);
Assert.Equal(null, frame.HostComputer);
Assert.Equal(48, frame.ColorMap.Length);
Assert.Equal(10537, frame.ColorMap[0]);
Assert.Equal(14392, frame.ColorMap[1]);
Assert.Equal(58596, frame.ColorMap[46]);
Assert.Equal(3855, frame.ColorMap[47]);
Assert.Equal(null, frame.ExtraSamples);
Assert.Equal(TiffPredictor.None, frame.Predictor);
Assert.Equal(null, frame.SampleFormat);
Assert.Equal("This is Авторские права", frame.Copyright);
}
}
[Theory]
[WithFile(TestImages.Tiff.MultiframeDeflateWithPreview, PixelTypes.Rgba32)]
public void SubfileType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new TiffDecoder()))
{
TiffMetadata meta = image.Metadata.GetTiffMetadata();
Assert.NotNull(meta);
Assert.Equal(2, image.Frames.Count);
TiffFrameMetadata frame0 = image.Frames[0].Metadata.GetTiffMetadata();
Assert.Equal(TiffNewSubfileType.FullImage, frame0.NewSubfileType);
Assert.Equal(null, frame0.SubfileType);
Assert.Equal(255u, frame0.Width);
Assert.Equal(255u, frame0.Height);
TiffFrameMetadata frame1 = image.Frames[1].Metadata.GetTiffMetadata();
Assert.Equal(TiffNewSubfileType.Preview, frame1.NewSubfileType);
Assert.Equal(TiffSubfileType.Preview, frame1.SubfileType);
Assert.Equal(255u, frame1.Width);
Assert.Equal(255u, frame1.Height);
}
}
}
}

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

@ -0,0 +1,326 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Formats.Tiff;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
[Trait("Category", "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));
}
}
}

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

Loading…
Cancel
Save