Browse Source

Merge remote-tracking branch 'origin/master' into webp

# Conflicts:
#	src/ImageSharp/Formats/ImageExtensions.Save.cs
#	src/ImageSharp/Formats/ImageExtensions.Save.tt
#	tests/ImageSharp.Benchmarks/Codecs/EncodeTga.cs
#	tests/ImageSharp.Tests/TestImages.cs
#	tests/ImageSharp.Tests/TestUtilities/TestEnvironment.Formats.cs
pull/1552/head
Brian Popow 5 years ago
parent
commit
df09743934
  1. 47
      README.md
  2. 2
      shared-infrastructure
  3. 7
      src/ImageSharp/Advanced/AotCompilerTools.cs
  4. 23
      src/ImageSharp/Common/ByteOrder.cs
  5. 77
      src/ImageSharp/Common/Helpers/Numerics.cs
  6. 50
      src/ImageSharp/Common/Helpers/UnitConverter.cs
  7. 2
      src/ImageSharp/Compression/Zlib/Adler32.cs
  8. 2
      src/ImageSharp/Compression/Zlib/Crc32.Lut.cs
  9. 2
      src/ImageSharp/Compression/Zlib/Crc32.cs
  10. 81
      src/ImageSharp/Compression/Zlib/DeflateCompressionLevel.cs
  11. 2
      src/ImageSharp/Compression/Zlib/DeflateThrowHelper.cs
  12. 2
      src/ImageSharp/Compression/Zlib/Deflater.cs
  13. 2
      src/ImageSharp/Compression/Zlib/DeflaterConstants.cs
  14. 2
      src/ImageSharp/Compression/Zlib/DeflaterEngine.cs
  15. 2
      src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs
  16. 2
      src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs
  17. 2
      src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs
  18. 0
      src/ImageSharp/Compression/Zlib/README.md
  19. 15
      src/ImageSharp/Compression/Zlib/ZlibDeflateStream.cs
  20. 2
      src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs
  21. 0
      src/ImageSharp/Compression/Zlib/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf
  22. 14
      src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs
  23. 10
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  24. 2
      src/ImageSharp/Formats/Bmp/BmpEncoder.cs
  25. 187
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  26. 6
      src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs
  27. 125
      src/ImageSharp/Formats/ImageExtensions.Save.cs
  28. 28
      src/ImageSharp/Formats/ImageExtensions.Save.tt
  29. 78
      src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
  30. 118
      src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
  31. 49
      src/ImageSharp/Formats/Png/Filters/SubFilter.cs
  32. 55
      src/ImageSharp/Formats/Png/Filters/UpFilter.cs
  33. 3
      src/ImageSharp/Formats/Png/PngCompressionLevel.cs
  34. 3
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  35. 4
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  36. 2
      src/ImageSharp/Formats/Tga/README.md
  37. 51
      src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs
  38. 62
      src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs
  39. 40
      src/ImageSharp/Formats/Tiff/Compression/Compressors/LzwCompressor.cs
  40. 34
      src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs
  41. 48
      src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsCompressor.cs
  42. 128
      src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsWriter.cs
  43. 592
      src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs
  44. 270
      src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs
  45. 63
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs
  46. 95
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwString.cs
  47. 45
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs
  48. 79
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs
  49. 35
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs
  50. 96
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs
  51. 842
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs
  52. 90
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs
  53. 257
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffLzwDecoder.cs
  54. 36
      src/ImageSharp/Formats/Tiff/Compression/FaxCompressionOptions.cs
  55. 138
      src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs
  56. 62
      src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs
  57. 48
      src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompressor.cs
  58. 54
      src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs
  59. 64
      src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs
  60. 41
      src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs
  61. 54
      src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs
  62. 23
      src/ImageSharp/Formats/Tiff/ConfigurationExtensions.cs
  63. 94
      src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs
  64. 113
      src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs
  65. 26
      src/ImageSharp/Formats/Tiff/Constants/TiffExtraSamples.cs
  66. 21
      src/ImageSharp/Formats/Tiff/Constants/TiffFillOrder.cs
  67. 44
      src/ImageSharp/Formats/Tiff/Constants/TiffNewSubfileType.cs
  68. 51
      src/ImageSharp/Formats/Tiff/Constants/TiffOrientation.cs
  69. 89
      src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs
  70. 31
      src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs
  71. 28
      src/ImageSharp/Formats/Tiff/Constants/TiffPredictor.cs
  72. 41
      src/ImageSharp/Formats/Tiff/Constants/TiffSampleFormat.cs
  73. 26
      src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs
  74. 26
      src/ImageSharp/Formats/Tiff/Constants/TiffThresholding.cs
  75. 16
      src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs
  76. 47
      src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
  77. 104
      src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs
  78. 65
      src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs
  79. 28
      src/ImageSharp/Formats/Tiff/MetadataExtensions.cs
  80. 46
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero1TiffColor{TPixel}.cs
  81. 58
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero4TiffColor{TPixel}.cs
  82. 39
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs
  83. 50
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs
  84. 67
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs
  85. 43
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs
  86. 76
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs
  87. 64
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs
  88. 28
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBaseColorDecoder{TPixel}.cs
  89. 94
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs
  90. 71
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs
  91. 45
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero1TiffColor{TPixel}.cs
  92. 58
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero4TiffColor{TPixel}.cs
  93. 39
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs
  94. 50
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs
  95. 253
      src/ImageSharp/Formats/Tiff/README.md
  96. BIN
      src/ImageSharp/Formats/Tiff/T-REC-T.4-198811-S!!PDF-E.pdf
  97. BIN
      src/ImageSharp/Formats/Tiff/T-REC-T.6-198811-I!!PDF-E.pdf
  98. BIN
      src/ImageSharp/Formats/Tiff/TIFF-v6.pdf
  99. 31
      src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs
  100. 36
      src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs

47
README.md

@ -1,4 +1,4 @@
<h1 align="center">
<h1 align="center">
<img src="https://github.com/SixLabors/Branding/raw/master/icons/imagesharp/sixlabors.imagesharp.svg?sanitize=true" alt="SixLabors.ImageSharp" width="256"/>
<br/>
@ -26,9 +26,16 @@ Built against [.NET Standard 1.3](https://docs.microsoft.com/en-us/dotnet/standa
## License
- ImageSharp is licensed under the [Apache License, Version 2.0](https://opensource.org/licenses/Apache-2.0)
- An alternative Commercial License can be purchased for projects and applications requiring support.
- An alternative Commercial Support License can be purchased **for projects and applications requiring support**.
Please visit https://sixlabors.com/pricing for details.
## Support Six Labors
Support the efforts of the development of the Six Labors projects.
- [Purchase a Commercial Support License :heart:](https://sixlabors.com/pricing/)
- [Become a sponsor via GitHub Sponsors :heart:]( https://github.com/sponsors/SixLabors)
- [Become a sponsor via Open Collective :heart:](https://opencollective.com/sixlabors)
## Documentation
- [Detailed documentation](https://sixlabors.github.io/docs/) for the ImageSharp API is available. This includes additional conceptual documentation to help you get started.
@ -57,7 +64,7 @@ If you prefer, you can compile ImageSharp yourself (please do and help!)
- Using [Visual Studio 2019](https://visualstudio.microsoft.com/vs/)
- Make sure you have the latest version installed
- Make sure you have [the .NET Core 3.1 SDK](https://www.microsoft.com/net/core#windows) installed
- Make sure you have [the .NET 5 SDK](https://www.microsoft.com/net/core#windows) installed
Alternatively, you can work from command line and/or with a lightweight editor on **both Linux/Unix and Windows**:
@ -96,40 +103,6 @@ Please... Spread the word, contribute algorithms, submit performance improvement
- [Scott Williams](https://github.com/tocsoft)
- [Brian Popow](https://github.com/brianpopow)
## Sponsor Six Labors
Support the efforts of the development of the Six Labors projects. [[Become a sponsor :heart:](https://opencollective.com/sixlabors#sponsor)]
### Platinum Sponsors
Become a platinum sponsor with a monthly donation of $2000 (providing 32 hours of maintenance and development) and get 2 hours of dedicated support (remote support available through chat or screen-sharing) per month.
In addition you get your logo (large) on our README on GitHub and the home page (large) of sixlabors.com
<a href="https://opencollective.com/sixlabors/tiers/platinum-sponsors/0/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/platinum-sponsors/0/avatar.svg?avatarHeight=192"></a>
### Gold Sponsors
Become a gold sponsor with a monthly donation of $1000 (providing 16 hours of maintenance and development) and get 1 hour of dedicated support (remote support available through chat or screen-sharing) per month.
In addition you get your logo (large) on our README on GitHub and the home page (medium) of sixlabors.com
<a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/0/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/0/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/1/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/1/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/2/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/2/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/3/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/3/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/4/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/4/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/5/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/5/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/6/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/6/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/7/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/7/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/8/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/8/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/9/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/9/avatar.svg?avatarHeight=156"></a><a href="https://opencollective.com/sixlabors/tiers/gold-sponsors/10/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/gold-sponsors/10/avatar.svg?avatarHeight=156"></a>
### Silver Sponsors
Become a silver sponsor with a monthly donation of $500 (providing 8 hours of maintenance and development) and get your logo (medium) on our README on GitHub and the product pages of sixlabors.com
<a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/0/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/0/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/1/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/1/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/2/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/2/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/3/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/3/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/4/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/4/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/5/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/5/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/6/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/6/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/7/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/7/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/8/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/8/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/9/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/9/avatar.svg?avatarHeight=128"></a><a href="https://opencollective.com/sixlabors/tiers/silver-sponsors/10/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/silver-sponsors/10/avatar.svg?avatarHeight=128"></a>
### Bronze Sponsors
Become a bronze sponsor with a monthly donation of $100 and get your logo (small) on our README on GitHub.
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/0/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/0/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/1/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/1/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/2/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/2/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/3/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/3/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/4/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/4/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/5/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/5/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/6/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/6/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/7/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/7/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/8/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/8/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/9/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/9/avatar.svg?avatarHeight=96"></a>
<a href="https://opencollective.com/sixlabors/tiers/bronze-sponsors/10/website" target="_blank"><img src="https://opencollective.com/sixlabors/tiers/bronze-sponsors/10/avatar.svg?avatarHeight=96"></a>

2
shared-infrastructure

@ -1 +1 @@
Subproject commit 41fff7bf7ddb1d118898db1ddba43b95ba6ed0bb
Subproject commit 48e73f455f15eafefbe3175efc7433e5f277e506

7
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -13,6 +13,7 @@ using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
@ -57,7 +58,7 @@ namespace SixLabors.ImageSharp.Advanced
/// necessary methods to complete the SaveAsGif call. That's it, otherwise you should NEVER need this method!!!
/// </remarks>
[Preserve]
private static void SeedEverything()
private static void SeedPixelFormats()
{
try
{
@ -201,6 +202,7 @@ namespace SixLabors.ImageSharp.Advanced
default(JpegEncoderCore).Encode<TPixel>(default, default, default);
default(PngEncoderCore).Encode<TPixel>(default, default, default);
default(TgaEncoderCore).Encode<TPixel>(default, default, default);
default(TiffEncoderCore).Encode<TPixel>(default, default, default);
}
/// <summary>
@ -217,6 +219,7 @@ namespace SixLabors.ImageSharp.Advanced
default(JpegDecoderCore).Decode<TPixel>(default, default, default);
default(PngDecoderCore).Decode<TPixel>(default, default, default);
default(TgaDecoderCore).Decode<TPixel>(default, default, default);
default(TiffDecoderCore).Decode<TPixel>(default, default, default);
}
/// <summary>
@ -233,6 +236,7 @@ namespace SixLabors.ImageSharp.Advanced
AotCompileImageEncoder<TPixel, JpegEncoder>();
AotCompileImageEncoder<TPixel, PngEncoder>();
AotCompileImageEncoder<TPixel, TgaEncoder>();
AotCompileImageEncoder<TPixel, TiffEncoder>();
}
/// <summary>
@ -249,6 +253,7 @@ namespace SixLabors.ImageSharp.Advanced
AotCompileImageDecoder<TPixel, JpegDecoder>();
AotCompileImageDecoder<TPixel, PngDecoder>();
AotCompileImageDecoder<TPixel, TgaDecoder>();
AotCompileImageDecoder<TPixel, TiffDecoder>();
}
/// <summary>

23
src/ImageSharp/Common/ByteOrder.cs

@ -0,0 +1,23 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp
{
/// <summary>
/// The byte order of the data stream.
/// </summary>
public enum ByteOrder
{
/// <summary>
/// The big-endian byte order (Motorola).
/// Most-significant byte comes first, and ends with the least-significant byte.
/// </summary>
BigEndian,
/// <summary>
/// The little-endian byte order (Intel).
/// Least-significant byte comes first and ends with the most-significant byte.
/// </summary>
LittleEndian
}
}

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

@ -748,5 +748,82 @@ namespace SixLabors.ImageSharp
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Lerp(float value1, float value2, float amount)
=> ((value2 - value1) * amount) + value1;
#if SUPPORTS_RUNTIME_INTRINSICS
/// <summary>
/// Accumulates 8-bit integers into <paramref name="accumulator"/> by
/// widening them to 32-bit integers and performing four additions.
/// </summary>
/// <remarks>
/// <code>byte(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)</code>
/// is widened and added onto <paramref name="accumulator"/> as such:
/// <code>
/// accumulator += i32(1, 2, 3, 4);
/// accumulator += i32(5, 6, 7, 8);
/// accumulator += i32(9, 10, 11, 12);
/// accumulator += i32(13, 14, 15, 16);
/// </code>
/// </remarks>
/// <param name="accumulator">The accumulator destination.</param>
/// <param name="values">The values to accumulate.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Accumulate(ref Vector<uint> accumulator, Vector<byte> values)
{
Vector.Widen(values, out Vector<ushort> shortLow, out Vector<ushort> shortHigh);
Vector.Widen(shortLow, out Vector<uint> intLow, out Vector<uint> intHigh);
accumulator += intLow;
accumulator += intHigh;
Vector.Widen(shortHigh, out intLow, out intHigh);
accumulator += intLow;
accumulator += intHigh;
}
/// <summary>
/// Reduces elements of the vector into one sum.
/// </summary>
/// <param name="accumulator">The accumulator to reduce.</param>
/// <returns>The sum of all elements.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ReduceSum(Vector128<int> accumulator)
{
if (Ssse3.IsSupported)
{
Vector128<int> hadd = Ssse3.HorizontalAdd(accumulator, accumulator);
Vector128<int> swapped = Sse2.Shuffle(hadd, 0x1);
Vector128<int> tmp = Sse2.Add(hadd, swapped);
// Vector128<int>.ToScalar() isn't optimized pre-net5.0 https://github.com/dotnet/runtime/pull/37882
return Sse2.ConvertToInt32(tmp);
}
else
{
int sum = 0;
for (int i = 0; i < Vector128<int>.Count; i++)
{
sum += accumulator.GetElement(i);
}
return sum;
}
}
/// <summary>
/// Reduces even elements of the vector into one sum.
/// </summary>
/// <param name="accumulator">The accumulator to reduce.</param>
/// <returns>The sum of even elements.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int EvenReduceSum(Vector256<int> accumulator)
{
Vector128<int> vsum = Sse2.Add(accumulator.GetLower(), accumulator.GetUpper()); // add upper lane to lower lane
vsum = Sse2.Add(vsum, Sse2.Shuffle(vsum, 0b_11_10_11_10)); // add high to low
// Vector128<int>.ToScalar() isn't optimized pre-net5.0 https://github.com/dotnet/runtime/pull/37882
return Sse2.ConvertToInt32(vsum);
}
#endif
}
}

50
src/ImageSharp/Common/Helpers/UnitConverter.cs

@ -30,6 +30,11 @@ namespace SixLabors.ImageSharp.Common.Helpers
/// </summary>
private const double InchesInMeter = 1 / 0.0254D;
/// <summary>
/// The default resolution unit value.
/// </summary>
private const PixelResolutionUnit DefaultResolutionUnit = PixelResolutionUnit.PixelsPerInch;
/// <summary>
/// Scales the value from centimeters to meters.
/// </summary>
@ -89,7 +94,50 @@ namespace SixLabors.ImageSharp.Common.Helpers
IExifValue<ushort> resolution = profile.GetValue(ExifTag.ResolutionUnit);
// EXIF is 1, 2, 3 so we minus "1" off the result.
return resolution is null ? default : (PixelResolutionUnit)(byte)(resolution.Value - 1);
return resolution is null ? DefaultResolutionUnit : (PixelResolutionUnit)(byte)(resolution.Value - 1);
}
/// <summary>
/// Sets the exif profile resolution values.
/// </summary>
/// <param name="exifProfile">The exif profile.</param>
/// <param name="unit">The resolution unit.</param>
/// <param name="horizontal">The horizontal resolution value.</param>
/// <param name="vertical">The vertical resolution value.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public static void SetResolutionValues(ExifProfile exifProfile, PixelResolutionUnit unit, double horizontal, double vertical)
{
switch (unit)
{
case PixelResolutionUnit.AspectRatio:
case PixelResolutionUnit.PixelsPerInch:
case PixelResolutionUnit.PixelsPerCentimeter:
break;
case PixelResolutionUnit.PixelsPerMeter:
{
unit = PixelResolutionUnit.PixelsPerCentimeter;
horizontal = UnitConverter.MeterToCm(horizontal);
vertical = UnitConverter.MeterToCm(vertical);
}
break;
default:
unit = PixelResolutionUnit.PixelsPerInch;
break;
}
exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)(unit + 1));
if (unit == PixelResolutionUnit.AspectRatio)
{
exifProfile.RemoveValue(ExifTag.XResolution);
exifProfile.RemoveValue(ExifTag.YResolution);
}
else
{
exifProfile.SetValue(ExifTag.XResolution, new Rational(horizontal));
exifProfile.SetValue(ExifTag.YResolution, new Rational(vertical));
}
}
}
}

2
src/ImageSharp/Formats/Png/Zlib/Adler32.cs → src/ImageSharp/Compression/Zlib/Adler32.cs

@ -9,7 +9,7 @@ using System.Runtime.Intrinsics.X86;
#endif
#pragma warning disable IDE0007 // Use implicit type
namespace SixLabors.ImageSharp.Formats.Png.Zlib
namespace SixLabors.ImageSharp.Compression.Zlib
{
/// <summary>
/// Calculates the 32 bit Adler checksum of a given buffer according to

2
src/ImageSharp/Formats/Png/Zlib/Crc32.Lut.cs → src/ImageSharp/Compression/Zlib/Crc32.Lut.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Png.Zlib
namespace SixLabors.ImageSharp.Compression.Zlib
{
/// <content>
/// Contains precalulated tables for scalar calculations.

2
src/ImageSharp/Formats/Png/Zlib/Crc32.cs → src/ImageSharp/Compression/Zlib/Crc32.cs

@ -9,7 +9,7 @@ using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
namespace SixLabors.ImageSharp.Formats.Png.Zlib
namespace SixLabors.ImageSharp.Compression.Zlib
{
/// <summary>
/// Calculates the 32 bit Cyclic Redundancy Check (CRC) checksum of a given buffer

81
src/ImageSharp/Compression/Zlib/DeflateCompressionLevel.cs

@ -0,0 +1,81 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Compression.Zlib
{
/// <summary>
/// Provides enumeration of available deflate compression levels.
/// </summary>
public enum DeflateCompressionLevel
{
/// <summary>
/// Level 0. Equivalent to <see cref="NoCompression"/>.
/// </summary>
Level0 = 0,
/// <summary>
/// No compression. Equivalent to <see cref="Level0"/>.
/// </summary>
NoCompression = Level0,
/// <summary>
/// Level 1. Equivalent to <see cref="BestSpeed"/>.
/// </summary>
Level1 = 1,
/// <summary>
/// Best speed compression level.
/// </summary>
BestSpeed = Level1,
/// <summary>
/// Level 2.
/// </summary>
Level2 = 2,
/// <summary>
/// Level 3.
/// </summary>
Level3 = 3,
/// <summary>
/// Level 4.
/// </summary>
Level4 = 4,
/// <summary>
/// Level 5.
/// </summary>
Level5 = 5,
/// <summary>
/// Level 6. Equivalent to <see cref="DefaultCompression"/>.
/// </summary>
Level6 = 6,
/// <summary>
/// The default compression level. Equivalent to <see cref="Level6"/>.
/// </summary>
DefaultCompression = Level6,
/// <summary>
/// Level 7.
/// </summary>
Level7 = 7,
/// <summary>
/// Level 8.
/// </summary>
Level8 = 8,
/// <summary>
/// Level 9. Equivalent to <see cref="BestCompression"/>.
/// </summary>
Level9 = 9,
/// <summary>
/// Best compression level. Equivalent to <see cref="Level9"/>.
/// </summary>
BestCompression = Level9,
}
}

2
src/ImageSharp/Formats/Png/Zlib/DeflateThrowHelper.cs → src/ImageSharp/Compression/Zlib/DeflateThrowHelper.cs

@ -4,7 +4,7 @@
using System;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
namespace SixLabors.ImageSharp.Compression.Zlib
{
internal static class DeflateThrowHelper
{

2
src/ImageSharp/Formats/Png/Zlib/Deflater.cs → src/ImageSharp/Compression/Zlib/Deflater.cs

@ -5,7 +5,7 @@ using System;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
namespace SixLabors.ImageSharp.Compression.Zlib
{
/// <summary>
/// This class compresses input with the deflate algorithm described in RFC 1951.

2
src/ImageSharp/Formats/Png/Zlib/DeflaterConstants.cs → src/ImageSharp/Compression/Zlib/DeflaterConstants.cs

@ -4,7 +4,7 @@
// <auto-generated/>
using System;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
namespace SixLabors.ImageSharp.Compression.Zlib
{
/// <summary>
/// This class contains constants used for deflation.

2
src/ImageSharp/Formats/Png/Zlib/DeflaterEngine.cs → src/ImageSharp/Compression/Zlib/DeflaterEngine.cs

@ -6,7 +6,7 @@ using System.Buffers;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
namespace SixLabors.ImageSharp.Compression.Zlib
{
/// <summary>
/// Strategies for deflater

2
src/ImageSharp/Formats/Png/Zlib/DeflaterHuffman.cs → src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs

@ -7,7 +7,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
namespace SixLabors.ImageSharp.Compression.Zlib
{
/// <summary>
/// Performs Deflate Huffman encoding.

2
src/ImageSharp/Formats/Png/Zlib/DeflaterOutputStream.cs → src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs

@ -5,7 +5,7 @@ using System;
using System.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
namespace SixLabors.ImageSharp.Compression.Zlib
{
/// <summary>
/// A special stream deflating or compressing the bytes that are

2
src/ImageSharp/Formats/Png/Zlib/DeflaterPendingBuffer.cs → src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs

@ -6,7 +6,7 @@ using System.Buffers;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
namespace SixLabors.ImageSharp.Compression.Zlib
{
/// <summary>
/// Stores pending data for writing data to the Deflater.

0
src/ImageSharp/Formats/Png/Zlib/README.md → src/ImageSharp/Compression/Zlib/README.md

15
src/ImageSharp/Formats/Png/Zlib/ZlibDeflateStream.cs → src/ImageSharp/Compression/Zlib/ZlibDeflateStream.cs

@ -4,9 +4,10 @@
using System;
using System.IO;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
namespace SixLabors.ImageSharp.Compression.Zlib
{
/// <summary>
/// Provides methods and properties for compressing streams by using the Zlib Deflate algorithm.
@ -39,9 +40,19 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <summary>
/// The stream responsible for compressing the input stream.
/// </summary>
// private DeflateStream deflateStream;
private DeflaterOutputStream deflateStream;
/// <summary>
/// Initializes a new instance of the <see cref="ZlibDeflateStream"/> class.
/// </summary>
/// <param name="memoryAllocator">The memory allocator to use for buffer allocations.</param>
/// <param name="stream">The stream to compress.</param>
/// <param name="level">The compression level.</param>
public ZlibDeflateStream(MemoryAllocator memoryAllocator, Stream stream, DeflateCompressionLevel level)
: this(memoryAllocator, stream, (PngCompressionLevel)level)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ZlibDeflateStream"/> class.
/// </summary>

2
src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs → src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs

@ -6,7 +6,7 @@ using System.IO;
using System.IO.Compression;
using SixLabors.ImageSharp.IO;
namespace SixLabors.ImageSharp.Formats.Png.Zlib
namespace SixLabors.ImageSharp.Compression.Zlib
{
/// <summary>
/// Provides methods and properties for deframing streams from PNGs.

0
src/ImageSharp/Formats/Png/Zlib/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf → src/ImageSharp/Compression/Zlib/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf

14
src/ImageSharp/Formats/Bmp/BmpBitsPerPixel.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Bmp
@ -8,6 +8,16 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
public enum BmpBitsPerPixel : short
{
/// <summary>
/// 1 bit per pixel.
/// </summary>
Pixel1 = 1,
/// <summary>
/// 4 bits per pixel.
/// </summary>
Pixel4 = 4,
/// <summary>
/// 8 bits per pixel. Each pixel consists of 1 byte.
/// </summary>
@ -28,4 +38,4 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
Pixel32 = 32
}
}
}

10
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs

@ -1303,15 +1303,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
short bitsPerPixel = this.infoHeader.BitsPerPixel;
this.bmpMetadata = this.metadata.GetBmpMetadata();
this.bmpMetadata.InfoHeaderType = infoHeaderType;
// We can only encode at these bit rates so far (1 bit and 4 bit are still missing).
if (bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel8)
|| bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel16)
|| bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel24)
|| bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel32))
{
this.bmpMetadata.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel;
}
this.bmpMetadata.BitsPerPixel = (BmpBitsPerPixel)bitsPerPixel;
}
/// <summary>

2
src/ImageSharp/Formats/Bmp/BmpEncoder.cs

@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// <summary>
/// Gets or sets the quantizer for reducing the color count for 8-Bit images.
/// Defaults to OctreeQuantizer.
/// Defaults to Wu Quantizer.
/// </summary>
public IQuantizer Quantizer { get; set; }

187
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs

@ -51,6 +51,16 @@ namespace SixLabors.ImageSharp.Formats.Bmp
/// </summary>
private const int ColorPaletteSize8Bit = 1024;
/// <summary>
/// The color palette for an 4 bit image will have 16 entry's with 4 bytes for each entry.
/// </summary>
private const int ColorPaletteSize4Bit = 64;
/// <summary>
/// The color palette for an 1 bit image will have 2 entry's with 4 bytes for each entry.
/// </summary>
private const int ColorPaletteSize1Bit = 8;
/// <summary>
/// Used for allocating memory during processing operations.
/// </summary>
@ -74,7 +84,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
private readonly bool writeV4Header;
/// <summary>
/// The quantizer for reducing the color count for 8-Bit images.
/// The quantizer for reducing the color count for 8-Bit, 4-Bit and 1-Bit images.
/// </summary>
private readonly IQuantizer quantizer;
@ -88,7 +98,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.memoryAllocator = memoryAllocator;
this.bitsPerPixel = options.BitsPerPixel;
this.writeV4Header = options.SupportTransparency;
this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
this.quantizer = options.Quantizer ?? KnownQuantizers.Wu;
}
/// <summary>
@ -107,7 +117,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.configuration = image.GetConfiguration();
ImageMetadata metadata = image.Metadata;
BmpMetadata bmpMetadata = metadata.GetBmpMetadata();
this.bitsPerPixel = this.bitsPerPixel ?? bmpMetadata.BitsPerPixel;
this.bitsPerPixel ??= bmpMetadata.BitsPerPixel;
short bpp = (short)this.bitsPerPixel;
int bytesPerLine = 4 * (((image.Width * bpp) + 31) / 32);
@ -166,7 +176,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp
infoHeader.Compression = BmpCompression.BitFields;
}
int colorPaletteSize = this.bitsPerPixel == BmpBitsPerPixel.Pixel8 ? ColorPaletteSize8Bit : 0;
int colorPaletteSize = 0;
if (this.bitsPerPixel == BmpBitsPerPixel.Pixel8)
{
colorPaletteSize = ColorPaletteSize8Bit;
}
else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel4)
{
colorPaletteSize = ColorPaletteSize4Bit;
}
else if (this.bitsPerPixel == BmpBitsPerPixel.Pixel1)
{
colorPaletteSize = ColorPaletteSize1Bit;
}
var fileHeader = new BmpFileHeader(
type: BmpConstants.TypeMarkers.Bitmap,
@ -224,6 +246,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp
case BmpBitsPerPixel.Pixel8:
this.Write8Bit(stream, image);
break;
case BmpBitsPerPixel.Pixel4:
this.Write4BitColor(stream, image);
break;
case BmpBitsPerPixel.Pixel1:
this.Write1BitColor(stream, image);
break;
}
}
@ -308,7 +338,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Writes an 8 Bit image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
/// Writes an 8 bit image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
@ -332,7 +362,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Writes an 8 Bit color image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
/// Writes an 8 bit color image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
@ -344,16 +374,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration);
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
ReadOnlySpan<TPixel> quantizedColors = quantized.Palette.Span;
var quantizedColorBytes = quantizedColors.Length * 4;
PixelOperations<TPixel>.Instance.ToBgra32(this.configuration, quantizedColors, MemoryMarshal.Cast<byte, Bgra32>(colorPalette.Slice(0, quantizedColorBytes)));
Span<uint> colorPaletteAsUInt = MemoryMarshal.Cast<byte, uint>(colorPalette);
for (int i = 0; i < colorPaletteAsUInt.Length; i++)
{
colorPaletteAsUInt[i] = colorPaletteAsUInt[i] & 0x00FFFFFF; // Padding byte, always 0.
}
stream.Write(colorPalette);
ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span;
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
for (int y = image.Height - 1; y >= 0; y--)
{
@ -368,7 +390,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
/// <summary>
/// Writes an 8 Bit gray image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
/// Writes an 8 bit gray image with a color palette. The color palette has 256 entry's with 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
@ -404,5 +426,136 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
}
}
/// <summary>
/// Writes an 4 bit color image with a color palette. The color palette has 16 entry's with 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write4BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
{
MaxColors = 16
});
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize4Bit, AllocationOptions.Clean);
Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span;
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
ReadOnlySpan<byte> pixelRowSpan = quantized.GetPixelRowSpan(0);
int rowPadding = pixelRowSpan.Length % 2 != 0 ? this.padding - 1 : this.padding;
for (int y = image.Height - 1; y >= 0; y--)
{
pixelRowSpan = quantized.GetPixelRowSpan(y);
int endIdx = pixelRowSpan.Length % 2 == 0 ? pixelRowSpan.Length : pixelRowSpan.Length - 1;
for (int i = 0; i < endIdx; i += 2)
{
stream.WriteByte((byte)((pixelRowSpan[i] << 4) | pixelRowSpan[i + 1]));
}
if (pixelRowSpan.Length % 2 != 0)
{
stream.WriteByte((byte)((pixelRowSpan[pixelRowSpan.Length - 1] << 4) | 0));
}
for (int i = 0; i < rowPadding; i++)
{
stream.WriteByte(0);
}
}
}
/// <summary>
/// Writes a 1 bit image with a color palette. The color palette has 2 entry's with 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="image"> The <see cref="ImageFrame{TPixel}"/> containing pixel data.</param>
private void Write1BitColor<TPixel>(Stream stream, ImageFrame<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(this.configuration, new QuantizerOptions()
{
MaxColors = 2
});
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize1Bit, AllocationOptions.Clean);
Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span;
this.WriteColorPalette(stream, quantizedColorPalette, colorPalette);
ReadOnlySpan<byte> quantizedPixelRow = quantized.GetPixelRowSpan(0);
int rowPadding = quantizedPixelRow.Length % 8 != 0 ? this.padding - 1 : this.padding;
for (int y = image.Height - 1; y >= 0; y--)
{
quantizedPixelRow = quantized.GetPixelRowSpan(y);
int endIdx = quantizedPixelRow.Length % 8 == 0 ? quantizedPixelRow.Length : quantizedPixelRow.Length - 8;
for (int i = 0; i < endIdx; i += 8)
{
Write1BitPalette(stream, i, i + 8, quantizedPixelRow);
}
if (quantizedPixelRow.Length % 8 != 0)
{
int startIdx = quantizedPixelRow.Length - 7;
endIdx = quantizedPixelRow.Length;
Write1BitPalette(stream, startIdx, endIdx, quantizedPixelRow);
}
for (int i = 0; i < rowPadding; i++)
{
stream.WriteByte(0);
}
}
}
/// <summary>
/// Writes the color palette to the stream. The color palette has 4 bytes for each entry.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="quantizedColorPalette">The color palette from the quantized image.</param>
/// <param name="colorPalette">A temporary byte span to write the color palette to.</param>
private void WriteColorPalette<TPixel>(Stream stream, ReadOnlySpan<TPixel> quantizedColorPalette, Span<byte> colorPalette)
where TPixel : unmanaged, IPixel<TPixel>
{
int quantizedColorBytes = quantizedColorPalette.Length * 4;
PixelOperations<TPixel>.Instance.ToBgra32(this.configuration, quantizedColorPalette, MemoryMarshal.Cast<byte, Bgra32>(colorPalette.Slice(0, quantizedColorBytes)));
Span<uint> colorPaletteAsUInt = MemoryMarshal.Cast<byte, uint>(colorPalette);
for (int i = 0; i < colorPaletteAsUInt.Length; i++)
{
colorPaletteAsUInt[i] = colorPaletteAsUInt[i] & 0x00FFFFFF; // Padding byte, always 0.
}
stream.Write(colorPalette);
}
/// <summary>
/// Writes a 1-bit palette.
/// </summary>
/// <param name="stream">The stream to write the palette to.</param>
/// <param name="startIdx">The start index.</param>
/// <param name="endIdx">The end index.</param>
/// <param name="quantizedPixelRow">A quantized pixel row.</param>
private static void Write1BitPalette(Stream stream, int startIdx, int endIdx, ReadOnlySpan<byte> quantizedPixelRow)
{
int shift = 7;
byte indices = 0;
for (int j = startIdx; j < endIdx; j++)
{
indices = (byte)(indices | ((byte)(quantizedPixelRow[j] & 1) << shift));
shift--;
}
stream.WriteByte(indices);
}
}
}

6
src/ImageSharp/Formats/Bmp/IBmpEncoderOptions.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing.Processors.Quantization;
@ -24,8 +24,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp
bool SupportTransparency { get; }
/// <summary>
/// Gets the quantizer for reducing the color count for 8-Bit images.
/// Gets the quantizer for reducing the color count for 8-Bit, 4-Bit, and 1-Bit images.
/// </summary>
IQuantizer Quantizer { get; }
}
}
}

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

@ -6,14 +6,14 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
// using SixLabors.ImageSharp.Formats.Experimental.Tiff;
using SixLabors.ImageSharp.Formats.Experimental.Webp;
using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Experimental.Webp;
using SixLabors.ImageSharp.Formats.Tiff;
namespace SixLabors.ImageSharp
{
@ -538,7 +538,7 @@ namespace SixLabors.ImageSharp
cancellationToken);
/// <summary>
/// EXPERIMENTAL! Saves the image to the given stream with the Webp format.
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
@ -546,7 +546,7 @@ namespace SixLabors.ImageSharp
public static void SaveAsWebp(this Image source, string path) => SaveAsWebp(source, path, null);
/// <summary>
/// EXPERIMENTAL! Saves the image to the given stream with the Webp format.
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
@ -555,7 +555,7 @@ namespace SixLabors.ImageSharp
public static Task SaveAsWebpAsync(this Image source, string path) => SaveAsWebpAsync(source, path, null);
/// <summary>
/// EXPERIMENTAL! Saves the image to the given stream with the Webp format.
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
@ -566,7 +566,7 @@ namespace SixLabors.ImageSharp
=> SaveAsWebpAsync(source, path, null, cancellationToken);
/// <summary>
/// EXPERIMENTAL! Saves the image to the given stream with the Webp format.
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
@ -578,7 +578,7 @@ namespace SixLabors.ImageSharp
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance));
/// <summary>
/// EXPERIMENTAL! Saves the image to the given stream with the Webp format.
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
@ -593,7 +593,7 @@ namespace SixLabors.ImageSharp
cancellationToken);
/// <summary>
/// EXPERIMENTAL! Saves the image to the given stream with the Webp format.
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
@ -602,7 +602,7 @@ namespace SixLabors.ImageSharp
=> SaveAsWebp(source, stream, null);
/// <summary>
/// EXPERIMENTAL! Saves the image to the given stream with the Webp format.
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
@ -613,7 +613,7 @@ namespace SixLabors.ImageSharp
=> SaveAsWebpAsync(source, stream, null, cancellationToken);
/// <summary>
/// EXPERIMENTAL! Saves the image to the given stream with the Webp format.
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
@ -626,7 +626,7 @@ namespace SixLabors.ImageSharp
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance));
/// <summary>
/// EXPERIMENTAL! Saves the image to the given stream with the Webp format.
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
@ -640,5 +640,108 @@ namespace SixLabors.ImageSharp
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance),
cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsTiff(this Image source, string path) => SaveAsTiff(source, path, null);
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsTiffAsync(this Image source, string path) => SaveAsTiffAsync(source, path, null);
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsTiffAsync(this Image source, string path, CancellationToken cancellationToken)
=> SaveAsTiffAsync(source, path, null, cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsTiff(this Image source, string path, TiffEncoder encoder) =>
source.Save(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsTiffAsync(this Image source, string path, TiffEncoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance),
cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsTiff(this Image source, Stream stream)
=> SaveAsTiff(source, stream, null);
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsTiffAsync(this Image source, Stream stream, CancellationToken cancellationToken = default)
=> SaveAsTiffAsync(source, stream, null, cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static void SaveAsTiff(this Image source, Stream stream, TiffEncoder encoder)
=> source.Save(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the Tiff format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsTiffAsync(this Image source, Stream stream, TiffEncoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TiffFormat.Instance),
cancellationToken);
}
}

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

@ -9,8 +9,6 @@ using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
// using SixLabors.ImageSharp.Formats.Experimental.Tiff;
using SixLabors.ImageSharp.Formats.Experimental.Webp;
<#
var formats = new []{
@ -19,15 +17,12 @@ using SixLabors.ImageSharp.Formats.Experimental.Webp;
"Jpeg",
"Png",
"Tga",
"Webp"
"Webp",
"Tiff",
};
foreach (string fmt in formats)
{
if (fmt == "Tiff" || fmt == "Webp")
{
continue;
}
#>
using SixLabors.ImageSharp.Formats.<#= fmt #>;
<#
@ -45,10 +40,9 @@ namespace SixLabors.ImageSharp
<#
foreach (string fmt in formats)
{
string experimentalString = fmt == "Tiff" || fmt == "Webp" ? @"EXPERIMENTAL! " : "";
#>
/// <summary>
/// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
/// Saves the image to the given stream with the <#= fmt #> format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
@ -56,7 +50,7 @@ namespace SixLabors.ImageSharp
public static void SaveAs<#= fmt #>(this Image source, string path) => SaveAs<#= fmt #>(source, path, null);
/// <summary>
/// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
/// Saves the image to the given stream with the <#= fmt #> format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
@ -65,7 +59,7 @@ namespace SixLabors.ImageSharp
public static Task SaveAs<#= fmt #>Async(this Image source, string path) => SaveAs<#= fmt #>Async(source, path, null);
/// <summary>
/// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
/// Saves the image to the given stream with the <#= fmt #> format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
@ -76,7 +70,7 @@ namespace SixLabors.ImageSharp
=> SaveAs<#= fmt #>Async(source, path, null, cancellationToken);
/// <summary>
/// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
/// Saves the image to the given stream with the <#= fmt #> format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
@ -88,7 +82,7 @@ namespace SixLabors.ImageSharp
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance));
/// <summary>
/// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
/// Saves the image to the given stream with the <#= fmt #> format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
@ -103,7 +97,7 @@ namespace SixLabors.ImageSharp
cancellationToken);
/// <summary>
/// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
/// Saves the image to the given stream with the <#= fmt #> format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
@ -112,7 +106,7 @@ namespace SixLabors.ImageSharp
=> SaveAs<#= fmt #>(source, stream, null);
/// <summary>
/// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
/// Saves the image to the given stream with the <#= fmt #> format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
@ -123,7 +117,7 @@ namespace SixLabors.ImageSharp
=> SaveAs<#= fmt #>Async(source, stream, null, cancellationToken);
/// <summary>
/// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
/// Saves the image to the given stream with the <#= fmt #> format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
@ -136,7 +130,7 @@ namespace SixLabors.ImageSharp
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(<#= fmt #>Format.Instance));
/// <summary>
/// <#= experimentalString #>Saves the image to the given stream with the <#= fmt #> format.
/// Saves the image to the given stream with the <#= fmt #> format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>

78
src/ImageSharp/Formats/Png/Filters/AverageFilter.cs

@ -5,6 +5,11 @@ using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
namespace SixLabors.ImageSharp.Formats.Png.Filters
{
/// <summary>
@ -79,6 +84,79 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
sum += Numerics.Abs(unchecked((sbyte)res));
}
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported)
{
Vector256<byte> zero = Vector256<byte>.Zero;
Vector256<int> sumAccumulator = Vector256<int>.Zero;
Vector256<byte> allBitsSet = Avx2.CompareEqual(sumAccumulator, sumAccumulator).AsByte();
for (int xLeft = x - bytesPerPixel; x + Vector256<byte>.Count <= scanline.Length; xLeft += Vector256<byte>.Count)
{
Vector256<byte> scan = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, x));
Vector256<byte> left = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft));
Vector256<byte> above = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref prevBaseRef, x));
Vector256<byte> avg = Avx2.Xor(Avx2.Average(Avx2.Xor(left, allBitsSet), Avx2.Xor(above, allBitsSet)), allBitsSet);
Vector256<byte> res = Avx2.Subtract(scan, avg);
Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
x += Vector256<byte>.Count;
sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32());
}
sum += Numerics.EvenReduceSum(sumAccumulator);
}
else if (Sse2.IsSupported)
{
Vector128<sbyte> zero8 = Vector128<sbyte>.Zero;
Vector128<short> zero16 = Vector128<short>.Zero;
Vector128<int> sumAccumulator = Vector128<int>.Zero;
Vector128<byte> allBitsSet = Sse2.CompareEqual(sumAccumulator, sumAccumulator).AsByte();
for (int xLeft = x - bytesPerPixel; x + Vector128<byte>.Count <= scanline.Length; xLeft += Vector128<byte>.Count)
{
Vector128<byte> scan = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref scanBaseRef, x));
Vector128<byte> left = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft));
Vector128<byte> above = Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref prevBaseRef, x));
Vector128<byte> avg = Sse2.Xor(Sse2.Average(Sse2.Xor(left, allBitsSet), Sse2.Xor(above, allBitsSet)), allBitsSet);
Vector128<byte> res = Sse2.Subtract(scan, avg);
Unsafe.As<byte, Vector128<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
x += Vector128<byte>.Count;
Vector128<sbyte> absRes;
if (Ssse3.IsSupported)
{
absRes = Ssse3.Abs(res.AsSByte()).AsSByte();
}
else
{
Vector128<sbyte> mask = Sse2.CompareGreaterThan(res.AsSByte(), zero8);
mask = Sse2.Xor(mask, allBitsSet.AsSByte());
absRes = Sse2.Xor(Sse2.Add(res.AsSByte(), mask), mask);
}
Vector128<short> loRes16 = Sse2.UnpackLow(absRes, zero8).AsInt16();
Vector128<short> hiRes16 = Sse2.UnpackHigh(absRes, zero8).AsInt16();
Vector128<int> loRes32 = Sse2.UnpackLow(loRes16, zero16).AsInt32();
Vector128<int> hiRes32 = Sse2.UnpackHigh(loRes16, zero16).AsInt32();
sumAccumulator = Sse2.Add(sumAccumulator, loRes32);
sumAccumulator = Sse2.Add(sumAccumulator, hiRes32);
loRes32 = Sse2.UnpackLow(hiRes16, zero16).AsInt32();
hiRes32 = Sse2.UnpackHigh(hiRes16, zero16).AsInt32();
sumAccumulator = Sse2.Add(sumAccumulator, loRes32);
sumAccumulator = Sse2.Add(sumAccumulator, hiRes32);
}
sum += Numerics.ReduceSum(sumAccumulator);
}
#endif
for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);

118
src/ImageSharp/Formats/Png/Filters/PaethFilter.cs

@ -2,9 +2,15 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
namespace SixLabors.ImageSharp.Formats.Png.Filters
{
/// <summary>
@ -82,6 +88,53 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
sum += Numerics.Abs(unchecked((sbyte)res));
}
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported)
{
Vector256<byte> zero = Vector256<byte>.Zero;
Vector256<int> sumAccumulator = Vector256<int>.Zero;
for (int xLeft = x - bytesPerPixel; x + Vector256<byte>.Count <= scanline.Length; xLeft += Vector256<byte>.Count)
{
Vector256<byte> scan = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, x));
Vector256<byte> left = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft));
Vector256<byte> above = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref prevBaseRef, x));
Vector256<byte> upperLeft = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref prevBaseRef, xLeft));
Vector256<byte> res = Avx2.Subtract(scan, PaethPredictor(left, above, upperLeft));
Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
x += Vector256<byte>.Count;
sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32());
}
sum += Numerics.EvenReduceSum(sumAccumulator);
}
else if (Vector.IsHardwareAccelerated)
{
Vector<uint> sumAccumulator = Vector<uint>.Zero;
for (int xLeft = x - bytesPerPixel; x + Vector<byte>.Count <= scanline.Length; xLeft += Vector<byte>.Count)
{
Vector<byte> scan = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref scanBaseRef, x));
Vector<byte> left = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft));
Vector<byte> above = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref prevBaseRef, x));
Vector<byte> upperLeft = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref prevBaseRef, xLeft));
Vector<byte> res = scan - PaethPredictor(left, above, upperLeft);
Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
x += Vector<byte>.Count;
Numerics.Accumulate(ref sumAccumulator, Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(res))));
}
for (int i = 0; i < Vector<uint>.Count; i++)
{
sum += (int)sumAccumulator[i];
}
}
#endif
for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
@ -127,5 +180,70 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
return upperLeft;
}
#if SUPPORTS_RUNTIME_INTRINSICS
private static Vector256<byte> PaethPredictor(Vector256<byte> left, Vector256<byte> above, Vector256<byte> upleft)
{
Vector256<byte> zero = Vector256<byte>.Zero;
// Here, we refactor pa = abs(p - left) = abs(left + above - upleft - left)
// to pa = abs(above - upleft). Same deal for pb.
// Using saturated subtraction, if the result is negative, the output is zero.
// If we subtract in both directions and `or` the results, only one can be
// non-zero, so we end up with the absolute value.
Vector256<byte> sac = Avx2.SubtractSaturate(above, upleft);
Vector256<byte> sbc = Avx2.SubtractSaturate(left, upleft);
Vector256<byte> pa = Avx2.Or(Avx2.SubtractSaturate(upleft, above), sac);
Vector256<byte> pb = Avx2.Or(Avx2.SubtractSaturate(upleft, left), sbc);
// pc = abs(left + above - upleft - upleft), or abs(left - upleft + above - upleft).
// We've already calculated left - upleft and above - upleft in `sac` and `sbc`.
// If they are both negative or both positive, the absolute value of their
// sum can't possibly be less than `pa` or `pb`, so we'll never use the value.
// We make a mask that sets the value to 255 if they either both got
// saturated to zero or both didn't. Then we calculate the absolute value
// of their difference using saturated subtract and `or`, same as before,
// keeping the value only where the mask isn't set.
Vector256<byte> pm = Avx2.CompareEqual(Avx2.CompareEqual(sac, zero), Avx2.CompareEqual(sbc, zero));
Vector256<byte> pc = Avx2.Or(pm, Avx2.Or(Avx2.SubtractSaturate(pb, pa), Avx2.SubtractSaturate(pa, pb)));
// Finally, blend the values together. We start with `upleft` and overwrite on
// tied values so that the `left`, `above`, `upleft` precedence is preserved.
Vector256<byte> minbc = Avx2.Min(pc, pb);
Vector256<byte> resbc = Avx2.BlendVariable(upleft, above, Avx2.CompareEqual(minbc, pb));
return Avx2.BlendVariable(resbc, left, Avx2.CompareEqual(Avx2.Min(minbc, pa), pa));
}
private static Vector<byte> PaethPredictor(Vector<byte> left, Vector<byte> above, Vector<byte> upperLeft)
{
Vector.Widen(left, out Vector<ushort> a1, out Vector<ushort> a2);
Vector.Widen(above, out Vector<ushort> b1, out Vector<ushort> b2);
Vector.Widen(upperLeft, out Vector<ushort> c1, out Vector<ushort> c2);
Vector<short> p1 = PaethPredictor(Vector.AsVectorInt16(a1), Vector.AsVectorInt16(b1), Vector.AsVectorInt16(c1));
Vector<short> p2 = PaethPredictor(Vector.AsVectorInt16(a2), Vector.AsVectorInt16(b2), Vector.AsVectorInt16(c2));
return Vector.AsVectorByte(Vector.Narrow(p1, p2));
}
private static Vector<short> PaethPredictor(Vector<short> left, Vector<short> above, Vector<short> upperLeft)
{
Vector<short> p = left + above - upperLeft;
var pa = Vector.Abs(p - left);
var pb = Vector.Abs(p - above);
var pc = Vector.Abs(p - upperLeft);
var pa_pb = Vector.LessThanOrEqual(pa, pb);
var pa_pc = Vector.LessThanOrEqual(pa, pc);
var pb_pc = Vector.LessThanOrEqual(pb, pc);
return Vector.ConditionalSelect(
condition: Vector.BitwiseAnd(pa_pb, pa_pc),
left: left,
right: Vector.ConditionalSelect(
condition: pb_pc,
left: above,
right: upperLeft));
}
#endif
}
}

49
src/ImageSharp/Formats/Png/Filters/SubFilter.cs

@ -2,9 +2,15 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
namespace SixLabors.ImageSharp.Formats.Png.Filters
{
/// <summary>
@ -64,6 +70,49 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
sum += Numerics.Abs(unchecked((sbyte)res));
}
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported)
{
Vector256<byte> zero = Vector256<byte>.Zero;
Vector256<int> sumAccumulator = Vector256<int>.Zero;
for (int xLeft = x - bytesPerPixel; x + Vector256<byte>.Count <= scanline.Length; xLeft += Vector256<byte>.Count)
{
Vector256<byte> scan = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, x));
Vector256<byte> prev = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft));
Vector256<byte> res = Avx2.Subtract(scan, prev);
Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
x += Vector256<byte>.Count;
sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32());
}
sum += Numerics.EvenReduceSum(sumAccumulator);
}
else if (Vector.IsHardwareAccelerated)
{
Vector<uint> sumAccumulator = Vector<uint>.Zero;
for (int xLeft = x - bytesPerPixel; x + Vector<byte>.Count <= scanline.Length; xLeft += Vector<byte>.Count)
{
Vector<byte> scan = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref scanBaseRef, x));
Vector<byte> prev = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref scanBaseRef, xLeft));
Vector<byte> res = scan - prev;
Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
x += Vector<byte>.Count;
Numerics.Accumulate(ref sumAccumulator, Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(res))));
}
for (int i = 0; i < Vector<uint>.Count; i++)
{
sum += (int)sumAccumulator[i];
}
}
#endif
for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);

55
src/ImageSharp/Formats/Png/Filters/UpFilter.cs

@ -1,10 +1,16 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
namespace SixLabors.ImageSharp.Formats.Png.Filters
{
/// <summary>
@ -57,7 +63,52 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
// Up(x) = Raw(x) - Prior(x)
resultBaseRef = 2;
for (int x = 0; x < scanline.Length; /* Note: ++x happens in the body to avoid one add operation */)
int x = 0;
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported)
{
Vector256<byte> zero = Vector256<byte>.Zero;
Vector256<int> sumAccumulator = Vector256<int>.Zero;
for (; x + Vector256<byte>.Count <= scanline.Length;)
{
Vector256<byte> scan = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref scanBaseRef, x));
Vector256<byte> above = Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref prevBaseRef, x));
Vector256<byte> res = Avx2.Subtract(scan, above);
Unsafe.As<byte, Vector256<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
x += Vector256<byte>.Count;
sumAccumulator = Avx2.Add(sumAccumulator, Avx2.SumAbsoluteDifferences(Avx2.Abs(res.AsSByte()), zero).AsInt32());
}
sum += Numerics.EvenReduceSum(sumAccumulator);
}
else if (Vector.IsHardwareAccelerated)
{
Vector<uint> sumAccumulator = Vector<uint>.Zero;
for (; x + Vector<byte>.Count <= scanline.Length;)
{
Vector<byte> scan = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref scanBaseRef, x));
Vector<byte> above = Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref prevBaseRef, x));
Vector<byte> res = scan - above;
Unsafe.As<byte, Vector<byte>>(ref Unsafe.Add(ref resultBaseRef, x + 1)) = res; // +1 to skip filter type
x += Vector<byte>.Count;
Numerics.Accumulate(ref sumAccumulator, Vector.AsVectorByte(Vector.Abs(Vector.AsVectorSByte(res))));
}
for (int i = 0; i < Vector<uint>.Count; i++)
{
sum += (int)sumAccumulator[i];
}
}
#endif
for (; x < scanline.Length; /* Note: ++x happens in the body to avoid one add operation */)
{
byte scan = Unsafe.Add(ref scanBaseRef, x);
byte above = Unsafe.Add(ref prevBaseRef, x);

3
src/ImageSharp/Formats/Png/PngCompressionLevel.cs

@ -1,11 +1,14 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.ComponentModel;
namespace SixLabors.ImageSharp.Formats.Png
{
/// <summary>
/// Provides enumeration of available PNG compression levels.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public enum PngCompressionLevel
{
/// <summary>

3
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -10,10 +10,9 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Png.Chunks;
using SixLabors.ImageSharp.Formats.Png.Filters;
using SixLabors.ImageSharp.Formats.Png.Zlib;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;

4
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -8,11 +8,9 @@ using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Png.Chunks;
using SixLabors.ImageSharp.Formats.Png.Filters;
using SixLabors.ImageSharp.Formats.Png.Zlib;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;

2
src/ImageSharp/Formats/README.md → src/ImageSharp/Formats/Tga/README.md

@ -1,4 +1,4 @@
# Encoder/Decoder for true vision targa files
# Encoder/Decoder for true vision targa files
Useful links for reference:

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

@ -0,0 +1,51 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression
{
internal static class BitWriterUtils
{
public static void WriteBits(Span<byte> buffer, int pos, uint count, byte value)
{
int bitPos = pos % 8;
int bufferPos = pos / 8;
int startIdx = bufferPos + bitPos;
int endIdx = (int)(startIdx + count);
if (value == 1)
{
for (int i = startIdx; i < endIdx; i++)
{
WriteBit(buffer, bufferPos, bitPos);
bitPos++;
if (bitPos >= 8)
{
bitPos = 0;
bufferPos++;
}
}
}
else
{
for (int i = startIdx; i < endIdx; i++)
{
WriteZeroBit(buffer, bufferPos, bitPos);
bitPos++;
if (bitPos >= 8)
{
bitPos = 0;
bufferPos++;
}
}
}
}
public static void WriteBit(Span<byte> buffer, int bufferPos, int bitPos) => buffer[bufferPos] |= (byte)(1 << (7 - bitPos));
public static void WriteZeroBit(Span<byte> buffer, int bufferPos, int bitPos) => buffer[bufferPos] = (byte)(buffer[bufferPos] & ~(1 << (7 - bitPos)));
}
}

62
src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs

@ -0,0 +1,62 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
{
internal sealed class DeflateCompressor : TiffBaseCompressor
{
private readonly DeflateCompressionLevel compressionLevel;
private readonly MemoryStream memoryStream = new MemoryStream();
public DeflateCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor, DeflateCompressionLevel compressionLevel)
: base(output, allocator, width, bitsPerPixel, predictor)
=> this.compressionLevel = compressionLevel;
/// <inheritdoc/>
public override TiffCompression Method => TiffCompression.Deflate;
/// <inheritdoc/>
public override void Initialize(int rowsPerStrip)
{
}
/// <inheritdoc/>
public override void CompressStrip(Span<byte> rows, int height)
{
this.memoryStream.Seek(0, SeekOrigin.Begin);
using (var stream = new ZlibDeflateStream(this.Allocator, this.memoryStream, this.compressionLevel))
{
if (this.Predictor == TiffPredictor.Horizontal)
{
HorizontalPredictor.ApplyHorizontalPrediction(rows, this.BytesPerRow, this.BitsPerPixel);
}
stream.Write(rows);
stream.Flush();
}
int size = (int)this.memoryStream.Position;
#if !NETSTANDARD1_3
byte[] buffer = this.memoryStream.GetBuffer();
this.Output.Write(buffer, 0, size);
#else
this.memoryStream.SetLength(size);
this.memoryStream.Position = 0;
this.memoryStream.CopyTo(this.Output);
#endif
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
}
}

40
src/ImageSharp/Formats/Tiff/Compression/Compressors/LzwCompressor.cs

@ -0,0 +1,40 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
{
internal sealed class LzwCompressor : TiffBaseCompressor
{
private TiffLzwEncoder lzwEncoder;
public LzwCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor)
: base(output, allocator, width, bitsPerPixel, predictor)
{
}
/// <inheritdoc/>
public override TiffCompression Method => TiffCompression.Lzw;
/// <inheritdoc/>
public override void Initialize(int rowsPerStrip) => this.lzwEncoder = new TiffLzwEncoder(this.Allocator);
/// <inheritdoc/>
public override void CompressStrip(Span<byte> rows, int height)
{
if (this.Predictor == TiffPredictor.Horizontal)
{
HorizontalPredictor.ApplyHorizontalPrediction(rows, this.BytesPerRow, this.BitsPerPixel);
}
this.lzwEncoder.Encode(rows, this.Output);
}
/// <inheritdoc/>
protected override void Dispose(bool disposing) => this.lzwEncoder?.Dispose();
}
}

34
src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs

@ -0,0 +1,34 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
{
internal sealed class NoCompressor : TiffBaseCompressor
{
public NoCompressor(Stream output, MemoryAllocator memoryAllocator, int width, int bitsPerPixel)
: base(output, memoryAllocator, width, bitsPerPixel)
{
}
/// <inheritdoc/>
public override TiffCompression Method => TiffCompression.None;
/// <inheritdoc/>
public override void Initialize(int rowsPerStrip)
{
}
/// <inheritdoc/>
public override void CompressStrip(Span<byte> rows, int height) => this.Output.Write(rows);
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
}
}

48
src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsCompressor.cs

@ -0,0 +1,48 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
{
internal sealed class PackBitsCompressor : TiffBaseCompressor
{
private IManagedByteBuffer pixelData;
public PackBitsCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel)
: base(output, allocator, width, bitsPerPixel)
{
}
/// <inheritdoc/>
public override TiffCompression Method => TiffCompression.PackBits;
/// <inheritdoc/>
public override void Initialize(int rowsPerStrip)
{
int additionalBytes = ((this.BytesPerRow + 126) / 127) + 1;
this.pixelData = this.Allocator.AllocateManagedByteBuffer(this.BytesPerRow + additionalBytes);
}
/// <inheritdoc/>
public override void CompressStrip(Span<byte> rows, int height)
{
DebugGuard.IsTrue(rows.Length % height == 0, "Invalid height");
DebugGuard.IsTrue(this.BytesPerRow == rows.Length / height, "The widths must match");
Span<byte> span = this.pixelData.GetSpan();
for (int i = 0; i < height; i++)
{
Span<byte> row = rows.Slice(i * this.BytesPerRow, this.BytesPerRow);
int size = PackBitsWriter.PackBits(row, span);
this.Output.Write(span.Slice(0, size));
}
}
/// <inheritdoc/>
protected override void Dispose(bool disposing) => this.pixelData?.Dispose();
}
}

128
src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsWriter.cs

@ -0,0 +1,128 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
{
/// <summary>
/// Pack Bits compression for tiff images. See Tiff Spec v6, section 9.
/// </summary>
internal static class PackBitsWriter
{
public static int PackBits(ReadOnlySpan<byte> rowSpan, Span<byte> compressedRowSpan)
{
int maxRunLength = 127;
int posInRowSpan = 0;
int bytesWritten = 0;
int literalRunLength = 0;
while (posInRowSpan < rowSpan.Length)
{
bool useReplicateRun = IsReplicateRun(rowSpan, posInRowSpan);
if (useReplicateRun)
{
if (literalRunLength > 0)
{
WriteLiteralRun(rowSpan, posInRowSpan, literalRunLength, compressedRowSpan, bytesWritten);
bytesWritten += literalRunLength + 1;
}
// Write a run with the same bytes.
int runLength = FindRunLength(rowSpan, posInRowSpan, maxRunLength);
WriteRun(rowSpan, posInRowSpan, runLength, compressedRowSpan, bytesWritten);
bytesWritten += 2;
literalRunLength = 0;
posInRowSpan += runLength;
continue;
}
literalRunLength++;
posInRowSpan++;
if (literalRunLength >= maxRunLength)
{
WriteLiteralRun(rowSpan, posInRowSpan, literalRunLength, compressedRowSpan, bytesWritten);
bytesWritten += literalRunLength + 1;
literalRunLength = 0;
}
}
if (literalRunLength > 0)
{
WriteLiteralRun(rowSpan, posInRowSpan, literalRunLength, compressedRowSpan, bytesWritten);
bytesWritten += literalRunLength + 1;
}
return bytesWritten;
}
private static void WriteLiteralRun(ReadOnlySpan<byte> rowSpan, int end, int literalRunLength, Span<byte> compressedRowSpan, int compressedRowPos)
{
DebugGuard.MustBeLessThanOrEqualTo(literalRunLength, 127, nameof(literalRunLength));
int literalRunStart = end - literalRunLength;
sbyte runLength = (sbyte)(literalRunLength - 1);
compressedRowSpan[compressedRowPos] = (byte)runLength;
rowSpan.Slice(literalRunStart, literalRunLength).CopyTo(compressedRowSpan.Slice(compressedRowPos + 1));
}
private static void WriteRun(ReadOnlySpan<byte> rowSpan, int start, int runLength, Span<byte> compressedRowSpan, int compressedRowPos)
{
DebugGuard.MustBeLessThanOrEqualTo(runLength, 127, nameof(runLength));
sbyte headerByte = (sbyte)(-runLength + 1);
compressedRowSpan[compressedRowPos] = (byte)headerByte;
compressedRowSpan[compressedRowPos + 1] = rowSpan[start];
}
private static bool IsReplicateRun(ReadOnlySpan<byte> rowSpan, int startPos)
{
// We consider run which has at least 3 same consecutive bytes a candidate for a run.
var startByte = rowSpan[startPos];
int count = 0;
for (int i = startPos + 1; i < rowSpan.Length; i++)
{
if (rowSpan[i] == startByte)
{
count++;
if (count >= 2)
{
return true;
}
}
else
{
break;
}
}
return false;
}
private static int FindRunLength(ReadOnlySpan<byte> rowSpan, int startPos, int maxRunLength)
{
var startByte = rowSpan[startPos];
int count = 1;
for (int i = startPos + 1; i < rowSpan.Length; i++)
{
if (rowSpan[i] == startByte)
{
count++;
}
else
{
break;
}
if (count == maxRunLength)
{
break;
}
}
return count;
}
}
}

592
src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs

@ -0,0 +1,592 @@
// 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 SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
{
/// <summary>
/// Bitwriter for writing compressed CCITT T4 1D data.
/// </summary>
internal sealed class T4BitCompressor : TiffBaseCompressor
{
private const uint WhiteZeroRunTermCode = 0x35;
private const uint BlackZeroRunTermCode = 0x37;
private static readonly uint[] MakeupRunLength =
{
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>()
{
{ 2, 0x7 }, { 3, 0x8 }, { 4, 0xB }, { 5, 0xC }, { 6, 0xE }, { 7, 0xF }
};
private static readonly Dictionary<uint, uint> WhiteLen5TermCodes = new Dictionary<uint, uint>()
{
{ 8, 0x13 }, { 9, 0x14 }, { 10, 0x7 }, { 11, 0x8 }
};
private static readonly Dictionary<uint, uint> WhiteLen6TermCodes = new Dictionary<uint, uint>()
{
{ 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>()
{
{ 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>()
{
{ 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 },
{ 45, 0x4 }, { 46, 0x5 }, { 47, 0xA }, { 48, 0xB }, { 49, 0x52 }, { 50, 0x53 }, { 51, 0x54 }, { 52, 0x55 }, { 53, 0x24 },
{ 54, 0x25 }, { 55, 0x58 }, { 56, 0x59 }, { 57, 0x5A }, { 58, 0x5B }, { 59, 0x4A }, { 60, 0x4B }, { 61, 0x32 }, { 62, 0x33 },
{ 63, 0x34 }
};
private static readonly Dictionary<uint, uint> BlackLen2TermCodes = new Dictionary<uint, uint>()
{
{ 2, 0x3 }, { 3, 0x2 }
};
private static readonly Dictionary<uint, uint> BlackLen3TermCodes = new Dictionary<uint, uint>()
{
{ 1, 0x2 }, { 4, 0x3 }
};
private static readonly Dictionary<uint, uint> BlackLen4TermCodes = new Dictionary<uint, uint>()
{
{ 5, 0x3 }, { 6, 0x2 }
};
private static readonly Dictionary<uint, uint> BlackLen5TermCodes = new Dictionary<uint, uint>()
{
{ 7, 0x3 }
};
private static readonly Dictionary<uint, uint> BlackLen6TermCodes = new Dictionary<uint, uint>()
{
{ 8, 0x5 }, { 9, 0x4 }
};
private static readonly Dictionary<uint, uint> BlackLen7TermCodes = new Dictionary<uint, uint>()
{
{ 10, 0x4 }, { 11, 0x5 }, { 12, 0x7 }
};
private static readonly Dictionary<uint, uint> BlackLen8TermCodes = new Dictionary<uint, uint>()
{
{ 13, 0x4 }, { 14, 0x7 }
};
private static readonly Dictionary<uint, uint> BlackLen9TermCodes = new Dictionary<uint, uint>()
{
{ 15, 0x18 }
};
private static readonly Dictionary<uint, uint> BlackLen10TermCodes = new Dictionary<uint, uint>()
{
{ 0, BlackZeroRunTermCode }, { 16, 0x17 }, { 17, 0x18 }, { 18, 0x8 }
};
private static readonly Dictionary<uint, uint> BlackLen11TermCodes = new Dictionary<uint, uint>()
{
{ 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>()
{
{ 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 },
{ 44, 0x54 }, { 45, 0x55 }, { 46, 0x56 }, { 47, 0x57 }, { 48, 0x64 }, { 49, 0x65 }, { 50, 0x52 }, { 51, 0x53 }, { 52, 0x24 },
{ 53, 0x37 }, { 54, 0x38 }, { 55, 0x27 }, { 56, 0x28 }, { 57, 0x58 }, { 58, 0x59 }, { 59, 0x2B }, { 60, 0x2C }, { 61, 0x5A },
{ 62, 0x66 }, { 63, 0x67 }
};
private static readonly Dictionary<uint, uint> WhiteLen5MakeupCodes = new Dictionary<uint, uint>()
{
{ 64, 0x1B }, { 128, 0x12 }
};
private static readonly Dictionary<uint, uint> WhiteLen6MakeupCodes = new Dictionary<uint, uint>()
{
{ 192, 0x17 }, { 1664, 0x18 }
};
private static readonly Dictionary<uint, uint> WhiteLen8MakeupCodes = new Dictionary<uint, uint>()
{
{ 320, 0x36 }, { 384, 0x37 }, { 448, 0x64 }, { 512, 0x65 }, { 576, 0x68 }, { 640, 0x67 }
};
private static readonly Dictionary<uint, uint> WhiteLen7MakeupCodes = new Dictionary<uint, uint>()
{
{ 256, 0x37 }
};
private static readonly Dictionary<uint, uint> WhiteLen9MakeupCodes = new Dictionary<uint, uint>()
{
{ 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>()
{
{ 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD }
};
private static readonly Dictionary<uint, uint> WhiteLen12MakeupCodes = new Dictionary<uint, uint>()
{
{ 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>()
{
{ 64, 0xF }
};
private static readonly Dictionary<uint, uint> BlackLen11MakeupCodes = new Dictionary<uint, uint>()
{
{ 1792, 0x8 }, { 1856, 0xC }, { 1920, 0xD }
};
private static readonly Dictionary<uint, uint> BlackLen12MakeupCodes = new Dictionary<uint, uint>()
{
{ 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>()
{
{ 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 },
{ 1408, 0x54 }, { 1472, 0x55 }, { 1536, 0x5A }, { 1600, 0x5B }, { 1664, 0x64 }, { 1728, 0x65 }
};
/// <summary>
/// The modified huffman is basically the same as CCITT T4, but without EOL markers and padding at the end of the rows.
/// </summary>
private readonly bool useModifiedHuffman;
private IMemoryOwner<byte> compressedDataBuffer;
private int bytePosition;
private byte bitPosition;
/// <summary>
/// Initializes a new instance of the <see cref="T4BitCompressor" /> class.
/// </summary>
/// <param name="output">The output.</param>
/// <param name="allocator">The allocator.</param>
/// <param name="width">The width.</param>
/// <param name="bitsPerPixel">The bits per pixel.</param>
/// <param name="useModifiedHuffman">Indicates if the modified huffman RLE should be used.</param>
public T4BitCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, bool useModifiedHuffman = false)
: base(output, allocator, width, bitsPerPixel)
{
this.bytePosition = 0;
this.bitPosition = 0;
this.useModifiedHuffman = useModifiedHuffman;
}
/// <inheritdoc/>
public override TiffCompression Method => this.useModifiedHuffman ? TiffCompression.Ccitt1D : TiffCompression.CcittGroup3Fax;
/// <inheritdoc/>
public override void Initialize(int rowsPerStrip)
{
// This is too much memory allocated, but just 1 bit per pixel will not do, if the compression rate is not good.
int maxNeededBytes = this.Width * rowsPerStrip;
this.compressedDataBuffer = this.Allocator.Allocate<byte>(maxNeededBytes);
}
/// <summary>Writes a image compressed with CCITT T4 to the stream.</summary>
/// <param name="pixelsAsGray">The pixels as 8-bit gray array.</param>
/// <param name="height">The strip height.</param>
public override void CompressStrip(Span<byte> pixelsAsGray, int height)
{
DebugGuard.IsTrue(pixelsAsGray.Length / height == this.Width, "Values must be equals");
DebugGuard.IsTrue(pixelsAsGray.Length % height == 0, "Values must be equals");
this.compressedDataBuffer.Clear();
Span<byte> compressedData = this.compressedDataBuffer.GetSpan();
this.bytePosition = 0;
this.bitPosition = 0;
if (!this.useModifiedHuffman)
{
// An EOL code is expected at the start of the data.
this.WriteCode(12, 1, compressedData);
}
for (int y = 0; y < height; y++)
{
bool isWhiteRun = true;
bool isStartOrRow = true;
int x = 0;
Span<byte> row = pixelsAsGray.Slice(y * this.Width, this.Width);
while (x < this.Width)
{
uint runLength = 0;
for (int i = x; i < this.Width; i++)
{
if (isWhiteRun && row[i] != 255)
{
break;
}
if (isWhiteRun && row[i] == 255)
{
runLength++;
continue;
}
if (!isWhiteRun && row[i] != 0)
{
break;
}
if (!isWhiteRun && row[i] == 0)
{
runLength++;
}
}
if (isStartOrRow && runLength == 0)
{
this.WriteCode(8, WhiteZeroRunTermCode, compressedData);
isWhiteRun = false;
isStartOrRow = false;
continue;
}
uint code;
uint codeLength;
if (runLength <= 63)
{
code = this.GetTermCode(runLength, out codeLength, isWhiteRun);
this.WriteCode(codeLength, code, compressedData);
x += (int)runLength;
}
else
{
runLength = this.GetBestFittingMakeupRunLength(runLength);
code = this.GetMakeupCode(runLength, out codeLength, isWhiteRun);
this.WriteCode(codeLength, code, compressedData);
x += (int)runLength;
// If we are at the end of the line with a makeup code, we need to write a final term code with a length of zero.
if (x == this.Width)
{
if (isWhiteRun)
{
this.WriteCode(8, WhiteZeroRunTermCode, compressedData);
}
else
{
this.WriteCode(10, BlackZeroRunTermCode, compressedData);
}
}
continue;
}
isStartOrRow = false;
isWhiteRun = !isWhiteRun;
}
this.WriteEndOfLine(compressedData);
}
// Write the compressed data to the stream.
int bytesToWrite = this.bitPosition != 0 ? this.bytePosition + 1 : this.bytePosition;
this.Output.Write(compressedData.Slice(0, bytesToWrite));
}
protected override void Dispose(bool disposing) => this.compressedDataBuffer?.Dispose();
private void WriteEndOfLine(Span<byte> compressedData)
{
if (this.useModifiedHuffman)
{
// Check if padding is necessary.
if (this.bitPosition % 8 != 0)
{
// Skip padding bits, move to next byte.
this.bytePosition++;
this.bitPosition = 0;
}
}
else
{
// Write EOL.
this.WriteCode(12, 1, compressedData);
}
}
private void WriteCode(uint codeLength, uint code, Span<byte> compressedData)
{
while (codeLength > 0)
{
int bitNumber = (int)codeLength;
bool bit = (code & (1 << (bitNumber - 1))) != 0;
if (bit)
{
BitWriterUtils.WriteBit(compressedData, this.bytePosition, this.bitPosition);
}
else
{
BitWriterUtils.WriteZeroBit(compressedData, this.bytePosition, this.bitPosition);
}
this.bitPosition++;
if (this.bitPosition == 8)
{
this.bytePosition++;
this.bitPosition = 0;
}
codeLength--;
}
}
private uint GetBestFittingMakeupRunLength(uint runLength)
{
for (int i = 0; i < MakeupRunLength.Length - 1; i++)
{
if (MakeupRunLength[i] <= runLength && MakeupRunLength[i + 1] > runLength)
{
return MakeupRunLength[i];
}
}
return MakeupRunLength[MakeupRunLength.Length - 1];
}
private uint GetTermCode(uint runLength, out uint codeLength, bool isWhiteRun)
{
if (isWhiteRun)
{
return this.GetWhiteTermCode(runLength, out codeLength);
}
return this.GetBlackTermCode(runLength, out codeLength);
}
private uint GetMakeupCode(uint runLength, out uint codeLength, bool isWhiteRun)
{
if (isWhiteRun)
{
return this.GetWhiteMakeupCode(runLength, out codeLength);
}
return this.GetBlackMakeupCode(runLength, out codeLength);
}
private uint GetWhiteMakeupCode(uint runLength, out uint codeLength)
{
codeLength = 0;
if (WhiteLen5MakeupCodes.ContainsKey(runLength))
{
codeLength = 5;
return WhiteLen5MakeupCodes[runLength];
}
if (WhiteLen6MakeupCodes.ContainsKey(runLength))
{
codeLength = 6;
return WhiteLen6MakeupCodes[runLength];
}
if (WhiteLen7MakeupCodes.ContainsKey(runLength))
{
codeLength = 7;
return WhiteLen7MakeupCodes[runLength];
}
if (WhiteLen8MakeupCodes.ContainsKey(runLength))
{
codeLength = 8;
return WhiteLen8MakeupCodes[runLength];
}
if (WhiteLen9MakeupCodes.ContainsKey(runLength))
{
codeLength = 9;
return WhiteLen9MakeupCodes[runLength];
}
if (WhiteLen11MakeupCodes.ContainsKey(runLength))
{
codeLength = 11;
return WhiteLen11MakeupCodes[runLength];
}
if (WhiteLen12MakeupCodes.ContainsKey(runLength))
{
codeLength = 12;
return WhiteLen12MakeupCodes[runLength];
}
return 0;
}
private uint GetBlackMakeupCode(uint runLength, out uint codeLength)
{
codeLength = 0;
if (BlackLen10MakeupCodes.ContainsKey(runLength))
{
codeLength = 10;
return BlackLen10MakeupCodes[runLength];
}
if (BlackLen11MakeupCodes.ContainsKey(runLength))
{
codeLength = 11;
return BlackLen11MakeupCodes[runLength];
}
if (BlackLen12MakeupCodes.ContainsKey(runLength))
{
codeLength = 12;
return BlackLen12MakeupCodes[runLength];
}
if (BlackLen13MakeupCodes.ContainsKey(runLength))
{
codeLength = 13;
return BlackLen13MakeupCodes[runLength];
}
return 0;
}
private uint GetWhiteTermCode(uint runLength, out uint codeLength)
{
codeLength = 0;
if (WhiteLen4TermCodes.ContainsKey(runLength))
{
codeLength = 4;
return WhiteLen4TermCodes[runLength];
}
if (WhiteLen5TermCodes.ContainsKey(runLength))
{
codeLength = 5;
return WhiteLen5TermCodes[runLength];
}
if (WhiteLen6TermCodes.ContainsKey(runLength))
{
codeLength = 6;
return WhiteLen6TermCodes[runLength];
}
if (WhiteLen7TermCodes.ContainsKey(runLength))
{
codeLength = 7;
return WhiteLen7TermCodes[runLength];
}
if (WhiteLen8TermCodes.ContainsKey(runLength))
{
codeLength = 8;
return WhiteLen8TermCodes[runLength];
}
return 0;
}
private uint GetBlackTermCode(uint runLength, out uint codeLength)
{
codeLength = 0;
if (BlackLen2TermCodes.ContainsKey(runLength))
{
codeLength = 2;
return BlackLen2TermCodes[runLength];
}
if (BlackLen3TermCodes.ContainsKey(runLength))
{
codeLength = 3;
return BlackLen3TermCodes[runLength];
}
if (BlackLen4TermCodes.ContainsKey(runLength))
{
codeLength = 4;
return BlackLen4TermCodes[runLength];
}
if (BlackLen5TermCodes.ContainsKey(runLength))
{
codeLength = 5;
return BlackLen5TermCodes[runLength];
}
if (BlackLen6TermCodes.ContainsKey(runLength))
{
codeLength = 6;
return BlackLen6TermCodes[runLength];
}
if (BlackLen7TermCodes.ContainsKey(runLength))
{
codeLength = 7;
return BlackLen7TermCodes[runLength];
}
if (BlackLen8TermCodes.ContainsKey(runLength))
{
codeLength = 8;
return BlackLen8TermCodes[runLength];
}
if (BlackLen9TermCodes.ContainsKey(runLength))
{
codeLength = 9;
return BlackLen9TermCodes[runLength];
}
if (BlackLen10TermCodes.ContainsKey(runLength))
{
codeLength = 10;
return BlackLen10TermCodes[runLength];
}
if (BlackLen11TermCodes.ContainsKey(runLength))
{
codeLength = 11;
return BlackLen11TermCodes[runLength];
}
if (BlackLen12TermCodes.ContainsKey(runLength))
{
codeLength = 12;
return BlackLen12TermCodes[runLength];
}
return 0;
}
}
}

270
src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs

@ -0,0 +1,270 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.IO;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
{
/*
This implementation is a port of a java tiff encoder by Harald Kuhr: https://github.com/haraldk/TwelveMonkeys
Original licence:
BSD 3-Clause License
* Copyright (c) 2015, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
** Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/// <summary>
/// Encodes and compresses the image data using dynamic Lempel-Ziv compression.
/// </summary>
/// <remarks>
/// <para>
/// This code is based on the <see cref="LzwEncoder"/> used for GIF encoding. There is potential
/// for a shared implementation. Differences between the GIF and TIFF implementations of the LZW
/// encoding are: (i) The GIF implementation includes an initial 'data size' byte, whilst this is
/// always 8 for TIFF. (ii) The GIF implementation writes a number of sub-blocks with an initial
/// byte indicating the length of the sub-block. In TIFF the data is written as a single block
/// with no length indicator (this can be determined from the 'StripByteCounts' entry).
/// </para>
/// </remarks>
internal sealed class TiffLzwEncoder : IDisposable
{
// Clear: Re-initialize tables.
private static readonly int ClearCode = 256;
// End of Information.
private static readonly int EoiCode = 257;
private static readonly int MinBits = 9;
private static readonly int MaxBits = 12;
private static readonly int TableSize = 1 << MaxBits;
// A child is made up of a parent (or prefix) code plus a suffix byte
// and siblings are strings with a common parent(or prefix) and different suffix bytes.
private readonly IMemoryOwner<int> children;
private readonly IMemoryOwner<int> siblings;
private readonly IMemoryOwner<int> suffixes;
// Initial setup
private int parent;
private int bitsPerCode;
private int nextValidCode;
private int maxCode;
// Buffer for partial codes
private int bits;
private int bitPos;
private int bufferPosition;
/// <summary>
/// Initializes a new instance of the <see cref="TiffLzwEncoder"/> class.
/// </summary>
/// <param name="memoryAllocator">The memory allocator.</param>
public TiffLzwEncoder(MemoryAllocator memoryAllocator)
{
this.children = memoryAllocator.Allocate<int>(TableSize);
this.siblings = memoryAllocator.Allocate<int>(TableSize);
this.suffixes = memoryAllocator.Allocate<int>(TableSize);
}
/// <summary>
/// Encodes and compresses the indexed pixels to the stream.
/// </summary>
/// <param name="data">The data to compress.</param>
/// <param name="stream">The stream to write to.</param>
public void Encode(Span<byte> data, Stream stream)
{
this.Reset();
Span<int> childrenSpan = this.children.GetSpan();
Span<int> suffixesSpan = this.suffixes.GetSpan();
Span<int> siblingsSpan = this.siblings.GetSpan();
int length = data.Length;
if (length == 0)
{
return;
}
if (this.parent == -1)
{
// Init stream.
this.WriteCode(stream, ClearCode);
this.parent = this.ReadNextByte(data);
}
while (this.bufferPosition < data.Length)
{
int value = this.ReadNextByte(data);
int child = childrenSpan[this.parent];
if (child > 0)
{
if (suffixesSpan[child] == value)
{
this.parent = child;
}
else
{
int sibling = child;
while (true)
{
if (siblingsSpan[sibling] > 0)
{
sibling = siblingsSpan[sibling];
if (suffixesSpan[sibling] == value)
{
this.parent = sibling;
break;
}
}
else
{
siblingsSpan[sibling] = (short)this.nextValidCode;
suffixesSpan[this.nextValidCode] = (short)value;
this.WriteCode(stream, this.parent);
this.parent = value;
this.nextValidCode++;
this.IncreaseCodeSizeOrResetIfNeeded(stream);
break;
}
}
}
}
else
{
childrenSpan[this.parent] = (short)this.nextValidCode;
suffixesSpan[this.nextValidCode] = (short)value;
this.WriteCode(stream, this.parent);
this.parent = value;
this.nextValidCode++;
this.IncreaseCodeSizeOrResetIfNeeded(stream);
}
}
// Write EOI when we are done.
this.WriteCode(stream, this.parent);
this.WriteCode(stream, EoiCode);
// Flush partial codes by writing 0 pad.
if (this.bitPos > 0)
{
this.WriteCode(stream, 0);
}
}
/// <inheritdoc />
public void Dispose()
{
this.children.Dispose();
this.siblings.Dispose();
this.suffixes.Dispose();
}
private void Reset()
{
this.children.Clear();
this.siblings.Clear();
this.suffixes.Clear();
this.parent = -1;
this.bitsPerCode = MinBits;
this.nextValidCode = EoiCode + 1;
this.maxCode = (1 << this.bitsPerCode) - 1;
this.bits = 0;
this.bitPos = 0;
this.bufferPosition = 0;
}
private byte ReadNextByte(Span<byte> data) => data[this.bufferPosition++];
private void IncreaseCodeSizeOrResetIfNeeded(Stream stream)
{
if (this.nextValidCode > this.maxCode)
{
if (this.bitsPerCode == MaxBits)
{
// Reset stream by writing Clear code.
this.WriteCode(stream, ClearCode);
// Reset tables.
this.ResetTables();
}
else
{
// Increase code size.
this.bitsPerCode++;
this.maxCode = MaxValue(this.bitsPerCode);
}
}
}
private void WriteCode(Stream stream, int code)
{
this.bits = (this.bits << this.bitsPerCode) | (code & this.maxCode);
this.bitPos += this.bitsPerCode;
while (this.bitPos >= 8)
{
int b = (this.bits >> (this.bitPos - 8)) & 0xff;
stream.WriteByte((byte)b);
this.bitPos -= 8;
}
this.bits &= BitmaskFor(this.bitPos);
}
private void ResetTables()
{
this.children.GetSpan().Fill(0);
this.siblings.GetSpan().Fill(0);
this.bitsPerCode = MinBits;
this.maxCode = MaxValue(this.bitsPerCode);
this.nextValidCode = EoiCode + 1;
}
private static int MaxValue(int codeLen) => (1 << codeLen) - 1;
private static int BitmaskFor(int bits) => MaxValue(bits);
}
}

63
src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs

@ -0,0 +1,63 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO.Compression;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Tiff.Compression;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
/// <summary>
/// Class to handle cases where TIFF image data is compressed using Deflate compression.
/// </summary>
/// <remarks>
/// Note that the 'OldDeflate' compression type is identical to the 'Deflate' compression type.
/// </remarks>
internal class DeflateTiffCompression : TiffBaseDecompressor
{
/// <summary>
/// Initializes a new instance of the <see cref="DeflateTiffCompression" /> class.
/// </summary>
/// <param name="memoryAllocator">The memoryAllocator to use for buffer allocations.</param>
/// <param name="width">The image width.</param>
/// <param name="bitsPerPixel">The bits used per pixel.</param>
/// <param name="predictor">The tiff predictor used.</param>
public DeflateTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor)
: base(memoryAllocator, width, bitsPerPixel, predictor)
{
}
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
{
long pos = stream.Position;
using (var deframeStream = new ZlibInflateStream(
stream,
() =>
{
int left = (int)(byteCount - (stream.Position - pos));
return left > 0 ? left : 0;
}))
{
deframeStream.AllocateNewBytes(byteCount, true);
DeflateStream dataStream = deframeStream.CompressedStream;
dataStream.Read(buffer, 0, buffer.Length);
}
if (this.Predictor == TiffPredictor.Horizontal)
{
HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel);
}
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
}
}

95
src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwString.cs

@ -0,0 +1,95 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
/// <summary>
/// Represents a lzw string with a code word and a code length.
/// </summary>
public class LzwString
{
private static readonly LzwString Empty = new LzwString(0, 0, 0, null);
private readonly LzwString previous;
private readonly byte value;
/// <summary>
/// Initializes a new instance of the <see cref="LzwString"/> class.
/// </summary>
/// <param name="code">The code word.</param>
public LzwString(byte code)
: this(code, code, 1, null)
{
}
private LzwString(byte value, byte firstChar, int length, LzwString previous)
{
this.value = value;
this.FirstChar = firstChar;
this.Length = length;
this.previous = previous;
}
/// <summary>
/// Gets the code length;
/// </summary>
public int Length { get; }
/// <summary>
/// Gets the first character of the codeword.
/// </summary>
public byte FirstChar { get; }
/// <summary>
/// Concatenates two code words.
/// </summary>
/// <param name="other">The code word to concatenate.</param>
/// <returns>A concatenated lzw string.</returns>
public LzwString Concatenate(byte other)
{
if (this == Empty)
{
return new LzwString(other);
}
return new LzwString(other, this.FirstChar, this.Length + 1, this);
}
/// <summary>
/// Writes decoded pixel to buffer at a given position.
/// </summary>
/// <param name="buffer">The buffer to write to.</param>
/// <param name="offset">The position to write to.</param>
/// <returns>The number of bytes written.</returns>
public int WriteTo(Span<byte> buffer, int offset)
{
if (this.Length == 0)
{
return 0;
}
if (this.Length == 1)
{
buffer[offset] = this.value;
return 1;
}
LzwString e = this;
int endIdx = this.Length - 1;
if (endIdx >= buffer.Length)
{
TiffThrowHelper.ThrowImageFormatException("Error reading lzw compressed stream. Either pixel buffer to write to is to small or code length is invalid!");
}
for (int i = endIdx; i >= 0; i--)
{
buffer[offset + i] = e.value;
e = e.previous;
}
return this.Length;
}
}
}

45
src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs

@ -0,0 +1,45 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
/// <summary>
/// Class to handle cases where TIFF image data is compressed using LZW compression.
/// </summary>
internal class LzwTiffCompression : TiffBaseDecompressor
{
/// <summary>
/// Initializes a new instance of the <see cref="LzwTiffCompression" /> class.
/// </summary>
/// <param name="memoryAllocator">The memoryAllocator to use for buffer allocations.</param>
/// <param name="width">The image width.</param>
/// <param name="bitsPerPixel">The bits used per pixel.</param>
/// <param name="predictor">The tiff predictor used.</param>
public LzwTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor)
: base(memoryAllocator, width, bitsPerPixel, predictor)
{
}
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
{
var decoder = new TiffLzwDecoder(stream);
decoder.DecodePixels(buffer);
if (this.Predictor == TiffPredictor.Horizontal)
{
HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel);
}
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
}
}

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

@ -0,0 +1,79 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
/// <summary>
/// Class to handle cases where TIFF image data is compressed using Modified Huffman Compression.
/// </summary>
internal class ModifiedHuffmanTiffCompression : T4TiffCompression
{
private readonly byte whiteValue;
private readonly byte blackValue;
/// <summary>
/// Initializes a new instance of the <see cref="ModifiedHuffmanTiffCompression" /> class.
/// </summary>
/// <param name="allocator">The memory allocator.</param>
/// <param name="width">The image width.</param>
/// <param name="bitsPerPixel">The number of bits per pixel.</param>
/// <param name="photometricInterpretation">The photometric interpretation.</param>
public ModifiedHuffmanTiffCompression(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPhotometricInterpretation photometricInterpretation)
: base(allocator, width, bitsPerPixel, FaxCompressionOptions.None, photometricInterpretation)
{
bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero;
this.whiteValue = (byte)(isWhiteZero ? 0 : 1);
this.blackValue = (byte)(isWhiteZero ? 1 : 0);
}
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
{
using var bitReader = new T4BitReader(stream, byteCount, this.Allocator, eolPadding: false, isModifiedHuffman: true);
buffer.Clear();
uint bitsWritten = 0;
uint pixelsWritten = 0;
while (bitReader.HasMoreData)
{
bitReader.ReadNextRun();
if (bitReader.RunLength > 0)
{
if (bitReader.IsWhiteRun)
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue);
bitsWritten += bitReader.RunLength;
pixelsWritten += bitReader.RunLength;
}
else
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue);
bitsWritten += bitReader.RunLength;
pixelsWritten += bitReader.RunLength;
}
}
if (pixelsWritten % this.Width == 0)
{
bitReader.StartNewRow();
// Write padding bits, if necessary.
uint pad = 8 - (bitsWritten % 8);
if (pad != 8)
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, pad, 0);
bitsWritten += pad;
}
}
}
}
}
}

35
src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs

@ -0,0 +1,35 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
/// <summary>
/// Class to handle cases where TIFF image data is not compressed.
/// </summary>
internal class NoneTiffCompression : TiffBaseDecompressor
{
/// <summary>
/// Initializes a new instance of the <see cref="NoneTiffCompression" /> class.
/// </summary>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="width">The width of the image.</param>
/// <param name="bitsPerPixel">The bits per pixel.</param>
public NoneTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel)
: base(memoryAllocator, width, bitsPerPixel)
{
}
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer) => _ = stream.Read(buffer, 0, Math.Min(buffer.Length, byteCount));
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
}
}

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

@ -0,0 +1,96 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
/// <summary>
/// Class to handle cases where TIFF image data is compressed using PackBits compression.
/// </summary>
internal class PackBitsTiffCompression : TiffBaseDecompressor
{
private IMemoryOwner<byte> compressedDataMemory;
/// <summary>
/// Initializes a new instance of the <see cref="PackBitsTiffCompression" /> class.
/// </summary>
/// <param name="memoryAllocator">The memoryAllocator to use for buffer allocations.</param>
/// <param name="width">The width of the image.</param>
/// <param name="bitsPerPixel">The number of bits per pixel.</param>
public PackBitsTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel)
: base(memoryAllocator, width, bitsPerPixel)
{
}
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
{
if (this.compressedDataMemory == null)
{
this.compressedDataMemory = this.Allocator.Allocate<byte>(byteCount);
}
else if (this.compressedDataMemory.Length() < byteCount)
{
this.compressedDataMemory.Dispose();
this.compressedDataMemory = this.Allocator.Allocate<byte>(byteCount);
}
Span<byte> compressedData = this.compressedDataMemory.GetSpan();
stream.Read(compressedData, 0, byteCount);
int compressedOffset = 0;
int decompressedOffset = 0;
while (compressedOffset < byteCount)
{
byte headerByte = compressedData[compressedOffset];
if (headerByte <= 127)
{
int literalOffset = compressedOffset + 1;
int literalLength = compressedData[compressedOffset] + 1;
if ((literalOffset + literalLength) > compressedData.Length)
{
TiffThrowHelper.ThrowImageFormatException("Tiff packbits compression error: not enough data.");
}
compressedData.Slice(literalOffset, literalLength).CopyTo(buffer.Slice(decompressedOffset));
compressedOffset += literalLength + 1;
decompressedOffset += literalLength;
}
else if (headerByte == 0x80)
{
compressedOffset += 1;
}
else
{
byte repeatData = compressedData[compressedOffset + 1];
int repeatLength = 257 - headerByte;
ArrayCopyRepeat(repeatData, buffer, decompressedOffset, repeatLength);
compressedOffset += 2;
decompressedOffset += repeatLength;
}
}
}
private static void ArrayCopyRepeat(byte value, Span<byte> destinationArray, int destinationIndex, int length)
{
for (int i = 0; i < length; i++)
{
destinationArray[i + destinationIndex] = value;
}
}
/// <inheritdoc/>
protected override void Dispose(bool disposing) => this.compressedDataMemory?.Dispose();
}
}

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

@ -0,0 +1,842 @@
// 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 SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
/// <summary>
/// Bitreader for reading compressed CCITT T4 1D data.
/// </summary>
internal class T4BitReader : IDisposable
{
/// <summary>
/// Number of bits read.
/// </summary>
private int bitsRead;
/// <summary>
/// Current value.
/// </summary>
private uint value;
/// <summary>
/// Number of bits read for the current run value.
/// </summary>
private int curValueBitsRead;
/// <summary>
/// Byte position in the buffer.
/// </summary>
private ulong position;
/// <summary>
/// Indicates whether its the first line of data which is read from the image.
/// </summary>
private bool isFirstScanLine;
/// <summary>
/// Indicates whether we have found a termination code which signals the end of a run.
/// </summary>
private bool terminationCodeFound;
/// <summary>
/// We keep track if its the start of the row, because each run is expected to start with a white run.
/// If the image row itself starts with black, a white run of zero is expected.
/// </summary>
private bool isStartOfRow;
/// <summary>
/// Indicates whether the modified huffman compression, as specified in the TIFF spec in section 10, is used.
/// </summary>
private readonly bool isModifiedHuffmanRle;
/// <summary>
/// Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false.
/// </summary>
private readonly bool eolPadding;
private readonly int dataLength;
private const int MinCodeLength = 2;
private readonly int maxCodeLength = 13;
private static readonly Dictionary<uint, uint> WhiteLen4TermCodes = new Dictionary<uint, uint>()
{
{ 0x7, 2 }, { 0x8, 3 }, { 0xB, 4 }, { 0xC, 5 }, { 0xE, 6 }, { 0xF, 7 }
};
private static readonly Dictionary<uint, uint> WhiteLen5TermCodes = new Dictionary<uint, uint>()
{
{ 0x13, 8 }, { 0x14, 9 }, { 0x7, 10 }, { 0x8, 11 }
};
private static readonly Dictionary<uint, uint> WhiteLen6TermCodes = new Dictionary<uint, uint>()
{
{ 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>()
{
{ 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>()
{
{ 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 },
{ 0x5, 46 }, { 0xA, 47 }, { 0xB, 48 }, { 0x52, 49 }, { 0x53, 50 }, { 0x54, 51 }, { 0x55, 52 }, { 0x24, 53 }, { 0x25, 54 },
{ 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>()
{
{ 0x3, 2 }, { 0x2, 3 }
};
private static readonly Dictionary<uint, uint> BlackLen3TermCodes = new Dictionary<uint, uint>()
{
{ 0x2, 1 }, { 0x3, 4 }
};
private static readonly Dictionary<uint, uint> BlackLen4TermCodes = new Dictionary<uint, uint>()
{
{ 0x3, 5 }, { 0x2, 6 }
};
private static readonly Dictionary<uint, uint> BlackLen5TermCodes = new Dictionary<uint, uint>()
{
{ 0x3, 7 }
};
private static readonly Dictionary<uint, uint> BlackLen6TermCodes = new Dictionary<uint, uint>()
{
{ 0x5, 8 }, { 0x4, 9 }
};
private static readonly Dictionary<uint, uint> BlackLen7TermCodes = new Dictionary<uint, uint>()
{
{ 0x4, 10 }, { 0x5, 11 }, { 0x7, 12 }
};
private static readonly Dictionary<uint, uint> BlackLen8TermCodes = new Dictionary<uint, uint>()
{
{ 0x4, 13 }, { 0x7, 14 }
};
private static readonly Dictionary<uint, uint> BlackLen9TermCodes = new Dictionary<uint, uint>()
{
{ 0x18, 15 }
};
private static readonly Dictionary<uint, uint> BlackLen10TermCodes = new Dictionary<uint, uint>()
{
{ 0x37, 0 }, { 0x17, 16 }, { 0x18, 17 }, { 0x8, 18 }
};
private static readonly Dictionary<uint, uint> BlackLen11TermCodes = new Dictionary<uint, uint>()
{
{ 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>()
{
{ 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 },
{ 0x54, 44 }, { 0x55, 45 }, { 0x56, 46 }, { 0x57, 47 }, { 0x64, 48 }, { 0x65, 49 }, { 0x52, 50 }, { 0x53, 51 }, { 0x24, 52 },
{ 0x37, 53 }, { 0x38, 54 }, { 0x27, 55 }, { 0x28, 56 }, { 0x58, 57 }, { 0x59, 58 }, { 0x2B, 59 }, { 0x2C, 60 }, { 0x5A, 61 },
{ 0x66, 62 }, { 0x67, 63 }
};
private static readonly Dictionary<uint, uint> WhiteLen5MakeupCodes = new Dictionary<uint, uint>()
{
{ 0x1B, 64 }, { 0x12, 128 }
};
private static readonly Dictionary<uint, uint> WhiteLen6MakeupCodes = new Dictionary<uint, uint>()
{
{ 0x17, 192 }, { 0x18, 1664 }
};
private static readonly Dictionary<uint, uint> WhiteLen8MakeupCodes = new Dictionary<uint, uint>()
{
{ 0x36, 320 }, { 0x37, 384 }, { 0x64, 448 }, { 0x65, 512 }, { 0x68, 576 }, { 0x67, 640 }
};
private static readonly Dictionary<uint, uint> WhiteLen7MakeupCodes = new Dictionary<uint, uint>()
{
{ 0x37, 256 }
};
private static readonly Dictionary<uint, uint> WhiteLen9MakeupCodes = new Dictionary<uint, uint>()
{
{ 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>()
{
{ 0x8, 1792 }, { 0xC, 1856 }, { 0xD, 1920 }
};
private static readonly Dictionary<uint, uint> WhiteLen12MakeupCodes = new Dictionary<uint, uint>()
{
{ 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>()
{
{ 0xF, 64 }
};
private static readonly Dictionary<uint, uint> BlackLen11MakeupCodes = new Dictionary<uint, uint>()
{
{ 0x8, 1792 }, { 0xC, 1856 }, { 0xD, 1920 }
};
private static readonly Dictionary<uint, uint> BlackLen12MakeupCodes = new Dictionary<uint, uint>()
{
{ 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>()
{
{ 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>
/// Initializes a new instance of the <see cref="T4BitReader" /> class.
/// </summary>
/// <param name="input">The compressed input stream.</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>
/// <param name="isModifiedHuffman">Indicates, if its the modified huffman code variation. Defaults to false.</param>
public T4BitReader(Stream input, int bytesToRead, MemoryAllocator allocator, bool eolPadding = false, bool isModifiedHuffman = false)
{
this.Data = allocator.Allocate<byte>(bytesToRead);
this.ReadImageDataFromStream(input, bytesToRead);
this.isModifiedHuffmanRle = isModifiedHuffman;
this.dataLength = bytesToRead;
this.bitsRead = 0;
this.value = 0;
this.curValueBitsRead = 0;
this.position = 0;
this.IsWhiteRun = true;
this.isFirstScanLine = true;
this.isStartOfRow = true;
this.terminationCodeFound = false;
this.RunLength = 0;
this.eolPadding = eolPadding;
if (this.eolPadding)
{
this.maxCodeLength = 24;
}
}
/// <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>
public bool HasMoreData
{
get
{
if (this.isModifiedHuffmanRle)
{
return this.position < (ulong)this.dataLength - 1 || (this.bitsRead > 0 && this.bitsRead < 7);
}
return this.position < (ulong)this.dataLength - 1;
}
}
/// <summary>
/// Gets a value indicating whether the current run is a white pixel run, otherwise its a black pixel run.
/// </summary>
public bool IsWhiteRun { get; private set; }
/// <summary>
/// Gets the number of pixels in the current run.
/// </summary>
public uint RunLength { get; private set; }
/// <summary>
/// Gets a value indicating whether the end of a pixel row has been reached.
/// </summary>
public bool IsEndOfScanLine
{
get
{
if (this.eolPadding)
{
return this.curValueBitsRead >= 12 && this.value == 1;
}
return this.curValueBitsRead == 12 && this.value == 1;
}
}
/// <summary>
/// Read the next run of pixels.
/// </summary>
public void ReadNextRun()
{
if (this.terminationCodeFound)
{
this.IsWhiteRun = !this.IsWhiteRun;
this.terminationCodeFound = false;
}
this.Reset();
if (this.isFirstScanLine && !this.isModifiedHuffmanRle)
{
// We expect an EOL before the first data.
this.value = this.ReadValue(this.eolPadding ? 16 : 12);
if (!this.IsEndOfScanLine)
{
TiffThrowHelper.ThrowImageFormatException("t4 parsing error: expected start of data marker not found");
}
this.Reset();
}
// A code word must have at least 2 bits.
this.value = this.ReadValue(MinCodeLength);
do
{
if (this.curValueBitsRead > this.maxCodeLength)
{
TiffThrowHelper.ThrowImageFormatException("t4 parsing error: invalid code length read");
}
bool isMakeupCode = this.IsMakeupCode();
if (isMakeupCode)
{
if (this.IsWhiteRun)
{
this.RunLength += this.WhiteMakeupCodeRunLength();
}
else
{
this.RunLength += this.BlackMakeupCodeRunLength();
}
this.isStartOfRow = false;
this.Reset(resetRunLength: false);
continue;
}
bool isTerminatingCode = this.IsTerminatingCode();
if (isTerminatingCode)
{
// Each line starts with a white run. If the image starts with black, a white run with length zero is written.
if (this.isStartOfRow && this.IsWhiteRun && this.WhiteTerminatingCodeRunLength() == 0)
{
this.IsWhiteRun = !this.IsWhiteRun;
this.Reset();
this.isStartOfRow = false;
continue;
}
if (this.IsWhiteRun)
{
this.RunLength += this.WhiteTerminatingCodeRunLength();
}
else
{
this.RunLength += this.BlackTerminatingCodeRunLength();
}
this.terminationCodeFound = true;
this.isStartOfRow = false;
break;
}
var currBit = this.ReadValue(1);
this.value = (this.value << 1) | currBit;
if (this.IsEndOfScanLine)
{
this.StartNewRow();
}
}
while (!this.IsEndOfScanLine);
this.isFirstScanLine = false;
}
public void StartNewRow()
{
// Each new row starts with a white run.
this.IsWhiteRun = true;
this.isStartOfRow = true;
this.terminationCodeFound = false;
if (this.isModifiedHuffmanRle)
{
int pad = 8 - (this.bitsRead % 8);
if (pad != 8)
{
// Skip padding bits, move to next byte.
this.position++;
this.bitsRead = 0;
}
}
}
/// <inheritdoc/>
public void Dispose() => this.Data.Dispose();
private uint WhiteTerminatingCodeRunLength()
{
switch (this.curValueBitsRead)
{
case 4:
{
return WhiteLen4TermCodes[this.value];
}
case 5:
{
return WhiteLen5TermCodes[this.value];
}
case 6:
{
return WhiteLen6TermCodes[this.value];
}
case 7:
{
return WhiteLen7TermCodes[this.value];
}
case 8:
{
return WhiteLen8TermCodes[this.value];
}
}
return 0;
}
private uint BlackTerminatingCodeRunLength()
{
switch (this.curValueBitsRead)
{
case 2:
{
return BlackLen2TermCodes[this.value];
}
case 3:
{
return BlackLen3TermCodes[this.value];
}
case 4:
{
return BlackLen4TermCodes[this.value];
}
case 5:
{
return BlackLen5TermCodes[this.value];
}
case 6:
{
return BlackLen6TermCodes[this.value];
}
case 7:
{
return BlackLen7TermCodes[this.value];
}
case 8:
{
return BlackLen8TermCodes[this.value];
}
case 9:
{
return BlackLen9TermCodes[this.value];
}
case 10:
{
return BlackLen10TermCodes[this.value];
}
case 11:
{
return BlackLen11TermCodes[this.value];
}
case 12:
{
return BlackLen12TermCodes[this.value];
}
}
return 0;
}
private uint WhiteMakeupCodeRunLength()
{
switch (this.curValueBitsRead)
{
case 5:
{
return WhiteLen5MakeupCodes[this.value];
}
case 6:
{
return WhiteLen6MakeupCodes[this.value];
}
case 7:
{
return WhiteLen7MakeupCodes[this.value];
}
case 8:
{
return WhiteLen8MakeupCodes[this.value];
}
case 9:
{
return WhiteLen9MakeupCodes[this.value];
}
case 11:
{
return WhiteLen11MakeupCodes[this.value];
}
case 12:
{
return WhiteLen12MakeupCodes[this.value];
}
}
return 0;
}
private uint BlackMakeupCodeRunLength()
{
switch (this.curValueBitsRead)
{
case 10:
{
return BlackLen10MakeupCodes[this.value];
}
case 11:
{
return BlackLen11MakeupCodes[this.value];
}
case 12:
{
return BlackLen12MakeupCodes[this.value];
}
case 13:
{
return BlackLen13MakeupCodes[this.value];
}
}
return 0;
}
private bool IsMakeupCode()
{
if (this.IsWhiteRun)
{
return this.IsWhiteMakeupCode();
}
return this.IsBlackMakeupCode();
}
private bool IsWhiteMakeupCode()
{
switch (this.curValueBitsRead)
{
case 5:
{
return WhiteLen5MakeupCodes.ContainsKey(this.value);
}
case 6:
{
return WhiteLen6MakeupCodes.ContainsKey(this.value);
}
case 7:
{
return WhiteLen7MakeupCodes.ContainsKey(this.value);
}
case 8:
{
return WhiteLen8MakeupCodes.ContainsKey(this.value);
}
case 9:
{
return WhiteLen9MakeupCodes.ContainsKey(this.value);
}
case 11:
{
return WhiteLen11MakeupCodes.ContainsKey(this.value);
}
case 12:
{
if (this.isModifiedHuffmanRle)
{
if (this.value == 1)
{
return true;
}
}
return WhiteLen12MakeupCodes.ContainsKey(this.value);
}
}
return false;
}
private bool IsBlackMakeupCode()
{
switch (this.curValueBitsRead)
{
case 10:
{
return BlackLen10MakeupCodes.ContainsKey(this.value);
}
case 11:
{
if (this.isModifiedHuffmanRle)
{
if (this.value == 0)
{
return true;
}
}
return BlackLen11MakeupCodes.ContainsKey(this.value);
}
case 12:
{
return BlackLen12MakeupCodes.ContainsKey(this.value);
}
case 13:
{
return BlackLen13MakeupCodes.ContainsKey(this.value);
}
}
return false;
}
private bool IsTerminatingCode()
{
if (this.IsWhiteRun)
{
return this.IsWhiteTerminatingCode();
}
return this.IsBlackTerminatingCode();
}
private bool IsWhiteTerminatingCode()
{
switch (this.curValueBitsRead)
{
case 4:
{
return WhiteLen4TermCodes.ContainsKey(this.value);
}
case 5:
{
return WhiteLen5TermCodes.ContainsKey(this.value);
}
case 6:
{
return WhiteLen6TermCodes.ContainsKey(this.value);
}
case 7:
{
return WhiteLen7TermCodes.ContainsKey(this.value);
}
case 8:
{
return WhiteLen8TermCodes.ContainsKey(this.value);
}
}
return false;
}
private bool IsBlackTerminatingCode()
{
switch (this.curValueBitsRead)
{
case 2:
{
return BlackLen2TermCodes.ContainsKey(this.value);
}
case 3:
{
return BlackLen3TermCodes.ContainsKey(this.value);
}
case 4:
{
return BlackLen4TermCodes.ContainsKey(this.value);
}
case 5:
{
return BlackLen5TermCodes.ContainsKey(this.value);
}
case 6:
{
return BlackLen6TermCodes.ContainsKey(this.value);
}
case 7:
{
return BlackLen7TermCodes.ContainsKey(this.value);
}
case 8:
{
return BlackLen8TermCodes.ContainsKey(this.value);
}
case 9:
{
return BlackLen9TermCodes.ContainsKey(this.value);
}
case 10:
{
return BlackLen10TermCodes.ContainsKey(this.value);
}
case 11:
{
return BlackLen11TermCodes.ContainsKey(this.value);
}
case 12:
{
return BlackLen12TermCodes.ContainsKey(this.value);
}
}
return false;
}
private void Reset(bool resetRunLength = true)
{
this.value = 0;
this.curValueBitsRead = 0;
if (resetRunLength)
{
this.RunLength = 0;
}
}
private uint ReadValue(int nBits)
{
Guard.MustBeGreaterThan(nBits, 0, nameof(nBits));
uint v = 0;
int shift = nBits;
while (shift-- > 0)
{
uint bit = this.GetBit();
v |= bit << shift;
this.curValueBitsRead++;
}
return v;
}
private uint GetBit()
{
if (this.bitsRead >= 8)
{
this.LoadNewByte();
}
Span<byte> dataSpan = this.Data.GetSpan();
int shift = 8 - this.bitsRead - 1;
var bit = (uint)((dataSpan[(int)this.position] & (1 << shift)) != 0 ? 1 : 0);
this.bitsRead++;
return bit;
}
private void LoadNewByte()
{
this.position++;
this.bitsRead = 0;
if (this.position >= (ulong)this.dataLength)
{
TiffThrowHelper.ThrowImageFormatException("tiff image has invalid t4 compressed data");
}
}
private void ReadImageDataFromStream(Stream input, int bytesToRead)
{
Span<byte> dataSpan = this.Data.GetSpan();
input.Read(dataSpan, 0, bytesToRead);
}
}
}

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

@ -0,0 +1,90 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
/// <summary>
/// Class to handle cases where TIFF image data is compressed using CCITT T4 compression.
/// </summary>
internal class T4TiffCompression : TiffBaseDecompressor
{
private readonly FaxCompressionOptions faxCompressionOptions;
private readonly byte whiteValue;
private readonly byte blackValue;
/// <summary>
/// Initializes a new instance of the <see cref="T4TiffCompression" /> class.
/// </summary>
/// <param name="allocator">The memory allocator.</param>
/// <param name="width">The image width.</param>
/// <param name="bitsPerPixel">The number of bits per pixel.</param>
/// <param name="faxOptions">Fax compression options.</param>
/// <param name="photometricInterpretation">The photometric interpretation.</param>
public T4TiffCompression(MemoryAllocator allocator, int width, int bitsPerPixel, FaxCompressionOptions faxOptions, TiffPhotometricInterpretation photometricInterpretation)
: base(allocator, width, bitsPerPixel)
{
this.faxCompressionOptions = faxOptions;
bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero;
this.whiteValue = (byte)(isWhiteZero ? 0 : 1);
this.blackValue = (byte)(isWhiteZero ? 1 : 0);
}
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer)
{
if (this.faxCompressionOptions.HasFlag(FaxCompressionOptions.TwoDimensionalCoding))
{
TiffThrowHelper.ThrowNotSupported("TIFF CCITT 2D compression is not yet supported");
}
var eolPadding = this.faxCompressionOptions.HasFlag(FaxCompressionOptions.EolPadding);
using var bitReader = new T4BitReader(stream, byteCount, this.Allocator, eolPadding);
buffer.Clear();
uint bitsWritten = 0;
while (bitReader.HasMoreData)
{
bitReader.ReadNextRun();
if (bitReader.RunLength > 0)
{
if (bitReader.IsWhiteRun)
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue);
bitsWritten += bitReader.RunLength;
}
else
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue);
bitsWritten += bitReader.RunLength;
}
}
if (bitReader.IsEndOfScanLine)
{
// Write padding bytes, if necessary.
uint pad = 8 - (bitsWritten % 8);
if (pad != 8)
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, pad, 0);
bitsWritten += pad;
}
}
}
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
}
}

257
src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffLzwDecoder.cs

@ -0,0 +1,257 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
/*
This implementation is based on a port of a java tiff decoder by Harald Kuhr: https://github.com/haraldk/TwelveMonkeys
Original licence:
BSD 3-Clause License
* Copyright (c) 2015, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
** Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/// <summary>
/// Decompresses and decodes data using the dynamic LZW algorithms, see TIFF spec Section 13.
/// </summary>
internal sealed class TiffLzwDecoder
{
/// <summary>
/// The stream to decode.
/// </summary>
private readonly Stream stream;
/// <summary>
/// As soon as we use entry 4094 of the table (maxTableSize - 2), the lzw compressor write out a (12-bit) ClearCode.
/// At this point, the compressor reinitializes the string table and then writes out 9-bit codes again.
/// </summary>
private const int ClearCode = 256;
/// <summary>
/// End of Information.
/// </summary>
private const int EoiCode = 257;
/// <summary>
/// Minimum code length of 9 bits.
/// </summary>
private const int MinBits = 9;
/// <summary>
/// Maximum code length of 12 bits.
/// </summary>
private const int MaxBits = 12;
/// <summary>
/// Maximum table size of 4096.
/// </summary>
private const int TableSize = 1 << MaxBits;
private readonly LzwString[] table;
private int tableLength;
private int bitsPerCode;
private int oldCode = ClearCode;
private int maxCode;
private int bitMask;
private int maxString;
private bool eofReached;
private int nextData;
private int nextBits;
/// <summary>
/// Initializes a new instance of the <see cref="TiffLzwDecoder" /> class
/// and sets the stream, where the compressed data should be read from.
/// </summary>
/// <param name="stream">The stream to read from.</param>
/// <exception cref="ArgumentNullException"><paramref name="stream" /> is null.</exception>
public TiffLzwDecoder(Stream stream)
{
Guard.NotNull(stream, nameof(stream));
this.stream = stream;
// TODO: Investigate a manner by which we can avoid this allocation.
this.table = new LzwString[TableSize];
for (int i = 0; i < 256; i++)
{
this.table[i] = new LzwString((byte)i);
}
this.Init();
}
private void Init()
{
// Table length is 256 + 2, because of special clear code and end of information code.
this.tableLength = 258;
this.bitsPerCode = MinBits;
this.bitMask = BitmaskFor(this.bitsPerCode);
this.maxCode = this.MaxCode();
this.maxString = 1;
}
/// <summary>
/// Decodes and decompresses all pixel indices from the stream.
/// </summary>
/// <param name="pixels">The pixel array to decode to.</param>
public void DecodePixels(Span<byte> pixels)
{
// Adapted from the pseudo-code example found in the TIFF 6.0 Specification, 1992.
// See Section 13: "LZW Compression"/"LZW Decoding", page 61+
int code;
int offset = 0;
while ((code = this.GetNextCode()) != EoiCode)
{
if (code == ClearCode)
{
this.Init();
code = this.GetNextCode();
if (code == EoiCode)
{
break;
}
if (this.table[code] == null)
{
TiffThrowHelper.ThrowImageFormatException($"Corrupted TIFF LZW: code {code} (table size: {this.tableLength})");
}
offset += this.table[code].WriteTo(pixels, offset);
}
else
{
if (this.table[this.oldCode] == null)
{
TiffThrowHelper.ThrowImageFormatException($"Corrupted TIFF LZW: code {this.oldCode} (table size: {this.tableLength})");
}
if (this.IsInTable(code))
{
offset += this.table[code].WriteTo(pixels, offset);
this.AddStringToTable(this.table[this.oldCode].Concatenate(this.table[code].FirstChar));
}
else
{
LzwString outString = this.table[this.oldCode].Concatenate(this.table[this.oldCode].FirstChar);
offset += outString.WriteTo(pixels, offset);
this.AddStringToTable(outString);
}
}
this.oldCode = code;
if (offset >= pixels.Length)
{
break;
}
}
}
private void AddStringToTable(LzwString lzwString)
{
if (this.tableLength > this.table.Length)
{
TiffThrowHelper.ThrowImageFormatException($"TIFF LZW with more than {MaxBits} bits per code encountered (table overflow)");
}
this.table[this.tableLength++] = lzwString;
if (this.tableLength > this.maxCode)
{
this.bitsPerCode++;
if (this.bitsPerCode > MaxBits)
{
// Continue reading MaxBits (12 bit) length codes.
this.bitsPerCode = MaxBits;
}
this.bitMask = BitmaskFor(this.bitsPerCode);
this.maxCode = this.MaxCode();
}
if (lzwString.Length > this.maxString)
{
this.maxString = lzwString.Length;
}
}
private int GetNextCode()
{
if (this.eofReached)
{
return EoiCode;
}
int read = this.stream.ReadByte();
if (read < 0)
{
this.eofReached = true;
return EoiCode;
}
this.nextData = (this.nextData << 8) | read;
this.nextBits += 8;
if (this.nextBits < this.bitsPerCode)
{
read = this.stream.ReadByte();
if (read < 0)
{
this.eofReached = true;
return EoiCode;
}
this.nextData = (this.nextData << 8) | read;
this.nextBits += 8;
}
int code = (this.nextData >> (this.nextBits - this.bitsPerCode)) & this.bitMask;
this.nextBits -= this.bitsPerCode;
return code;
}
private bool IsInTable(int code) => code < this.tableLength;
private int MaxCode() => this.bitMask - 1;
private static int BitmaskFor(int bits) => (1 << bits) - 1;
}
}

36
src/ImageSharp/Formats/Tiff/Compression/FaxCompressionOptions.cs

@ -0,0 +1,36 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression
{
/// <summary>
/// Fax compression options, see TIFF spec page 51f (T4Options).
/// </summary>
[Flags]
public enum FaxCompressionOptions : uint
{
/// <summary>
/// No options.
/// </summary>
None = 0,
/// <summary>
/// If set, 2-dimensional coding is used (otherwise 1-dimensional is assumed).
/// </summary>
TwoDimensionalCoding = 1,
/// <summary>
/// If set, uncompressed mode is used.
/// </summary>
UncompressedMode = 2,
/// <summary>
/// If set, fill bits have been added as necessary before EOL codes such that
/// EOL always ends on a byte boundary, thus ensuring an EOL-sequence of 1 byte
/// preceded by a zero nibble: xxxx-0000 0000-0001.
/// </summary>
EolPadding = 4
}
}

138
src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs

@ -0,0 +1,138 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression
{
/// <summary>
/// Methods for undoing the horizontal prediction used in combination with deflate and LZW compressed TIFF images.
/// </summary>
internal static class HorizontalPredictor
{
/// <summary>
/// Inverts the horizontal prediction.
/// </summary>
/// <param name="pixelBytes">Buffer with decompressed pixel data.</param>
/// <param name="width">The width of the image or strip.</param>
/// <param name="bitsPerPixel">Bits per pixel.</param>
public static void Undo(Span<byte> pixelBytes, int width, int bitsPerPixel)
{
if (bitsPerPixel == 8)
{
Undo8Bit(pixelBytes, width);
}
else if (bitsPerPixel == 24)
{
Undo24Bit(pixelBytes, width);
}
}
public static void ApplyHorizontalPrediction(Span<byte> rows, int width, int bitsPerPixel)
{
if (bitsPerPixel == 8)
{
ApplyHorizontalPrediction8Bit(rows, width);
}
else if (bitsPerPixel == 24)
{
ApplyHorizontalPrediction24Bit(rows, width);
}
}
/// <summary>
/// Applies a horizontal predictor to the rgb row.
/// Make use of the fact that many continuous-tone images rarely vary much in pixel value from one pixel to the next.
/// In such images, if we replace the pixel values by differences between consecutive pixels, many of the differences should be 0, plus
/// or minus 1, and so on.This reduces the apparent information content and allows LZW to encode the data more compactly.
/// </summary>
/// <param name="rows">The rgb pixel rows.</param>
/// <param name="width">The width.</param>
[MethodImpl(InliningOptions.ShortMethod)]
private static void ApplyHorizontalPrediction24Bit(Span<byte> rows, int width)
{
DebugGuard.IsTrue(rows.Length % width == 0, "Values must be equals");
int height = rows.Length / width;
for (int y = 0; y < height; y++)
{
Span<byte> rowSpan = rows.Slice(y * width, width);
Span<Rgb24> rowRgb = MemoryMarshal.Cast<byte, Rgb24>(rowSpan);
for (int x = rowRgb.Length - 1; x >= 1; x--)
{
byte r = (byte)(rowRgb[x].R - rowRgb[x - 1].R);
byte g = (byte)(rowRgb[x].G - rowRgb[x - 1].G);
byte b = (byte)(rowRgb[x].B - rowRgb[x - 1].B);
var rgb = new Rgb24(r, g, b);
rowRgb[x].FromRgb24(rgb);
}
}
}
/// <summary>
/// Applies a horizontal predictor to a gray pixel row.
/// </summary>
/// <param name="rows">The gray pixel rows.</param>
/// <param name="width">The width.</param>
[MethodImpl(InliningOptions.ShortMethod)]
private static void ApplyHorizontalPrediction8Bit(Span<byte> rows, int width)
{
DebugGuard.IsTrue(rows.Length % width == 0, "Values must be equals");
int height = rows.Length / width;
for (int y = 0; y < height; y++)
{
Span<byte> rowSpan = rows.Slice(y * width, width);
for (int x = rowSpan.Length - 1; x >= 1; x--)
{
rowSpan[x] -= rowSpan[x - 1];
}
}
}
private static void Undo8Bit(Span<byte> pixelBytes, int width)
{
int rowBytesCount = width;
int height = pixelBytes.Length / rowBytesCount;
for (int y = 0; y < height; y++)
{
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
byte pixelValue = rowBytes[0];
for (int x = 1; x < width; x++)
{
pixelValue += rowBytes[x];
rowBytes[x] = pixelValue;
}
}
}
private static void Undo24Bit(Span<byte> pixelBytes, int width)
{
int rowBytesCount = width * 3;
int height = pixelBytes.Length / rowBytesCount;
for (int y = 0; y < height; y++)
{
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
Span<Rgb24> rowRgb = MemoryMarshal.Cast<byte, Rgb24>(rowBytes).Slice(0, width);
ref Rgb24 rowRgbBase = ref MemoryMarshal.GetReference(rowRgb);
byte r = rowRgbBase.R;
byte g = rowRgbBase.G;
byte b = rowRgbBase.B;
for (int x = 1; x < rowRgb.Length; x++)
{
ref Rgb24 pixel = ref rowRgb[x];
r += pixel.R;
g += pixel.G;
b += pixel.B;
var rgb = new Rgb24(r, g, b);
pixel.FromRgb24(rgb);
}
}
}
}
}

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

@ -0,0 +1,62 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression
{
internal abstract class TiffBaseCompression : IDisposable
{
private bool isDisposed;
protected TiffBaseCompression(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None)
{
this.Allocator = allocator;
this.Width = width;
this.BitsPerPixel = bitsPerPixel;
this.Predictor = predictor;
this.BytesPerRow = ((width * bitsPerPixel) + 7) / 8;
}
/// <summary>
/// Gets the image width.
/// </summary>
public int Width { get; }
/// <summary>
/// Gets the bits per pixel.
/// </summary>
public int BitsPerPixel { get; }
/// <summary>
/// Gets the bytes per row.
/// </summary>
public int BytesPerRow { get; }
/// <summary>
/// Gets the predictor to use. Should only be used with deflate or lzw compression.
/// </summary>
public TiffPredictor Predictor { get; }
/// <summary>
/// Gets the memory allocator.
/// </summary>
protected MemoryAllocator Allocator { get; }
/// <inheritdoc />
public void Dispose()
{
if (this.isDisposed)
{
return;
}
this.isDisposed = true;
this.Dispose(true);
}
protected abstract void Dispose(bool disposing);
}
}

48
src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompressor.cs

@ -0,0 +1,48 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression
{
internal abstract class TiffBaseCompressor : TiffBaseCompression
{
/// <summary>
/// Initializes a new instance of the <see cref="TiffBaseCompressor"/> class.
/// </summary>
/// <param name="output">The output stream to write the compressed image to.</param>
/// <param name="allocator">The memory allocator.</param>
/// <param name="width">The image width.</param>
/// <param name="bitsPerPixel">Bits per pixel.</param>
/// <param name="predictor">The predictor to use (should only be used with deflate or lzw compression). Defaults to none.</param>
protected TiffBaseCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None)
: base(allocator, width, bitsPerPixel, predictor)
=> this.Output = output;
/// <summary>
/// Gets the compression method to use.
/// </summary>
public abstract TiffCompression Method { get; }
/// <summary>
/// Gets the output stream to write the compressed image to.
/// </summary>
public Stream Output { get; }
/// <summary>
/// Does any initialization required for the compression.
/// </summary>
/// <param name="rowsPerStrip">The number of rows per strip.</param>
public abstract void Initialize(int rowsPerStrip);
/// <summary>
/// Compresses a strip of the image.
/// </summary>
/// <param name="rows">Image rows to compress.</param>
/// <param name="height">Image height.</param>
public abstract void CompressStrip(Span<byte> rows, int height);
}
}

54
src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs

@ -0,0 +1,54 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression
{
/// <summary>
/// The base tiff decompressor class.
/// </summary>
internal abstract class TiffBaseDecompressor : TiffBaseCompression
{
protected TiffBaseDecompressor(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None)
: base(allocator, width, bitsPerPixel, predictor)
{
}
/// <summary>
/// Decompresses image data into the supplied buffer.
/// </summary>
/// <param name="stream">The <see cref="Stream" /> to read image data from.</param>
/// <param name="stripOffset">The strip offset of stream.</param>
/// <param name="stripByteCount">The number of bytes to read from the input stream.</param>
/// <param name="buffer">The output buffer for uncompressed data.</param>
public void Decompress(BufferedReadStream stream, uint stripOffset, uint stripByteCount, Span<byte> buffer)
{
if (stripByteCount > int.MaxValue)
{
TiffThrowHelper.ThrowImageFormatException("The StripByteCount value is too big.");
}
stream.Seek(stripOffset, SeekOrigin.Begin);
this.Decompress(stream, (int)stripByteCount, buffer);
if (stripOffset + stripByteCount < stream.Position)
{
TiffThrowHelper.ThrowImageFormatException("Out of range when reading a strip.");
}
}
/// <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>
protected abstract void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer);
}
}

64
src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs

@ -0,0 +1,64 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression
{
internal static class TiffCompressorFactory
{
public static TiffBaseCompressor Create(
TiffCompression method,
Stream output,
MemoryAllocator allocator,
int width,
int bitsPerPixel,
DeflateCompressionLevel compressionLevel,
TiffPredictor predictor)
{
switch (method)
{
// The following compression types are not implemented in the encoder and will default to no compression instead.
case TiffCompression.ItuTRecT43:
case TiffCompression.ItuTRecT82:
case TiffCompression.Jpeg:
case TiffCompression.OldJpeg:
case TiffCompression.OldDeflate:
case TiffCompression.None:
DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set");
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new NoCompressor(output, allocator, width, bitsPerPixel);
case TiffCompression.PackBits:
DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set");
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new PackBitsCompressor(output, allocator, width, bitsPerPixel);
case TiffCompression.Deflate:
return new DeflateCompressor(output, allocator, width, bitsPerPixel, predictor, compressionLevel);
case TiffCompression.Lzw:
DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set");
return new LzwCompressor(output, allocator, width, bitsPerPixel, predictor);
case TiffCompression.CcittGroup3Fax:
DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set");
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new T4BitCompressor(output, allocator, width, bitsPerPixel, false);
case TiffCompression.Ccitt1D:
DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set");
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new T4BitCompressor(output, allocator, width, bitsPerPixel, true);
default:
throw TiffThrowHelper.NotSupportedCompressor(method.ToString());
}
}
}
}

41
src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs

@ -0,0 +1,41 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff.Compression
{
/// <summary>
/// Provides enumeration of the various TIFF compression types the decoder can handle.
/// </summary>
internal enum TiffDecoderCompressionType
{
/// <summary>
/// Image data is stored uncompressed in the TIFF file.
/// </summary>
None = 0,
/// <summary>
/// Image data is compressed using PackBits compression.
/// </summary>
PackBits = 1,
/// <summary>
/// Image data is compressed using Deflate compression.
/// </summary>
Deflate = 2,
/// <summary>
/// Image data is compressed using LZW compression.
/// </summary>
Lzw = 3,
/// <summary>
/// Image data is compressed using T4-encoding: CCITT T.4.
/// </summary>
T4 = 4,
/// <summary>
/// Image data is compressed using modified huffman compression.
/// </summary>
HuffmanRle = 5,
}
}

54
src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs

@ -0,0 +1,54 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression
{
internal static class TiffDecompressorsFactory
{
public static TiffBaseDecompressor Create(
TiffDecoderCompressionType method,
MemoryAllocator allocator,
TiffPhotometricInterpretation photometricInterpretation,
int width,
int bitsPerPixel,
TiffPredictor predictor,
FaxCompressionOptions faxOptions)
{
switch (method)
{
case TiffDecoderCompressionType.None:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected");
return new NoneTiffCompression(allocator, width, bitsPerPixel);
case TiffDecoderCompressionType.PackBits:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected");
return new PackBitsTiffCompression(allocator, width, bitsPerPixel);
case TiffDecoderCompressionType.Deflate:
DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected");
return new DeflateTiffCompression(allocator, width, bitsPerPixel, predictor);
case TiffDecoderCompressionType.Lzw:
DebugGuard.IsTrue(faxOptions == FaxCompressionOptions.None, "No fax compression options are expected");
return new LzwTiffCompression(allocator, width, bitsPerPixel, predictor);
case TiffDecoderCompressionType.T4:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new T4TiffCompression(allocator, width, bitsPerPixel, faxOptions, photometricInterpretation);
case TiffDecoderCompressionType.HuffmanRle:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new ModifiedHuffmanTiffCompression(allocator, width, bitsPerPixel, photometricInterpretation);
default:
throw TiffThrowHelper.NotSupportedDecompressor(nameof(method));
}
}
}
}

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

@ -0,0 +1,23 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Helper methods for the Configuration.
/// </summary>
public static class ConfigurationExtensions
{
/// <summary>
/// Registers the tiff format detector, encoder and decoder.
/// </summary>
/// <param name="configuration">The configuration.</param>
public static void AddTiff(this Configuration configuration)
{
configuration.ImageFormatsManager.AddImageFormat(TiffFormat.Instance);
configuration.ImageFormatsManager.AddImageFormatDetector(new TiffImageFormatDetector());
configuration.ImageFormatsManager.SetDecoder(TiffFormat.Instance, new TiffDecoder());
configuration.ImageFormatsManager.SetEncoder(TiffFormat.Instance, new TiffEncoder());
}
}
}

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

@ -0,0 +1,94 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff.Constants
{
/// <summary>
/// Enumeration representing the compression formats defined by the Tiff file-format.
/// </summary>
public enum TiffCompression : ushort
{
/// <summary>
/// A invalid compression value.
/// </summary>
Invalid = 0,
/// <summary>
/// No compression.
/// </summary>
None = 1,
/// <summary>
/// CCITT Group 3 1-Dimensional Modified Huffman run-length encoding.
/// </summary>
Ccitt1D = 2,
/// <summary>
/// PackBits compression
/// </summary>
PackBits = 32773,
/// <summary>
/// T4-encoding: CCITT T.4 bi-level encoding (see Section 11 of the TIFF 6.0 specification).
/// </summary>
CcittGroup3Fax = 3,
/// <summary>
/// T6-encoding: CCITT T.6 bi-level encoding (see Section 11 of the TIFF 6.0 specification).
///
/// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead,
/// if this is choosen.
/// </summary>
CcittGroup4Fax = 4,
/// <summary>
/// LZW compression (see Section 13 of the TIFF 6.0 specification).
/// </summary>
Lzw = 5,
/// <summary>
/// JPEG compression - obsolete (see Section 22 of the TIFF 6.0 specification).
///
/// Note: The TIFF encoder does not support this compression and will default to use no compression instead,
/// if this is chosen.
/// </summary>
OldJpeg = 6,
/// <summary>
/// JPEG compression (see TIFF Specification, supplement 2).
///
/// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead,
/// if this is chosen.
/// </summary>
Jpeg = 7,
/// <summary>
/// Deflate compression, using zlib data format (see TIFF Specification, supplement 2).
/// </summary>
Deflate = 8,
/// <summary>
/// Deflate compression - old.
///
/// Note: The TIFF encoder does not support this compression and will default to use no compression instead,
/// if this is chosen.
/// </summary>
OldDeflate = 32946,
/// <summary>
/// ITU-T Rec. T.82 coding, applying ITU-T Rec. T.85 (JBIG) (see RFC2301).
///
/// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead,
/// if this is chosen.
/// </summary>
ItuTRecT82 = 9,
/// <summary>
/// ITU-T Rec. T.43 representation, using ITU-T Rec. T.82 (JBIG) (see RFC2301).
///
/// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead,
/// if this is chosen.
/// </summary>
ItuTRecT43 = 10
}
}

113
src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs

@ -0,0 +1,113 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
namespace SixLabors.ImageSharp.Formats.Tiff.Constants
{
/// <summary>
/// Defines constants defined in the TIFF specification.
/// </summary>
internal static class TiffConstants
{
/// <summary>
/// Byte order markers for indicating little endian encoding.
/// </summary>
public const byte ByteOrderLittleEndian = 0x49;
/// <summary>
/// Byte order markers for indicating big endian encoding.
/// </summary>
public const byte ByteOrderBigEndian = 0x4D;
/// <summary>
/// Byte order markers for indicating little endian encoding.
/// </summary>
public const ushort ByteOrderLittleEndianShort = 0x4949;
/// <summary>
/// Byte order markers for indicating big endian encoding.
/// </summary>
public const ushort ByteOrderBigEndianShort = 0x4D4D;
/// <summary>
/// Magic number used within the image file header to identify a TIFF format file.
/// </summary>
public const ushort HeaderMagicNumber = 42;
/// <summary>
/// RowsPerStrip default value, which is effectively infinity.
/// </summary>
public const int RowsPerStripInfinity = 2147483647;
/// <summary>
/// Size (in bytes) of the TIFF file header.
/// </summary>
public const int SizeOfTiffHeader = 8;
/// <summary>
/// Size (in bytes) of each individual TIFF IFD entry
/// </summary>
public const int SizeOfIfdEntry = 12;
/// <summary>
/// Size (in bytes) of the Short and SShort data types
/// </summary>
public const int SizeOfShort = 2;
/// <summary>
/// Size (in bytes) of the Long and SLong data types
/// </summary>
public const int SizeOfLong = 4;
/// <summary>
/// Size (in bytes) of the Rational and SRational data types
/// </summary>
public const int SizeOfRational = 8;
/// <summary>
/// Size (in bytes) of the Float data type
/// </summary>
public const int SizeOfFloat = 4;
/// <summary>
/// Size (in bytes) of the Double data type
/// </summary>
public const int SizeOfDouble = 8;
/// <summary>
/// The default strip size is 8k.
/// </summary>
public const int DefaultStripSize = 8 * 1024;
/// <summary>
/// The bits per sample for 1 bit bicolor images.
/// </summary>
public static readonly ushort[] BitsPerSample1Bit = { 1 };
/// <summary>
/// The bits per sample for images with a 4 color palette.
/// </summary>
public static readonly ushort[] BitsPerSample4Bit = { 4 };
/// <summary>
/// The bits per sample for 8 bit images.
/// </summary>
public static readonly ushort[] BitsPerSample8Bit = { 8 };
/// <summary>
/// The bits per sample for images with 8 bits for each color channel.
/// </summary>
public static readonly ushort[] BitsPerSampleRgb8Bit = { 8, 8, 8 };
/// <summary>
/// The list of mimetypes that equate to a tiff.
/// </summary>
public static readonly IEnumerable<string> MimeTypes = new[] { "image/tiff", "image/tiff-fx" };
/// <summary>
/// The list of file extensions that equate to a tiff.
/// </summary>
public static readonly IEnumerable<string> FileExtensions = new[] { "tiff", "tif" };
}
}

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

@ -0,0 +1,26 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff.Constants
{
/// <summary>
/// Enumeration representing the possible uses of extra components in TIFF format files.
/// </summary>
internal enum TiffExtraSamples
{
/// <summary>
/// Unspecified data.
/// </summary>
Unspecified = 0,
/// <summary>
/// Associated alpha data (with pre-multiplied color).
/// </summary>
AssociatedAlpha = 1,
/// <summary>
/// Unassociated alpha data.
/// </summary>
UnassociatedAlpha = 2
}
}

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

@ -0,0 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff.Constants
{
/// <summary>
/// Enumeration representing the fill orders defined by the Tiff file-format.
/// </summary>
internal enum TiffFillOrder : ushort
{
/// <summary>
/// Pixels with lower column values are stored in the higher-order bits of the byte.
/// </summary>
MostSignificantBitFirst = 1,
/// <summary>
/// Pixels with lower column values are stored in the lower-order bits of the byte.
/// </summary>
LeastSignificantBitFirst = 2
}
}

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

@ -0,0 +1,44 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.Tiff.Constants
{
/// <summary>
/// Enumeration representing the sub-file types defined by the Tiff file-format.
/// </summary>
[Flags]
public enum TiffNewSubfileType : uint
{
/// <summary>
/// A full-resolution image.
/// </summary>
FullImage = 0,
/// <summary>
/// Reduced-resolution version of another image in this TIFF file.
/// </summary>
Preview = 1,
/// <summary>
/// A single page of a multi-page image.
/// </summary>
SinglePage = 2,
/// <summary>
/// A transparency mask for another image in this TIFF file.
/// </summary>
TransparencyMask = 4,
/// <summary>
/// Alternative reduced-resolution version of another image in this TIFF file (see DNG specification).
/// </summary>
AlternativePreview = 65536,
/// <summary>
/// Mixed raster content (see RFC2301).
/// </summary>
MixedRasterContent = 8
}
}

51
src/ImageSharp/Formats/Tiff/Constants/TiffOrientation.cs

@ -0,0 +1,51 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff.Constants
{
/// <summary>
/// Enumeration representing the image orientations defined by the Tiff file-format.
/// </summary>
internal enum TiffOrientation
{
/// <summary>
/// The 0th row and 0th column represent the visual top and left-hand side of the image respectively.
/// </summary>
TopLeft = 1,
/// <summary>
/// The 0th row and 0th column represent the visual top and right-hand side of the image respectively.
/// </summary>
TopRight = 2,
/// <summary>
/// The 0th row and 0th column represent the visual bottom and right-hand side of the image respectively.
/// </summary>
BottomRight = 3,
/// <summary>
/// The 0th row and 0th column represent the visual bottom and left-hand side of the image respectively.
/// </summary>
BottomLeft = 4,
/// <summary>
/// The 0th row and 0th column represent the visual left-hand side and top of the image respectively.
/// </summary>
LeftTop = 5,
/// <summary>
/// The 0th row and 0th column represent the visual right-hand side and top of the image respectively.
/// </summary>
RightTop = 6,
/// <summary>
/// The 0th row and 0th column represent the visual right-hand side and bottom of the image respectively.
/// </summary>
RightBottom = 7,
/// <summary>
/// The 0th row and 0th column represent the visual left-hand side and bottom of the image respectively.
/// </summary>
LeftBottom = 8
}
}

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

@ -0,0 +1,89 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff.Constants
{
/// <summary>
/// Enumeration representing the photometric interpretation formats defined by the Tiff file-format.
/// </summary>
public enum TiffPhotometricInterpretation : ushort
{
/// <summary>
/// Bilevel and grayscale: 0 is imaged as white. The maximum value is imaged as black.
///
/// Not supported by the TiffEncoder.
/// </summary>
WhiteIsZero = 0,
/// <summary>
/// Bilevel and grayscale: 0 is imaged as black. The maximum value is imaged as white.
/// </summary>
BlackIsZero = 1,
/// <summary>
/// RGB image.
/// </summary>
Rgb = 2,
/// <summary>
/// Palette Color.
/// </summary>
PaletteColor = 3,
/// <summary>
/// A transparency mask.
///
/// Not supported by the TiffEncoder.
/// </summary>
TransparencyMask = 4,
/// <summary>
/// Separated: usually CMYK (see Section 16 of the TIFF 6.0 specification).
///
/// Not supported by the TiffEncoder.
/// </summary>
Separated = 5,
/// <summary>
/// YCbCr (see Section 21 of the TIFF 6.0 specification).
///
/// Not supported by the TiffEncoder.
/// </summary>
YCbCr = 6,
/// <summary>
/// 1976 CIE L*a*b* (see Section 23 of the TIFF 6.0 specification).
///
/// Not supported by the TiffEncoder.
/// </summary>
CieLab = 8,
/// <summary>
/// ICC L*a*b* (see TIFF Specification, supplement 1).
///
/// Not supported by the TiffEncoder.
/// </summary>
IccLab = 9,
/// <summary>
/// ITU L*a*b* (see RFC2301).
///
/// Not supported by the TiffEncoder.
/// </summary>
ItuLab = 10,
/// <summary>
/// Color Filter Array (see the DNG specification).
///
/// Not supported by the TiffEncoder.
/// </summary>
ColorFilterArray = 32803,
/// <summary>
/// Linear Raw (see the DNG specification).
///
/// Not supported by the TiffEncoder.
/// </summary>
LinearRaw = 34892
}
}

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

@ -0,0 +1,31 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff.Constants
{
/// <summary>
/// Enumeration representing how the components of each pixel are stored the Tiff file-format.
/// </summary>
public enum TiffPlanarConfiguration : ushort
{
/// <summary>
/// Chunky format.
/// The component values for each pixel are stored contiguously.
/// The order of the components within the pixel is specified by
/// PhotometricInterpretation. For example, for RGB data, the data is stored as RGBRGBRGB.
/// </summary>
Chunky = 1,
/// <summary>
/// Planar format.
/// The components are stored in separate “component planes.” The
/// values in StripOffsets and StripByteCounts are then arranged as a 2-dimensional
/// array, with SamplesPerPixel rows and StripsPerImage columns. (All of the columns
/// for row 0 are stored first, followed by the columns of row 1, and so on.)
/// PhotometricInterpretation describes the type of data stored in each component
/// plane. For example, RGB data is stored with the Red components in one component
/// plane, the Green in another, and the Blue in another.
/// </summary>
Planar = 2
}
}

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

@ -0,0 +1,28 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff.Constants
{
/// <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.
/// </summary>
None = 1,
/// <summary>
/// Horizontal differencing.
/// </summary>
Horizontal = 2,
/// <summary>
/// Floating point horizontal differencing.
///
/// Note: The Tiff Encoder does not yet support this. If this is chosen, the encoder will fallback to none.
/// </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.Constants
{
/// <summary>
/// Specifies how to interpret each data sample in a pixel.
/// </summary>
public enum TiffSampleFormat : ushort
{
/// <summary>
/// Unsigned integer data. Default value.
/// </summary>
UnsignedInteger = 1,
/// <summary>
/// Signed integer data.
/// </summary>
SignedInteger = 2,
/// <summary>
/// IEEE floating point data.
/// </summary>
Float = 3,
/// <summary>
/// Undefined data format.
/// </summary>
Undefined = 4,
/// <summary>
/// The complex int.
/// </summary>
ComplexInt = 5,
/// <summary>
/// The complex float.
/// </summary>
ComplexFloat = 6
}
}

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

@ -0,0 +1,26 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff.Constants
{
/// <summary>
/// Enumeration representing the sub-file types defined by the Tiff file-format.
/// </summary>
public enum TiffSubfileType : ushort
{
/// <summary>
/// Full-resolution image data.
/// </summary>
FullImage = 1,
/// <summary>
/// Reduced-resolution image data.
/// </summary>
Preview = 2,
/// <summary>
/// A single page of a multi-page image.
/// </summary>
SinglePage = 3
}
}

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

@ -0,0 +1,26 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff.Constants
{
/// <summary>
/// Enumeration representing the thresholding applied to image data defined by the Tiff file-format.
/// </summary>
internal enum TiffThresholding
{
/// <summary>
/// No dithering or halftoning.
/// </summary>
None = 1,
/// <summary>
/// An ordered dither or halftone technique.
/// </summary>
Ordered = 2,
/// <summary>
/// A randomized process such as error diffusion.
/// </summary>
Random = 3
}
}

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

@ -0,0 +1,16 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Encapsulates the options for the <see cref="TiffDecoder"/>.
/// </summary>
internal interface ITiffDecoderOptions
{
/// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
/// </summary>
bool IgnoreMetadata { get; }
}
}

47
src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs

@ -0,0 +1,47 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Processing.Processors.Quantization;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Encapsulates the options for the <see cref="TiffEncoder"/>.
/// </summary>
internal interface ITiffEncoderOptions
{
/// <summary>
/// Gets the number of bits per pixel.
/// </summary>
TiffBitsPerPixel? BitsPerPixel { get; }
/// <summary>
/// Gets the compression type to use.
/// </summary>
TiffCompression? Compression { get; }
/// <summary>
/// Gets the compression level 1-9 for the deflate compression mode.
/// <remarks>Defaults to <see cref="DeflateCompressionLevel.DefaultCompression"/>.</remarks>
/// </summary>
DeflateCompressionLevel? CompressionLevel { get; }
/// <summary>
/// Gets the PhotometricInterpretation to use. Possible options are RGB, RGB with a color palette, gray or BiColor.
/// If no PhotometricInterpretation is specified or it is unsupported by the encoder, RGB will be used.
/// </summary>
TiffPhotometricInterpretation? PhotometricInterpretation { get; }
/// <summary>
/// Gets a value indicating which horizontal prediction to use. This can improve the compression ratio with deflate or lzw compression.
/// </summary>
TiffPredictor? HorizontalPredictor { get; }
/// <summary>
/// Gets the quantizer for creating a color palette image.
/// </summary>
IQuantizer Quantizer { get; }
}
}

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

@ -0,0 +1,104 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.IO;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// The TIFF IFD reader class.
/// </summary>
internal class DirectoryReader
{
private readonly Stream stream;
private uint nextIfdOffset;
// used for sequential read big values (actual for multiframe big files)
// todo: different tags can link to the same data (stream offset) - investigate
private readonly SortedList<uint, Action> lazyLoaders = new SortedList<uint, Action>(new DuplicateKeyComparer<uint>());
public DirectoryReader(Stream stream) => this.stream = stream;
/// <summary>
/// Gets the byte order.
/// </summary>
public ByteOrder ByteOrder { get; private set; }
/// <summary>
/// Reads image file directories.
/// </summary>
/// <returns>Image file directories.</returns>
public IEnumerable<ExifProfile> Read()
{
this.ByteOrder = ReadByteOrder(this.stream);
this.nextIfdOffset = new HeaderReader(this.stream, this.ByteOrder).ReadFileHeader();
return this.ReadIfds();
}
private static ByteOrder ReadByteOrder(Stream stream)
{
var headerBytes = new byte[2];
stream.Read(headerBytes, 0, 2);
if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian)
{
return ByteOrder.LittleEndian;
}
else if (headerBytes[0] == TiffConstants.ByteOrderBigEndian && headerBytes[1] == TiffConstants.ByteOrderBigEndian)
{
return ByteOrder.BigEndian;
}
throw TiffThrowHelper.ThrowInvalidHeader();
}
private IEnumerable<ExifProfile> ReadIfds()
{
var readers = new List<EntryReader>();
while (this.nextIfdOffset != 0 && this.nextIfdOffset < this.stream.Length)
{
var reader = new EntryReader(this.stream, this.ByteOrder, this.nextIfdOffset, this.lazyLoaders);
reader.ReadTags();
this.nextIfdOffset = reader.NextIfdOffset;
readers.Add(reader);
}
// Sequential reading big values.
foreach (Action loader in this.lazyLoaders.Values)
{
loader();
}
var list = new List<ExifProfile>();
foreach (EntryReader reader in readers)
{
var profile = new ExifProfile(reader.Values, reader.InvalidTags);
list.Add(profile);
}
return list;
}
/// <summary>
/// <see cref="DuplicateKeyComparer{TKey}"/> used for possibility add a duplicate offsets (but tags don't duplicate).
/// </summary>
/// <typeparam name="TKey">The type of the key.</typeparam>
private class DuplicateKeyComparer<TKey> : IComparer<TKey>
where TKey : IComparable
{
public int Compare(TKey x, TKey y)
{
int result = x.CompareTo(y);
// Handle equality as being greater.
return (result == 0) ? 1 : result;
}
}
}
}

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

@ -0,0 +1,65 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.IO;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
namespace SixLabors.ImageSharp.Formats.Tiff
{
internal class EntryReader : BaseExifReader
{
private readonly uint startOffset;
private readonly SortedList<uint, Action> lazyLoaders;
public EntryReader(Stream stream, ByteOrder byteOrder, uint ifdOffset, SortedList<uint, Action> lazyLoaders)
: base(stream)
{
this.IsBigEndian = byteOrder == ByteOrder.BigEndian;
this.startOffset = ifdOffset;
this.lazyLoaders = lazyLoaders;
}
public List<IExifValue> Values { get; } = new List<IExifValue>();
public uint NextIfdOffset { get; private set; }
public void ReadTags()
{
this.ReadValues(this.Values, this.startOffset);
this.NextIfdOffset = this.ReadUInt32();
this.ReadSubIfd(this.Values);
}
protected override void RegisterExtLoader(uint offset, Action reader) =>
this.lazyLoaders.Add(offset, reader);
}
internal class HeaderReader : BaseExifReader
{
public HeaderReader(Stream stream, ByteOrder byteOrder)
: base(stream) =>
this.IsBigEndian = byteOrder == ByteOrder.BigEndian;
public uint FirstIfdOffset { get; private set; }
public uint ReadFileHeader()
{
ushort magic = this.ReadUInt16();
if (magic != TiffConstants.HeaderMagicNumber)
{
TiffThrowHelper.ThrowInvalidHeader();
}
this.FirstIfdOffset = this.ReadUInt32();
return this.FirstIfdOffset;
}
protected override void RegisterExtLoader(uint offset, Action reader) => throw new NotSupportedException();
}
}

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

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

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

@ -0,0 +1,46 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation (optimized for bilevel images).
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class BlackIsZero1TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
int offset = 0;
Color black = Color.Black;
Color white = Color.White;
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x += 8)
{
byte b = data[offset++];
int maxShift = Math.Min(left + width - x, 8);
for (int shift = 0; shift < maxShift; shift++)
{
int bit = (b >> (7 - shift)) & 1;
color.FromRgba32(bit == 0 ? black : white);
pixels[x + shift, y] = color;
}
}
}
}
}
}

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

@ -0,0 +1,58 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation (optimized for 4-bit grayscale images).
/// </summary>
internal class BlackIsZero4TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
int offset = 0;
bool isOddWidth = (width & 1) == 1;
var l8 = default(L8);
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width - 1;)
{
byte byteData = data[offset++];
byte intensity1 = (byte)(((byteData & 0xF0) >> 4) * 17);
l8.PackedValue = intensity1;
color.FromL8(l8);
pixels[x++, y] = color;
byte intensity2 = (byte)((byteData & 0x0F) * 17);
l8.PackedValue = intensity2;
color.FromL8(l8);
pixels[x++, y] = color;
}
if (isOddWidth)
{
byte byteData = data[offset++];
byte intensity1 = (byte)(((byteData & 0xF0) >> 4) * 17);
l8.PackedValue = intensity1;
color.FromL8(l8);
pixels[left + width - 1, y] = color;
}
}
}
}
}

39
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero8TiffColor{TPixel}.cs

@ -0,0 +1,39 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation (optimized for 8-bit grayscale images).
/// </summary>
internal class BlackIsZero8TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
int offset = 0;
var l8 = default(L8);
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
{
byte intensity = data[offset++];
l8.PackedValue = intensity;
color.FromL8(l8);
pixels[x, y] = color;
}
}
}
}
}

50
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs

@ -0,0 +1,50 @@
// Copyright (c) Six Labors.
// 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;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'BlackIsZero' photometric interpretation (for all bit depths).
/// </summary>
internal class BlackIsZeroTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly ushort bitsPerSample0;
private readonly float factor;
public BlackIsZeroTiffColor(ushort[] bitsPerSample)
{
this.bitsPerSample0 = bitsPerSample[0];
this.factor = (1 << this.bitsPerSample0) - 1.0f;
}
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
var bitReader = new BitReader(data);
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
{
int value = bitReader.ReadBits(this.bitsPerSample0);
float intensity = value / this.factor;
color.FromVector4(new Vector4(intensity, intensity, intensity, 1.0f));
pixels[x, y] = color;
}
bitReader.NextRow();
}
}
}
}

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

@ -0,0 +1,67 @@
// Copyright (c) Six Labors.
// 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;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'PaletteTiffColor' photometric interpretation (for all bit depths).
/// </summary>
internal class PaletteTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly ushort bitsPerSample0;
private readonly TPixel[] palette;
/// <param name="bitsPerSample">The number of bits per sample for each pixel.</param>
/// <param name="colorMap">The RGB color lookup table to use for decoding the image.</param>
public PaletteTiffColor(ushort[] bitsPerSample, ushort[] colorMap)
{
this.bitsPerSample0 = bitsPerSample[0];
int colorCount = 1 << this.bitsPerSample0;
this.palette = GeneratePalette(colorMap, colorCount);
}
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var bitReader = new BitReader(data);
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
{
int index = bitReader.ReadBits(this.bitsPerSample0);
pixels[x, y] = this.palette[index];
}
bitReader.NextRow();
}
}
private static TPixel[] GeneratePalette(ushort[] colorMap, int colorCount)
{
var palette = new TPixel[colorCount];
const int rOffset = 0;
int gOffset = colorCount;
int bOffset = colorCount * 2;
for (int i = 0; i < palette.Length; i++)
{
float r = colorMap[rOffset + i] / 65535F;
float g = colorMap[gOffset + i] / 65535F;
float b = colorMap[bOffset + i] / 65535F;
palette[i].FromVector4(new Vector4(r, g, b, 1.0f));
}
return palette;
}
}
}

43
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb888TiffColor{TPixel}.cs

@ -0,0 +1,43 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'RGB' photometric interpretation (optimized for 8-bit full color images).
/// </summary>
internal class Rgb888TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
int offset = 0;
var rgba = default(Rgba32);
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++];
rgba.PackedValue = (uint)(r | (g << 8) | (b << 16) | (0xff << 24));
color.FromRgba32(rgba);
pixelRow[x] = color;
}
}
}
}
}

76
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs

@ -0,0 +1,76 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'RGB' photometric interpretation with 'Planar' layout (for all bit depths).
/// </summary>
internal class RgbPlanarTiffColor<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly float rFactor;
private readonly float gFactor;
private readonly float bFactor;
private readonly ushort bitsPerSampleR;
private readonly ushort bitsPerSampleG;
private readonly ushort bitsPerSampleB;
public RgbPlanarTiffColor(ushort[] bitsPerSample)
/* : base(bitsPerSample, null) */
{
this.bitsPerSampleR = bitsPerSample[0];
this.bitsPerSampleG = bitsPerSample[1];
this.bitsPerSampleB = bitsPerSample[2];
this.rFactor = (1 << this.bitsPerSampleR) - 1.0f;
this.gFactor = (1 << this.bitsPerSampleG) - 1.0f;
this.bFactor = (1 << this.bitsPerSampleB) - 1.0f;
}
/// <summary>
/// Decodes pixel data using the current photometric interpretation.
/// </summary>
/// <param name="data">The buffers to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
public void Decode(IManagedByteBuffer[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
var rBitReader = new BitReader(data[0].GetSpan());
var gBitReader = new BitReader(data[1].GetSpan());
var bBitReader = new BitReader(data[2].GetSpan());
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
{
float r = rBitReader.ReadBits(this.bitsPerSampleR) / this.rFactor;
float g = gBitReader.ReadBits(this.bitsPerSampleG) / this.gFactor;
float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor;
color.FromVector4(new Vector4(r, g, b, 1.0f));
pixels[x, y] = color;
}
rBitReader.NextRow();
gBitReader.NextRow();
bBitReader.NextRow();
}
}
}
}

64
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs

@ -0,0 +1,64 @@
// Copyright (c) Six Labors.
// 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;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'RGB' photometric interpretation (for all bit depths).
/// </summary>
internal class RgbTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly float rFactor;
private readonly float gFactor;
private readonly float bFactor;
private readonly ushort bitsPerSampleR;
private readonly ushort bitsPerSampleG;
private readonly ushort bitsPerSampleB;
public RgbTiffColor(ushort[] bitsPerSample)
{
this.bitsPerSampleR = bitsPerSample[0];
this.bitsPerSampleG = bitsPerSample[1];
this.bitsPerSampleB = bitsPerSample[2];
this.rFactor = (1 << this.bitsPerSampleR) - 1.0f;
this.gFactor = (1 << this.bitsPerSampleG) - 1.0f;
this.bFactor = (1 << this.bitsPerSampleB) - 1.0f;
}
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
var bitReader = new BitReader(data);
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
{
float r = bitReader.ReadBits(this.bitsPerSampleR) / this.rFactor;
float g = bitReader.ReadBits(this.bitsPerSampleG) / this.gFactor;
float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor;
color.FromVector4(new Vector4(r, g, b, 1.0f));
pixels[x, y] = color;
}
bitReader.NextRow();
}
}
}
}

28
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffBaseColorDecoder{TPixel}.cs

@ -0,0 +1,28 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// The base class for photometric interpretation decoders.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal abstract class TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Decodes source raw pixel data using the current photometric interpretation.
/// </summary>
/// <param name="data">The buffer to read image data from.</param>
/// <param name="pixels">The image buffer to write pixels to.</param>
/// <param name="left">The x-coordinate of the left-hand side of the image block.</param>
/// <param name="top">The y-coordinate of the top of the image block.</param>
/// <param name="width">The width of the image block.</param>
/// <param name="height">The height of the image block.</param>
public abstract void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height);
}
}

94
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs

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

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

@ -0,0 +1,71 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Provides enumeration of the various TIFF photometric interpretation implementation types.
/// </summary>
internal enum TiffColorType
{
/// <summary>
/// Grayscale: 0 is imaged as black. The maximum value is imaged as white.
/// </summary>
BlackIsZero,
/// <summary>
/// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for bilevel images.
/// </summary>
BlackIsZero1,
/// <summary>
/// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 4-bit images.
/// </summary>
BlackIsZero4,
/// <summary>
/// Grayscale: 0 is imaged as black. The maximum value is imaged as white. Optimized implementation for 8-bit images.
/// </summary>
BlackIsZero8,
/// <summary>
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black.
/// </summary>
WhiteIsZero,
/// <summary>
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for bilevel images.
/// </summary>
WhiteIsZero1,
/// <summary>
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 4-bit images.
/// </summary>
WhiteIsZero4,
/// <summary>
/// Grayscale: 0 is imaged as white. The maximum value is imaged as black. Optimized implementation for 8-bit images.
/// </summary>
WhiteIsZero8,
/// <summary>
/// Palette-color.
/// </summary>
PaletteColor,
/// <summary>
/// RGB Full Color.
/// </summary>
Rgb,
/// <summary>
/// RGB Full Color. Optimized implementation for 8-bit images.
/// </summary>
Rgb888,
/// <summary>
/// RGB Full Color. Planar configuration of data.
/// </summary>
RgbPlanar,
}
}

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

@ -0,0 +1,45 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation (optimized for bilevel images).
/// </summary>
internal class WhiteIsZero1TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
int offset = 0;
Color black = Color.Black;
Color white = Color.White;
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x += 8)
{
byte b = data[offset++];
int maxShift = Math.Min(left + width - x, 8);
for (int shift = 0; shift < maxShift; shift++)
{
int bit = (b >> (7 - shift)) & 1;
color.FromRgba32(bit == 0 ? white : black);
pixels[x + shift, y] = color;
}
}
}
}
}
}

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

@ -0,0 +1,58 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation (optimized for 4-bit grayscale images).
/// </summary>
internal class WhiteIsZero4TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
int offset = 0;
bool isOddWidth = (width & 1) == 1;
var l8 = default(L8);
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width - 1;)
{
byte byteData = data[offset++];
byte intensity1 = (byte)((15 - ((byteData & 0xF0) >> 4)) * 17);
l8.PackedValue = intensity1;
color.FromL8(l8);
pixels[x++, y] = color;
byte intensity2 = (byte)((15 - (byteData & 0x0F)) * 17);
l8.PackedValue = intensity2;
color.FromL8(l8);
pixels[x++, y] = color;
}
if (isOddWidth)
{
byte byteData = data[offset++];
byte intensity1 = (byte)((15 - ((byteData & 0xF0) >> 4)) * 17);
l8.PackedValue = intensity1;
color.FromL8(l8);
pixels[left + width - 1, y] = color;
}
}
}
}
}

39
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero8TiffColor{TPixel}.cs

@ -0,0 +1,39 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation (optimized for 8-bit grayscale images).
/// </summary>
internal class WhiteIsZero8TiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
int offset = 0;
var l8 = default(L8);
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
{
byte intensity = (byte)(255 - data[offset++]);
l8.PackedValue = intensity;
color.FromL8(l8);
pixels[x, y] = color;
}
}
}
}
}

50
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs

@ -0,0 +1,50 @@
// Copyright (c) Six Labors.
// 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;
namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
/// <summary>
/// Implements the 'WhiteIsZero' photometric interpretation (for all bit depths).
/// </summary>
internal class WhiteIsZeroTiffColor<TPixel> : TiffBaseColorDecoder<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly ushort bitsPerSample0;
private readonly float factor;
public WhiteIsZeroTiffColor(ushort[] bitsPerSample)
{
this.bitsPerSample0 = bitsPerSample[0];
this.factor = (float)Math.Pow(2, this.bitsPerSample0) - 1.0f;
}
/// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
var color = default(TPixel);
var bitReader = new BitReader(data);
for (int y = top; y < top + height; y++)
{
for (int x = left; x < left + width; x++)
{
int value = bitReader.ReadBits(this.bitsPerSample0);
float intensity = 1.0f - (value / this.factor);
color.FromVector4(new Vector4(intensity, intensity, intensity, 1.0f));
pixels[x, y] = color;
}
bitReader.NextRow();
}
}
}
}

253
src/ImageSharp/Formats/Tiff/README.md

@ -0,0 +1,253 @@
# ImageSharp TIFF codec
## References
- TIFF
- [TIFF 6.0 Specification](http://partners.adobe.com/public/developer/en/tiff/TIFF6.pdf),(http://www.npes.org/pdf/TIFF-v6.pdf)
- [TIFF Supplement 1](http://partners.adobe.com/public/developer/en/tiff/TIFFPM6.pdf)
- [TIFF Supplement 2](http://partners.adobe.com/public/developer/en/tiff/TIFFphotoshop.pdf)
- [TIFF Supplement 3](http://chriscox.org/TIFFTN3d1.pdf)
- [TIFF-F/FX Extension (RFC2301)](http://www.ietf.org/rfc/rfc2301.txt)
- [TIFF/EP Extension (Wikipedia)](https://en.wikipedia.org/wiki/TIFF/EP)
- [Adobe TIFF Pages](http://partners.adobe.com/public/developer/tiff/index.html)
- [Unofficial TIFF FAQ](http://www.awaresystems.be/imaging/tiff/faq.html)
- [CCITT T.4 Compression](https://www.itu.int/rec/T-REC-T.4-198811-S/_page.print)
- [CCITT T.6 Compression](https://www.itu.int/rec/T-REC-T.6/en)
- DNG
- [Adobe DNG Pages](https://helpx.adobe.com/photoshop/digital-negative.html)
- Metadata (EXIF)
- [EXIF 2.3 Specification](http://www.cipa.jp/std/documents/e/DC-008-2012_E.pdf)
- Metadata (XMP)
- [Adobe XMP Pages](http://www.adobe.com/products/xmp.html)
- [Adobe XMP Developer Center](http://www.adobe.com/devnet/xmp.html)
## Implementation Status
- The Decoder and Encoder currently only supports a single frame per image.
- Some compression formats are not yet supported. See the list below.
### Deviations from the TIFF spec (to be fixed)
- Decoder
- A Baseline TIFF reader must skip over extra components (e.g. RGB with 4 samples per pixels)
- NB: Need to handle this for both planar and chunky data
- If the SampleFormat field is present and not 1 - fail gracefully if you cannot handle this
- Compression=None should treat 16/32-BitsPerSample for all samples as SHORT/LONG (for byte order and padding rows)
- Check Planar format data - is this encoded as strips in order RGBRGBRGB or RRRGGGBBB?
### Compression Formats
| |Encoder|Decoder|Comments |
|---------------------------|:-----:|:-----:|--------------------------|
|None | Y | Y | |
|Ccitt1D | Y | Y | |
|PackBits | Y | Y | |
|CcittGroup3Fax | Y | Y | |
|CcittGroup4Fax | | | |
|Lzw | Y | Y | Based on ImageSharp GIF LZW implementation - this code could be modified to be (i) shared, or (ii) optimised for each case |
|Old Jpeg | | | We should not even try to support this |
|Jpeg (Technote 2) | | | |
|Deflate (Technote 2) | Y | Y | Based on PNG Deflate. |
|Old Deflate (Technote 2) | | Y | |
### Photometric Interpretation Formats
| |Encoder|Decoder|Comments |
|---------------------------|:-----:|:-----:|--------------------------|
|WhiteIsZero | Y | Y | General + 1/4/8-bit optimised implementations |
|BlackIsZero | Y | Y | General + 1/4/8-bit optimised implementations |
|Rgb (Chunky) | Y | Y | General + Rgb888 optimised implementation |
|Rgb (Planar) | | Y | General implementation only |
|PaletteColor | Y | Y | General implementation only |
|TransparencyMask | | | |
|Separated (TIFF Extension) | | | |
|YCbCr (TIFF Extension) | | | |
|CieLab (TIFF Extension) | | | |
|IccLab (TechNote 1) | | | |
### Baseline TIFF Tags
| |Encoder|Decoder|Comments |
|---------------------------|:-----:|:-----:|--------------------------|
|NewSubfileType | | | |
|SubfileType | | | |
|ImageWidth | Y | Y | |
|ImageLength | Y | Y | |
|BitsPerSample | Y | Y | |
|Compression | Y | Y | |
|PhotometricInterpretation | Y | Y | |
|Thresholding | | | |
|CellWidth | | | |
|CellLength | | | |
|FillOrder | | - | Ignore. In practice is very uncommon, and is not recommended. |
|ImageDescription | Y | Y | |
|Make | Y | Y | |
|Model | Y | Y | |
|StripOffsets | Y | Y | |
|Orientation | | - | Ignore. Many readers ignore this tag. |
|SamplesPerPixel | Y | - | Currently ignored, as can be inferred from count of BitsPerSample |
|RowsPerStrip | Y | Y | |
|StripByteCounts | Y | Y | |
|MinSampleValue | | | |
|MaxSampleValue | | | |
|XResolution | Y | Y | |
|YResolution | Y | Y | |
|PlanarConfiguration | | Y | Encoding support only chunky. |
|FreeOffsets | | | |
|FreeByteCounts | | | |
|GrayResponseUnit | | | |
|GrayResponseCurve | | | |
|ResolutionUnit | Y | Y | |
|Software | Y | Y | |
|DateTime | Y | Y | |
|Artist | Y | Y | |
|HostComputer | Y | Y | |
|ColorMap | Y | Y | |
|ExtraSamples | | - | |
|Copyright | Y | Y | |
### Extension TIFF Tags
| |Encoder|Decoder|Comments |
|---------------------------|:-----:|:-----:|--------------------------|
|NewSubfileType | | | |
|DocumentName | Y | Y | |
|PageName | | | |
|XPosition | | | |
|YPosition | | | |
|T4Options | | Y | |
|T6Options | | | |
|PageNumber | | | |
|TransferFunction | | | |
|Predictor | Y | Y | only Horizontal |
|WhitePoint | | | |
|PrimaryChromaticities | | | |
|HalftoneHints | | | |
|TileWidth | | - | |
|TileLength | | - | |
|TileOffsets | | - | |
|TileByteCounts | | - | |
|BadFaxLines | | | |
|CleanFaxData | | | |
|ConsecutiveBadFaxLines | | | |
|SubIFDs | | - | |
|InkSet | | | |
|InkNames | | | |
|NumberOfInks | | | |
|DotRange | | | |
|TargetPrinter | | | |
|SampleFormat | | - | |
|SMinSampleValue | | | |
|SMaxSampleValue | | | |
|TransferRange | | | |
|ClipPath | | | |
|XClipPathUnits | | | |
|YClipPathUnits | | | |
|Indexed | | | |
|JPEGTables | | | |
|OPIProxy | | | |
|GlobalParametersIFD | | | |
|ProfileType | | | |
|FaxProfile | | | |
|CodingMethods | | | |
|VersionYear | | | |
|ModeNumber | | | |
|Decode | | | |
|DefaultImageColor | | | |
|JPEGProc | | | |
|JPEGInterchangeFormat | | | |
|JPEGInterchangeFormatLength| | | |
|JPEGRestartInterval | | | |
|JPEGLosslessPredictors | | | |
|JPEGPointTransforms | | | |
|JPEGQTables | | | |
|JPEGDCTables | | | |
|JPEGACTables | | | |
|YCbCrCoefficients | | | |
|YCbCrSubSampling | | | |
|YCbCrPositioning | | | |
|ReferenceBlackWhite | | | |
|StripRowCounts | - | - | See RFC 2301 (File Format for Internet Fax). |
|XMP | Y | Y | |
|ImageID | | | |
|ImageLayer | | | |
### Private TIFF Tags
| |Encoder|Decoder|Comments |
|---------------------------|:-----:|:-----:|--------------------------|
|Wang Annotation | | | |
|MD FileTag | | | |
|MD ScalePixel | | | |
|MD ColorTable | | | |
|MD LabName | | | |
|MD SampleInfo | | | |
|MD PrepDate | | | |
|MD PrepTime | | | |
|MD FileUnits | | | |
|ModelPixelScaleTag | | | |
|IPTC | Y | Y | |
|INGR Packet Data Tag | | | |
|INGR Flag Registers | | | |
|IrasB Transformation Matrix| | | |
|ModelTiepointTag | | | |
|ModelTransformationTag | | | |
|Photoshop | | | |
|Exif IFD | | - | 0x8769 SubExif |
|ICC Profile | Y | Y | |
|GeoKeyDirectoryTag | | | |
|GeoDoubleParamsTag | | | |
|GeoAsciiParamsTag | | | |
|GPS IFD | | | |
|HylaFAX FaxRecvParams | | | |
|HylaFAX FaxSubAddress | | | |
|HylaFAX FaxRecvTime | | | |
|ImageSourceData | | | |
|Interoperability IFD | | | |
|GDAL_METADATA | | | |
|GDAL_NODATA | | | |
|Oce Scanjob Description | | | |
|Oce Application Selector | | | |
|Oce Identification Number | | | |
|Oce ImageLogic Characteristics| | | |
|DNGVersion | | | |
|DNGBackwardVersion | | | |
|UniqueCameraModel | | | |
|LocalizedCameraModel | | | |
|CFAPlaneColor | | | |
|CFALayout | | | |
|LinearizationTable | | | |
|BlackLevelRepeatDim | | | |
|BlackLevel | | | |
|BlackLevelDeltaH | | | |
|BlackLevelDeltaV | | | |
|WhiteLevel | | | |
|DefaultScale | | | |
|DefaultCropOrigin | | | |
|DefaultCropSize | | | |
|ColorMatrix1 | | | |
|ColorMatrix2 | | | |
|CameraCalibration1 | | | |
|CameraCalibration2 | | | |
|ReductionMatrix1 | | | |
|ReductionMatrix2 | | | |
|AnalogBalance | | | |
|AsShotNeutral | | | |
|AsShotWhiteXY | | | |
|BaselineExposure | | | |
|BaselineNoise | | | |
|BaselineSharpness | | | |
|BayerGreenSplit | | | |
|LinearResponseLimit | | | |
|CameraSerialNumber | | | |
|LensInfo | | | |
|ChromaBlurRadius | | | |
|AntiAliasStrength | | | |
|DNGPrivateData | | | |
|MakerNoteSafety | | | |
|CalibrationIlluminant1 | | | |
|CalibrationIlluminant2 | | | |
|BestQualityScale | | | |
|Alias Layer Metadata | | | |

BIN
src/ImageSharp/Formats/Tiff/T-REC-T.4-198811-S!!PDF-E.pdf

Binary file not shown.

BIN
src/ImageSharp/Formats/Tiff/T-REC-T.6-198811-I!!PDF-E.pdf

Binary file not shown.

BIN
src/ImageSharp/Formats/Tiff/TIFF-v6.pdf

Binary file not shown.

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

@ -0,0 +1,31 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// Enumerates the available bits per pixel for the tiff format.
/// </summary>
public enum TiffBitsPerPixel
{
/// <summary>
/// 1 bit per pixel, for bi-color image.
/// </summary>
Bit1 = 1,
/// <summary>
/// 4 bits per pixel, for images with a color palette.
/// </summary>
Bit4 = 4,
/// <summary>
/// 8 bits per pixel, grayscale or color palette images.
/// </summary>
Bit8 = 8,
/// <summary>
/// 24 bits per pixel. One byte for each color channel.
/// </summary>
Bit24 = 24,
}
}

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

@ -0,0 +1,36 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff
{
/// <summary>
/// The number of bits per component.
/// </summary>
public enum TiffBitsPerSample
{
/// <summary>
/// The Bits per samples is not known.
/// </summary>
Unknown = 0,
/// <summary>
/// One bit per sample for bicolor images.
/// </summary>
Bit1 = 1,
/// <summary>
/// Four bits per sample for grayscale images with 16 different levels of gray or paletted images with a palette of 16 colors.
/// </summary>
Bit4 = 4,
/// <summary>
/// Eight bits per sample for grayscale images with 256 different levels of gray or paletted images with a palette of 256 colors.
/// </summary>
Bit8 = 8,
/// <summary>
/// 24 bits per sample, each color channel has 8 Bits.
/// </summary>
Bit24 = 24,
}
}

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

Loading…
Cancel
Save