Browse Source

Deep refactoring and improves for tiff classes.

In particular:
* Support multi framing.
* Using Metadata\Profiles\Exif\* types instead their duplicate tag types (moved to __obsolete directory).
* Implement useful Metadata classes.
* Add extensions.
* Test coverage.
pull/1570/head
Ildar Khayrutdinov 6 years ago
parent
commit
768ff6b582
  1. 106
      src/ImageSharp/Formats/ImageExtensions.Save.cs
  2. 3
      src/ImageSharp/Formats/ImageExtensions.Save.tt
  3. 42
      src/ImageSharp/Formats/Tiff/Compression/CompressionFactory.cs
  4. 2
      src/ImageSharp/Formats/Tiff/Compression/DeflateTiffCompression.cs
  5. 21
      src/ImageSharp/Formats/Tiff/Constants/TiffByteOrder.cs
  6. 4
      src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs
  7. 4
      src/ImageSharp/Formats/Tiff/Constants/TiffFillOrder.cs
  8. 4
      src/ImageSharp/Formats/Tiff/Constants/TiffNewSubfileType.cs
  9. 4
      src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs
  10. 4
      src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs
  11. 4
      src/ImageSharp/Formats/Tiff/Constants/TiffResolutionUnit.cs
  12. 4
      src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs
  13. 28
      src/ImageSharp/Formats/Tiff/MetadataExtensions.cs
  14. 15
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor.cs
  15. 15
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor.cs
  16. 15
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor.cs
  17. 26
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor.cs
  18. 34
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor.cs
  19. 15
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor.cs
  20. 50
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor.cs
  21. 44
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor.cs
  22. 52
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoder.cs
  23. 95
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory.cs
  24. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs
  25. 14
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor.cs
  26. 15
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor.cs
  27. 15
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor.cs
  28. 27
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor.cs
  29. 88
      src/ImageSharp/Formats/Tiff/Streams/TiffBigEndianStream.cs
  30. 88
      src/ImageSharp/Formats/Tiff/Streams/TiffLittleEndianStream.cs
  31. 94
      src/ImageSharp/Formats/Tiff/Streams/TiffStream.cs
  32. 59
      src/ImageSharp/Formats/Tiff/Streams/TiffStreamFactory.cs
  33. 23
      src/ImageSharp/Formats/Tiff/TiffDecoder.cs
  34. 1205
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  35. 344
      src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs
  36. 113
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  37. 11
      src/ImageSharp/Formats/Tiff/TiffFormat.cs
  38. 324
      src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
  39. 125
      src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfd.cs
  40. 328
      src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfdEntry.cs
  41. 44
      src/ImageSharp/Formats/Tiff/TiffMetadata.cs
  42. 0
      src/ImageSharp/Formats/Tiff/__obsolete/TiffIfdEntryCreator.cs
  43. 0
      src/ImageSharp/Formats/Tiff/__obsolete/TiffMetadataNames.cs
  44. 0
      src/ImageSharp/Formats/Tiff/__obsolete/TiffTagId.cs
  45. 0
      src/ImageSharp/Formats/Tiff/__obsolete/TiffTagType.cs
  46. 6
      src/ImageSharp/ImageSharp.csproj
  47. 7
      src/ImageSharp/Metadata/Profiles/Exif/ExifDataType.cs
  48. 22
      src/ImageSharp/Metadata/Profiles/Exif/ExifWriter.cs
  49. 10
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.ByteArray.cs
  50. 5
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.Long.cs
  51. 6
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs
  52. 287
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTagValue.cs
  53. 6
      src/ImageSharp/Metadata/Profiles/Exif/Values/ExifValues.cs
  54. 172
      tests/ImageSharp.Tests/Formats/Tiff/ImageExtensionsTest.cs
  55. 28
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs
  56. 30
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs
  57. 24
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PhotometricInterpretationTestBase.cs
  58. 46
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs
  59. 50
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs
  60. 28
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColorTests.cs
  61. 43
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  62. 105
      tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs
  63. 0
      tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TestUtilities/Tiff/ITiffGenDataSource.cs
  64. 0
      tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TestUtilities/Tiff/TiffGenDataBlock.cs
  65. 0
      tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TestUtilities/Tiff/TiffGenDataReference.cs
  66. 0
      tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TestUtilities/Tiff/TiffGenEntry.cs
  67. 0
      tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TestUtilities/Tiff/TiffGenExtensions.cs
  68. 0
      tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TestUtilities/Tiff/TiffGenHeader.cs
  69. 0
      tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TestUtilities/Tiff/TiffGenIfd.cs
  70. 0
      tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TestUtilities/Tiff/TiffGenIfdExtensions.cs
  71. 0
      tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TestUtilities/Tiff/TiffIfdParser.cs
  72. 0
      tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TiffDecoderHeaderTests.cs
  73. 0
      tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TiffDecoderIfdEntryTests.cs
  74. 0
      tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TiffDecoderIfdTests.cs
  75. 0
      tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TiffDecoderImageTests.cs
  76. 0
      tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TiffDecoderMetadataTests.cs
  77. 0
      tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TiffEncoderIfdTests.cs
  78. 0
      tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TiffEncoderMetadataTests.cs
  79. 0
      tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TiffIfd/TiffIfdEntryCreatorTests.cs
  80. 0
      tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TiffIfd/TiffIfdEntryTests.cs
  81. 0
      tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TiffIfd/TiffIfdTests.cs
  82. 0
      tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TiffImageFormatDetectorTests.cs
  83. 6
      tests/ImageSharp.Tests/ImageSharp.Tests.csproj
  84. 45
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs
  85. 25
      tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

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)

42
src/ImageSharp/Formats/Tiff/Compression/CompressionFactory.cs

@ -0,0 +1,42 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
namespace SixLabors.ImageSharp.Formats.Tiff
{
internal static class CompressionFactory
{
/// <summary>
/// Decompresses an image block from the input stream into the specified buffer.
/// </summary>
/// <param name="stream">The input stream.</param>
/// <param name="compressionType">Type of the compression.</param>
/// <param name="offset">The offset within the file of the image block.</param>
/// <param name="byteCount">The size (in bytes) of the compressed data.</param>
/// <param name="buffer">The buffer to write the uncompressed data.</param>
public static void DecompressImageBlock(Stream stream, TiffCompressionType compressionType, uint offset, uint byteCount, byte[] buffer)
{
stream.Seek(offset, SeekOrigin.Begin);
switch (compressionType)
{
case TiffCompressionType.None:
NoneTiffCompression.Decompress(stream, (int)byteCount, buffer);
break;
case TiffCompressionType.PackBits:
PackBitsTiffCompression.Decompress(stream, (int)byteCount, buffer);
break;
case TiffCompressionType.Deflate:
DeflateTiffCompression.Decompress(stream, (int)byteCount, buffer);
break;
case TiffCompressionType.Lzw:
LzwTiffCompression.Decompress(stream, (int)byteCount, buffer);
break;
default:
throw new InvalidOperationException();
}
}
}
}

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

@ -48,7 +48,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
// The subsequent data is the Deflate compressed data (except for the last four bytes of checksum)
int headerLength = fdict ? 10 : 6;
SubStream subStream = new SubStream(stream, byteCount - headerLength);
using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress, true))
using (DeflateStream deflateStream = new DeflateStream(subStream, CompressionMode.Decompress, true))
{
deflateStream.ReadFull(buffer);
}

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

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

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Enumeration representing the compression formats defined by the Tiff file-format.
/// </summary>
internal enum TiffCompression
public enum TiffCompression : ushort
{
/// <summary>
/// No compression.
@ -68,4 +68,4 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary>
ItuTRecT43 = 10
}
}
}

4
src/ImageSharp/Formats/Tiff/Constants/TiffFillOrder.cs

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Enumeration representing the fill orders defined by the Tiff file-format.
/// </summary>
internal enum TiffFillOrder
internal enum TiffFillOrder : ushort
{
/// <summary>
/// Pixels with lower column values are stored in the higher-order bits of the byte.
@ -18,4 +18,4 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary>
LeastSignificantBitFirst = 2
}
}
}

4
src/ImageSharp/Formats/Tiff/Constants/TiffNewSubfileType.cs

@ -9,7 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// Enumeration representing the sub-file types defined by the Tiff file-format.
/// </summary>
[Flags]
internal enum TiffNewSubfileType
public enum TiffNewSubfileType : uint
{
/// <summary>
/// A full-resolution image.
@ -41,4 +41,4 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary>
MixedRasterContent = 0x0008
}
}
}

4
src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Enumeration representing the photometric interpretation formats defined by the Tiff file-format.
/// </summary>
internal enum TiffPhotometricInterpretation
public enum TiffPhotometricInterpretation : ushort
{
/// <summary>
/// Bilevel and grayscale: 0 is imaged as white. The maximum value is imaged as black.
@ -68,4 +68,4 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary>
LinearRaw = 34892
}
}
}

4
src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Enumeration representing how the components of each pixel are stored the Tiff file-format.
/// </summary>
internal enum TiffPlanarConfiguration
public enum TiffPlanarConfiguration : ushort
{
/// <summary>
/// Chunky format.
@ -18,4 +18,4 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary>
Planar = 2
}
}
}

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

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Enumeration representing the resolution units defined by the Tiff file-format.
/// </summary>
internal enum TiffResolutionUnit
public enum TiffResolutionUnit : ushort
{
/// <summary>
/// No absolute unit of measurement.
@ -23,4 +23,4 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary>
Centimeter = 3
}
}
}

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

@ -6,7 +6,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Enumeration representing the sub-file types defined by the Tiff file-format.
/// </summary>
internal enum TiffSubfileType
public enum TiffSubfileType : uint
{
/// <summary>
/// Full-resolution image data.
@ -23,4 +23,4 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary>
SinglePage = 3
}
}
}

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);
}
}

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

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -11,21 +10,25 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation (optimised for bilevel images).
/// </summary>
internal static class BlackIsZero1TiffColor
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class BlackIsZero1TiffColor<TPixel> : TiffColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public BlackIsZero1TiffColor()
: base(null, null)
{
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TPixel>(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
where TPixel : unmanaged, IPixel<TPixel>
public override void Decode(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default(TPixel);

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

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -10,21 +10,24 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation (optimised for 4-bit grayscale images).
/// </summary>
internal static class BlackIsZero4TiffColor
internal class BlackIsZero4TiffColor<TPixel> : TiffColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public BlackIsZero4TiffColor()
: base(null, null)
{
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TPixel>(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
where TPixel : unmanaged, IPixel<TPixel>
public override void Decode(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default(TPixel);

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

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -10,21 +10,24 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation (optimised for 8-bit grayscale images).
/// </summary>
internal static class BlackIsZero8TiffColor
internal class BlackIsZero8TiffColor<TPixel> : TiffColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public BlackIsZero8TiffColor()
: base(null, null)
{
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TPixel>(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
where TPixel : unmanaged, IPixel<TPixel>
public override void Decode(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default(TPixel);

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

@ -3,7 +3,6 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -12,34 +11,41 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation (for all bit depths).
/// </summary>
internal static class BlackIsZeroTiffColor
internal class BlackIsZeroTiffColor<TPixel> : TiffColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly ushort bitsPerSample0;
private readonly float factor;
public BlackIsZeroTiffColor(ushort[] bitsPerSample)
: base(bitsPerSample, null)
{
this.bitsPerSample0 = bitsPerSample[0];
this.factor = (float)Math.Pow(2, this.bitsPerSample0) - 1.0f;
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="bitsPerSample">The number of bits per sample for each pixel.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TPixel>(byte[] data, uint[] bitsPerSample, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
where TPixel : unmanaged, IPixel<TPixel>
public override void Decode(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default(TPixel);
BitReader bitReader = new BitReader(data);
float factor = (float)Math.Pow(2, bitsPerSample[0]) - 1.0f;
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
{
int value = bitReader.ReadBits(bitsPerSample[0]);
float intensity = ((float)value) / factor;
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;
}

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

@ -12,35 +12,40 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Implements the 'PaletteTiffColor' photometric interpretation (for all bit depths).
/// </summary>
internal static class PaletteTiffColor
internal class PaletteTiffColor<TPixel> : TiffColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly ushort bitsPerSample0;
private readonly TPixel[] palette;
public PaletteTiffColor(ushort[] bitsPerSample, ushort[] colorMap)
: base(bitsPerSample, colorMap)
{
this.bitsPerSample0 = bitsPerSample[0];
int colorCount = (int)Math.Pow(2, this.bitsPerSample0);
this.palette = GeneratePalette(colorMap, colorCount);
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="bitsPerSample">The number of bits per sample for each pixel.</param>
/// <param name="colorMap">The RGB color lookup table to use for decoding the image.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TPixel>(byte[] data, uint[] bitsPerSample, uint[] colorMap, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
where TPixel : unmanaged, IPixel<TPixel>
public override void Decode(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
int colorCount = (int)Math.Pow(2, bitsPerSample[0]);
TPixel[] palette = GeneratePalette<TPixel>(colorMap, colorCount);
BitReader bitReader = new BitReader(data);
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
{
int index = bitReader.ReadBits(bitsPerSample[0]);
pixels[x, y] = palette[index];
int index = bitReader.ReadBits(this.bitsPerSample0);
pixels[x, y] = this.palette[index];
}
bitReader.NextRow();
@ -48,10 +53,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static TPixel[] GeneratePalette<TPixel>(uint[] colorMap, int colorCount)
where TPixel : unmanaged, IPixel<TPixel>
private static TPixel[] GeneratePalette(ushort[] colorMap, int colorCount)
{
TPixel[] palette = new TPixel[colorCount];
var palette = new TPixel[colorCount];
int rOffset = 0;
int gOffset = colorCount;

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

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -10,21 +10,24 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Implements the 'RGB' photometric interpretation (optimised for 8-bit full color images).
/// </summary>
internal static class Rgb888TiffColor
internal class Rgb888TiffColor<TPixel> : TiffColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public Rgb888TiffColor()
: base(null, null)
{
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TPixel>(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
where TPixel : unmanaged, IPixel<TPixel>
public override void Decode(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default(TPixel);

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

@ -3,7 +3,6 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -12,39 +11,58 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Implements the 'RGB' photometric interpretation with 'Planar' layout (for all bit depths).
/// </summary>
internal static class RgbPlanarTiffColor
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 uint bitsPerSampleR;
private readonly uint bitsPerSampleG;
private readonly uint bitsPerSampleB;
public RgbPlanarTiffColor(ushort[] bitsPerSample)
/* : base(bitsPerSample, null) */
{
this.bitsPerSampleR = bitsPerSample[0];
this.bitsPerSampleG = bitsPerSample[1];
this.bitsPerSampleB = bitsPerSample[2];
this.rFactor = (float)Math.Pow(2, this.bitsPerSampleR) - 1.0f;
this.gFactor = (float)Math.Pow(2, this.bitsPerSampleG) - 1.0f;
this.bFactor = (float)Math.Pow(2, this.bitsPerSampleB) - 1.0f;
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffers to read image data from.</param>
/// <param name="bitsPerSample">The number of bits per sample for each pixel.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TPixel>(byte[][] data, uint[] bitsPerSample, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
where TPixel : unmanaged, IPixel<TPixel>
public void Decode(byte[][] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default(TPixel);
BitReader rBitReader = new BitReader(data[0]);
BitReader gBitReader = new BitReader(data[1]);
BitReader bBitReader = new BitReader(data[2]);
float rFactor = (float)Math.Pow(2, bitsPerSample[0]) - 1.0f;
float gFactor = (float)Math.Pow(2, bitsPerSample[1]) - 1.0f;
float bFactor = (float)Math.Pow(2, bitsPerSample[2]) - 1.0f;
var rBitReader = new BitReader(data[0]);
var gBitReader = new BitReader(data[1]);
var bBitReader = new BitReader(data[2]);
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
{
float r = ((float)rBitReader.ReadBits(bitsPerSample[0])) / rFactor;
float g = ((float)gBitReader.ReadBits(bitsPerSample[1])) / gFactor;
float b = ((float)bBitReader.ReadBits(bitsPerSample[2])) / bFactor;
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;
}

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

@ -3,7 +3,6 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -12,37 +11,56 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Implements the 'RGB' photometric interpretation (for all bit depths).
/// </summary>
internal static class RgbTiffColor
internal class RgbTiffColor<TPixel> : TiffColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly float rFactor;
private readonly float gFactor;
private readonly float bFactor;
private readonly uint bitsPerSampleR;
private readonly uint bitsPerSampleG;
private readonly uint bitsPerSampleB;
public RgbTiffColor(ushort[] bitsPerSample)
: base(bitsPerSample, null)
{
this.bitsPerSampleR = bitsPerSample[0];
this.bitsPerSampleG = bitsPerSample[1];
this.bitsPerSampleB = bitsPerSample[2];
this.rFactor = (float)Math.Pow(2, this.bitsPerSampleR) - 1.0f;
this.gFactor = (float)Math.Pow(2, this.bitsPerSampleG) - 1.0f;
this.bFactor = (float)Math.Pow(2, this.bitsPerSampleB) - 1.0f;
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="bitsPerSample">The number of bits per sample for each pixel.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TPixel>(byte[] data, uint[] bitsPerSample, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
where TPixel : unmanaged, IPixel<TPixel>
public override void Decode(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default(TPixel);
BitReader bitReader = new BitReader(data);
float rFactor = (float)Math.Pow(2, bitsPerSample[0]) - 1.0f;
float gFactor = (float)Math.Pow(2, bitsPerSample[1]) - 1.0f;
float bFactor = (float)Math.Pow(2, bitsPerSample[2]) - 1.0f;
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
{
float r = ((float)bitReader.ReadBits(bitsPerSample[0])) / rFactor;
float g = ((float)bitReader.ReadBits(bitsPerSample[1])) / gFactor;
float b = ((float)bitReader.ReadBits(bitsPerSample[2])) / bFactor;
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;
}

52
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoder.cs

@ -0,0 +1,52 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
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 TiffColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly ushort[] bitsPerSample;
private readonly ushort[] colorMap;
/// <summary>
/// Initializes a new instance of the <see cref="TiffColorDecoder{TPixel}"/> class.
/// </summary>
/// <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>
protected TiffColorDecoder(ushort[] bitsPerSample, ushort[] colorMap)
{
this.bitsPerSample = bitsPerSample;
this.colorMap = colorMap;
}
/*
/// <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> [MethodImpl(MethodImplOptions.AggressiveInlining)]
public abstract void Decode(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 TiffColorDecoder<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();
}
}
}
}

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

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff

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

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -11,21 +10,24 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation (optimised for bilevel images).
/// </summary>
internal static class WhiteIsZero1TiffColor
internal class WhiteIsZero1TiffColor<TPixel> : TiffColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public WhiteIsZero1TiffColor()
: base(null, null)
{
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TPixel>(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
where TPixel : unmanaged, IPixel<TPixel>
public override void Decode(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default(TPixel);

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

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -10,21 +10,24 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation (optimised for 4-bit grayscale images).
/// </summary>
internal static class WhiteIsZero4TiffColor
internal class WhiteIsZero4TiffColor<TPixel> : TiffColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public WhiteIsZero4TiffColor()
: base(null, null)
{
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TPixel>(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
where TPixel : unmanaged, IPixel<TPixel>
public override void Decode(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default(TPixel);

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

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -10,21 +10,24 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation (optimised for 8-bit grayscale images).
/// </summary>
internal static class WhiteIsZero8TiffColor
internal class WhiteIsZero8TiffColor<TPixel> : TiffColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public WhiteIsZero8TiffColor()
: base(null, null)
{
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TPixel>(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
where TPixel : unmanaged, IPixel<TPixel>
public override void Decode(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default(TPixel);

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

@ -3,7 +3,6 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -12,34 +11,42 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation (for all bit depths).
/// </summary>
internal static class WhiteIsZeroTiffColor
internal class WhiteIsZeroTiffColor<TPixel> : TiffColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly ushort bitsPerSample0;
private readonly float factor;
public WhiteIsZeroTiffColor(ushort[] bitsPerSample)
: base(bitsPerSample, null)
{
this.bitsPerSample0 = bitsPerSample[0];
this.factor = (float)Math.Pow(2, this.bitsPerSample0) - 1.0f;
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="bitsPerSample">The number of bits per sample for each pixel.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode<TPixel>(byte[] data, uint[] bitsPerSample, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
where TPixel : unmanaged, IPixel<TPixel>
public override void Decode(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default(TPixel);
BitReader bitReader = new BitReader(data);
float factor = (float)Math.Pow(2, bitsPerSample[0]) - 1.0f;
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
{
int value = bitReader.ReadBits(bitsPerSample[0]);
float intensity = 1.0f - (((float)value) / factor);
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;
}

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();
}
}

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

@ -0,0 +1,59 @@
// 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
{
public static TiffStream CreateBySignature(Stream stream)
{
TiffByteOrder order = ReadByteOrder(stream);
return Create(order, stream);
}
/// <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>
private 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.");
}
}
}

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

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Image decoder for generating an image out of a TIFF stream.
/// </summary>
public class TiffDecoder : IImageDecoder, ITiffDecoderOptions
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.
@ -24,17 +24,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
Guard.NotNull(stream, "stream");
using (var decoder = new TiffDecoderCore(configuration, this))
using (var decoder = new TiffDecoderCore(stream, configuration, this))
{
return decoder.Decode<TPixel>(stream);
return decoder.Decode<TPixel>();
}
}
/// <inheritdoc/>
public Image Decode(Configuration configuration, Stream stream)
{
throw new System.NotImplementedException();
}
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)
@ -48,5 +45,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
throw new System.NotImplementedException();
}
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
throw new System.NotImplementedException();
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
throw new System.NotImplementedException();
}
}
}

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

File diff suppressed because it is too large

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

@ -0,0 +1,344 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Headers;
using SixLabors.ImageSharp.Formats.Tiff;
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<TPixel>(this IList<ImageFrame<TPixel>> frames, bool ignoreMetadata, TiffByteOrder byteOrder)
where TPixel : unmanaged, IPixel<TPixel>
{
var coreMetadata = new ImageMetadata();
TiffMetadata tiffMetadata = coreMetadata.GetTiffMetadata();
tiffMetadata.ByteOrder = byteOrder;
TiffFrameMetadata rootFrameMetadata = frames.First().Metadata.GetTiffMetadata();
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 (ImageFrame<TPixel> frame in frames)
{
TiffFrameMetadata frameMetadata = frame.Metadata.GetTiffMetadata();
if (tiffMetadata.XmpProfile == null)
{
byte[] buf = frameMetadata.GetArrayValue<byte>(ExifTag.XMP, true);
if (buf != null)
{
tiffMetadata.XmpProfile = buf;
}
}
if (coreMetadata.IptcProfile == null)
{
byte[] buf = frameMetadata.GetArrayValue<byte>(ExifTag.IPTC, true);
if (buf != null)
{
coreMetadata.IptcProfile = new IptcProfile(buf);
}
}
if (coreMetadata.IccProfile == null)
{
byte[] buf = frameMetadata.GetArrayValue<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.GetArrayValue<uint>(ExifTag.TileOffsets, true) != null)
{
throw new NotSupportedException("The Tile images is not supported.");
}
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);
}
}
}
}
}

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

@ -5,6 +5,7 @@ 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
@ -45,9 +46,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
using (TiffWriter writer = new TiffWriter(stream))
using (var writer = new TiffWriter(stream))
{
long firstIfdMarker = this.WriteHeader(writer);
//// todo: multiframing is not support
long nextIfdMarker = this.WriteImage(writer, image, firstIfdMarker);
}
}
@ -59,8 +61,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <returns>The marker to write the first IFD offset.</returns>
public long WriteHeader(TiffWriter writer)
{
ushort byteOrderMarker = BitConverter.IsLittleEndian ? TiffConstants.ByteOrderLittleEndianShort
: TiffConstants.ByteOrderBigEndianShort;
ushort byteOrderMarker = BitConverter.IsLittleEndian
? TiffConstants.ByteOrderLittleEndianShort
: TiffConstants.ByteOrderBigEndianShort;
writer.Write(byteOrderMarker);
writer.Write((ushort)42);
@ -75,7 +78,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <param name="writer">The <see cref="BinaryWriter"/> to write data to.</param>
/// <param name="entries">The IFD entries to write to the file.</param>
/// <returns>The marker to write the next IFD offset (if present).</returns>
public long WriteIfd(TiffWriter writer, List<TiffIfdEntry> entries)
public long WriteIfd(TiffWriter writer, List<IExifValue> entries)
{
if (entries.Count == 0)
{
@ -83,27 +86,31 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
uint dataOffset = (uint)writer.Position + (uint)(6 + (entries.Count * 12));
List<byte[]> largeDataBlocks = new List<byte[]>();
var largeDataBlocks = new List<byte[]>();
entries.Sort((a, b) => a.Tag - b.Tag);
entries.Sort((a, b) => (ushort)a.Tag - (ushort)b.Tag);
writer.Write((ushort)entries.Count);
foreach (TiffIfdEntry entry in entries)
foreach (ExifValue entry in entries)
{
writer.Write(entry.Tag);
writer.Write((ushort)entry.Type);
writer.Write(entry.Count);
if (entry.Value.Length <= 4)
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(entry.Value);
writer.WritePadded(raw);
}
else
{
largeDataBlocks.Add(entry.Value);
largeDataBlocks.Add(raw);
writer.Write(dataOffset);
dataOffset += (uint)(entry.Value.Length + (entry.Value.Length % 2));
dataOffset += (uint)(raw.Length + (raw.Length % 2));
}
}
@ -133,10 +140,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
public long WriteImage<TPixel>(TiffWriter writer, Image<TPixel> image, long ifdOffset)
where TPixel : unmanaged, IPixel<TPixel>
{
List<TiffIfdEntry> ifdEntries = new List<TiffIfdEntry>();
var ifdEntries = new List<IExifValue>();
this.AddImageFormat(image, ifdEntries);
this.AddMetadata(image, ifdEntries);
writer.WriteMarker(ifdOffset, (uint)writer.Position);
long nextIfdMarker = this.WriteIfd(writer, ifdEntries);
@ -144,83 +150,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return nextIfdMarker;
}
/// <summary>
/// Adds image metadata to the specified IFD.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="ifdEntries">The metadata entries to add to the IFD.</param>
public void AddMetadata<TPixel>(Image<TPixel> image, List<TiffIfdEntry> ifdEntries)
where TPixel : unmanaged, IPixel<TPixel>
{
ifdEntries.AddUnsignedRational(TiffTags.XResolution, new Rational(image.Metadata.HorizontalResolution));
ifdEntries.AddUnsignedRational(TiffTags.YResolution, new Rational(image.Metadata.VerticalResolution));
ifdEntries.AddUnsignedShort(TiffTags.ResolutionUnit, (uint)TiffResolutionUnit.Inch);
/*
foreach (ImageProperty metadata in image.Metadata.Properties)
{
switch (metadata.Name)
{
case TiffMetadataNames.Artist:
{
ifdEntries.AddAscii(TiffTags.Artist, metadata.Value);
break;
}
case TiffMetadataNames.Copyright:
{
ifdEntries.AddAscii(TiffTags.Copyright, metadata.Value);
break;
}
case TiffMetadataNames.DateTime:
{
ifdEntries.AddAscii(TiffTags.DateTime, metadata.Value);
break;
}
case TiffMetadataNames.HostComputer:
{
ifdEntries.AddAscii(TiffTags.HostComputer, metadata.Value);
break;
}
case TiffMetadataNames.ImageDescription:
{
ifdEntries.AddAscii(TiffTags.ImageDescription, metadata.Value);
break;
}
case TiffMetadataNames.Make:
{
ifdEntries.AddAscii(TiffTags.Make, metadata.Value);
break;
}
case TiffMetadataNames.Model:
{
ifdEntries.AddAscii(TiffTags.Model, metadata.Value);
break;
}
case TiffMetadataNames.Software:
{
ifdEntries.AddAscii(TiffTags.Software, metadata.Value);
break;
}
}
} */
}
/// <summary>
/// Adds image format information to the specified IFD.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The <see cref="Image{TPixel}"/> to encode from.</param>
/// <param name="ifdEntries">The image format entries to add to the IFD.</param>
public void AddImageFormat<TPixel>(Image<TPixel> image, List<TiffIfdEntry> ifdEntries)
where TPixel : unmanaged, IPixel<TPixel>
public void AddImageFormat<TPixel>(Image<TPixel> image, List<IExifValue> ifdEntries)
where TPixel : unmanaged, IPixel<TPixel>
{
throw new NotImplementedException();
}

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

@ -1,15 +1,14 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using SixLabors.ImageSharp.Formats.Tiff;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Encapsulates the means to encode and decode Tiff images.
/// </summary>
public class TiffFormat : IImageFormat
public class TiffFormat : IImageFormat<TiffMetadata, TiffFrameMetadata>
{
private TiffFormat()
{
@ -31,5 +30,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <inheritdoc/>
public IEnumerable<string> FileExtensions => TiffConstants.FileExtensions;
/// <inheritdoc/>
public TiffMetadata CreateDefaultFormatMetadata() => new TiffMetadata();
/// <inheritdoc/>
public TiffFrameMetadata CreateDefaultFormatFrameMetadata() => new TiffFrameMetadata();
}
}

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

@ -0,0 +1,324 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
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;
/// <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.GetSingleUInt(ExifTag.ImageWidth);
/// <summary>
/// Gets the number of rows of pixels in the image.
/// </summary>
public uint Height => this.GetSingleUInt(ExifTag.ImageLength);
/// <summary>
/// Gets the number of bits per component.
/// </summary>
public ushort[] BitsPerSample => this.GetArrayValue<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.GetArrayValue<uint>(ExifTag.StripOffsets);
/// <summary>
/// Gets the number of rows per strip.
/// </summary>
public uint RowsPerStrip => this.GetSingleUInt(ExifTag.RowsPerStrip);
/// <summary>
/// Gets for each strip, the number of bytes in the strip after compression.
/// </summary>
public uint[] StripByteCounts => this.GetArrayValue<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.GetArrayValue<ushort>(ExifTag.ColorMap, true);
/// <summary>
/// Gets the description of extra components.
/// </summary>
public ushort[] ExtraSamples => this.GetArrayValue<ushort>(ExifTag.ExtraSamples, true);
/// <summary>
/// Gets the copyright notice.
/// </summary>
public string Copyright => this.GetString(ExifTag.Copyright);
internal T[] GetArrayValue<T>(ExifTag tag, bool optional = false)
where T : struct
{
if (this.TryGetArrayValue(tag, out T[] result))
{
return result;
}
if (!optional)
{
throw new ArgumentException("Required tag is not founded: " + tag, nameof(tag));
}
return null;
}
private bool TryGetArrayValue<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 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
{
if (!this.TryGetSingle(tag, out TTagValue value))
{
if (defaultValue != null)
{
return defaultValue.Value;
}
throw new ArgumentException("Required tag is not founded: " + tag, nameof(tag));
}
return (TEnum)(object)value;
}
/*
private TEnum GetSingleEnum<TEnum, TEnumParent, TTagValue>(ExifTag tag, TEnum? defaultValue = null)
where TEnum : struct
where TEnumParent : struct
where TTagValue : struct
{
if (!this.TryGetSingle(tag, out TTagValue value))
{
if (defaultValue != null)
{
return defaultValue.Value;
}
throw new ArgumentException("Required tag is not founded: " + tag, nameof(tag));
}
return (TEnum)(object)(TEnumParent)Convert.ChangeType(value, typeof(TEnumParent));
} */
private uint GetSingleUInt(ExifTag tag)
{
if (this.TryGetSingle(tag, out uint 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 };
}
}
}

125
src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfd.cs

@ -1,63 +1,104 @@
// Copyright (c) Six Labors.
// 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>
/// Data structure for holding details of each TIFF IFD.
/// The TIFF IFD reader class.
/// </summary>
internal struct TiffIfd
internal class DirectoryReader
{
/// <summary>
/// An array of the entries within this IFD.
/// </summary>
public TiffIfdEntry[] Entries;
/// <summary>
/// Offset (in bytes) to the next IFD, or zero if this is the last IFD.
/// </summary>
public uint NextIfdOffset;
/// <summary>
/// Initializes a new instance of the <see cref="TiffIfd"/> struct.
/// </summary>
/// <param name="entries">An array of the entries within the IFD.</param>
/// <param name="nextIfdOffset">Offset (in bytes) to the next IFD, or zero if this is the last IFD.</param>
public TiffIfd(TiffIfdEntry[] entries, uint nextIfdOffset)
private readonly TiffStream stream;
private readonly EntryReader tagReader;
private uint nextIfdOffset;
public DirectoryReader(TiffStream stream)
{
this.Entries = entries;
this.NextIfdOffset = nextIfdOffset;
this.stream = stream;
this.tagReader = new EntryReader(stream);
}
/// <summary>
/// Gets the child <see cref="TiffIfdEntry"/> with the specified tag ID.
/// </summary>
/// <param name="tag">The tag ID to search for.</param>
/// <returns>The resulting <see cref="TiffIfdEntry"/>, or null if it does not exists.</returns>
public TiffIfdEntry? GetIfdEntry(ushort tag)
public IEnumerable<IExifValue[]> Read()
{
for (int i = 0; i < this.Entries.Length; i++)
if (this.ReadHeader())
{
if (this.Entries[i].Tag == tag)
{
return this.Entries[i];
}
return this.ReadIfds();
}
return null;
}
/// <summary>
/// Gets the child <see cref="TiffIfdEntry"/> with the specified tag ID.
/// </summary>
/// <param name="tag">The tag ID to search for.</param>
/// <param name="entry">The resulting <see cref="TiffIfdEntry"/>, if it exists.</param>
/// <returns>A flag indicating whether the requested entry exists.</returns>
public bool TryGetIfdEntry(ushort tag, out TiffIfdEntry entry)
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()
{
TiffIfdEntry? nullableEntry = this.GetIfdEntry(tag);
entry = nullableEntry ?? default(TiffIfdEntry);
return nullableEntry.HasValue;
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();
}
}
}

328
src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfdEntry.cs

@ -1,46 +1,310 @@
// Copyright (c) Six Labors.
// 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
{
/// <summary>
/// Data structure for holding details of each TIFF IFD entry.
/// </summary>
internal struct TiffIfdEntry
internal class EntryReader
{
/// <summary>
/// The Tag ID for this entry. See <see cref="TiffTags"/> for typical values.
/// </summary>
public ushort Tag;
private readonly TiffStream stream;
/// <summary>
/// The data-type of this entry.
/// </summary>
public TiffType Type;
private readonly SortedDictionary<uint, Action> extValueLoaders = new SortedDictionary<uint, Action>();
/// <summary>
/// The number of array items in this entry, or one if only a single value.
/// Initializes a new instance of the <see cref="EntryReader" /> class.
/// </summary>
public uint Count;
/// <param name="stream">The stream.</param>
public EntryReader(TiffStream stream)
{
this.stream = stream;
}
/// <summary>
/// The raw byte data for this entry.
/// </summary>
public byte[] Value;
public IExifValue ReadNext()
{
var tagId = (ExifTagValue)this.stream.ReadUInt16();
var dataType = (ExifDataType)EnumUtils.Parse(this.stream.ReadUInt16(), ExifDataType.Unknown);
uint count = this.stream.ReadUInt32();
/// <summary>
/// Initializes a new instance of the <see cref="TiffIfdEntry"/> struct.
/// </summary>
/// <param name="tag">The Tag ID for this entry.</param>
/// <param name="type">The data-type of this entry.</param>
/// <param name="count">The number of array items in this entry.</param>
/// <param name="value">The raw byte data for this entry.</param>
public TiffIfdEntry(ushort tag, TiffType type, uint count, byte[] value)
{
this.Tag = tag;
this.Type = type;
this.Count = count;
this.Value = value;
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:
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;
}
}
}
}

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);
}
}

0
src/ImageSharp/Formats/Tiff/TiffIfd/TiffIfdEntryCreator.cs → src/ImageSharp/Formats/Tiff/__obsolete/TiffIfdEntryCreator.cs

0
src/ImageSharp/Formats/Tiff/TiffMetadataNames.cs → src/ImageSharp/Formats/Tiff/__obsolete/TiffMetadataNames.cs

0
src/ImageSharp/Formats/Tiff/Constants/TiffTags.cs → src/ImageSharp/Formats/Tiff/__obsolete/TiffTagId.cs

0
src/ImageSharp/Formats/Tiff/Constants/TiffType.cs → src/ImageSharp/Formats/Tiff/__obsolete/TiffTagType.cs

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>

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

@ -56,6 +56,12 @@ 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>
/// </value>
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);

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);
}
}
}
}

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

@ -19,52 +19,52 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
private static Rgba32 Bit0 = new Rgba32(0, 0, 0, 255);
private static Rgba32 Bit1 = new Rgba32(255, 255, 255, 255);
private static byte[] Bilevel_Bytes4x4 = new byte[] { 0b01010000,
private static readonly byte[] Bilevel_Bytes4x4 = new byte[] { 0b01010000,
0b11110000,
0b01110000,
0b10010000 };
private static Rgba32[][] Bilevel_Result4x4 = new[] { new[] { Bit0, Bit1, Bit0, Bit1 },
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 byte[] Bilevel_Bytes12x4 = new byte[] { 0b01010101, 0b01010000,
private static readonly byte[] Bilevel_Bytes12x4 = new byte[] { 0b01010101, 0b01010000,
0b11111111, 0b11111111,
0b01101001, 0b10100000,
0b10010000, 0b01100000};
private static Rgba32[][] Bilevel_Result12x4 = new[] { new[] { Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1 },
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 byte[] Grayscale4_Bytes4x4 = new byte[] { 0x8F, 0x0F,
private static readonly byte[] Grayscale4_Bytes4x4 = new byte[] { 0x8F, 0x0F,
0xFF, 0xFF,
0x08, 0x8F,
0xF0, 0xF8 };
private static Rgba32[][] Grayscale4_Result4x4 = new[] { new[] { Gray8, GrayF, Gray0, GrayF },
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 byte[] Grayscale4_Bytes3x4 = new byte[] { 0x8F, 0x00,
private static readonly byte[] Grayscale4_Bytes3x4 = new byte[] { 0x8F, 0x00,
0xFF, 0xF0,
0x08, 0x80,
0xF0, 0xF0 };
private static Rgba32[][] Grayscale4_Result3x4 = new[] { new[] { Gray8, GrayF, Gray0 },
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 byte[] Grayscale8_Bytes4x4 = new byte[] { 128, 255, 000, 255,
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 Rgba32[][] Grayscale8_Result4x4 = new[] { new[] { Gray128, Gray255, Gray000, Gray255 },
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 }};
@ -125,7 +125,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
AssertDecode(expectedResult, pixels =>
{
BlackIsZeroTiffColor.Decode(inputData, new[] { (uint)bitsPerSample }, pixels, left, top, width, height);
new BlackIsZeroTiffColor<Rgba32>(new[] { (ushort)bitsPerSample }).Decode(inputData, pixels, left, top, width, height);
});
}
@ -135,7 +135,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
AssertDecode(expectedResult, pixels =>
{
BlackIsZero1TiffColor.Decode(inputData, pixels, left, top, width, height);
new BlackIsZero1TiffColor<Rgba32>().Decode(inputData, pixels, left, top, width, height);
});
}
@ -145,7 +145,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
AssertDecode(expectedResult, pixels =>
{
BlackIsZero4TiffColor.Decode(inputData, pixels, left, top, width, height);
new BlackIsZero4TiffColor<Rgba32>().Decode(inputData, pixels, left, top, width, height);
});
}
@ -155,7 +155,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
AssertDecode(expectedResult, pixels =>
{
BlackIsZero8TiffColor.Decode(inputData, pixels, left, top, width, height);
new BlackIsZero8TiffColor<Rgba32>().Decode(inputData, pixels, left, top, width, height);
});
}
}

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

@ -12,25 +12,25 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
public static uint[][] Palette4_ColorPalette { get => GeneratePalette(16); }
public static uint[] Palette4_ColorMap { get => GenerateColorMap(Palette4_ColorPalette); }
public static ushort[] Palette4_ColorMap { get => GenerateColorMap(Palette4_ColorPalette); }
private static byte[] Palette4_Bytes4x4 = new byte[] { 0x01, 0x23,
private static readonly byte[] Palette4_Bytes4x4 = new byte[] { 0x01, 0x23,
0x4A, 0xD2,
0x12, 0x34,
0xAB, 0xEF };
private static Rgba32[][] Palette4_Result4x4 = GenerateResult(Palette4_ColorPalette,
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 byte[] Palette4_Bytes3x4 = new byte[] { 0x01, 0x20,
private static readonly byte[] Palette4_Bytes3x4 = new byte[] { 0x01, 0x20,
0x4A, 0xD0,
0x12, 0x30,
0xAB, 0xE0 };
private static Rgba32[][] Palette4_Result3x4 = GenerateResult(Palette4_ColorPalette,
private static readonly Rgba32[][] Palette4_Result3x4 = GenerateResult(Palette4_ColorPalette,
new[] { new[] { 0x00, 0x01, 0x02 },
new[] { 0x04, 0x0A, 0x0D },
new[] { 0x01, 0x02, 0x03 },
@ -57,14 +57,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public static uint[][] Palette8_ColorPalette { get => GeneratePalette(256); }
public static uint[] Palette8_ColorMap { get => GenerateColorMap(Palette8_ColorPalette); }
public static ushort[] Palette8_ColorMap { get => GenerateColorMap(Palette8_ColorPalette); }
private static byte[] Palette8_Bytes4x4 = new byte[] { 000, 001, 002, 003,
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 Rgba32[][] Palette8_Result4x4 = GenerateResult(Palette8_ColorPalette,
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 },
@ -85,11 +85,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[Theory]
[MemberData(nameof(Palette4_Data))]
[MemberData(nameof(Palette8_Data))]
public void Decode_WritesPixelData(byte[] inputData, int bitsPerSample, uint[] colorMap, int left, int top, int width, int height, Rgba32[][] expectedResult)
public void Decode_WritesPixelData(byte[] inputData, ushort bitsPerSample, ushort[] colorMap, int left, int top, int width, int height, Rgba32[][] expectedResult)
{
AssertDecode(expectedResult, pixels =>
{
PaletteTiffColor.Decode(inputData, new[] { (uint)bitsPerSample }, colorMap, pixels, left, top, width, height);
new PaletteTiffColor<Rgba32>(new[] { (ushort)bitsPerSample }, colorMap).Decode(inputData, pixels, left, top, width, height);
});
}
@ -105,16 +105,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
return palette;
}
private static uint[] GenerateColorMap(uint[][] colorPalette)
private static ushort[] GenerateColorMap(uint[][] colorPalette)
{
int colorCount = colorPalette.Length;
uint[] colorMap = new uint[colorCount * 3];
ushort[] colorMap = new ushort[colorCount * 3];
for (int i = 0; i < colorCount; i++)
{
colorMap[colorCount * 0 + i] = colorPalette[i][0];
colorMap[colorCount * 1 + i] = colorPalette[i][1];
colorMap[colorCount * 2 + i] = colorPalette[i][2];
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;

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

@ -2,8 +2,6 @@
// 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 SixLabors.ImageSharp.Processing;
@ -48,18 +46,22 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
int resultWidth = expectedResult[0].Length;
int resultHeight = expectedResult.Length;
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++)
using (Image<Rgba32> image = new Image<Rgba32>(resultWidth, resultHeight))
{
for (int x = 0; x < resultWidth; x++)
image.Mutate(x => x.BackgroundColor(DefaultColor));
Buffer2D<Rgba32> pixels = image.GetRootFramePixelBuffer();
decodeAction(pixels);
for (int y = 0; y < resultHeight; y++)
{
Assert.True(expectedResult[y][x] == pixels[x, y],
$"Pixel ({x}, {y}) should be {expectedResult[y][x]} but was {pixels[x, 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]}");
}
}
}
}

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

@ -72,17 +72,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
get
{
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 0, 0, 4, 4, Rgb4_Result4x4 };
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 0, 0, 4, 4, Offset(Rgb4_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 1, 0, 4, 4, Offset(Rgb4_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 0, 1, 4, 4, Offset(Rgb4_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 1, 1, 4, 4, Offset(Rgb4_Result4x4, 1, 1, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 0, 0, 3, 4, Rgb4_Result3x4 };
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 0, 0, 3, 4, Offset(Rgb4_Result3x4, 0, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 1, 0, 3, 4, Offset(Rgb4_Result3x4, 1, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 0, 1, 3, 4, Offset(Rgb4_Result3x4, 0, 1, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 1, 1, 3, 4, Offset(Rgb4_Result3x4, 1, 1, 6, 6) };
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) };
}
}
@ -126,11 +126,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
get
{
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 0, 0, 4, 4, Rgb8_Result4x4 };
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 0, 0, 4, 4, Offset(Rgb8_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 1, 0, 4, 4, Offset(Rgb8_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 0, 1, 4, 4, Offset(Rgb8_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 1, 1, 4, 4, Offset(Rgb8_Result4x4, 1, 1, 6, 6) };
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) };
}
}
@ -174,11 +174,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
get
{
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 0, 0, 4, 4, Rgb484_Result4x4 };
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 0, 0, 4, 4, Offset(Rgb484_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 1, 0, 4, 4, Offset(Rgb484_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 0, 1, 4, 4, Offset(Rgb484_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 1, 1, 4, 4, Offset(Rgb484_Result4x4, 1, 1, 6, 6) };
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) };
}
}
@ -186,11 +186,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[MemberData(nameof(Rgb4_Data))]
[MemberData(nameof(Rgb8_Data))]
[MemberData(nameof(Rgb484_Data))]
public void Decode_WritesPixelData(byte[][] inputData, uint[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
public void Decode_WritesPixelData(byte[][] inputData, ushort[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
{
AssertDecode(expectedResult, pixels =>
{
RgbPlanarTiffColor.Decode(inputData, bitsPerSample, pixels, left, top, width, height);
new RgbPlanarTiffColor<Rgba32>(bitsPerSample).Decode(inputData, pixels, left, top, width, height);
});
}
}

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

@ -48,17 +48,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
get
{
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 0, 0, 4, 4, Rgb4_Result4x4 };
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 0, 0, 4, 4, Offset(Rgb4_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 1, 0, 4, 4, Offset(Rgb4_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 0, 1, 4, 4, Offset(Rgb4_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Rgb4_Bytes4x4, new[] { 4u, 4u, 4u }, 1, 1, 4, 4, Offset(Rgb4_Result4x4, 1, 1, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 0, 0, 3, 4, Rgb4_Result3x4 };
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 0, 0, 3, 4, Offset(Rgb4_Result3x4, 0, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 1, 0, 3, 4, Offset(Rgb4_Result3x4, 1, 0, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 0, 1, 3, 4, Offset(Rgb4_Result3x4, 0, 1, 6, 6) };
yield return new object[] { Rgb4_Bytes3x4, new[] { 4u, 4u, 4u }, 1, 1, 3, 4, Offset(Rgb4_Result3x4, 1, 1, 6, 6) };
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) };
}
}
@ -90,11 +90,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
get
{
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 0, 0, 4, 4, Rgb8_Result4x4 };
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 0, 0, 4, 4, Offset(Rgb8_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 1, 0, 4, 4, Offset(Rgb8_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 0, 1, 4, 4, Offset(Rgb8_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Rgb8_Bytes4x4, new[] { 8u, 8u, 8u }, 1, 1, 4, 4, Offset(Rgb8_Result4x4, 1, 1, 6, 6) };
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) };
}
}
@ -126,11 +126,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
get
{
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 0, 0, 4, 4, Rgb484_Result4x4 };
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 0, 0, 4, 4, Offset(Rgb484_Result4x4, 0, 0, 6, 6) };
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 1, 0, 4, 4, Offset(Rgb484_Result4x4, 1, 0, 6, 6) };
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 0, 1, 4, 4, Offset(Rgb484_Result4x4, 0, 1, 6, 6) };
yield return new object[] { Rgb484_Bytes4x4, new[] { 4u, 8u, 4u }, 1, 1, 4, 4, Offset(Rgb484_Result4x4, 1, 1, 6, 6) };
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) };
}
}
@ -138,21 +138,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[MemberData(nameof(Rgb4_Data))]
[MemberData(nameof(Rgb8_Data))]
[MemberData(nameof(Rgb484_Data))]
public void Decode_WritesPixelData(byte[] inputData, uint[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
public void Decode_WritesPixelData(byte[] inputData, ushort[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
{
AssertDecode(expectedResult, pixels =>
{
RgbTiffColor.Decode(inputData, bitsPerSample, pixels, left, top, width, height);
new RgbTiffColor<Rgba32>(bitsPerSample).Decode(inputData, pixels, left, top, width, height);
});
}
[Theory]
[MemberData(nameof(Rgb8_Data))]
public void Decode_WritesPixelData_8Bit(byte[] inputData, uint[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
public void Decode_WritesPixelData_8Bit(byte[] inputData, ushort[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
{
AssertDecode(expectedResult, pixels =>
{
Rgb888TiffColor.Decode(inputData, pixels, left, top, width, height);
new Rgb888TiffColor<Rgba32>().Decode(inputData, pixels, left, top, width, height);
});
}
}

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

@ -19,52 +19,52 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
private static Rgba32 Bit0 = new Rgba32(255, 255, 255, 255);
private static Rgba32 Bit1 = new Rgba32(0, 0, 0, 255);
private static byte[] Bilevel_Bytes4x4 = new byte[] { 0b01010000,
private static readonly byte[] Bilevel_Bytes4x4 = new byte[] { 0b01010000,
0b11110000,
0b01110000,
0b10010000 };
private static Rgba32[][] Bilevel_Result4x4 = new[] { new[] { Bit0, Bit1, Bit0, Bit1 },
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 byte[] Bilevel_Bytes12x4 = new byte[] { 0b01010101, 0b01010000,
private static readonly byte[] Bilevel_Bytes12x4 = new byte[] { 0b01010101, 0b01010000,
0b11111111, 0b11111111,
0b01101001, 0b10100000,
0b10010000, 0b01100000};
private static Rgba32[][] Bilevel_Result12x4 = new[] { new[] { Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1, Bit0, Bit1 },
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 byte[] Grayscale4_Bytes4x4 = new byte[] { 0x8F, 0x0F,
private static readonly byte[] Grayscale4_Bytes4x4 = new byte[] { 0x8F, 0x0F,
0xFF, 0xFF,
0x08, 0x8F,
0xF0, 0xF8 };
private static Rgba32[][] Grayscale4_Result4x4 = new[] { new[] { Gray8, GrayF, Gray0, GrayF },
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 byte[] Grayscale4_Bytes3x4 = new byte[] { 0x8F, 0x00,
private static readonly byte[] Grayscale4_Bytes3x4 = new byte[] { 0x8F, 0x00,
0xFF, 0xF0,
0x08, 0x80,
0xF0, 0xF0 };
private static Rgba32[][] Grayscale4_Result3x4 = new[] { new[] { Gray8, GrayF, Gray0 },
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 byte[] Grayscale8_Bytes4x4 = new byte[] { 128, 255, 000, 255,
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 Rgba32[][] Grayscale8_Result4x4 = new[] { new[] { Gray128, Gray255, Gray000, Gray255 },
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 }};
@ -125,7 +125,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
AssertDecode(expectedResult, pixels =>
{
WhiteIsZeroTiffColor.Decode(inputData, new[] { (uint)bitsPerSample }, pixels, left, top, width, height);
new WhiteIsZeroTiffColor<Rgba32>(new[] { (ushort)bitsPerSample }).Decode(inputData, pixels, left, top, width, height);
});
}
@ -135,7 +135,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
AssertDecode(expectedResult, pixels =>
{
WhiteIsZero1TiffColor.Decode(inputData, pixels, left, top, width, height);
new WhiteIsZero1TiffColor<Rgba32>().Decode(inputData, pixels, left, top, width, height);
});
}
@ -145,7 +145,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
AssertDecode(expectedResult, pixels =>
{
WhiteIsZero4TiffColor.Decode(inputData, pixels, left, top, width, height);
new WhiteIsZero4TiffColor<Rgba32>().Decode(inputData, pixels, left, top, width, height);
});
}
@ -155,7 +155,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
AssertDecode(expectedResult, pixels =>
{
WhiteIsZero8TiffColor.Decode(inputData, pixels, left, top, width, height);
new WhiteIsZero8TiffColor<Rgba32>().Decode(inputData, pixels, left, top, width, height);
});
}
}

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

@ -1,12 +1,9 @@
// Copyright (c) Six Labors and contributors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
// ReSharper disable InconsistentNaming
using System.IO;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Png;
using System;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
@ -15,13 +12,26 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
[Trait("Category", "Tiff.BlackBox")]
[Trait("Category", "Tiff.BlackBox.Decoder")]
[Trait("Category", "Tiff")]
public class TiffDecoderTests
{
public static readonly string[] CommonTestImages = TestImages.Tiff.All;
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(CommonTestImages), PixelTypes.Rgba32)]
[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]
[WithFileCollection(nameof(SingleTestImages), PixelTypes.Rgba32)]
public void Decode<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -31,5 +41,22 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
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());
}
}
}
}

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

@ -0,0 +1,105 @@
// 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.BlackBox")]
[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);
}
}
}
[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[] { 8, 8, 8 }, frame.BitsPerSample);
Assert.Equal(TiffCompression.Lzw, frame.Compression);
Assert.Equal(TiffPhotometricInterpretation.Rgb, 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(32u, frame.RowsPerStrip);
Assert.Equal(new uint[] { 750 }, 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;Автор", frame.Artist);
Assert.Equal(null, frame.HostComputer);
Assert.Equal(null, frame.ColorMap);
Assert.Equal(null, frame.ExtraSamples);
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);
}
}
}
}

0
tests/ImageSharp.Tests/TestUtilities/Tiff/ITiffGenDataSource.cs → tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TestUtilities/Tiff/ITiffGenDataSource.cs

0
tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenDataBlock.cs → tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TestUtilities/Tiff/TiffGenDataBlock.cs

0
tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenDataReference.cs → tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TestUtilities/Tiff/TiffGenDataReference.cs

0
tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenEntry.cs → tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TestUtilities/Tiff/TiffGenEntry.cs

0
tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenExtensions.cs → tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TestUtilities/Tiff/TiffGenExtensions.cs

0
tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenHeader.cs → tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TestUtilities/Tiff/TiffGenHeader.cs

0
tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenIfd.cs → tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TestUtilities/Tiff/TiffGenIfd.cs

0
tests/ImageSharp.Tests/TestUtilities/Tiff/TiffGenIfdExtensions.cs → tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TestUtilities/Tiff/TiffGenIfdExtensions.cs

0
tests/ImageSharp.Tests/TestUtilities/Tiff/TiffIfdParser.cs → tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TestUtilities/Tiff/TiffIfdParser.cs

0
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderHeaderTests.cs → tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TiffDecoderHeaderTests.cs

0
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderIfdEntryTests.cs → tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TiffDecoderIfdEntryTests.cs

0
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderIfdTests.cs → tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TiffDecoderIfdTests.cs

0
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderImageTests.cs → tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TiffDecoderImageTests.cs

0
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderMetadataTests.cs → tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TiffDecoderMetadataTests.cs

0
tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderIfdTests.cs → tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TiffEncoderIfdTests.cs

0
tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderMetadataTests.cs → tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TiffEncoderMetadataTests.cs

0
tests/ImageSharp.Tests/Formats/Tiff/TiffIfd/TiffIfdEntryCreatorTests.cs → tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TiffIfd/TiffIfdEntryCreatorTests.cs

0
tests/ImageSharp.Tests/Formats/Tiff/TiffIfd/TiffIfdEntryTests.cs → tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TiffIfd/TiffIfdEntryTests.cs

0
tests/ImageSharp.Tests/Formats/Tiff/TiffIfd/TiffIfdTests.cs → tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TiffIfd/TiffIfdTests.cs

0
tests/ImageSharp.Tests/Formats/Tiff/TiffImageFormatDetectorTests.cs → tests/ImageSharp.Tests/Formats/Tiff/__obsolete/TiffImageFormatDetectorTests.cs

6
tests/ImageSharp.Tests/ImageSharp.Tests.csproj

@ -12,6 +12,12 @@
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Formats\Tiff\__obsolete\**" />
<EmbeddedResource Remove="Formats\Tiff\__obsolete\**" />
<None Remove="Formats\Tiff\__obsolete\**" />
</ItemGroup>
<ItemGroup>
<DotNetCliToolReference Include="dotnet-xunit" />
<InternalsVisibleTo Include="ImageSharp.Tests.ProfilingSandbox" PublicKey="$(SixLaborsPublicKey)" />

45
tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
@ -9,6 +10,7 @@ using System.Threading.Tasks;
using ImageMagick;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
@ -54,30 +56,37 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
public Image<TPixel> Decode<TPixel>(Configuration configuration, Stream stream)
where TPixel : unmanaged, IPixel<TPixel>
{
using var magickImage = new MagickImage(stream);
var result = new Image<TPixel>(configuration, magickImage.Width, magickImage.Height);
MemoryGroup<TPixel> resultPixels = result.GetRootFramePixelBuffer().FastMemoryGroup;
using (IPixelCollection pixels = magickImage.GetPixelsUnsafe())
using var magickImageCollection = new MagickImageCollection(stream);
var framesList = new List<ImageFrame<TPixel>>();
foreach (IMagickImage magicFrame in magickImageCollection)
{
if (magickImage.Depth == 8)
{
byte[] data = pixels.ToByteArray(PixelMapping.RGBA);
var frame = new ImageFrame<TPixel>(configuration, magicFrame.Width, magicFrame.Height);
framesList.Add(frame);
FromRgba32Bytes(configuration, data, resultPixels);
}
else if (magickImage.Depth == 16)
MemoryGroup<TPixel> framePixels = frame.PixelBuffer.FastMemoryGroup;
using (IPixelCollection pixels = magicFrame.GetPixelsUnsafe())
{
ushort[] data = pixels.ToShortArray(PixelMapping.RGBA);
Span<byte> bytes = MemoryMarshal.Cast<ushort, byte>(data.AsSpan());
FromRgba64Bytes(configuration, bytes, resultPixels);
}
else
{
throw new InvalidOperationException();
if (magicFrame.Depth == 8)
{
byte[] data = pixels.ToByteArray(PixelMapping.RGBA);
FromRgba32Bytes(configuration, data, framePixels);
}
else if (magicFrame.Depth == 16)
{
ushort[] data = pixels.ToShortArray(PixelMapping.RGBA);
Span<byte> bytes = MemoryMarshal.Cast<ushort, byte>(data.AsSpan());
FromRgba64Bytes(configuration, bytes, framePixels);
}
else
{
throw new InvalidOperationException();
}
}
}
var result = new Image<TPixel>(configuration, new ImageMetadata(), framesList);
return result;
}

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

@ -537,6 +537,31 @@ namespace SixLabors.ImageSharp.Tests
return image;
}
public static Image<TPixel> CompareToOriginalMultiFrame<TPixel>(
this Image<TPixel> image,
ITestImageProvider provider,
ImageComparer comparer,
IImageDecoder referenceDecoder = null)
where TPixel : unmanaged, IPixel<TPixel>
{
string path = TestImageProvider<TPixel>.GetFilePathOrNull(provider);
if (path == null)
{
throw new InvalidOperationException("CompareToOriginal() works only with file providers!");
}
var testFile = TestFile.Create(path);
referenceDecoder = referenceDecoder ?? TestEnvironment.GetReferenceDecoder(path);
using (var original = Image.Load<TPixel>(testFile.Bytes, referenceDecoder))
{
comparer.VerifySimilarity(original, image);
}
return image;
}
/// <summary>
/// Utility method for doing the following in one step:
/// 1. Executing an operation (taken as a delegate)

Loading…
Cancel
Save