Browse Source

Improve decoders - performance and memory usage.

Implement Identify methods and add tests.
Report not supported Predictor and SampleFormat features.
pull/1330/head
Ildar Khayrutdinov 6 years ago
parent
commit
89bd36a7d9
  1. 42
      src/ImageSharp/Formats/Tiff/Compression/CompressionFactory.cs
  2. 23
      src/ImageSharp/Formats/Tiff/Compression/DeflateTiffCompression.cs
  3. 22
      src/ImageSharp/Formats/Tiff/Compression/LzwTiffCompression.cs
  4. 20
      src/ImageSharp/Formats/Tiff/Compression/NoneTiffCompression.cs
  5. 80
      src/ImageSharp/Formats/Tiff/Compression/PackBitsTiffCompression.cs
  6. 29
      src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs
  7. 28
      src/ImageSharp/Formats/Tiff/Compression/TiffCompressionFactory.cs
  8. 26
      src/ImageSharp/Formats/Tiff/Constants/TiffPredictor.cs
  9. 41
      src/ImageSharp/Formats/Tiff/Constants/TiffSampleFormat.cs
  10. 1
      src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs
  11. 20
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor.cs
  12. 19
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor.cs
  13. 20
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor.cs
  14. 22
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor.cs
  15. 23
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor.cs
  16. 24
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor.cs
  17. 23
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor.cs
  18. 31
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor.cs
  19. 20
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBaseColorDecoder.cs
  20. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory.cs
  21. 20
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor.cs
  22. 19
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor.cs
  23. 20
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor.cs
  24. 19
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor.cs
  25. 10
      src/ImageSharp/Formats/Tiff/Streams/TiffStreamFactory.cs
  26. 29
      src/ImageSharp/Formats/Tiff/TiffDecoder.cs
  27. 168
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  28. 36
      src/ImageSharp/Formats/Tiff/TiffDecoderHelpers.cs
  29. 99
      src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
  30. 14
      src/ImageSharp/Formats/Tiff/Utils/BitReader.cs
  31. 6
      src/ImageSharp/Formats/Tiff/Utils/TiffLzwDecoder.cs
  32. 7
      src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs
  33. 1
      src/ImageSharp/Metadata/Profiles/Exif/Tags/ExifTag.LongArray.cs
  34. 2
      tests/ImageSharp.Benchmarks/Codecs/DecodeTiff.cs
  35. 4
      tests/ImageSharp.Tests/FileTestBase.cs
  36. 3
      tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
  37. 2
      tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs
  38. 2
      tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs
  39. 3
      tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs
  40. 11
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs
  41. 23
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  42. 20
      tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs
  43. 21
      tests/ImageSharp.Tests/TestImages.cs
  44. BIN
      tests/Images/Input/Tiff/issues/net472/Decode_Rgba32_Calliphora_rgb_deflate.png
  45. BIN
      tests/Images/Input/Tiff/issues/net472/Decode_Rgba32_Calliphora_rgb_lzw.png
  46. BIN
      tests/Images/Input/Tiff/issues/net472/Decode_Rgba32_metadata_sample.png
  47. BIN
      tests/Images/Input/Tiff/issues/net472/Decode_Rgba32_multipage_lzw.png
  48. BIN
      tests/Images/Input/Tiff/issues/net472/Decode_Rgba32_rgb_deflate.png
  49. BIN
      tests/Images/Input/Tiff/issues/net472/Decode_Rgba32_rgb_lzw_multistrip.png
  50. BIN
      tests/Images/Input/Tiff/issues/net472/Decode_Rgba32_rgb_small_lzw.png
  51. BIN
      tests/Images/Input/Tiff/issues/netcoreapp2.1/Decode_Rgba32_Calliphora_rgb_deflate.png
  52. BIN
      tests/Images/Input/Tiff/issues/netcoreapp2.1/Decode_Rgba32_Calliphora_rgb_lzw.png
  53. BIN
      tests/Images/Input/Tiff/issues/netcoreapp2.1/Decode_Rgba32_metadata_sample.png
  54. BIN
      tests/Images/Input/Tiff/issues/netcoreapp2.1/Decode_Rgba32_multipage_lzw.png
  55. BIN
      tests/Images/Input/Tiff/issues/netcoreapp2.1/Decode_Rgba32_rgb_deflate.png
  56. BIN
      tests/Images/Input/Tiff/issues/netcoreapp2.1/Decode_Rgba32_rgb_lzw_multistrip.png
  57. BIN
      tests/Images/Input/Tiff/issues/netcoreapp2.1/Decode_Rgba32_rgb_small_deflate.png
  58. BIN
      tests/Images/Input/Tiff/issues/netcoreapp3.1/Decode_Rgba32_Calliphora_rgb_deflate.png
  59. BIN
      tests/Images/Input/Tiff/issues/netcoreapp3.1/Decode_Rgba32_Calliphora_rgb_lzw.png
  60. BIN
      tests/Images/Input/Tiff/issues/netcoreapp3.1/Decode_Rgba32_metadata_sample.png
  61. BIN
      tests/Images/Input/Tiff/issues/netcoreapp3.1/Decode_Rgba32_multipage_lzw.png
  62. BIN
      tests/Images/Input/Tiff/issues/netcoreapp3.1/Decode_Rgba32_rgb_deflate.png
  63. BIN
      tests/Images/Input/Tiff/issues/netcoreapp3.1/Decode_Rgba32_rgb_lzw.png
  64. BIN
      tests/Images/Input/Tiff/issues/netcoreapp3.1/Decode_Rgba32_rgb_lzw_multistrip.png
  65. BIN
      tests/Images/Input/Tiff/issues/netcoreapp3.1/Decode_Rgba32_rgb_small_lzw.png
  66. BIN
      tests/Images/Input/Tiff/metadata_sample.tiff
  67. BIN
      tests/Images/Input/Tiff/rgb_small_deflate.tiff
  68. BIN
      tests/Images/Input/Tiff/rgb_small_lzw.tiff

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

@ -1,42 +0,0 @@
// 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();
}
}
}
}

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

@ -4,7 +4,7 @@
using System;
using System.IO;
using System.IO.Compression;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff
{
@ -14,16 +14,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <remarks>
/// Note that the 'OldDeflate' compression type is identical to the 'Deflate' compression type.
/// </remarks>
internal static class DeflateTiffCompression
internal class DeflateTiffCompression : TiffBaseCompression
{
/// <summary>
/// Decompresses image data into the supplied buffer.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to read image data from.</param>
/// <param name="byteCount">The number of bytes to read from the input stream.</param>
/// <param name="buffer">The output buffer for uncompressed data.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decompress(Stream stream, int byteCount, byte[] buffer)
public DeflateTiffCompression(MemoryAllocator allocator)
: base(allocator)
{
}
/// <inheritdoc/>
public override void Decompress(Stream stream, int byteCount, Span<byte> buffer)
{
// Read the 'zlib' header information
int cmf = stream.ReadByte();
@ -47,8 +46,8 @@ 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(subStream, CompressionMode.Decompress, true))
var subStream = new SubStream(stream, byteCount - headerLength);
using (var deflateStream = new DeflateStream(subStream, CompressionMode.Decompress, true))
{
deflateStream.ReadFull(buffer);
}

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

@ -1,26 +1,26 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Class to handle cases where TIFF image data is compressed using LZW compression.
/// </summary>
internal static class LzwTiffCompression
internal class LzwTiffCompression : TiffBaseCompression
{
/// <summary>
/// Decompresses image data into the supplied buffer.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to read image data from.</param>
/// <param name="byteCount">The number of bytes to read from the input stream.</param>
/// <param name="buffer">The output buffer for uncompressed data.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decompress(Stream stream, int byteCount, byte[] buffer)
public LzwTiffCompression(MemoryAllocator allocator)
: base(allocator)
{
SubStream subStream = new SubStream(stream, byteCount);
}
/// <inheritdoc/>
public override void Decompress(Stream stream, int byteCount, Span<byte> buffer)
{
var subStream = new SubStream(stream, byteCount);
using (var decoder = new TiffLzwDecoder(subStream))
{
decoder.DecodePixels(buffer.Length, 8, buffer);

20
src/ImageSharp/Formats/Tiff/Compression/NoneTiffCompression.cs

@ -1,24 +1,24 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Class to handle cases where TIFF image data is not compressed.
/// </summary>
internal static class NoneTiffCompression
internal class NoneTiffCompression : TiffBaseCompression
{
/// <summary>
/// Decompresses image data into the supplied buffer.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to read image data from.</param>
/// <param name="byteCount">The number of bytes to read from the input stream.</param>
/// <param name="buffer">The output buffer for uncompressed data.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decompress(Stream stream, int byteCount, byte[] buffer)
public NoneTiffCompression(MemoryAllocator allocator)
: base(allocator)
{
}
/// <inheritdoc/>
public override void Decompress(Stream stream, int byteCount, Span<byte> buffer)
{
stream.ReadFull(buffer, byteCount);
}

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

@ -4,69 +4,63 @@
using System;
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Class to handle cases where TIFF image data is compressed using PackBits compression.
/// </summary>
internal static class PackBitsTiffCompression
internal class PackBitsTiffCompression : TiffBaseCompression
{
/// <summary>
/// Decompresses image data into the supplied buffer.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to read image data from.</param>
/// <param name="byteCount">The number of bytes to read from the input stream.</param>
/// <param name="buffer">The output buffer for uncompressed data.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decompress(Stream stream, int byteCount, byte[] buffer)
public PackBitsTiffCompression(MemoryAllocator allocator)
: base(allocator)
{
byte[] compressedData = ArrayPool<byte>.Shared.Rent(byteCount);
}
/// <inheritdoc/>
public override void Decompress(Stream stream, int byteCount, Span<byte> buffer)
{
using IMemoryOwner<byte> compressedDataMemory = this.Allocator.Allocate<byte>(byteCount);
try
Span<byte> compressedData = compressedDataMemory.GetSpan();
stream.ReadFull(compressedData, byteCount);
int compressedOffset = 0;
int decompressedOffset = 0;
while (compressedOffset < byteCount)
{
stream.ReadFull(compressedData, byteCount);
int compressedOffset = 0;
int decompressedOffset = 0;
byte headerByte = compressedData[compressedOffset];
while (compressedOffset < byteCount)
if (headerByte <= (byte)127)
{
byte headerByte = compressedData[compressedOffset];
if (headerByte <= (byte)127)
{
int literalOffset = compressedOffset + 1;
int literalLength = compressedData[compressedOffset] + 1;
int literalOffset = compressedOffset + 1;
int literalLength = compressedData[compressedOffset] + 1;
Array.Copy(compressedData, literalOffset, buffer, decompressedOffset, literalLength);
compressedData.Slice(literalOffset, literalLength).CopyTo(buffer.Slice(decompressedOffset));
compressedOffset += literalLength + 1;
decompressedOffset += literalLength;
}
else if (headerByte == (byte)0x80)
{
compressedOffset += 1;
}
else
{
byte repeatData = compressedData[compressedOffset + 1];
int repeatLength = 257 - headerByte;
compressedOffset += literalLength + 1;
decompressedOffset += literalLength;
}
else if (headerByte == (byte)0x80)
{
compressedOffset += 1;
}
else
{
byte repeatData = compressedData[compressedOffset + 1];
int repeatLength = 257 - headerByte;
ArrayCopyRepeat(repeatData, buffer, decompressedOffset, repeatLength);
ArrayCopyRepeat(repeatData, buffer, decompressedOffset, repeatLength);
compressedOffset += 2;
decompressedOffset += repeatLength;
}
compressedOffset += 2;
decompressedOffset += repeatLength;
}
}
finally
{
ArrayPool<byte>.Shared.Return(compressedData);
}
}
private static void ArrayCopyRepeat(byte value, byte[] destinationArray, int destinationIndex, int length)
private static void ArrayCopyRepeat(byte value, Span<byte> destinationArray, int destinationIndex, int length)
{
for (int i = 0; i < length; i++)
{

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

@ -0,0 +1,29 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Base tiff decompressor class.
/// </summary>
internal abstract class TiffBaseCompression
{
private readonly MemoryAllocator allocator;
public TiffBaseCompression(MemoryAllocator allocator) => this.allocator = allocator;
protected MemoryAllocator Allocator => this.allocator;
/// <summary>
/// Decompresses image data into the supplied buffer.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> to read image data from.</param>
/// <param name="byteCount">The number of bytes to read from the input stream.</param>
/// <param name="buffer">The output buffer for uncompressed data.</param>
public abstract void Decompress(Stream stream, int byteCount, Span<byte> buffer);
}
}

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

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

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

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

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

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

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

@ -105,6 +105,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
case ExifTagValue.TileByteCounts:
case ExifTagValue.ColorMap:
case ExifTagValue.ExtraSamples:
case ExifTagValue.SampleFormat:
return true;
default:

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

@ -11,28 +11,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// Implements the 'BlackIsZero' photometric interpretation (optimised for bilevel images).
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class BlackIsZero1TiffColor<TPixel> : TiffColorDecoder<TPixel>
internal class BlackIsZero1TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public BlackIsZero1TiffColor()
: base(null, null)
{
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
public override void Decode(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default(TPixel);
var color = default(TPixel);
uint offset = 0;
int offset = 0;
for (int y = top; y < top + height; y++)
{
@ -45,6 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
int bit = (b >> (7 - shift)) & 1;
byte intensity = (bit == 1) ? (byte)255 : (byte)0;
color.FromRgba32(new Rgba32(intensity, intensity, intensity, 255));
pixels[x + shift, y] = color;
}

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

@ -10,28 +10,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation (optimised for 4-bit grayscale images).
/// </summary>
internal class BlackIsZero4TiffColor<TPixel> : TiffColorDecoder<TPixel>
internal class BlackIsZero4TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public BlackIsZero4TiffColor()
: base(null, null)
{
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
public override void Decode(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default(TPixel);
var color = default(TPixel);
uint offset = 0;
int offset = 0;
bool isOddWidth = (width & 1) == 1;
for (int y = top; y < top + height; y++)

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

@ -10,34 +10,26 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation (optimised for 8-bit grayscale images).
/// </summary>
internal class BlackIsZero8TiffColor<TPixel> : TiffColorDecoder<TPixel>
internal class BlackIsZero8TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public BlackIsZero8TiffColor()
: base(null, null)
{
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
public override void Decode(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default(TPixel);
var color = default(TPixel);
uint offset = 0;
int offset = 0;
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
{
byte intensity = data[offset++];
color.FromRgba32(new Rgba32(intensity, intensity, intensity, 255));
pixels[x, y] = color;
}

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

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation (for all bit depths).
/// </summary>
internal class BlackIsZeroTiffColor<TPixel> : TiffColorDecoder<TPixel>
internal class BlackIsZeroTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly ushort bitsPerSample0;
@ -19,26 +19,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff
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;
this.factor = (float)(1 << this.bitsPerSample0) - 1.0f;
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
public override void Decode(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default(TPixel);
var color = default(TPixel);
BitReader bitReader = new BitReader(data);
var bitReader = new BitReader(data);
for (int y = top; y < top + height; y++)
{
@ -46,6 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
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;
}

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

@ -3,7 +3,6 @@
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -12,33 +11,26 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Implements the 'PaletteTiffColor' photometric interpretation (for all bit depths).
/// </summary>
internal class PaletteTiffColor<TPixel> : TiffColorDecoder<TPixel>
internal class PaletteTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly ushort bitsPerSample0;
private readonly TPixel[] palette;
/// <param name="bitsPerSample">The number of bits per sample for each pixel.</param>
/// <param name="colorMap">The RGB color lookup table to use for decoding the image.</param>
public PaletteTiffColor(ushort[] bitsPerSample, ushort[] colorMap)
: base(bitsPerSample, colorMap)
{
this.bitsPerSample0 = bitsPerSample[0];
int colorCount = (int)Math.Pow(2, this.bitsPerSample0);
int colorCount = 1 << this.bitsPerSample0;
this.palette = GeneratePalette(colorMap, colorCount);
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
public override void Decode(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
BitReader bitReader = new BitReader(data);
var bitReader = new BitReader(data);
for (int y = top; y < top + height; y++)
{
@ -52,7 +44,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static TPixel[] GeneratePalette(ushort[] colorMap, int colorCount)
{
var palette = new TPixel[colorCount];

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

@ -10,38 +10,32 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Implements the 'RGB' photometric interpretation (optimised for 8-bit full color images).
/// </summary>
internal class Rgb888TiffColor<TPixel> : TiffColorDecoder<TPixel>
internal class Rgb888TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public Rgb888TiffColor()
: base(null, null)
{
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
public override void Decode(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default(TPixel);
var color = default(TPixel);
uint offset = 0;
int offset = 0;
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRow = pixels.GetRowSpan(y);
for (int x = left; x < left + width; x++)
{
byte r = data[offset++];
byte g = data[offset++];
byte b = data[offset++];
color.FromRgba32(new Rgba32(r, g, b, 255));
pixels[x, y] = color;
pixelRow[x] = color;
}
}
}

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

@ -1,7 +1,6 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -20,11 +19,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
private readonly float bFactor;
private readonly uint bitsPerSampleR;
private readonly ushort bitsPerSampleR;
private readonly uint bitsPerSampleG;
private readonly ushort bitsPerSampleG;
private readonly uint bitsPerSampleB;
private readonly ushort bitsPerSampleB;
public RgbPlanarTiffColor(ushort[] bitsPerSample)
/* : base(bitsPerSample, null) */
@ -33,9 +32,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
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;
this.rFactor = (float)(1 << this.bitsPerSampleR) - 1.0f;
this.gFactor = (float)(1 << this.bitsPerSampleG) - 1.0f;
this.bFactor = (float)(1 << this.bitsPerSampleB) - 1.0f;
}
/// <summary>
@ -47,13 +46,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
public void Decode(byte[][] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
public void Decode(IManagedByteBuffer[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default(TPixel);
var color = default(TPixel);
var rBitReader = new BitReader(data[0]);
var gBitReader = new BitReader(data[1]);
var bBitReader = new BitReader(data[2]);
var rBitReader = new BitReader(data[0].GetSpan());
var gBitReader = new BitReader(data[1].GetSpan());
var bBitReader = new BitReader(data[2].GetSpan());
for (int y = top; y < top + height; y++)
{

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

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Implements the 'RGB' photometric interpretation (for all bit depths).
/// </summary>
internal class RgbTiffColor<TPixel> : TiffColorDecoder<TPixel>
internal class RgbTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly float rFactor;
@ -20,38 +20,29 @@ namespace SixLabors.ImageSharp.Formats.Tiff
private readonly float bFactor;
private readonly uint bitsPerSampleR;
private readonly ushort bitsPerSampleR;
private readonly uint bitsPerSampleG;
private readonly ushort bitsPerSampleG;
private readonly uint bitsPerSampleB;
private readonly ushort 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;
this.rFactor = (float)(1 << this.bitsPerSampleR) - 1.0f;
this.gFactor = (float)(1 << this.bitsPerSampleG) - 1.0f;
this.bFactor = (float)(1 << this.bitsPerSampleB) - 1.0f;
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
public override void Decode(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default(TPixel);
var color = default(TPixel);
BitReader bitReader = new BitReader(data);
var bitReader = new BitReader(data);
for (int y = top; y < top + height; y++)
{

20
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoder.cs → src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBaseColorDecoder.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -10,22 +11,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// The base class for photometric interpretation decoders.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal abstract class TiffColorDecoder<TPixel>
internal abstract class TiffBaseColorDecoder<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)
protected TiffBaseColorDecoder()
{
this.bitsPerSample = bitsPerSample;
this.colorMap = colorMap;
}
/*
@ -46,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <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);
/// <param name="height">The height of the image block.</param>
public abstract void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height);
}
}

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

@ -9,7 +9,7 @@ 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)
public static TiffBaseColorDecoder<TPixel> Create(TiffColorType colorType, ushort[] bitsPerSample, ushort[] colorMap)
{
switch (colorType)
{

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

@ -10,28 +10,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation (optimised for bilevel images).
/// </summary>
internal class WhiteIsZero1TiffColor<TPixel> : TiffColorDecoder<TPixel>
internal class WhiteIsZero1TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public WhiteIsZero1TiffColor()
: base(null, null)
{
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
public override void Decode(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default(TPixel);
var color = default(TPixel);
uint offset = 0;
int offset = 0;
for (int y = top; y < top + height; y++)
{
@ -44,6 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
int bit = (b >> (7 - shift)) & 1;
byte intensity = (bit == 1) ? (byte)0 : (byte)255;
color.FromRgba32(new Rgba32(intensity, intensity, intensity, 255));
pixels[x + shift, y] = color;
}

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

@ -10,28 +10,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation (optimised for 4-bit grayscale images).
/// </summary>
internal class WhiteIsZero4TiffColor<TPixel> : TiffColorDecoder<TPixel>
internal class WhiteIsZero4TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public WhiteIsZero4TiffColor()
: base(null, null)
{
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
public override void Decode(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default(TPixel);
var color = default(TPixel);
uint offset = 0;
int offset = 0;
bool isOddWidth = (width & 1) == 1;
for (int y = top; y < top + height; y++)

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

@ -10,34 +10,26 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation (optimised for 8-bit grayscale images).
/// </summary>
internal class WhiteIsZero8TiffColor<TPixel> : TiffColorDecoder<TPixel>
internal class WhiteIsZero8TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
public WhiteIsZero8TiffColor()
: base(null, null)
{
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
public override void Decode(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default(TPixel);
var color = default(TPixel);
uint offset = 0;
int offset = 0;
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
{
byte intensity = (byte)(255 - data[offset++]);
color.FromRgba32(new Rgba32(intensity, intensity, intensity, 255));
pixels[x, y] = color;
}

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

@ -11,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation (for all bit depths).
/// </summary>
internal class WhiteIsZeroTiffColor<TPixel> : TiffColorDecoder<TPixel>
internal class WhiteIsZeroTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly ushort bitsPerSample0;
@ -19,26 +19,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff
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>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
public override void Decode(byte[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
TPixel color = default(TPixel);
var color = default(TPixel);
BitReader bitReader = new BitReader(data);
var bitReader = new BitReader(data);
for (int y = top; y < top + height; y++)
{

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

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -11,12 +11,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </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>
@ -40,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// Reads the byte order of stream.
/// </summary>
/// <param name="stream">The stream.</param>
private static TiffByteOrder ReadByteOrder(Stream stream)
public static TiffByteOrder ReadByteOrder(Stream stream)
{
byte[] headerBytes = new byte[2];
stream.Read(headerBytes, 0, 2);

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

@ -4,6 +4,7 @@
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff
@ -24,10 +25,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
Guard.NotNull(stream, "stream");
using (var decoder = new TiffDecoderCore(stream, configuration, this))
{
return decoder.Decode<TPixel>();
}
using var decoder = new TiffDecoderCore(stream, configuration, this);
return decoder.Decode<TPixel>(configuration, stream);
}
/// <inheritdoc/>
@ -37,25 +36,33 @@ namespace SixLabors.ImageSharp.Formats.Tiff
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
throw new System.NotImplementedException();
Guard.NotNull(stream, nameof(stream));
var decoder = new TiffDecoderCore(stream, configuration, this);
return decoder.DecodeAsync<TPixel>(configuration, stream, cancellationToken);
}
/// <inheritdoc/>
public Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
throw new System.NotImplementedException();
}
public async Task<Image> DecodeAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
=> await this.DecodeAsync<Rgba32>(configuration, stream, cancellationToken)
.ConfigureAwait(false);
/// <inheritdoc/>
public IImageInfo Identify(Configuration configuration, Stream stream)
{
throw new System.NotImplementedException();
Guard.NotNull(stream, nameof(stream));
var decoder = new TiffDecoderCore(stream, configuration, this);
return decoder.Identify(configuration, stream);
}
/// <inheritdoc/>
public Task<IImageInfo> IdentifyAsync(Configuration configuration, Stream stream, CancellationToken cancellationToken)
{
throw new System.NotImplementedException();
Guard.NotNull(stream, nameof(stream));
var decoder = new TiffDecoderCore(stream, configuration, this);
return decoder.IdentifyAsync(configuration, stream, cancellationToken);
}
}
}

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

@ -25,6 +25,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary>
private readonly Configuration configuration;
/// <summary>
/// Used for allocating memory during processing operations.
/// </summary>
private readonly MemoryAllocator memoryAllocator;
/// <summary>
/// Gets or sets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
@ -41,6 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.configuration = configuration ?? Configuration.Default;
this.ignoreMetadata = options.IgnoreMetadata;
this.memoryAllocator = this.configuration.MemoryAllocator;
}
/// <summary>
@ -52,26 +58,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff
public TiffDecoderCore(Stream stream, Configuration configuration, ITiffDecoderOptions options)
: this(configuration, options)
{
this.Stream = TiffStreamFactory.CreateBySignature(stream);
this.ByteOrder = TiffStreamFactory.ReadByteOrder(stream);
}
/// <summary>
/// Initializes a new instance of the <see cref="TiffDecoderCore" /> class.
/// Gets the byte order.
/// </summary>
/// <param name="byteOrder">The byte order.</param>
/// <param name="stream">The input stream.</param>
/// <param name="configuration">The configuration.</param>
/// <param name="options">The decoder options.</param>
public TiffDecoderCore(TiffByteOrder byteOrder, Stream stream, Configuration configuration, ITiffDecoderOptions options)
: this(configuration, options)
{
this.Stream = TiffStreamFactory.Create(byteOrder, stream);
}
public TiffByteOrder ByteOrder { get; }
/// <summary>
/// Gets the input stream.
/// </summary>
public TiffStream Stream { get; }
public TiffStream Stream { get; private set; }
/// <summary>
/// Gets or sets the number of bits for each sample of the pixel format used to encode the image.
@ -109,32 +107,31 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <inheritdoc/>
public Size Dimensions { get; private set; }
/// <summary>
/// Decodes the image from the specified <see cref="Stream"/> and sets
/// the data to image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The decoded image.</returns>
public Image<TPixel> Decode<TPixel>()
/// <inheritdoc/>
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
this.Stream = TiffStreamFactory.Create(this.ByteOrder, stream);
var reader = new DirectoryReader(this.Stream);
IEnumerable<IExifValue[]> directories = reader.Read();
var frames = new List<ImageFrame<TPixel>>();
var framesMetadata = new List<TiffFrameMetadata>();
foreach (IExifValue[] ifd in directories)
{
ImageFrame<TPixel> frame = this.DecodeFrame<TPixel>(ifd);
ImageFrame<TPixel> frame = this.DecodeFrame<TPixel>(ifd, out TiffFrameMetadata frameMetadata);
frames.Add(frame);
framesMetadata.Add(frameMetadata);
}
ImageMetadata metadata = frames.CreateMetadata(this.ignoreMetadata, this.Stream.ByteOrder);
ImageMetadata metadata = framesMetadata.CreateMetadata(this.ignoreMetadata, this.Stream.ByteOrder);
// todo: tiff frames can have different sizes
{
var root = frames.First();
ImageFrame<TPixel> root = frames.First();
this.Dimensions = root.Size();
foreach (var frame in frames)
foreach (ImageFrame<TPixel> frame in frames)
{
if (frame.Size() != root.Size())
{
@ -148,9 +145,33 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return image;
}
/// <summary>
/// Dispose
/// </summary>
/// <inheritdoc/>
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
this.Stream = TiffStreamFactory.Create(this.ByteOrder, stream);
var reader = new DirectoryReader(this.Stream);
IEnumerable<IExifValue[]> directories = reader.Read();
var framesMetadata = new List<TiffFrameMetadata>();
foreach (IExifValue[] ifd in directories)
{
framesMetadata.Add(new TiffFrameMetadata() { Tags = ifd });
}
ImageMetadata metadata = framesMetadata.CreateMetadata(this.ignoreMetadata, this.Stream.ByteOrder);
TiffFrameMetadata root = framesMetadata.First();
int bitsPerPixel = 0;
foreach (var bits in root.BitsPerSample)
{
bitsPerPixel += bits;
}
return new ImageInfo(new PixelTypeInfo(bitsPerPixel), (int)root.Width, (int)root.Height, metadata);
}
/// <inheritdoc/>
public void Dispose()
{
// nothing
@ -161,11 +182,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="tags">The IFD tags.</param>
private ImageFrame<TPixel> DecodeFrame<TPixel>(IExifValue[] tags)
/// <param name="metadata">The frame metadata.</param>
/// <returns>
/// The tiff frame.
/// </returns>
private ImageFrame<TPixel> DecodeFrame<TPixel>(IExifValue[] tags, out TiffFrameMetadata metadata)
where TPixel : unmanaged, IPixel<TPixel>
{
var coreMetadata = new ImageFrameMetadata();
TiffFrameMetadata metadata = coreMetadata.GetTiffMetadata();
metadata = coreMetadata.GetTiffMetadata();
metadata.Tags = tags;
this.VerifyAndParseOptions(metadata);
@ -178,7 +203,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff
uint[] stripOffsets = metadata.StripOffsets;
uint[] stripByteCounts = metadata.StripByteCounts;
this.DecodeImageStrips(frame, rowsPerStrip, stripOffsets, stripByteCounts);
if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar)
{
this.DecodeStripsPlanar(frame, rowsPerStrip, stripOffsets, stripByteCounts);
}
else
{
this.DecodeStripsChunky(frame, rowsPerStrip, stripOffsets, stripByteCounts);
}
return frame;
}
@ -190,12 +222,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <param name="height">The height for the desired pixel buffer.</param>
/// <param name="plane">The index of the plane for planar image configuration (or zero for chunky).</param>
/// <returns>The size (in bytes) of the required pixel buffer.</returns>
private int CalculateImageBufferSize(int width, int height, int plane)
private int CalculateStripBufferSize(int width, int height, int plane = -1)
{
uint bitsPerPixel = 0;
if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky)
{
DebugGuard.IsTrue(plane == -1, "Excepted Chunky planar.");
for (int i = 0; i < this.BitsPerSample.Length; i++)
{
bitsPerPixel += this.BitsPerSample[i];
@ -207,7 +240,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
int bytesPerRow = ((width * (int)bitsPerPixel) + 7) / 8;
return bytesPerRow * height;
int stripBytes = bytesPerRow * height;
return stripBytes;
}
/// <summary>
@ -218,35 +253,28 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <param name="rowsPerStrip">The number of rows per strip of data.</param>
/// <param name="stripOffsets">An array of byte offsets to each strip in the image.</param>
/// <param name="stripByteCounts">An array of the size of each strip (in bytes).</param>
private void DecodeImageStrips<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts)
private void DecodeStripsPlanar<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts)
where TPixel : unmanaged, IPixel<TPixel>
{
int stripsPerPixel = this.PlanarConfiguration == TiffPlanarConfiguration.Chunky ? 1 : this.BitsPerSample.Length;
int stripsPerPixel = this.BitsPerSample.Length;
int stripsPerPlane = stripOffsets.Length / stripsPerPixel;
Buffer2D<TPixel> pixels = frame.PixelBuffer;
byte[][] stripBytes = new byte[stripsPerPixel][];
for (int stripIndex = 0; stripIndex < stripBytes.Length; stripIndex++)
{
int uncompressedStripSize = this.CalculateImageBufferSize(frame.Width, rowsPerStrip, stripIndex);
stripBytes[stripIndex] = ArrayPool<byte>.Shared.Rent(uncompressedStripSize);
}
var stripBuffers = new IManagedByteBuffer[stripsPerPixel];
try
{
TiffColorDecoder<TPixel> chunkyDecoder = null;
RgbPlanarTiffColor<TPixel> planarDecoder = null;
if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky)
{
chunkyDecoder = TiffColorDecoderFactory<TPixel>.Create(this.ColorType, this.BitsPerSample, this.ColorMap);
}
else
for (int stripIndex = 0; stripIndex < stripBuffers.Length; stripIndex++)
{
planarDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap);
int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip, stripIndex);
stripBuffers[stripIndex] = this.memoryAllocator.AllocateManagedByteBuffer(uncompressedStripSize);
}
TiffBaseCompression decompressor = TiffCompressionFactory.Create(this.CompressionType, this.memoryAllocator);
RgbPlanarTiffColor<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap);
for (int i = 0; i < stripsPerPlane; i++)
{
int stripHeight = i < stripsPerPlane - 1 || frame.Height % rowsPerStrip == 0 ? rowsPerStrip : frame.Height % rowsPerStrip;
@ -254,37 +282,45 @@ namespace SixLabors.ImageSharp.Formats.Tiff
for (int planeIndex = 0; planeIndex < stripsPerPixel; planeIndex++)
{
int stripIndex = (i * stripsPerPixel) + planeIndex;
CompressionFactory.DecompressImageBlock(this.Stream.InputStream, this.CompressionType, stripOffsets[stripIndex], stripByteCounts[stripIndex], stripBytes[planeIndex]);
}
if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky)
{
chunkyDecoder.Decode(stripBytes[0], pixels, 0, rowsPerStrip * i, frame.Width, stripHeight);
}
else
{
planarDecoder.Decode(stripBytes, pixels, 0, rowsPerStrip * i, frame.Width, stripHeight);
this.Stream.Seek(stripOffsets[stripIndex]);
decompressor.Decompress(this.Stream.InputStream, (int)stripByteCounts[stripIndex], stripBuffers[planeIndex].GetSpan());
}
colorDecoder.Decode(stripBuffers, pixels, 0, rowsPerStrip * i, frame.Width, stripHeight);
}
}
finally
{
for (int stripIndex = 0; stripIndex < stripBytes.Length; stripIndex++)
foreach (IManagedByteBuffer buf in stripBuffers)
{
ArrayPool<byte>.Shared.Return(stripBytes[stripIndex]);
buf?.Dispose();
}
}
}
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
private void DecodeStripsChunky<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, uint[] stripOffsets, uint[] stripByteCounts)
where TPixel : unmanaged, IPixel<TPixel>
{
throw new NotImplementedException();
}
int uncompressedStripSize = this.CalculateStripBufferSize(frame.Width, rowsPerStrip);
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{
throw new NotImplementedException();
using IManagedByteBuffer stripBuffer = this.memoryAllocator.AllocateManagedByteBuffer(uncompressedStripSize);
Buffer2D<TPixel> pixels = frame.PixelBuffer;
TiffBaseCompression decompressor = TiffCompressionFactory.Create(this.CompressionType, this.memoryAllocator);
TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(this.ColorType, this.BitsPerSample, this.ColorMap);
for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++)
{
int stripHeight = stripIndex < stripOffsets.Length - 1 || frame.Height % rowsPerStrip == 0 ? rowsPerStrip : frame.Height % rowsPerStrip;
this.Stream.Seek(stripOffsets[stripIndex]);
decompressor.Decompress(this.Stream.InputStream, (int)stripByteCounts[stripIndex], stripBuffer.GetSpan());
colorDecoder.Decode(stripBuffer.GetSpan(), pixels, 0, rowsPerStrip * stripIndex, frame.Width, stripHeight);
}
}
}
}

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

@ -2,11 +2,8 @@
// 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;
@ -20,14 +17,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary>
internal static class TiffDecoderHelpers
{
public static ImageMetadata CreateMetadata<TPixel>(this IList<ImageFrame<TPixel>> frames, bool ignoreMetadata, TiffByteOrder byteOrder)
where TPixel : unmanaged, IPixel<TPixel>
public static ImageMetadata CreateMetadata(this IList<TiffFrameMetadata> frames, bool ignoreMetadata, TiffByteOrder byteOrder)
{
var coreMetadata = new ImageMetadata();
TiffMetadata tiffMetadata = coreMetadata.GetTiffMetadata();
tiffMetadata.ByteOrder = byteOrder;
TiffFrameMetadata rootFrameMetadata = frames.First().Metadata.GetTiffMetadata();
TiffFrameMetadata rootFrameMetadata = frames.First();
switch (rootFrameMetadata.ResolutionUnit)
{
case TiffResolutionUnit.None:
@ -53,13 +49,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
if (!ignoreMetadata)
{
foreach (ImageFrame<TPixel> frame in frames)
foreach (TiffFrameMetadata frame in frames)
{
TiffFrameMetadata frameMetadata = frame.Metadata.GetTiffMetadata();
if (tiffMetadata.XmpProfile == null)
{
byte[] buf = frameMetadata.GetArrayValue<byte>(ExifTag.XMP, true);
byte[] buf = frame.GetArray<byte>(ExifTag.XMP, true);
if (buf != null)
{
tiffMetadata.XmpProfile = buf;
@ -68,7 +62,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
if (coreMetadata.IptcProfile == null)
{
byte[] buf = frameMetadata.GetArrayValue<byte>(ExifTag.IPTC, true);
byte[] buf = frame.GetArray<byte>(ExifTag.IPTC, true);
if (buf != null)
{
coreMetadata.IptcProfile = new IptcProfile(buf);
@ -77,7 +71,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
if (coreMetadata.IccProfile == null)
{
byte[] buf = frameMetadata.GetArrayValue<byte>(ExifTag.IccProfile, true);
byte[] buf = frame.GetArray<byte>(ExifTag.IccProfile, true);
if (buf != null)
{
coreMetadata.IccProfile = new IccProfile(buf);
@ -106,11 +100,27 @@ namespace SixLabors.ImageSharp.Formats.Tiff
throw new NotSupportedException("The lower-order bits of the byte FillOrder is not supported.");
}
if (entries.GetArrayValue<uint>(ExifTag.TileOffsets, true) != null)
if (entries.GetArray<uint>(ExifTag.TileOffsets, true) != null)
{
throw new NotSupportedException("The Tile images is not supported.");
}
if (entries.Predictor != TiffPredictor.None)
{
throw new NotSupportedException("At the moment support only None Predictor.");
}
if (entries.SampleFormat != null)
{
foreach (TiffSampleFormat format in entries.SampleFormat)
{
if (format != TiffSampleFormat.UnsignedInteger)
{
throw new NotSupportedException("At the moment support only UnsignedInteger SampleFormat.");
}
}
}
ParseCompression(options, entries.Compression);
options.PlanarConfiguration = entries.PlanarConfiguration;

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

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
namespace SixLabors.ImageSharp.Formats.Tiff
@ -16,6 +17,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
private const TiffPlanarConfiguration DefaultPlanarConfiguration = TiffPlanarConfiguration.Chunky;
private const TiffPredictor DefaultPredictor = TiffPredictor.None;
/// <summary>
/// Initializes a new instance of the <see cref="TiffFrameMetadata"/> class.
/// </summary>
@ -39,17 +42,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <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);
public uint Width => this.GetSingle<uint>(ExifTag.ImageWidth);
/// <summary>
/// Gets the number of rows of pixels in the image.
/// </summary>
public uint Height => this.GetSingleUInt(ExifTag.ImageLength);
public uint Height => this.GetSingle<uint>(ExifTag.ImageLength);
/// <summary>
/// Gets the number of bits per component.
/// </summary>
public ushort[] BitsPerSample => this.GetArrayValue<ushort>(ExifTag.BitsPerSample, true);
public ushort[] BitsPerSample => this.GetArray<ushort>(ExifTag.BitsPerSample, true);
/// <summary>Gets the compression scheme used on the image data.</summary>
/// <value>The compression scheme used on the image data.</value>
@ -81,17 +84,22 @@ namespace SixLabors.ImageSharp.Formats.Tiff
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);
public uint[] StripOffsets => this.GetArray<uint>(ExifTag.StripOffsets);
/// <summary>
/// Gets the number of components per pixel.
/// </summary>
public ushort SamplesPerPixel => this.GetSingle<ushort>(ExifTag.SamplesPerPixel);
/// <summary>
/// Gets the number of rows per strip.
/// </summary>
public uint RowsPerStrip => this.GetSingleUInt(ExifTag.RowsPerStrip);
public uint RowsPerStrip => this.GetSingle<uint>(ExifTag.RowsPerStrip);
/// <summary>
/// Gets for each strip, the number of bytes in the strip after compression.
/// </summary>
public uint[] StripByteCounts => this.GetArrayValue<uint>(ExifTag.StripByteCounts);
public uint[] StripByteCounts => this.GetArray<uint>(ExifTag.StripByteCounts);
/// <summary>Gets the resolution of the image in x- direction.</summary>
/// <value>The density of the image in x- direction.</value>
@ -168,22 +176,33 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <summary>
/// Gets a color map for palette color images.
/// </summary>
public ushort[] ColorMap => this.GetArrayValue<ushort>(ExifTag.ColorMap, true);
public ushort[] ColorMap => this.GetArray<ushort>(ExifTag.ColorMap, true);
/// <summary>
/// Gets the description of extra components.
/// </summary>
public ushort[] ExtraSamples => this.GetArrayValue<ushort>(ExifTag.ExtraSamples, true);
public ushort[] ExtraSamples => this.GetArray<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)
/// <summary>
/// Gets a mathematical operator that is applied to the image data before an encoding scheme is applied.
/// </summary>
public TiffPredictor Predictor => this.GetSingleEnum<TiffPredictor, ushort>(ExifTag.Predictor, DefaultPredictor);
/// <summary>
/// Gets the specifies how to interpret each data sample in a pixel.
/// <see cref="SamplesPerPixel"/>
/// </summary>
public TiffSampleFormat[] SampleFormat => this.GetEnumArray<TiffSampleFormat, ushort>(ExifTag.SampleFormat, true);
internal T[] GetArray<T>(ExifTag tag, bool optional = false)
where T : struct
{
if (this.TryGetArrayValue(tag, out T[] result))
if (this.TryGetArray(tag, out T[] result))
{
return result;
}
@ -196,7 +215,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return null;
}
private bool TryGetArrayValue<T>(ExifTag tag, out T[] result)
private bool TryGetArray<T>(ExifTag tag, out T[] result)
where T : struct
{
foreach (IExifValue entry in this.Tags)
@ -214,6 +233,24 @@ namespace SixLabors.ImageSharp.Formats.Tiff
return false;
}
private TEnum[] GetEnumArray<TEnum, TTagValue>(ExifTag tag, bool optional = false)
where TEnum : struct
where TTagValue : struct
{
if (this.TryGetArray(tag, out TTagValue[] result))
{
// todo: improve
return result.Select(a => (TEnum)(object)a).ToArray();
}
if (!optional)
{
throw new ArgumentException("Required tag is not founded: " + tag, nameof(tag));
}
return null;
}
private string GetString(ExifTag tag)
{
foreach (IExifValue entry in this.Tags)
@ -246,42 +283,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff
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;
}
=> this.GetSingleEnumNullable<TEnum, TTagValue>(tag) ?? (defaultValue != null ? defaultValue.Value : throw new ArgumentException("Required tag is not founded: " + tag, nameof(tag)));
/*
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)
private T GetSingle<T>(ExifTag tag)
where T : struct
{
if (this.TryGetSingle(tag, out uint result))
if (this.TryGetSingle(tag, out T result))
{
return result;
}
@ -290,7 +297,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
private bool TryGetSingle<T>(ExifTag tag, out T result)
where T : struct
where T : struct
{
foreach (IExifValue entry in this.Tags)
{

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

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

6
src/ImageSharp/Formats/Tiff/Utils/TiffLzwDecoder.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <param name="length">The length of the compressed data.</param>
/// <param name="dataSize">Size of the data.</param>
/// <param name="pixels">The pixel array to decode to.</param>
public void DecodePixels(int length, int dataSize, byte[] pixels)
public void DecodePixels(int length, int dataSize, Span<byte> pixels)
{
Guard.MustBeLessThan(dataSize, int.MaxValue, nameof(dataSize));
@ -268,4 +268,4 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.isDisposed = true;
}
}
}
}

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

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
namespace SixLabors.ImageSharp.Formats.Tiff
@ -16,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <param name="stream">The stream to read from.</param>
/// <param name="buffer">A buffer to store the retrieved data.</param>
/// <param name="count">The number of bytes to read.</param>
public static void ReadFull(this Stream stream, byte[] buffer, int count)
public static void ReadFull(this Stream stream, Span<byte> buffer, int count)
{
int offset = 0;
@ -39,9 +40,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary>
/// <param name="stream">The stream to read from.</param>
/// <param name="buffer">A buffer to store the retrieved data.</param>
public static void ReadFull(this Stream stream, byte[] buffer)
public static void ReadFull(this Stream stream, Span<byte> buffer)
{
ReadFull(stream, buffer, buffer.Length);
}
}
}
}

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

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

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

@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
[Params(TestImages.Tiff.RgbLzw)]
[Params(TestImages.Tiff.RgbPackbits)]
public string TestImage { get; set; }
[GlobalSetup]

4
tests/ImageSharp.Tests/FileTestBase.cs

@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests
TestImages.Png.Splash,
TestImages.Gif.Trans,
TestImages.Tga.Bit24PalRleTopRight,
TestImages.Tiff.RgbLzw,
TestImages.Tiff.RgbPackbits,
};
/// <summary>
@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Tests
// TestFile.Create(TestImages.Gif.Cheers), // Perf: Enable for local testing only
// TestFile.Create(TestImages.Gif.Giphy) // Perf: Enable for local testing only
TestFile.Create(TestImages.Tga.Bit24PalRleTopRight),
TestFile.Create(TestImages.Tiff.RgbLzw),
TestFile.Create(TestImages.Tiff.RgbPackbits),
};
#pragma warning restore SA1515 // Single-line comment should be preceded by blank line
}

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

@ -4,6 +4,7 @@
using System.IO;
using SixLabors.ImageSharp.Formats.Png.Zlib;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Memory;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff
@ -23,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
byte[] buffer = new byte[data.Length];
DeflateTiffCompression.Decompress(stream, (int)stream.Length, buffer);
new DeflateTiffCompression(null).Decompress(stream, (int)stream.Length, buffer);
Assert.Equal(data, buffer);
}

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

@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
byte[] buffer = new byte[data.Length];
LzwTiffCompression.Decompress(stream, (int)stream.Length, buffer);
new LzwTiffCompression(null).Decompress(stream, (int)stream.Length, buffer);
Assert.Equal(data, buffer);
}

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

@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
Stream stream = new MemoryStream(inputData);
byte[] buffer = new byte[expectedResult.Length];
NoneTiffCompression.Decompress(stream, byteCount, buffer);
new NoneTiffCompression(null).Decompress(stream, byteCount, buffer);
Assert.Equal(expectedResult, buffer);
}

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

@ -3,6 +3,7 @@
using System.IO;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Memory;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Formats.Tiff
@ -25,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Formats.Tiff
Stream stream = new MemoryStream(inputData);
byte[] buffer = new byte[expectedResult.Length];
PackBitsTiffCompression.Decompress(stream, inputData.Length, buffer);
new PackBitsTiffCompression(new ArrayPoolMemoryAllocator()).Decompress(stream, inputData.Length, buffer);
Assert.Equal(expectedResult, buffer);
}

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

@ -1,8 +1,10 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using Xunit;
@ -190,7 +192,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
AssertDecode(expectedResult, pixels =>
{
new RgbPlanarTiffColor<Rgba32>(bitsPerSample).Decode(inputData, pixels, left, top, width, height);
var buffers = new IManagedByteBuffer[inputData.Length];
for (int i = 0; i < buffers.Length; i++)
{
buffers[i] = Configuration.Default.MemoryAllocator.AllocateManagedByteBuffer(inputData[i].Length);
((Span<byte>)inputData[i]).CopyTo(buffers[i].GetSpan());
}
new RgbPlanarTiffColor<Rgba32>(bitsPerSample).Decode(buffers, pixels, left, top, width, height);
});
}
}

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

@ -4,7 +4,9 @@
// ReSharper disable InconsistentNaming
using System;
using System.IO;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs;
@ -30,6 +32,27 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
Assert.Throws<NotSupportedException>(() => provider.GetImage(new TiffDecoder()));
}
[Theory]
[InlineData(TestImages.Tiff.RgbUncompressed, 24, 256, 256, 300, 300, PixelResolutionUnit.PixelsPerInch)]
[InlineData(TestImages.Tiff.SmallRgbDeflate, 24, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)]
[InlineData(TestImages.Tiff.Calliphora_GrayscaleUncompressed, 8, 804, 1198, 96, 96, PixelResolutionUnit.PixelsPerInch)]
public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
IImageInfo info = Image.Identify(stream);
Assert.Equal(expectedPixelSize, info.PixelType?.BitsPerPixel);
Assert.Equal(expectedWidth, info.Width);
Assert.Equal(expectedHeight, info.Height);
Assert.NotNull(info.Metadata);
Assert.Equal(expectedHResolution, info.Metadata.HorizontalResolution);
Assert.Equal(expectedVResolution, info.Metadata.VerticalResolution);
Assert.Equal(expectedResolutionUnit, info.Metadata.ResolutionUnits);
}
}
[Theory]
[WithFileCollection(nameof(SingleTestImages), PixelTypes.Rgba32)]
public void Decode<TPixel>(TestImageProvider<TPixel> provider)

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

@ -8,7 +8,6 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
[Trait("Category", "Tiff.BlackBox")]
[Trait("Category", "Tiff")]
public class TiffMetadataTests
{
@ -31,6 +30,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
else
{
Assert.NotNull(meta.XmpProfile);
Assert.Equal(2599, meta.XmpProfile.Length);
}
}
}
@ -53,25 +53,33 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
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(new ushort[] { 4 }, frame.BitsPerSample);
Assert.Equal(TiffCompression.Lzw, frame.Compression);
Assert.Equal(TiffPhotometricInterpretation.Rgb, frame.PhotometricInterpretation);
Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frame.PhotometricInterpretation);
Assert.Equal("This is Название", frame.ImageDescription);
Assert.Equal("This is Изготовитель камеры", frame.Make);
Assert.Equal("This is Модель камеры", frame.Model);
Assert.Equal(new uint[] { 8 }, frame.StripOffsets);
Assert.Equal(1, frame.SamplesPerPixel);
Assert.Equal(32u, frame.RowsPerStrip);
Assert.Equal(new uint[] { 750 }, frame.StripByteCounts);
Assert.Equal(new uint[] { 297 }, frame.StripByteCounts);
Assert.Equal(10, frame.HorizontalResolution);
Assert.Equal(10, frame.VerticalResolution);
Assert.Equal(TiffPlanarConfiguration.Chunky, frame.PlanarConfiguration);
Assert.Equal(TiffResolutionUnit.Inch, frame.ResolutionUnit);
Assert.Equal("IrfanView", frame.Software);
Assert.Equal(null, frame.DateTime);
Assert.Equal("This is;Автор", frame.Artist);
Assert.Equal("This is author1;Author2", frame.Artist);
Assert.Equal(null, frame.HostComputer);
Assert.Equal(null, frame.ColorMap);
Assert.Equal(48, frame.ColorMap.Length);
Assert.Equal(10537, frame.ColorMap[0]);
Assert.Equal(14392, frame.ColorMap[1]);
Assert.Equal(58596, frame.ColorMap[46]);
Assert.Equal(3855, frame.ColorMap[47]);
Assert.Equal(null, frame.ExtraSamples);
Assert.Equal(TiffPredictor.None, frame.Predictor);
Assert.Equal(null, frame.SampleFormat);
Assert.Equal("This is Авторские права", frame.Copyright);
}
}

21
tests/ImageSharp.Tests/TestImages.cs

@ -502,9 +502,9 @@ namespace SixLabors.ImageSharp.Tests
public const string Calliphora_GrayscaleUncompressed = "Tiff/Calliphora_grayscale_uncompressed.tiff";
public const string Calliphora_PaletteUncompressed = "Tiff/Calliphora_palette_uncompressed.tiff";
public const string Calliphora_RgbDeflate = "Tiff/Calliphora_rgb_deflate.tiff";
public const string Calliphora_RgbDeflate_Predictor = "Tiff/Calliphora_rgb_deflate.tiff";
public const string Calliphora_RgbJpeg = "Tiff/Calliphora_rgb_jpeg.tiff";
public const string Calliphora_RgbLzw = "Tiff/Calliphora_rgb_lzw.tiff";
public const string Calliphora_RgbLzwe_Predictor = "Tiff/Calliphora_rgb_lzw.tiff";
public const string Calliphora_RgbPackbits = "Tiff/Calliphora_rgb_packbits.tiff";
public const string Calliphora_RgbUncompressed = "Tiff/Calliphora_rgb_uncompressed.tiff";
@ -512,32 +512,35 @@ namespace SixLabors.ImageSharp.Tests
public const string GrayscaleUncompressed = "Tiff/grayscale_uncompressed.tiff";
public const string PaletteDeflateMultistrip = "Tiff/palette_grayscale_deflate_multistrip.tiff";
public const string PaletteUncompressed = "Tiff/palette_uncompressed.tiff";
public const string RgbDeflate = "Tiff/rgb_deflate.tiff";
public const string RgbDeflate_Predictor = "Tiff/rgb_deflate.tiff";
public const string RgbDeflateMultistrip = "Tiff/rgb_deflate_multistrip.tiff";
public const string RgbJpeg = "Tiff/rgb_jpeg.tiff";
public const string RgbLzw = "Tiff/rgb_lzw.tiff";
public const string RgbLzwMultistrip = "Tiff/rgb_lzw_multistrip.tiff";
public const string RgbLzw_Predictor = "Tiff/rgb_lzw.tiff";
public const string RgbLzwMultistrip_Predictor = "Tiff/rgb_lzw_multistrip.tiff";
public const string RgbPackbits = "Tiff/rgb_packbits.tiff";
public const string RgbPackbitsMultistrip = "Tiff/rgb_packbits_multistrip.tiff";
public const string RgbUncompressed = "Tiff/rgb_uncompressed.tiff";
public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff";
public const string SmallRgbLzw = "Tiff/rgb_small_lzw.tiff";
public const string RgbUncompressedTiled = "Tiff/rgb_uncompressed_tiled.tiff";
public const string MultiframeDifferentSizeTiled = "Tiff/multipage_ withPreview_differentSize_tiled.tiff";
public const string MultiframeLzw = "Tiff/multipage_lzw.tiff";
public const string MultiframeLzw_Predictor = "Tiff/multipage_lzw.tiff";
public const string MultiframeDeflateWithPreview = "Tiff/multipage_deflate_withPreview.tiff";
public const string MultiframeDifferentSize = "Tiff/multipage_differentSize.tiff";
public const string MultiframeDifferentVariants = "Tiff/multipage_differentVariants.tiff";
public const string SampleMetadata = "Tiff/metadata_sample.tiff";
public static readonly string[] All = { Calliphora_GrayscaleUncompressed, Calliphora_PaletteUncompressed, Calliphora_RgbDeflate, Calliphora_RgbLzw, Calliphora_RgbPackbits, Calliphora_RgbUncompressed, GrayscaleDeflateMultistrip, GrayscaleUncompressed, PaletteDeflateMultistrip, PaletteUncompressed, RgbDeflate, RgbDeflateMultistrip, /*RgbJpeg,*/ RgbLzw, RgbLzwMultistrip, RgbPackbits, RgbPackbitsMultistrip, RgbUncompressed, MultiframeLzw, /*MultiFrameDifferentVariants,*/ SampleMetadata, };
public static readonly string[] All = { Calliphora_GrayscaleUncompressed, Calliphora_PaletteUncompressed, /*Calliphora_RgbDeflate_Predictor, Calliphora_RgbLzwe_Predictor, */ Calliphora_RgbPackbits, Calliphora_RgbUncompressed, GrayscaleDeflateMultistrip, GrayscaleUncompressed, PaletteDeflateMultistrip, PaletteUncompressed, /*RgbDeflate_Predictor,*/ RgbDeflateMultistrip, /*RgbJpeg,*/ /*RgbLzw_Predictor, RgbLzwMultistrip_Predictor,*/ RgbPackbits, RgbPackbitsMultistrip, RgbUncompressed, /* MultiframeLzw_Predictor, MultiFrameDifferentVariants, SampleMetadata,*/ SmallRgbDeflate, SmallRgbLzw };
public static readonly string[] Multiframes = { MultiframeLzw, MultiframeDeflateWithPreview /*MultiFrameDifferentSize, MultiframeDifferentSizeTiled, MultiFrameDifferentVariants,*/ };
public static readonly string[] Multiframes = { MultiframeDeflateWithPreview /*MultiframeLzw_Predictor, MultiFrameDifferentSize, MultiframeDifferentSizeTiled, MultiFrameDifferentVariants,*/ };
public static readonly string[] Metadata = { SampleMetadata };
public static readonly string[] NotSupported = { Calliphora_RgbJpeg, RgbJpeg, RgbUncompressedTiled, MultiframeDifferentSize, MultiframeDifferentVariants };
public static readonly string[] NotSupported = { Calliphora_RgbJpeg, Calliphora_RgbDeflate_Predictor, Calliphora_RgbLzwe_Predictor, RgbDeflate_Predictor, RgbLzw_Predictor, RgbLzwMultistrip_Predictor, RgbJpeg, RgbUncompressedTiled, MultiframeLzw_Predictor, MultiframeDifferentSize, MultiframeDifferentVariants };
}
}
}

BIN
tests/Images/Input/Tiff/issues/net472/Decode_Rgba32_Calliphora_rgb_deflate.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

BIN
tests/Images/Input/Tiff/issues/net472/Decode_Rgba32_Calliphora_rgb_lzw.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

BIN
tests/Images/Input/Tiff/issues/net472/Decode_Rgba32_metadata_sample.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 B

BIN
tests/Images/Input/Tiff/issues/net472/Decode_Rgba32_multipage_lzw.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

BIN
tests/Images/Input/Tiff/issues/net472/Decode_Rgba32_rgb_deflate.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

BIN
tests/Images/Input/Tiff/issues/net472/Decode_Rgba32_rgb_lzw_multistrip.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

BIN
tests/Images/Input/Tiff/issues/net472/Decode_Rgba32_rgb_small_lzw.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

BIN
tests/Images/Input/Tiff/issues/netcoreapp2.1/Decode_Rgba32_Calliphora_rgb_deflate.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

BIN
tests/Images/Input/Tiff/issues/netcoreapp2.1/Decode_Rgba32_Calliphora_rgb_lzw.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

BIN
tests/Images/Input/Tiff/issues/netcoreapp2.1/Decode_Rgba32_metadata_sample.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

BIN
tests/Images/Input/Tiff/issues/netcoreapp2.1/Decode_Rgba32_multipage_lzw.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

BIN
tests/Images/Input/Tiff/issues/netcoreapp2.1/Decode_Rgba32_rgb_deflate.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

BIN
tests/Images/Input/Tiff/issues/netcoreapp2.1/Decode_Rgba32_rgb_lzw_multistrip.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

BIN
tests/Images/Input/Tiff/issues/netcoreapp2.1/Decode_Rgba32_rgb_small_deflate.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
tests/Images/Input/Tiff/issues/netcoreapp3.1/Decode_Rgba32_Calliphora_rgb_deflate.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

BIN
tests/Images/Input/Tiff/issues/netcoreapp3.1/Decode_Rgba32_Calliphora_rgb_lzw.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

BIN
tests/Images/Input/Tiff/issues/netcoreapp3.1/Decode_Rgba32_metadata_sample.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 821 B

BIN
tests/Images/Input/Tiff/issues/netcoreapp3.1/Decode_Rgba32_multipage_lzw.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

BIN
tests/Images/Input/Tiff/issues/netcoreapp3.1/Decode_Rgba32_rgb_deflate.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

BIN
tests/Images/Input/Tiff/issues/netcoreapp3.1/Decode_Rgba32_rgb_lzw.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

BIN
tests/Images/Input/Tiff/issues/netcoreapp3.1/Decode_Rgba32_rgb_lzw_multistrip.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

BIN
tests/Images/Input/Tiff/issues/netcoreapp3.1/Decode_Rgba32_rgb_small_lzw.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

BIN
tests/Images/Input/Tiff/metadata_sample.tiff

Binary file not shown.

BIN
tests/Images/Input/Tiff/rgb_small_deflate.tiff

Binary file not shown.

BIN
tests/Images/Input/Tiff/rgb_small_lzw.tiff

Binary file not shown.
Loading…
Cancel
Save