Browse Source

Merge pull request #2134 from SixLabors/bp/Issue2132

Tiff: Performance improvements for Fax4 decompression
pull/2150/head
Brian Popow 4 years ago
committed by GitHub
parent
commit
a194b9ead7
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      src/ImageSharp/Common/Helpers/Numerics.cs
  2. 30
      src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs
  3. 60
      src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffCcittCompressor.cs
  4. 2
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittReferenceScanline.cs
  5. 12
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCode.cs
  6. 15
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs
  7. 30
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs
  8. 10
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs
  9. 144
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs
  10. 31
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs
  11. 108
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs
  12. 44
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs
  13. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs
  14. 67
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor{TPixel}.cs
  15. 9
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs
  16. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs
  17. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs
  18. 7
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor{TPixel}.cs
  19. 8
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs
  20. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs
  21. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs
  22. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs
  23. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs
  24. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs
  25. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs
  26. 4
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs
  27. 4
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs
  28. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs
  29. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs
  30. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs
  31. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs
  32. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs
  33. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs
  34. 4
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs
  35. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs
  36. 67
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor{TPixel}.cs
  37. 9
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs
  38. 4
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs
  39. 5
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs
  40. 7
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor{TPixel}.cs
  41. 31
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  42. 2
      src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs
  43. 1
      tests/ImageSharp.Benchmarks/Codecs/Tiff/DecodeTiff.cs
  44. 39
      tests/ImageSharp.Benchmarks/Codecs/Tiff/EncodeTiff.cs
  45. 8
      tests/ImageSharp.Benchmarks/Config.cs
  46. 19
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs
  47. 2
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  48. 22
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
  49. 2
      tests/ImageSharp.Tests/TestImages.cs
  50. 3
      tests/Images/Input/Tiff/CCITTGroup4.tiff
  51. 3
      tests/Images/Input/Tiff/CCITTGroup4_minisblack.tiff

6
src/ImageSharp/Common/Helpers/Numerics.cs

@ -75,6 +75,12 @@ namespace SixLabors.ImageSharp
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Modulo8(int x) => x & 7;
/// <summary>
/// Calculates <paramref name="x"/> % 8
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static nint Modulo8(nint x) => x & 7;
/// <summary>
/// Fast (x mod m) calculator, with the restriction that
/// <paramref name="m"/> should be power of 2.

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

@ -2,21 +2,23 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression
{
internal static class BitWriterUtils
{
public static void WriteBits(Span<byte> buffer, int pos, uint count, byte value)
public static void WriteBits(Span<byte> buffer, nint pos, nint count, byte value)
{
int bitPos = pos % 8;
int bufferPos = pos / 8;
int startIdx = bufferPos + bitPos;
int endIdx = (int)(startIdx + count);
nint bitPos = Numerics.Modulo8(pos);
nint bufferPos = pos / 8;
nint startIdx = bufferPos + bitPos;
nint endIdx = startIdx + count;
if (value == 1)
{
for (int i = startIdx; i < endIdx; i++)
for (nint i = startIdx; i < endIdx; i++)
{
WriteBit(buffer, bufferPos, bitPos);
@ -30,7 +32,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
}
else
{
for (int i = startIdx; i < endIdx; i++)
for (nint i = startIdx; i < endIdx; i++)
{
WriteZeroBit(buffer, bufferPos, bitPos);
@ -44,8 +46,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
}
}
public static void WriteBit(Span<byte> buffer, int bufferPos, int bitPos) => buffer[bufferPos] |= (byte)(1 << (7 - bitPos));
[MethodImpl(InliningOptions.ShortMethod)]
public static void WriteBit(Span<byte> buffer, nint bufferPos, nint bitPos)
{
ref byte b = ref Unsafe.Add(ref MemoryMarshal.GetReference(buffer), bufferPos);
b |= (byte)(1 << (int)(7 - bitPos));
}
public static void WriteZeroBit(Span<byte> buffer, int bufferPos, int bitPos) => buffer[bufferPos] = (byte)(buffer[bufferPos] & ~(1 << (7 - bitPos)));
[MethodImpl(InliningOptions.ShortMethod)]
public static void WriteZeroBit(Span<byte> buffer, nint bufferPos, nint bitPos)
{
ref byte b = ref Unsafe.Add(ref MemoryMarshal.GetReference(buffer), bufferPos);
b = (byte)(b & ~(1 << (int)(7 - bitPos)));
}
}
}

60
src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffCcittCompressor.cs

@ -23,28 +23,28 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
64, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 1792, 1856, 1920, 1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560
};
private static readonly Dictionary<uint, uint> WhiteLen4TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen4TermCodes = new()
{
{ 2, 0x7 }, { 3, 0x8 }, { 4, 0xB }, { 5, 0xC }, { 6, 0xE }, { 7, 0xF }
};
private static readonly Dictionary<uint, uint> WhiteLen5TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen5TermCodes = new()
{
{ 8, 0x13 }, { 9, 0x14 }, { 10, 0x7 }, { 11, 0x8 }
};
private static readonly Dictionary<uint, uint> WhiteLen6TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen6TermCodes = new()
{
{ 1, 0x7 }, { 12, 0x8 }, { 13, 0x3 }, { 14, 0x34 }, { 15, 0x35 }, { 16, 0x2A }, { 17, 0x2B }
};
private static readonly Dictionary<uint, uint> WhiteLen7TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen7TermCodes = new()
{
{ 18, 0x27 }, { 19, 0xC }, { 20, 0x8 }, { 21, 0x17 }, { 22, 0x3 }, { 23, 0x4 }, { 24, 0x28 }, { 25, 0x2B }, { 26, 0x13 },
{ 27, 0x24 }, { 28, 0x18 }
};
private static readonly Dictionary<uint, uint> WhiteLen8TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen8TermCodes = new()
{
{ 0, WhiteZeroRunTermCode }, { 29, 0x2 }, { 30, 0x3 }, { 31, 0x1A }, { 32, 0x1B }, { 33, 0x12 }, { 34, 0x13 }, { 35, 0x14 },
{ 36, 0x15 }, { 37, 0x16 }, { 38, 0x17 }, { 39, 0x28 }, { 40, 0x29 }, { 41, 0x2A }, { 42, 0x2B }, { 43, 0x2C }, { 44, 0x2D },
@ -53,57 +53,57 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
{ 63, 0x34 }
};
private static readonly Dictionary<uint, uint> BlackLen2TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen2TermCodes = new()
{
{ 2, 0x3 }, { 3, 0x2 }
};
private static readonly Dictionary<uint, uint> BlackLen3TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen3TermCodes = new()
{
{ 1, 0x2 }, { 4, 0x3 }
};
private static readonly Dictionary<uint, uint> BlackLen4TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen4TermCodes = new()
{
{ 5, 0x3 }, { 6, 0x2 }
};
private static readonly Dictionary<uint, uint> BlackLen5TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen5TermCodes = new()
{
{ 7, 0x3 }
};
private static readonly Dictionary<uint, uint> BlackLen6TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen6TermCodes = new()
{
{ 8, 0x5 }, { 9, 0x4 }
};
private static readonly Dictionary<uint, uint> BlackLen7TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen7TermCodes = new()
{
{ 10, 0x4 }, { 11, 0x5 }, { 12, 0x7 }
};
private static readonly Dictionary<uint, uint> BlackLen8TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen8TermCodes = new()
{
{ 13, 0x4 }, { 14, 0x7 }
};
private static readonly Dictionary<uint, uint> BlackLen9TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen9TermCodes = new()
{
{ 15, 0x18 }
};
private static readonly Dictionary<uint, uint> BlackLen10TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen10TermCodes = new()
{
{ 0, BlackZeroRunTermCode }, { 16, 0x17 }, { 17, 0x18 }, { 18, 0x8 }
};
private static readonly Dictionary<uint, uint> BlackLen11TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen11TermCodes = new()
{
{ 19, 0x67 }, { 20, 0x68 }, { 21, 0x6C }, { 22, 0x37 }, { 23, 0x28 }, { 24, 0x17 }, { 25, 0x18 }
};
private static readonly Dictionary<uint, uint> BlackLen12TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen12TermCodes = new()
{
{ 26, 0xCA }, { 27, 0xCB }, { 28, 0xCC }, { 29, 0xCD }, { 30, 0x68 }, { 31, 0x69 }, { 32, 0x6A }, { 33, 0x6B }, { 34, 0xD2 },
{ 35, 0xD3 }, { 36, 0xD4 }, { 37, 0xD5 }, { 38, 0xD6 }, { 39, 0xD7 }, { 40, 0x6C }, { 41, 0x6D }, { 42, 0xDA }, { 43, 0xDB },
@ -112,62 +112,62 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
{ 62, 0x66 }, { 63, 0x67 }
};
private static readonly Dictionary<uint, uint> WhiteLen5MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen5MakeupCodes = new()
{
{ 64, 0x1B }, { 128, 0x12 }
};
private static readonly Dictionary<uint, uint> WhiteLen6MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen6MakeupCodes = new()
{
{ 192, 0x17 }, { 1664, 0x18 }
};
private static readonly Dictionary<uint, uint> WhiteLen8MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen8MakeupCodes = new()
{
{ 320, 0x36 }, { 384, 0x37 }, { 448, 0x64 }, { 512, 0x65 }, { 576, 0x68 }, { 640, 0x67 }
};
private static readonly Dictionary<uint, uint> WhiteLen7MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen7MakeupCodes = new()
{
{ 256, 0x37 }
};
private static readonly Dictionary<uint, uint> WhiteLen9MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen9MakeupCodes = new()
{
{ 704, 0xCC }, { 768, 0xCD }, { 832, 0xD2 }, { 896, 0xD3 }, { 960, 0xD4 }, { 1024, 0xD5 }, { 1088, 0xD6 },
{ 1152, 0xD7 }, { 1216, 0xD8 }, { 1280, 0xD9 }, { 1344, 0xDA }, { 1408, 0xDB }, { 1472, 0x98 }, { 1536, 0x99 },
{ 1600, 0x9A }, { 1728, 0x9B }
};
private static readonly Dictionary<uint, uint> WhiteLen11MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen11MakeupCodes = new()
{
{ 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD }
};
private static readonly Dictionary<uint, uint> WhiteLen12MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen12MakeupCodes = new()
{
{ 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C },
{ 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F }
};
private static readonly Dictionary<uint, uint> BlackLen10MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen10MakeupCodes = new()
{
{ 64, 0xF }
};
private static readonly Dictionary<uint, uint> BlackLen11MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen11MakeupCodes = new()
{
{ 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD }
};
private static readonly Dictionary<uint, uint> BlackLen12MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen12MakeupCodes = new()
{
{ 128, 0xC8 }, { 192, 0xC9 }, { 256, 0x5B }, { 320, 0x33 }, { 384, 0x34 }, { 448, 0x35 },
{ 1984, 0x12 }, { 2048, 0x13 }, { 2112, 0x14 }, { 2176, 0x15 }, { 2240, 0x16 }, { 2304, 0x17 }, { 2368, 0x1C },
{ 2432, 0x1D }, { 2496, 0x1E }, { 2560, 0x1F }
};
private static readonly Dictionary<uint, uint> BlackLen13MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen13MakeupCodes = new()
{
{ 512, 0x6C }, { 576, 0x6D }, { 640, 0x4A }, { 704, 0x4B }, { 768, 0x4C }, { 832, 0x4D }, { 896, 0x72 },
{ 960, 0x73 }, { 1024, 0x74 }, { 1088, 0x75 }, { 1152, 0x76 }, { 1216, 0x77 }, { 1280, 0x52 }, { 1344, 0x53 },
@ -442,16 +442,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
}
/// <summary>
/// Pads output to the next byte
/// Pads output to the next byte.
/// </summary>
/// <remarks>
/// If the output is not currently on a byte boundary,
/// zero-pad it to the next byte
/// zero-pad it to the next byte.
/// </remarks>
protected void PadByte()
{
// Check if padding is necessary.
if (this.bitPosition % 8 != 0)
if (Numerics.Modulo8(this.bitPosition) != 0)
{
// Skip padding bits, move to next byte.
this.bytePosition++;

2
src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittReferenceScanline.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
@ -130,6 +131,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
return index + offset;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int FindB2ForImaginaryWhiteLine() => this.width;
private int FindB2ForNormalLine(int b1)

12
src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCode.cs

@ -13,15 +13,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <summary>
/// Initializes a new instance of the <see cref="CcittTwoDimensionalCode"/> struct.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="code">The code word.</param>
/// <param name="type">The type of the code.</param>
/// <param name="bitsRequired">The bits required.</param>
/// <param name="extensionBits">The extension bits.</param>
public CcittTwoDimensionalCode(CcittTwoDimensionalCodeType type, int bitsRequired, int extensionBits = 0)
=> this.value = (ushort)((byte)type | ((bitsRequired & 0b1111) << 8) | ((extensionBits & 0b111) << 11));
public CcittTwoDimensionalCode(int code, CcittTwoDimensionalCodeType type, int bitsRequired, int extensionBits = 0)
{
this.Code = code;
this.value = (ushort)((byte)type | ((bitsRequired & 0b1111) << 8) | ((extensionBits & 0b111) << 11));
}
/// <summary>
/// Gets the code type.
/// </summary>
public CcittTwoDimensionalCodeType Type => (CcittTwoDimensionalCodeType)(this.value & 0b11111111);
public int Code { get; }
}
}

15
src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs

@ -1,9 +1,8 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.IO;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
@ -19,14 +18,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <param name="input">The compressed input stream.</param>
/// <param name="fillOrder">The logical order of bits within a byte.</param>
/// <param name="bytesToRead">The number of bytes to read from the stream.</param>
/// <param name="allocator">The memory allocator.</param>
public ModifiedHuffmanBitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator)
: base(input, fillOrder, bytesToRead, allocator)
public ModifiedHuffmanBitReader(BufferedReadStream input, TiffFillOrder fillOrder, int bytesToRead)
: base(input, fillOrder, bytesToRead)
{
}
/// <inheritdoc/>
public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || ((uint)(this.BitsRead - 1) < (7 - 1));
public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || (uint)(this.BitsRead - 1) < 6;
/// <inheritdoc/>
public override bool IsEndOfScanLine
@ -53,12 +51,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
base.StartNewRow();
int remainder = this.BitsRead & 7; // bit-hack for % 8
int remainder = Numerics.Modulo8(this.BitsRead);
if (remainder != 0)
{
// Skip padding bits, move to next byte.
this.Position++;
this.ResetBitsRead();
this.AdvancePosition();
}
}

30
src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs

@ -42,11 +42,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
{
using var bitReader = new ModifiedHuffmanBitReader(stream, this.FillOrder, byteCount, this.Allocator);
var bitReader = new ModifiedHuffmanBitReader(stream, this.FillOrder, byteCount);
buffer.Clear();
uint bitsWritten = 0;
uint pixelsWritten = 0;
nint bitsWritten = 0;
nuint pixelsWritten = 0;
nint rowsWritten = 0;
while (bitReader.HasMoreData)
{
bitReader.ReadNextRun();
@ -55,32 +56,39 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
if (bitReader.IsWhiteRun)
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue);
BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.whiteValue);
}
else
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue);
BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.blackValue);
}
bitsWritten += bitReader.RunLength;
bitsWritten += (int)bitReader.RunLength;
pixelsWritten += bitReader.RunLength;
}
if (pixelsWritten == this.Width)
if (pixelsWritten == (ulong)this.Width)
{
bitReader.StartNewRow();
rowsWritten++;
pixelsWritten = 0;
// Write padding bits, if necessary.
uint pad = 8 - (bitsWritten % 8);
nint pad = 8 - Numerics.Modulo8(bitsWritten);
if (pad != 8)
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, pad, 0);
BitWriterUtils.WriteBits(buffer, bitsWritten, pad, 0);
bitsWritten += pad;
}
if (rowsWritten >= stripHeight)
{
break;
}
bitReader.StartNewRow();
}
if (pixelsWritten > this.Width)
if (pixelsWritten > (ulong)this.Width)
{
TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error, decoded more pixels then image width");
}

10
src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs

@ -73,7 +73,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
byte repeatData = compressedData[compressedOffset + 1];
int repeatLength = 257 - headerByte;
ArrayCopyRepeat(repeatData, buffer, decompressedOffset, repeatLength);
buffer.Slice(decompressedOffset, repeatLength).Fill(repeatData);
compressedOffset += 2;
decompressedOffset += repeatLength;
@ -81,14 +81,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
}
private static void ArrayCopyRepeat(byte value, Span<byte> destinationArray, int destinationIndex, int length)
{
for (int i = 0; i < length; i++)
{
destinationArray[i + destinationIndex] = value;
}
}
/// <inheritdoc/>
protected override void Dispose(bool disposing) => this.compressedDataMemory?.Dispose();
}

144
src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs

@ -1,20 +1,17 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.IO;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
/// <summary>
/// Bitreader for reading compressed CCITT T4 1D data.
/// </summary>
internal class T4BitReader : IDisposable
internal class T4BitReader
{
/// <summary>
/// The logical order of bits within a byte.
@ -52,28 +49,28 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// </summary>
private readonly int maxCodeLength = 13;
private static readonly Dictionary<uint, uint> WhiteLen4TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen4TermCodes = new()
{
{ 0x7, 2 }, { 0x8, 3 }, { 0xB, 4 }, { 0xC, 5 }, { 0xE, 6 }, { 0xF, 7 }
};
private static readonly Dictionary<uint, uint> WhiteLen5TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen5TermCodes = new()
{
{ 0x13, 8 }, { 0x14, 9 }, { 0x7, 10 }, { 0x8, 11 }
};
private static readonly Dictionary<uint, uint> WhiteLen6TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen6TermCodes = new()
{
{ 0x7, 1 }, { 0x8, 12 }, { 0x3, 13 }, { 0x34, 14 }, { 0x35, 15 }, { 0x2A, 16 }, { 0x2B, 17 }
};
private static readonly Dictionary<uint, uint> WhiteLen7TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen7TermCodes = new()
{
{ 0x27, 18 }, { 0xC, 19 }, { 0x8, 20 }, { 0x17, 21 }, { 0x3, 22 }, { 0x4, 23 }, { 0x28, 24 }, { 0x2B, 25 }, { 0x13, 26 },
{ 0x24, 27 }, { 0x18, 28 }
};
private static readonly Dictionary<uint, uint> WhiteLen8TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen8TermCodes = new()
{
{ 0x35, 0 }, { 0x2, 29 }, { 0x3, 30 }, { 0x1A, 31 }, { 0x1B, 32 }, { 0x12, 33 }, { 0x13, 34 }, { 0x14, 35 }, { 0x15, 36 },
{ 0x16, 37 }, { 0x17, 38 }, { 0x28, 39 }, { 0x29, 40 }, { 0x2A, 41 }, { 0x2B, 42 }, { 0x2C, 43 }, { 0x2D, 44 }, { 0x4, 45 },
@ -81,57 +78,57 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{ 0x58, 55 }, { 0x59, 56 }, { 0x5A, 57 }, { 0x5B, 58 }, { 0x4A, 59 }, { 0x4B, 60 }, { 0x32, 61 }, { 0x33, 62 }, { 0x34, 63 }
};
private static readonly Dictionary<uint, uint> BlackLen2TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen2TermCodes = new()
{
{ 0x3, 2 }, { 0x2, 3 }
};
private static readonly Dictionary<uint, uint> BlackLen3TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen3TermCodes = new()
{
{ 0x2, 1 }, { 0x3, 4 }
};
private static readonly Dictionary<uint, uint> BlackLen4TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen4TermCodes = new()
{
{ 0x3, 5 }, { 0x2, 6 }
};
private static readonly Dictionary<uint, uint> BlackLen5TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen5TermCodes = new()
{
{ 0x3, 7 }
};
private static readonly Dictionary<uint, uint> BlackLen6TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen6TermCodes = new()
{
{ 0x5, 8 }, { 0x4, 9 }
};
private static readonly Dictionary<uint, uint> BlackLen7TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen7TermCodes = new()
{
{ 0x4, 10 }, { 0x5, 11 }, { 0x7, 12 }
};
private static readonly Dictionary<uint, uint> BlackLen8TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen8TermCodes = new()
{
{ 0x4, 13 }, { 0x7, 14 }
};
private static readonly Dictionary<uint, uint> BlackLen9TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen9TermCodes = new()
{
{ 0x18, 15 }
};
private static readonly Dictionary<uint, uint> BlackLen10TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen10TermCodes = new()
{
{ 0x37, 0 }, { 0x17, 16 }, { 0x18, 17 }, { 0x8, 18 }
};
private static readonly Dictionary<uint, uint> BlackLen11TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen11TermCodes = new()
{
{ 0x67, 19 }, { 0x68, 20 }, { 0x6C, 21 }, { 0x37, 22 }, { 0x28, 23 }, { 0x17, 24 }, { 0x18, 25 }
};
private static readonly Dictionary<uint, uint> BlackLen12TermCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen12TermCodes = new()
{
{ 0xCA, 26 }, { 0xCB, 27 }, { 0xCC, 28 }, { 0xCD, 29 }, { 0x68, 30 }, { 0x69, 31 }, { 0x6A, 32 }, { 0x6B, 33 }, { 0xD2, 34 },
{ 0xD3, 35 }, { 0xD4, 36 }, { 0xD5, 37 }, { 0xD6, 38 }, { 0xD7, 39 }, { 0x6C, 40 }, { 0x6D, 41 }, { 0xDA, 42 }, { 0xDB, 43 },
@ -140,82 +137,84 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{ 0x66, 62 }, { 0x67, 63 }
};
private static readonly Dictionary<uint, uint> WhiteLen5MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen5MakeupCodes = new()
{
{ 0x1B, 64 }, { 0x12, 128 }
};
private static readonly Dictionary<uint, uint> WhiteLen6MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen6MakeupCodes = new()
{
{ 0x17, 192 }, { 0x18, 1664 }
};
private static readonly Dictionary<uint, uint> WhiteLen8MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen8MakeupCodes = new()
{
{ 0x36, 320 }, { 0x37, 384 }, { 0x64, 448 }, { 0x65, 512 }, { 0x68, 576 }, { 0x67, 640 }
};
private static readonly Dictionary<uint, uint> WhiteLen7MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen7MakeupCodes = new()
{
{ 0x37, 256 }
};
private static readonly Dictionary<uint, uint> WhiteLen9MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen9MakeupCodes = new()
{
{ 0xCC, 704 }, { 0xCD, 768 }, { 0xD2, 832 }, { 0xD3, 896 }, { 0xD4, 960 }, { 0xD5, 1024 }, { 0xD6, 1088 },
{ 0xD7, 1152 }, { 0xD8, 1216 }, { 0xD9, 1280 }, { 0xDA, 1344 }, { 0xDB, 1408 }, { 0x98, 1472 }, { 0x99, 1536 },
{ 0x9A, 1600 }, { 0x9B, 1728 }
};
private static readonly Dictionary<uint, uint> WhiteLen11MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen11MakeupCodes = new()
{
{ 0x8, 1792 }, { 0xC, 1856 }, { 0xD, 1920 }
};
private static readonly Dictionary<uint, uint> WhiteLen12MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> WhiteLen12MakeupCodes = new()
{
{ 0x12, 1984 }, { 0x13, 2048 }, { 0x14, 2112 }, { 0x15, 2176 }, { 0x16, 2240 }, { 0x17, 2304 }, { 0x1C, 2368 },
{ 0x1D, 2432 }, { 0x1E, 2496 }, { 0x1F, 2560 }
};
private static readonly Dictionary<uint, uint> BlackLen10MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen10MakeupCodes = new()
{
{ 0xF, 64 }
};
private static readonly Dictionary<uint, uint> BlackLen11MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen11MakeupCodes = new()
{
{ 0x8, 1792 }, { 0xC, 1856 }, { 0xD, 1920 }
};
private static readonly Dictionary<uint, uint> BlackLen12MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen12MakeupCodes = new()
{
{ 0xC8, 128 }, { 0xC9, 192 }, { 0x5B, 256 }, { 0x33, 320 }, { 0x34, 384 }, { 0x35, 448 },
{ 0x12, 1984 }, { 0x13, 2048 }, { 0x14, 2112 }, { 0x15, 2176 }, { 0x16, 2240 }, { 0x17, 2304 }, { 0x1C, 2368 },
{ 0x1D, 2432 }, { 0x1E, 2496 }, { 0x1F, 2560 }
};
private static readonly Dictionary<uint, uint> BlackLen13MakeupCodes = new Dictionary<uint, uint>()
private static readonly Dictionary<uint, uint> BlackLen13MakeupCodes = new()
{
{ 0x6C, 512 }, { 0x6D, 576 }, { 0x4A, 640 }, { 0x4B, 704 }, { 0x4C, 768 }, { 0x4D, 832 }, { 0x72, 896 },
{ 0x73, 960 }, { 0x74, 1024 }, { 0x75, 1088 }, { 0x76, 1152 }, { 0x77, 1216 }, { 0x52, 1280 }, { 0x53, 1344 },
{ 0x54, 1408 }, { 0x55, 1472 }, { 0x5A, 1536 }, { 0x5B, 1600 }, { 0x64, 1664 }, { 0x65, 1728 }
};
/// <summary>
/// The compressed input stream.
/// </summary>
private readonly BufferedReadStream stream;
/// <summary>
/// Initializes a new instance of the <see cref="T4BitReader" /> class.
/// </summary>
/// <param name="input">The compressed input stream.</param>
/// <param name="fillOrder">The logical order of bits within a byte.</param>
/// <param name="bytesToRead">The number of bytes to read from the stream.</param>
/// <param name="allocator">The memory allocator.</param>
/// <param name="eolPadding">Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false.</param>
public T4BitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator, bool eolPadding = false)
public T4BitReader(BufferedReadStream input, TiffFillOrder fillOrder, int bytesToRead, bool eolPadding = false)
{
this.stream = input;
this.fillOrder = fillOrder;
this.Data = allocator.Allocate<byte>(bytesToRead);
this.ReadImageDataFromStream(input, bytesToRead);
this.DataLength = bytesToRead;
this.BitsRead = 0;
this.Value = 0;
@ -228,12 +227,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
this.RunLength = 0;
this.eolPadding = eolPadding;
this.ReadNextByte();
if (this.eolPadding)
{
this.maxCodeLength = 24;
}
}
/// <summary>
/// Gets or sets the byte at the given position.
/// </summary>
private byte DataAtPosition { get; set; }
/// <summary>
/// Gets the current value.
/// </summary>
@ -259,11 +265,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// </summary>
protected ulong Position { get; set; }
/// <summary>
/// Gets the compressed image data.
/// </summary>
public IMemoryOwner<byte> Data { get; }
/// <summary>
/// Gets a value indicating whether there is more data to read left.
/// </summary>
@ -390,9 +391,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
this.terminationCodeFound = false;
}
/// <inheritdoc/>
public void Dispose() => this.Data.Dispose();
/// <summary>
/// An EOL is expected before the first data.
/// </summary>
@ -436,6 +434,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// </summary>
/// <param name="nBits">The number of bits to read.</param>
/// <returns>The value read.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
protected uint ReadValue(int nBits)
{
DebugGuard.MustBeGreaterThan(nBits, 0, nameof(nBits));
@ -452,6 +451,20 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
return v;
}
/// <summary>
/// Advances the position by one byte.
/// </summary>
/// <returns>True, if data could be advanced by one byte, otherwise false.</returns>
protected bool AdvancePosition()
{
if (this.LoadNewByte())
{
return true;
}
return false;
}
private uint WhiteTerminatingCodeRunLength()
{
switch (this.CurValueBitsRead)
@ -806,44 +819,49 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private uint GetBit()
{
if (this.BitsRead >= 8)
{
this.LoadNewByte();
this.AdvancePosition();
}
Span<byte> dataSpan = this.Data.GetSpan();
int shift = 8 - this.BitsRead - 1;
uint bit = (uint)((dataSpan[(int)this.Position] & (1 << shift)) != 0 ? 1 : 0);
uint bit = (uint)((this.DataAtPosition & (1 << shift)) != 0 ? 1 : 0);
this.BitsRead++;
return bit;
}
private void LoadNewByte()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool LoadNewByte()
{
this.Position++;
this.ResetBitsRead();
if (this.Position >= (ulong)this.DataLength)
if (this.Position < (ulong)this.DataLength)
{
TiffThrowHelper.ThrowImageFormatException("tiff image has invalid ccitt compressed data");
this.ReadNextByte();
this.Position++;
return true;
}
this.Position++;
this.DataAtPosition = 0;
return false;
}
private void ReadImageDataFromStream(Stream input, int bytesToRead)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadNextByte()
{
Span<byte> dataSpan = this.Data.GetSpan();
input.Read(dataSpan, 0, bytesToRead);
if (this.fillOrder == TiffFillOrder.LeastSignificantBitFirst)
int nextByte = this.stream.ReadByte();
if (nextByte == -1)
{
for (int i = 0; i < dataSpan.Length; i++)
{
dataSpan[i] = ReverseBits(dataSpan[i]);
}
TiffThrowHelper.ThrowImageFormatException("Tiff fax compression error: not enough data.");
}
this.ResetBitsRead();
this.DataAtPosition = this.fillOrder == TiffFillOrder.LeastSignificantBitFirst
? ReverseBits((byte)nextByte)
: (byte)nextByte;
}
// http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64Bits

31
src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs

@ -61,11 +61,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
bool eolPadding = this.faxCompressionOptions.HasFlag(FaxCompressionOptions.EolPadding);
using var bitReader = new T4BitReader(stream, this.FillOrder, byteCount, this.Allocator, eolPadding);
var bitReader = new T4BitReader(stream, this.FillOrder, byteCount, eolPadding);
buffer.Clear();
uint bitsWritten = 0;
uint pixelWritten = 0;
nint bitsWritten = 0;
nuint pixelsWritten = 0;
nint rowsWritten = 0;
while (bitReader.HasMoreData)
{
bitReader.ReadNextRun();
@ -74,41 +75,47 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
this.WritePixelRun(buffer, bitReader, bitsWritten);
bitsWritten += bitReader.RunLength;
pixelWritten += bitReader.RunLength;
bitsWritten += (int)bitReader.RunLength;
pixelsWritten += bitReader.RunLength;
}
if (bitReader.IsEndOfScanLine)
{
// Write padding bytes, if necessary.
uint pad = 8 - (bitsWritten % 8);
nint pad = 8 - Numerics.Modulo8(bitsWritten);
if (pad != 8)
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, pad, 0);
BitWriterUtils.WriteBits(buffer, bitsWritten, pad, 0);
bitsWritten += pad;
}
pixelWritten = 0;
pixelsWritten = 0;
rowsWritten++;
if (rowsWritten >= stripHeight)
{
break;
}
}
}
// Edge case for when we are at the last byte, but there are still some unwritten pixels left.
if (pixelWritten > 0 && pixelWritten < this.width)
if (pixelsWritten > 0 && pixelsWritten < (ulong)this.width)
{
bitReader.ReadNextRun();
this.WritePixelRun(buffer, bitReader, bitsWritten);
}
}
private void WritePixelRun(Span<byte> buffer, T4BitReader bitReader, uint bitsWritten)
private void WritePixelRun(Span<byte> buffer, T4BitReader bitReader, nint bitsWritten)
{
if (bitReader.IsWhiteRun)
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue);
BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.whiteValue);
}
else
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue);
BitWriterUtils.WriteBits(buffer, bitsWritten, (int)bitReader.RunLength, this.blackValue);
}
}

108
src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs

@ -1,10 +1,9 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.IO;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
@ -16,38 +15,23 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
private readonly int maxCodeLength = 12;
private static readonly CcittTwoDimensionalCode None = new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.None, 0);
private static readonly CcittTwoDimensionalCode None = new(0, CcittTwoDimensionalCodeType.None, 0);
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len1Codes = new Dictionary<uint, CcittTwoDimensionalCode>()
{
{ 0b1, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Vertical0, 1) }
};
private static readonly CcittTwoDimensionalCode Len1Code1 = new(0b1, CcittTwoDimensionalCodeType.Vertical0, 1);
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len3Codes = new Dictionary<uint, CcittTwoDimensionalCode>()
{
{ 0b001, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Horizontal, 3) },
{ 0b010, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalL1, 3) },
{ 0b011, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalR1, 3) }
};
private static readonly CcittTwoDimensionalCode Len3Code001 = new(0b001, CcittTwoDimensionalCodeType.Horizontal, 3);
private static readonly CcittTwoDimensionalCode Len3Code010 = new(0b010, CcittTwoDimensionalCodeType.VerticalL1, 3);
private static readonly CcittTwoDimensionalCode Len3Code011 = new(0b011, CcittTwoDimensionalCodeType.VerticalR1, 3);
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len4Codes = new Dictionary<uint, CcittTwoDimensionalCode>()
{
{ 0b0001, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Pass, 4) }
};
private static readonly CcittTwoDimensionalCode Len4Code0001 = new(0b0001, CcittTwoDimensionalCodeType.Pass, 4);
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len6Codes = new Dictionary<uint, CcittTwoDimensionalCode>()
{
{ 0b000011, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalR2, 6) },
{ 0b000010, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalL2, 6) }
};
private static readonly CcittTwoDimensionalCode Len6Code000011 = new(0b000011, CcittTwoDimensionalCodeType.VerticalR2, 6);
private static readonly CcittTwoDimensionalCode Len6Code000010 = new(0b000010, CcittTwoDimensionalCodeType.VerticalL2, 6);
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len7Codes = new Dictionary<uint, CcittTwoDimensionalCode>()
{
{ 0b0000011, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalR3, 7) },
{ 0b0000010, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalL3, 7) },
{ 0b0000001, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Extensions2D, 7) },
{ 0b0000000, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Extensions1D, 7) }
};
private static readonly CcittTwoDimensionalCode Len7Code0000011 = new(0b0000011, CcittTwoDimensionalCodeType.VerticalR3, 7);
private static readonly CcittTwoDimensionalCode Len7Code0000010 = new(0b0000010, CcittTwoDimensionalCodeType.VerticalL3, 7);
private static readonly CcittTwoDimensionalCode Len7Code0000001 = new(0b0000001, CcittTwoDimensionalCodeType.Extensions2D, 7);
private static readonly CcittTwoDimensionalCode Len7Code0000000 = new(0b0000000, CcittTwoDimensionalCodeType.Extensions1D, 7);
/// <summary>
/// Initializes a new instance of the <see cref="T6BitReader"/> class.
@ -55,14 +39,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <param name="input">The compressed input stream.</param>
/// <param name="fillOrder">The logical order of bits within a byte.</param>
/// <param name="bytesToRead">The number of bytes to read from the stream.</param>
/// <param name="allocator">The memory allocator.</param>
public T6BitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator)
: base(input, fillOrder, bytesToRead, allocator)
public T6BitReader(BufferedReadStream input, TiffFillOrder fillOrder, int bytesToRead)
: base(input, fillOrder, bytesToRead)
{
}
/// <inheritdoc/>
public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || ((uint)(this.BitsRead - 1) < (7 - 1));
public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || (uint)(this.BitsRead - 1) < (7 - 1);
/// <summary>
/// Gets or sets the two dimensional code.
@ -85,45 +68,81 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
switch (this.CurValueBitsRead)
{
case 1:
if (Len1Codes.ContainsKey(value))
if (value == Len1Code1.Code)
{
this.Code = Len1Codes[value];
this.Code = Len1Code1;
return false;
}
break;
case 3:
if (Len3Codes.ContainsKey(value))
if (value == Len3Code001.Code)
{
this.Code = Len3Codes[value];
this.Code = Len3Code001;
return false;
}
if (value == Len3Code010.Code)
{
this.Code = Len3Code010;
return false;
}
if (value == Len3Code011.Code)
{
this.Code = Len3Code011;
return false;
}
break;
case 4:
if (Len4Codes.ContainsKey(value))
if (value == Len4Code0001.Code)
{
this.Code = Len4Codes[value];
this.Code = Len4Code0001;
return false;
}
break;
case 6:
if (Len6Codes.ContainsKey(value))
if (value == Len6Code000010.Code)
{
this.Code = Len6Codes[value];
this.Code = Len6Code000010;
return false;
}
if (value == Len6Code000011.Code)
{
this.Code = Len6Code000011;
return false;
}
break;
case 7:
if (Len7Codes.ContainsKey(value))
if (value == Len7Code0000000.Code)
{
this.Code = Len7Code0000000;
return false;
}
if (value == Len7Code0000001.Code)
{
this.Code = Len7Code0000001;
return false;
}
if (value == Len7Code0000011.Code)
{
this.Code = Len7Code0000011;
return false;
}
if (value == Len7Code0000010.Code)
{
this.Code = Len7Codes[value];
this.Code = Len7Code0000010;
return false;
}
@ -154,6 +173,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <summary>
/// Swaps the white run to black run an vise versa.
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void SwapColor() => this.IsWhiteRun = !this.IsWhiteRun;
}
}

44
src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
@ -15,12 +17,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
private readonly bool isWhiteZero;
private readonly byte whiteValue;
private readonly byte blackValue;
private readonly int width;
private readonly byte white;
/// <summary>
/// Initializes a new instance of the <see cref="T6TiffCompression" /> class.
/// </summary>
@ -40,8 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
this.FillOrder = fillOrder;
this.width = width;
this.isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero;
this.whiteValue = (byte)(this.isWhiteZero ? 0 : 1);
this.blackValue = (byte)(this.isWhiteZero ? 1 : 0);
this.white = (byte)(this.isWhiteZero ? 0 : 255);
}
/// <summary>
@ -58,10 +57,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
Span<byte> scanLine = scanLineBuffer.GetSpan().Slice(0, this.width);
Span<byte> referenceScanLineSpan = scanLineBuffer.GetSpan().Slice(this.width, this.width);
using var bitReader = new T6BitReader(stream, this.FillOrder, byteCount, this.Allocator);
var bitReader = new T6BitReader(stream, this.FillOrder, byteCount);
var referenceScanLine = new CcittReferenceScanline(this.isWhiteZero, this.width);
uint bitsWritten = 0;
nint bitsWritten = 0;
for (int y = 0; y < height; y++)
{
scanLine.Clear();
@ -74,21 +73,34 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
}
private uint WriteScanLine(Span<byte> buffer, Span<byte> scanLine, uint bitsWritten)
private nint WriteScanLine(Span<byte> buffer, Span<byte> scanLine, nint bitsWritten)
{
byte white = (byte)(this.isWhiteZero ? 0 : 255);
for (int i = 0; i < scanLine.Length; i++)
nint bitPos = Numerics.Modulo8(bitsWritten);
nint bufferPos = bitsWritten / 8;
ref byte scanLineRef = ref MemoryMarshal.GetReference(scanLine);
for (nint i = 0; i < scanLine.Length; i++)
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, 1, scanLine[i] == white ? this.whiteValue : this.blackValue);
if (Unsafe.Add(ref scanLineRef, i) != this.white)
{
BitWriterUtils.WriteBit(buffer, bufferPos, bitPos);
}
bitPos++;
bitsWritten++;
if (bitPos >= 8)
{
bitPos = 0;
bufferPos++;
}
}
// Write padding bytes, if necessary.
uint remainder = bitsWritten % 8;
nint remainder = Numerics.Modulo8(bitsWritten);
if (remainder != 0)
{
uint padding = 8 - remainder;
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, padding, 0);
nint padding = 8 - remainder;
BitWriterUtils.WriteBits(buffer, bitsWritten, padding, 0);
bitsWritten += padding;
}
@ -122,7 +134,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
else
{
scanline.Fill((byte)255);
scanline.Fill(255);
}
break;

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -32,11 +33,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
L16 l16 = TiffUtils.L16Default;
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
for (int y = top; y < top + height; y++)

67
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor{TPixel}.cs

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -17,26 +19,65 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
nint offset = 0;
var colorBlack = default(TPixel);
var colorWhite = default(TPixel);
int offset = 0;
Color black = Color.Black;
Color white = Color.White;
for (int y = top; y < top + height; y++)
colorBlack.FromRgba32(Color.Black);
colorWhite.FromRgba32(Color.White);
ref byte dataRef = ref MemoryMarshal.GetReference(data);
for (nint y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x += 8)
Span<TPixel> pixelRowSpan = pixels.DangerousGetRowSpan((int)y);
ref TPixel pixelRowRef = ref MemoryMarshal.GetReference(pixelRowSpan);
for (nint x = left; x < left + width; x += 8)
{
byte b = data[offset++];
int maxShift = Math.Min(left + width - x, 8);
byte b = Unsafe.Add(ref dataRef, offset++);
nint maxShift = Math.Min(left + width - x, 8);
for (int shift = 0; shift < maxShift; shift++)
if (maxShift == 8)
{
int bit = (b >> (7 - shift)) & 1;
int bit = (b >> 7) & 1;
ref TPixel pixel0 = ref Unsafe.Add(ref pixelRowRef, x);
pixel0 = bit == 0 ? colorBlack : colorWhite;
bit = (b >> 6) & 1;
ref TPixel pixel1 = ref Unsafe.Add(ref pixelRowRef, x + 1);
pixel1 = bit == 0 ? colorBlack : colorWhite;
bit = (b >> 5) & 1;
ref TPixel pixel2 = ref Unsafe.Add(ref pixelRowRef, x + 2);
pixel2 = bit == 0 ? colorBlack : colorWhite;
bit = (b >> 4) & 1;
ref TPixel pixel3 = ref Unsafe.Add(ref pixelRowRef, x + 3);
pixel3 = bit == 0 ? colorBlack : colorWhite;
bit = (b >> 3) & 1;
ref TPixel pixel4 = ref Unsafe.Add(ref pixelRowRef, x + 4);
pixel4 = bit == 0 ? colorBlack : colorWhite;
color.FromRgba32(bit == 0 ? black : white);
bit = (b >> 2) & 1;
ref TPixel pixel5 = ref Unsafe.Add(ref pixelRowRef, x + 5);
pixel5 = bit == 0 ? colorBlack : colorWhite;
bit = (b >> 1) & 1;
ref TPixel pixel6 = ref Unsafe.Add(ref pixelRowRef, x + 6);
pixel6 = bit == 0 ? colorBlack : colorWhite;
bit = b & 1;
ref TPixel pixel7 = ref Unsafe.Add(ref pixelRowRef, x + 7);
pixel7 = bit == 0 ? colorBlack : colorWhite;
}
else
{
for (int shift = 0; shift < maxShift; shift++)
{
int bit = (b >> (7 - shift)) & 1;
pixels[x + shift, y] = color;
ref TPixel pixel = ref Unsafe.Add(ref pixelRowRef, x + shift);
pixel = bit == 0 ? colorBlack : colorWhite;
}
}
}
}

9
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -25,14 +26,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
byte[] buffer = new byte[4];
color.FromScaledVector4(Vector4.Zero);
Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;
Span<byte> bufferSpan = buffer.AsSpan(bufferStartIdx);
Span<byte> bufferSpan = buffer.Slice(bufferStartIdx);
int offset = 0;
for (int y = top; y < top + height; y++)
{

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs

@ -3,7 +3,6 @@
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -26,10 +25,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
byte[] buffer = new byte[4];
int offset = 0;

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -25,10 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
for (int y = top; y < top + height; y++)

7
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor{TPixel}.cs

@ -24,6 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
var l8 = default(L8);
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRowSpan = pixels.DangerousGetRowSpan(y);
for (int x = left; x < left + width - 1;)
{
byte byteData = data[offset++];
@ -32,13 +33,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
l8.PackedValue = intensity1;
color.FromL8(l8);
pixels[x++, y] = color;
pixelRowSpan[x++] = color;
byte intensity2 = (byte)((byteData & 0x0F) * 17);
l8.PackedValue = intensity2;
color.FromL8(l8);
pixels[x++, y] = color;
pixelRowSpan[x++] = color;
}
if (isOddWidth)
@ -49,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
l8.PackedValue = intensity1;
color.FromL8(l8);
pixels[left + width - 1, y] = color;
pixelRowSpan[left + width - 1] = color;
}
}
}

8
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs

@ -19,6 +19,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
private readonly TPixel[] palette;
private const float InvMax = 1.0f / 65535F;
/// <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(TiffBitsPerSample bitsPerSample, ushort[] colorMap)
@ -56,9 +58,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
for (int i = 0; i < palette.Length; i++)
{
float r = colorMap[rOffset + i] / 65535F;
float g = colorMap[gOffset + i] / 65535F;
float b = colorMap[bOffset + i] / 65535F;
float r = colorMap[rOffset + i] * InvMax;
float g = colorMap[gOffset + i] * InvMax;
float b = colorMap[bOffset + i] * InvMax;
palette[i].FromScaledVector4(new Vector4(r, g, b, 1.0f));
}

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -32,11 +33,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs

@ -3,6 +3,7 @@
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -26,11 +27,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan();

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -25,10 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs

@ -3,6 +3,7 @@
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -26,10 +27,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -25,10 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
for (int y = top; y < top + height; y++)

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs

@ -3,6 +3,7 @@
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -26,10 +27,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan();

4
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs

@ -26,10 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
byte[] buffer = new byte[4];

4
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs

@ -42,11 +42,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0;

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs

@ -3,6 +3,7 @@
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -33,11 +34,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan();

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -32,10 +33,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0;

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs

@ -3,6 +3,7 @@
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -33,10 +34,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -32,10 +33,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0;

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs

@ -3,6 +3,7 @@
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -33,10 +34,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan();

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs

@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
using IMemoryOwner<Vector4> vectors = hasAssociatedAlpha ? this.memoryAllocator.Allocate<Vector4>(width) : null;
Span<Vector4> vectorsSpan = hasAssociatedAlpha ? vectors.GetSpan() : Span<Vector4>.Empty;
for (int y = top; y < top + height; y++)

4
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs

@ -26,10 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
byte[] buffer = new byte[4];

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -25,11 +26,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
L16 l16 = TiffUtils.L16Default;
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
int offset = 0;
for (int y = top; y < top + height; y++)

67
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor{TPixel}.cs

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -16,26 +18,65 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
nint offset = 0;
var colorBlack = default(TPixel);
var colorWhite = default(TPixel);
int offset = 0;
Color black = Color.Black;
Color white = Color.White;
for (int y = top; y < top + height; y++)
colorBlack.FromRgba32(Color.Black);
colorWhite.FromRgba32(Color.White);
ref byte dataRef = ref MemoryMarshal.GetReference(data);
for (nint y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x += 8)
Span<TPixel> pixelRowSpan = pixels.DangerousGetRowSpan((int)y);
ref TPixel pixelRowRef = ref MemoryMarshal.GetReference(pixelRowSpan);
for (nint x = left; x < left + width; x += 8)
{
byte b = data[offset++];
int maxShift = Math.Min(left + width - x, 8);
byte b = Unsafe.Add(ref dataRef, offset++);
nint maxShift = Math.Min(left + width - x, 8);
for (int shift = 0; shift < maxShift; shift++)
if (maxShift == 8)
{
int bit = (b >> (7 - shift)) & 1;
int bit = (b >> 7) & 1;
ref TPixel pixel0 = ref Unsafe.Add(ref pixelRowRef, x);
pixel0 = bit == 0 ? colorWhite : colorBlack;
bit = (b >> 6) & 1;
ref TPixel pixel1 = ref Unsafe.Add(ref pixelRowRef, x + 1);
pixel1 = bit == 0 ? colorWhite : colorBlack;
bit = (b >> 5) & 1;
ref TPixel pixel2 = ref Unsafe.Add(ref pixelRowRef, x + 2);
pixel2 = bit == 0 ? colorWhite : colorBlack;
bit = (b >> 4) & 1;
ref TPixel pixel3 = ref Unsafe.Add(ref pixelRowRef, x + 3);
pixel3 = bit == 0 ? colorWhite : colorBlack;
bit = (b >> 3) & 1;
ref TPixel pixel4 = ref Unsafe.Add(ref pixelRowRef, x + 4);
pixel4 = bit == 0 ? colorWhite : colorBlack;
color.FromRgba32(bit == 0 ? white : black);
bit = (b >> 2) & 1;
ref TPixel pixel5 = ref Unsafe.Add(ref pixelRowRef, x + 5);
pixel5 = bit == 0 ? colorWhite : colorBlack;
bit = (b >> 1) & 1;
ref TPixel pixel6 = ref Unsafe.Add(ref pixelRowRef, x + 6);
pixel6 = bit == 0 ? colorWhite : colorBlack;
bit = b & 1;
ref TPixel pixel7 = ref Unsafe.Add(ref pixelRowRef, x + 7);
pixel7 = bit == 0 ? colorWhite : colorBlack;
}
else
{
for (int shift = 0; shift < maxShift; shift++)
{
int bit = (b >> (7 - shift)) & 1;
pixels[x + shift, y] = color;
ref TPixel pixel = ref Unsafe.Add(ref pixelRowRef, x + shift);
pixel = bit == 0 ? colorWhite : colorBlack;
}
}
}
}

9
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -25,15 +26,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
byte[] buffer = new byte[4];
color.FromScaledVector4(Vector4.Zero);
Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;
const uint maxValue = 0xFFFFFF;
Span<byte> bufferSpan = buffer.AsSpan(bufferStartIdx);
Span<byte> bufferSpan = buffer.Slice(bufferStartIdx);
int offset = 0;
for (int y = top; y < top + height; y++)
{

4
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs

@ -26,10 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
byte[] buffer = new byte[4];
int offset = 0;

5
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -25,10 +26,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(Vector4.Zero);
const uint maxValue = 0xFFFFFFFF;
int offset = 0;

7
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor{TPixel}.cs

@ -24,6 +24,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
var l8 = default(L8);
for (int y = top; y < top + height; y++)
{
Span<TPixel> pixelRowSpan = pixels.DangerousGetRowSpan(y);
for (int x = left; x < left + width - 1;)
{
byte byteData = data[offset++];
@ -32,13 +33,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
l8.PackedValue = intensity1;
color.FromL8(l8);
pixels[x++, y] = color;
pixelRowSpan[x++] = color;
byte intensity2 = (byte)((15 - (byteData & 0x0F)) * 17);
l8.PackedValue = intensity2;
color.FromL8(l8);
pixels[x++, y] = color;
pixelRowSpan[x++] = color;
}
if (isOddWidth)
@ -49,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
l8.PackedValue = intensity1;
color.FromL8(l8);
pixels[left + width - 1, y] = color;
pixelRowSpan[left + width - 1] = color;
}
}
}

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

@ -343,7 +343,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
switch (bitsPerPixel)
{
case TiffBitsPerPixel.Bit1:
if (compression == TiffCompression.Ccitt1D || compression == TiffCompression.CcittGroup3Fax || compression == TiffCompression.CcittGroup4Fax)
if (IsOneBitCompression(compression))
{
// The “normal” PhotometricInterpretation for bilevel CCITT compressed data is WhiteIsZero.
this.SetEncoderOptions(bitsPerPixel, TiffPhotometricInterpretation.WhiteIsZero, compression, TiffPredictor.None);
@ -375,6 +375,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff
break;
}
// Make sure 1 Bit compression is only used with 1 bit pixel type.
if (IsOneBitCompression(this.CompressionType) && this.BitsPerPixel != TiffBitsPerPixel.Bit1)
{
// Invalid compression / bits per pixel combination, fallback to no compression.
this.CompressionType = DefaultCompression;
}
return;
}
@ -396,18 +403,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{
case TiffPhotometricInterpretation.BlackIsZero:
case TiffPhotometricInterpretation.WhiteIsZero:
if (this.CompressionType == TiffCompression.Ccitt1D ||
this.CompressionType == TiffCompression.CcittGroup3Fax ||
this.CompressionType == TiffCompression.CcittGroup4Fax)
if (IsOneBitCompression(this.CompressionType))
{
this.SetEncoderOptions(TiffBitsPerPixel.Bit1, photometricInterpretation, compression, TiffPredictor.None);
return;
}
else
{
this.SetEncoderOptions(TiffBitsPerPixel.Bit8, photometricInterpretation, compression, predictor);
return;
}
this.SetEncoderOptions(TiffBitsPerPixel.Bit8, photometricInterpretation, compression, predictor);
return;
case TiffPhotometricInterpretation.PaletteColor:
this.SetEncoderOptions(TiffBitsPerPixel.Bit8, photometricInterpretation, compression, predictor);
@ -428,5 +431,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.CompressionType = compression;
this.HorizontalPredictor = predictor;
}
public static bool IsOneBitCompression(TiffCompression? compression)
{
if (compression is TiffCompression.Ccitt1D or TiffCompression.CcittGroup3Fax or TiffCompression.CcittGroup4Fax)
{
return true;
}
return false;
}
}
}

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

@ -18,8 +18,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
private const float Scale32Bit = 1.0f / 0xFFFFFFFF;
public static Vector4 Vector4Default { get; } = new(0.0f, 0.0f, 0.0f, 0.0f);
public static Rgba64 Rgba64Default { get; } = new(0, 0, 0, 0);
public static L16 L16Default { get; } = new(0);

1
tests/ImageSharp.Benchmarks/Codecs/Tiff/DecodeTiff.cs

@ -46,6 +46,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Params(
TestImages.Tiff.CcittFax3AllTermCodes,
TestImages.Tiff.Fax4Compressed2,
TestImages.Tiff.HuffmanRleAllMakeupCodes,
TestImages.Tiff.Calliphora_GrayscaleUncompressed,
TestImages.Tiff.Calliphora_RgbPaletteLzw_Predictor,

39
tests/ImageSharp.Benchmarks/Codecs/Tiff/EncodeTiff.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Drawing.Imaging;
using System.IO;
using BenchmarkDotNet.Attributes;
@ -9,6 +10,7 @@ using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests;
using SDImage = System.Drawing.Image;
namespace SixLabors.ImageSharp.Benchmarks.Codecs
{
@ -17,11 +19,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Config(typeof(Config.ShortMultiFramework))]
public class EncodeTiff
{
private System.Drawing.Image drawing;
private Stream stream;
private SDImage drawing;
private Image<Rgba32> core;
private Configuration configuration;
private string TestImageFullPath => Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, this.TestImage);
[Params(TestImages.Tiff.Calliphora_RgbUncompressed)]
@ -29,9 +30,11 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Params(
TiffCompression.None,
TiffCompression.Deflate,
// System.Drawing does not support Deflate or PackBits
// TiffCompression.Deflate,
// TiffCompression.PackBits,
TiffCompression.Lzw,
TiffCompression.PackBits,
TiffCompression.CcittGroup3Fax,
TiffCompression.Ccitt1D)]
public TiffCompression Compression { get; set; }
@ -39,11 +42,12 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[GlobalSetup]
public void ReadImages()
{
if (this.core == null)
if (this.stream == null)
{
this.configuration = new Configuration();
this.core = Image.Load<Rgba32>(this.configuration, this.TestImageFullPath);
this.drawing = System.Drawing.Image.FromFile(this.TestImageFullPath);
this.stream = File.OpenRead(this.TestImageFullPath);
this.core = Image.Load<Rgba32>(this.stream);
this.stream.Position = 0;
this.drawing = SDImage.FromStream(this.stream);
}
}
@ -70,7 +74,10 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
[Benchmark(Description = "ImageSharp Tiff")]
public void TiffCore()
{
TiffPhotometricInterpretation photometricInterpretation = TiffPhotometricInterpretation.Rgb;
TiffPhotometricInterpretation photometricInterpretation =
IsOneBitCompression(this.Compression) ?
TiffPhotometricInterpretation.WhiteIsZero :
TiffPhotometricInterpretation.Rgb;
var encoder = new TiffEncoder() { Compression = this.Compression, PhotometricInterpretation = photometricInterpretation };
using var memoryStream = new MemoryStream();
@ -109,8 +116,18 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs
return EncoderValue.CompressionLZW;
default:
throw new System.NotSupportedException(compression.ToString());
throw new NotSupportedException(compression.ToString());
}
}
public static bool IsOneBitCompression(TiffCompression compression)
{
if (compression is TiffCompression.Ccitt1D or TiffCompression.CcittGroup3Fax or TiffCompression.CcittGroup4Fax)
{
return true;
}
return false;
}
}
}

8
tests/ImageSharp.Benchmarks/Config.cs

@ -32,17 +32,13 @@ namespace SixLabors.ImageSharp.Benchmarks
public class MultiFramework : Config
{
public MultiFramework() => this.AddJob(
Job.Default.WithRuntime(ClrRuntime.Net472),
Job.Default.WithRuntime(CoreRuntime.Core31),
Job.Default.WithRuntime(CoreRuntime.Core50).WithArguments(new Argument[] { new MsBuildArgument("/p:DebugType=portable") }));
Job.Default.WithRuntime(CoreRuntime.Core60).WithArguments(new Argument[] { new MsBuildArgument("/p:DebugType=portable") }));
}
public class ShortMultiFramework : Config
{
public ShortMultiFramework() => this.AddJob(
Job.Default.WithRuntime(ClrRuntime.Net472).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3),
Job.Default.WithRuntime(CoreRuntime.Core31).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3),
Job.Default.WithRuntime(CoreRuntime.Core50).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3).WithArguments(new Argument[] { new MsBuildArgument("/p:DebugType=portable") }));
Job.Default.WithRuntime(CoreRuntime.Core60).WithLaunchCount(1).WithWarmupCount(3).WithIterationCount(3).WithArguments(new Argument[] { new MsBuildArgument("/p:DebugType=portable") }));
}
public class ShortCore31 : Config

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

@ -13,14 +13,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation
[Trait("Format", "Tiff")]
public class BlackIsZeroTiffColorTests : PhotometricInterpretationTestBase
{
private static readonly Rgba32 Gray000 = new Rgba32(0, 0, 0, 255);
private static readonly Rgba32 Gray128 = new Rgba32(128, 128, 128, 255);
private static readonly Rgba32 Gray255 = new Rgba32(255, 255, 255, 255);
private static readonly Rgba32 Gray0 = new Rgba32(0, 0, 0, 255);
private static readonly Rgba32 Gray8 = new Rgba32(136, 136, 136, 255);
private static readonly Rgba32 GrayF = new Rgba32(255, 255, 255, 255);
private static readonly Rgba32 Bit0 = new Rgba32(0, 0, 0, 255);
private static readonly Rgba32 Bit1 = new Rgba32(255, 255, 255, 255);
private static readonly Rgba32 Gray000 = new(0, 0, 0, 255);
private static readonly Rgba32 Gray128 = new(128, 128, 128, 255);
private static readonly Rgba32 Gray255 = new(255, 255, 255, 255);
private static readonly Rgba32 Gray0 = new(0, 0, 0, 255);
private static readonly Rgba32 Gray8 = new(136, 136, 136, 255);
private static readonly Rgba32 GrayF = new(255, 255, 255, 255);
private static readonly Rgba32 Bit0 = new(0, 0, 0, 255);
private static readonly Rgba32 Bit1 = new(255, 255, 255, 255);
private static readonly byte[] BilevelBytes4X4 =
{
@ -30,8 +30,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation
0b10010000
};
private static readonly Rgba32[][] BilevelResult4X4 = new[]
{
private static readonly Rgba32[][] BilevelResult4X4 = {
new[] { Bit0, Bit1, Bit0, Bit1 },
new[] { Bit1, Bit1, Bit1, Bit1 },
new[] { Bit0, Bit1, Bit1, Bit1 },

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

@ -611,6 +611,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[Theory]
[WithFile(Fax4Compressed, PixelTypes.Rgba32)]
[WithFile(Fax4Compressed2, PixelTypes.Rgba32)]
[WithFile(Fax4CompressedMinIsBlack, PixelTypes.Rgba32)]
[WithFile(Fax4CompressedLowerOrderBitsFirst, PixelTypes.Rgba32)]
[WithFile(Calliphora_Fax4Compressed, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_Fax4Compressed<TPixel>(TestImageProvider<TPixel> provider)

22
tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs

@ -95,6 +95,28 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
Assert.Equal(TiffBitsPerPixel.Bit24, frameMetaData.BitsPerPixel);
}
[Theory]
[InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Ccitt1D)]
[InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.CcittGroup3Fax)]
[InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.CcittGroup4Fax)]
public void EncoderOptions_WithInvalidCompressionAndPixelTypeCombination_DefaultsToRgb(TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression)
{
// arrange
var tiffEncoder = new TiffEncoder { PhotometricInterpretation = photometricInterpretation, Compression = compression };
using Image input = new Image<Rgb24>(10, 10);
using var memStream = new MemoryStream();
// act
input.Save(memStream, tiffEncoder);
// assert
memStream.Position = 0;
using var output = Image.Load<Rgba32>(memStream);
TiffFrameMetadata frameMetaData = output.Frames.RootFrame.Metadata.GetTiffMetadata();
Assert.Equal(TiffBitsPerPixel.Bit24, frameMetaData.BitsPerPixel);
}
[Theory]
[InlineData(null, TiffCompression.Deflate, TiffBitsPerPixel.Bit24, TiffCompression.Deflate)]
[InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate, TiffBitsPerPixel.Bit24, TiffCompression.Deflate)]

2
tests/ImageSharp.Tests/TestImages.cs

@ -761,7 +761,9 @@ namespace SixLabors.ImageSharp.Tests
public const string Calliphora_HuffmanCompressed = "Tiff/Calliphora_huffman_rle.tiff";
public const string Calliphora_BiColorUncompressed = "Tiff/Calliphora_bicolor_uncompressed.tiff";
public const string Fax4Compressed = "Tiff/basi3p02_fax4.tiff";
public const string Fax4Compressed2 = "Tiff/CCITTGroup4.tiff";
public const string Fax4CompressedLowerOrderBitsFirst = "Tiff/basi3p02_fax4_lowerOrderBitsFirst.tiff";
public const string Fax4CompressedMinIsBlack = "Tiff/CCITTGroup4_minisblack.tiff";
public const string CcittFax3AllTermCodes = "Tiff/ccitt_fax3_all_terminating_codes.tiff";
public const string CcittFax3AllMakeupCodes = "Tiff/ccitt_fax3_all_makeup_codes.tiff";

3
tests/Images/Input/Tiff/CCITTGroup4.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:120ad0814f207c45d968b05f7435034ecfee8ac1a0958cd984a070dad31f66f3
size 11082

3
tests/Images/Input/Tiff/CCITTGroup4_minisblack.tiff

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6c12c96059a6214739fe836572bce6912dcf4a0d2ff389c840f0d2daa4465f55
size 11637
Loading…
Cancel
Save