Browse Source

Merge branch 'master' into icc-color-conversion

pull/1567/head
James Jackson-South 5 years ago
committed by GitHub
parent
commit
aa2f1e9f3e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .editorconfig
  2. 1
      .gitignore
  3. 11
      Directory.Build.props
  4. 73
      ImageSharp.sln
  5. 2
      shared-infrastructure
  6. 7
      src/ImageSharp/Advanced/AotCompilerTools.cs
  7. 23
      src/ImageSharp/Common/ByteOrder.cs
  8. 7
      src/ImageSharp/Common/Extensions/StreamExtensions.cs
  9. 4
      src/ImageSharp/Common/Helpers/DebugGuard.cs
  10. 56
      src/ImageSharp/Common/Helpers/Numerics.cs
  11. 25
      src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs
  12. 50
      src/ImageSharp/Common/Helpers/UnitConverter.cs
  13. 2
      src/ImageSharp/Compression/Zlib/Adler32.cs
  14. 2
      src/ImageSharp/Compression/Zlib/Crc32.Lut.cs
  15. 2
      src/ImageSharp/Compression/Zlib/Crc32.cs
  16. 81
      src/ImageSharp/Compression/Zlib/DeflateCompressionLevel.cs
  17. 2
      src/ImageSharp/Compression/Zlib/DeflateThrowHelper.cs
  18. 4
      src/ImageSharp/Compression/Zlib/Deflater.cs
  19. 2
      src/ImageSharp/Compression/Zlib/DeflaterConstants.cs
  20. 43
      src/ImageSharp/Compression/Zlib/DeflaterEngine.cs
  21. 30
      src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs
  22. 29
      src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs
  23. 33
      src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs
  24. 0
      src/ImageSharp/Compression/Zlib/README.md
  25. 15
      src/ImageSharp/Compression/Zlib/ZlibDeflateStream.cs
  26. 2
      src/ImageSharp/Compression/Zlib/ZlibInflateStream.cs
  27. 0
      src/ImageSharp/Compression/Zlib/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf
  28. 13
      src/ImageSharp/Configuration.cs
  29. 310
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  30. 94
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  31. 32
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  32. 28
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  33. 106
      src/ImageSharp/Formats/ImageExtensions.Save.cs
  34. 3
      src/ImageSharp/Formats/ImageExtensions.Save.tt
  35. 228
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
  36. 16
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs
  37. 524
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
  38. 14
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.cs
  39. 2
      src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs
  40. 148
      src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs
  41. 195
      src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs
  42. 121
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs
  43. 122
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs
  44. 90
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
  45. 463
      src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs
  46. 22
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  47. 606
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  48. 3
      src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
  49. 4
      src/ImageSharp/Formats/Png/Filters/AverageFilter.cs
  50. 4
      src/ImageSharp/Formats/Png/Filters/PaethFilter.cs
  51. 2
      src/ImageSharp/Formats/Png/Filters/SubFilter.cs
  52. 4
      src/ImageSharp/Formats/Png/Filters/UpFilter.cs
  53. 6
      src/ImageSharp/Formats/Png/PngChunk.cs
  54. 3
      src/ImageSharp/Formats/Png/PngCompressionLevel.cs
  55. 310
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  56. 357
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  57. 2
      src/ImageSharp/Formats/Tga/README.md
  58. 178
      src/ImageSharp/Formats/Tga/TgaDecoderCore.cs
  59. 92
      src/ImageSharp/Formats/Tga/TgaEncoderCore.cs
  60. 51
      src/ImageSharp/Formats/Tiff/Compression/BitWriterUtils.cs
  61. 62
      src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs
  62. 40
      src/ImageSharp/Formats/Tiff/Compression/Compressors/LzwCompressor.cs
  63. 34
      src/ImageSharp/Formats/Tiff/Compression/Compressors/NoCompressor.cs
  64. 49
      src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsCompressor.cs
  65. 128
      src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsWriter.cs
  66. 594
      src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs
  67. 270
      src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffLzwEncoder.cs
  68. 63
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs
  69. 95
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwString.cs
  70. 45
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs
  71. 79
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs
  72. 35
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs
  73. 96
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs
  74. 842
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs
  75. 90
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs
  76. 257
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/TiffLzwDecoder.cs
  77. 36
      src/ImageSharp/Formats/Tiff/Compression/FaxCompressionOptions.cs
  78. 138
      src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs
  79. 62
      src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompression.cs
  80. 48
      src/ImageSharp/Formats/Tiff/Compression/TiffBaseCompressor.cs
  81. 54
      src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs
  82. 64
      src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs
  83. 41
      src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs
  84. 54
      src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs
  85. 94
      src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs
  86. 83
      src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs
  87. 26
      src/ImageSharp/Formats/Tiff/Constants/TiffExtraSamples.cs
  88. 21
      src/ImageSharp/Formats/Tiff/Constants/TiffFillOrder.cs
  89. 44
      src/ImageSharp/Formats/Tiff/Constants/TiffNewSubfileType.cs
  90. 51
      src/ImageSharp/Formats/Tiff/Constants/TiffOrientation.cs
  91. 89
      src/ImageSharp/Formats/Tiff/Constants/TiffPhotometricInterpretation.cs
  92. 31
      src/ImageSharp/Formats/Tiff/Constants/TiffPlanarConfiguration.cs
  93. 28
      src/ImageSharp/Formats/Tiff/Constants/TiffPredictor.cs
  94. 41
      src/ImageSharp/Formats/Tiff/Constants/TiffSampleFormat.cs
  95. 26
      src/ImageSharp/Formats/Tiff/Constants/TiffSubfileType.cs
  96. 26
      src/ImageSharp/Formats/Tiff/Constants/TiffThresholding.cs
  97. 16
      src/ImageSharp/Formats/Tiff/ITiffDecoderOptions.cs
  98. 47
      src/ImageSharp/Formats/Tiff/ITiffEncoderOptions.cs
  99. 104
      src/ImageSharp/Formats/Tiff/Ifd/DirectoryReader.cs
  100. 65
      src/ImageSharp/Formats/Tiff/Ifd/EntryReader.cs

2
.editorconfig

@ -75,7 +75,7 @@ indent_style = tab
[*.{cs,csx,cake,vb,vbx}]
# Default Severity for all .NET Code Style rules below
dotnet_analyzer_diagnostic.severity = warning
dotnet_analyzer_diagnostic.category-style.severity = warning
##########################################
# Language Rules

1
.gitignore

@ -221,4 +221,5 @@ artifacts/
# Tests
**/Images/ActualOutput
**/Images/ReferenceOutput
**/Images/Input/MemoryStress
.DS_Store

11
Directory.Build.props

@ -18,13 +18,12 @@
<!-- Import the shared global .props file -->
<Import Project="$(MSBuildThisFileDirectory)shared-infrastructure\msbuild\props\SixLabors.Global.props" />
<!-- Custom Project Configuration doesn't seem to copy this setting properly. -->
<PropertyGroup Condition="$(Configuration.StartsWith('Debug')) == true">
<Optimize>false</Optimize>
</PropertyGroup>
<!--
Ensure all custom build configurations based upon "Release" are optimized.
This is easier than setting each project individually.
-->
<PropertyGroup Condition="$(Configuration.StartsWith('Release')) == true">
<Optimize>true</Optimize>
</PropertyGroup>
</Project>

73
ImageSharp.sln

@ -1,4 +1,3 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28902.138
@ -480,115 +479,43 @@ Global
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Debug-InnerLoop|Any CPU = Debug-InnerLoop|Any CPU
Debug-InnerLoop|x64 = Debug-InnerLoop|x64
Debug-InnerLoop|x86 = Debug-InnerLoop|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
Release-InnerLoop|Any CPU = Release-InnerLoop|Any CPU
Release-InnerLoop|x64 = Release-InnerLoop|x64
Release-InnerLoop|x86 = Release-InnerLoop|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x64.ActiveCfg = Debug|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x64.Build.0 = Debug|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x86.ActiveCfg = Debug|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|x86.Build.0 = Debug|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug-InnerLoop|Any CPU.ActiveCfg = Debug-InnerLoop|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug-InnerLoop|Any CPU.Build.0 = Debug-InnerLoop|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug-InnerLoop|x64.ActiveCfg = Debug-InnerLoop|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug-InnerLoop|x64.Build.0 = Debug-InnerLoop|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug-InnerLoop|x86.ActiveCfg = Debug-InnerLoop|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug-InnerLoop|x86.Build.0 = Debug-InnerLoop|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|Any CPU.Build.0 = Release|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x64.ActiveCfg = Release|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x64.Build.0 = Release|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x86.ActiveCfg = Release|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|x86.Build.0 = Release|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release-InnerLoop|Any CPU.ActiveCfg = Release-InnerLoop|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release-InnerLoop|Any CPU.Build.0 = Release-InnerLoop|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release-InnerLoop|x64.ActiveCfg = Release-InnerLoop|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release-InnerLoop|x64.Build.0 = Release-InnerLoop|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release-InnerLoop|x86.ActiveCfg = Release-InnerLoop|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release-InnerLoop|x86.Build.0 = Release-InnerLoop|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x64.ActiveCfg = Debug|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x64.Build.0 = Debug|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x86.ActiveCfg = Debug|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|x86.Build.0 = Debug|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug-InnerLoop|Any CPU.ActiveCfg = Debug-InnerLoop|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug-InnerLoop|Any CPU.Build.0 = Debug-InnerLoop|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug-InnerLoop|x64.ActiveCfg = Debug-InnerLoop|x64
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug-InnerLoop|x64.Build.0 = Debug-InnerLoop|x64
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug-InnerLoop|x86.ActiveCfg = Debug-InnerLoop|x86
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug-InnerLoop|x86.Build.0 = Debug-InnerLoop|x86
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|Any CPU.Build.0 = Release|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x64.ActiveCfg = Release|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x64.Build.0 = Release|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x86.ActiveCfg = Release|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|x86.Build.0 = Release|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release-InnerLoop|Any CPU.ActiveCfg = Release-InnerLoop|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release-InnerLoop|Any CPU.Build.0 = Release-InnerLoop|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release-InnerLoop|x64.ActiveCfg = Release-InnerLoop|x64
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release-InnerLoop|x64.Build.0 = Release-InnerLoop|x64
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release-InnerLoop|x86.ActiveCfg = Release-InnerLoop|x86
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release-InnerLoop|x86.Build.0 = Release-InnerLoop|x86
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x64.ActiveCfg = Debug|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x64.Build.0 = Debug|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x86.ActiveCfg = Debug|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|x86.Build.0 = Debug|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|Any CPU.ActiveCfg = Debug-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|Any CPU.Build.0 = Debug-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|x64.ActiveCfg = Debug-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|x64.Build.0 = Debug-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|x86.ActiveCfg = Debug-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug-InnerLoop|x86.Build.0 = Debug-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|Any CPU.Build.0 = Release|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x64.ActiveCfg = Release|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x64.Build.0 = Release|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x86.ActiveCfg = Release|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|x86.Build.0 = Release|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release-InnerLoop|Any CPU.ActiveCfg = Release-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release-InnerLoop|Any CPU.Build.0 = Release-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release-InnerLoop|x64.ActiveCfg = Release-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release-InnerLoop|x64.Build.0 = Release-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release-InnerLoop|x86.ActiveCfg = Release-InnerLoop|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release-InnerLoop|x86.Build.0 = Release-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x64.ActiveCfg = Debug|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x64.Build.0 = Debug|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x86.ActiveCfg = Debug|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug|x86.Build.0 = Debug|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|Any CPU.ActiveCfg = Debug-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|Any CPU.Build.0 = Debug-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|x64.ActiveCfg = Debug-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|x64.Build.0 = Debug-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|x86.ActiveCfg = Debug-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug-InnerLoop|x86.Build.0 = Debug-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release|Any CPU.Build.0 = Release|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release|x64.ActiveCfg = Release|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release|x64.Build.0 = Release|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release|x86.ActiveCfg = Release|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release|x86.Build.0 = Release|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release-InnerLoop|Any CPU.ActiveCfg = Release-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release-InnerLoop|Any CPU.Build.0 = Release-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release-InnerLoop|x64.ActiveCfg = Release-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release-InnerLoop|x64.Build.0 = Release-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release-InnerLoop|x86.ActiveCfg = Release-InnerLoop|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release-InnerLoop|x86.Build.0 = Release-InnerLoop|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

2
shared-infrastructure

@ -1 +1 @@
Subproject commit 48e73f455f15eafefbe3175efc7433e5f277e506
Subproject commit 9b94ebc4be9b7a8d7620c257e6ee485455973332

7
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -12,6 +12,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;
@ -56,7 +57,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
{
@ -199,6 +200,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>
@ -214,6 +216,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>
@ -229,6 +232,7 @@ namespace SixLabors.ImageSharp.Advanced
AotCompileImageEncoder<TPixel, JpegEncoder>();
AotCompileImageEncoder<TPixel, PngEncoder>();
AotCompileImageEncoder<TPixel, TgaEncoder>();
AotCompileImageEncoder<TPixel, TiffEncoder>();
}
/// <summary>
@ -244,6 +248,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
}
}

7
src/ImageSharp/Common/Extensions/StreamExtensions.cs

@ -4,7 +4,6 @@
using System;
using System.Buffers;
using System.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp
{
@ -72,12 +71,6 @@ namespace SixLabors.ImageSharp
}
}
public static void Read(this Stream stream, IManagedByteBuffer buffer)
=> stream.Read(buffer.Array, 0, buffer.Length());
public static void Write(this Stream stream, IManagedByteBuffer buffer)
=> stream.Write(buffer.Array, 0, buffer.Length());
#if !SUPPORTS_SPAN_STREAM
// This is a port of the CoreFX implementation and is MIT Licensed:
// https://github.com/dotnet/corefx/blob/17300169760c61a90cab8d913636c1058a30a8c1/src/Common/src/CoreLib/System/IO/Stream.cs#L742

4
src/ImageSharp/Common/Helpers/DebugGuard.cs

@ -37,7 +37,7 @@ namespace SixLabors
/// <paramref name="target"/> has a different size than <paramref name="other"/>
/// </exception>
[Conditional("DEBUG")]
public static void MustBeSameSized<T>(Span<T> target, Span<T> other, string parameterName)
public static void MustBeSameSized<T>(ReadOnlySpan<T> target, ReadOnlySpan<T> other, string parameterName)
where T : struct
{
if (target.Length != other.Length)
@ -57,7 +57,7 @@ namespace SixLabors
/// <paramref name="target"/> has less items than <paramref name="minSpan"/>
/// </exception>
[Conditional("DEBUG")]
public static void MustBeSizedAtLeast<T>(Span<T> target, Span<T> minSpan, string parameterName)
public static void MustBeSizedAtLeast<T>(ReadOnlySpan<T> target, ReadOnlySpan<T> minSpan, string parameterName)
where T : struct
{
if (target.Length < minSpan.Length)

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

@ -23,6 +23,16 @@ namespace SixLabors.ImageSharp
private const int ShuffleAlphaControl = 0b_11_11_11_11;
#endif
#if !SUPPORTS_BITOPERATIONS
private static ReadOnlySpan<byte> Log2DeBruijn => new byte[32]
{
00, 09, 01, 10, 13, 21, 02, 29,
11, 14, 16, 18, 22, 25, 03, 30,
08, 12, 20, 28, 15, 17, 24, 07,
19, 27, 23, 06, 26, 05, 04, 31
};
#endif
/// <summary>
/// Determine the Greatest CommonDivisor (GCD) of two numbers.
/// </summary>
@ -756,7 +766,7 @@ namespace SixLabors.ImageSharp
/// 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>
/// <c>byte(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)</c>
/// is widened and added onto <paramref name="accumulator"/> as such:
/// <code>
/// accumulator += i32(1, 2, 3, 4);
@ -825,5 +835,49 @@ namespace SixLabors.ImageSharp
return Sse2.ConvertToInt32(vsum);
}
#endif
/// <summary>
/// Calculates floored log of the specified value, base 2.
/// Note that by convention, input value 0 returns 0 since Log(0) is undefined.
/// </summary>
/// <param name="value">The value.</param>
public static int Log2(uint value)
{
#if SUPPORTS_BITOPERATIONS
return BitOperations.Log2(value);
#else
return Log2SoftwareFallback(value);
#endif
}
#if !SUPPORTS_BITOPERATIONS
/// <summary>
/// Calculates floored log of the specified value, base 2.
/// Note that by convention, input value 0 returns 0 since Log(0) is undefined.
/// Bit hacking with deBruijn sequence, extremely fast yet does not use any intrinsics so will work on every platform/runtime.
/// </summary>
/// <remarks>
/// Description of this bit hacking can be found here:
/// https://cstheory.stackexchange.com/questions/19524/using-the-de-bruijn-sequence-to-find-the-lceil-log-2-v-rceil-of-an-integer
/// </remarks>
/// <param name="value">The value.</param>
private static int Log2SoftwareFallback(uint value)
{
// No AggressiveInlining due to large method size
// Has conventional contract 0->0 (Log(0) is undefined) by default, no need for if checking
// Fill trailing zeros with ones, eg 00010010 becomes 00011111
value |= value >> 01;
value |= value >> 02;
value |= value >> 04;
value |= value >> 08;
value |= value >> 16;
// uint.MaxValue >> 27 is always in range [0 - 31] so we use Unsafe.AddByteOffset to avoid bounds check
return Unsafe.AddByteOffset(
ref MemoryMarshal.GetReference(Log2DeBruijn),
(IntPtr)(int)((value * 0x07C4ACDDu) >> 27)); // uint|long -> IntPtr cast on 32-bit platforms does expensive overflow checks not needed here
}
#endif
}
}

25
src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs

@ -532,6 +532,7 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Performs a multiplication and an addition of the <see cref="Vector256{T}"/>.
/// </summary>
/// <remarks>ret = (vm0 * vm1) + va</remarks>
/// <param name="va">The vector to add to the intermediate result.</param>
/// <param name="vm0">The first vector to multiply.</param>
/// <param name="vm1">The second vector to multiply.</param>
@ -552,6 +553,30 @@ namespace SixLabors.ImageSharp
}
}
/// <summary>
/// Performs a multiplication and a substraction of the <see cref="Vector256{T}"/>.
/// </summary>
/// <remarks>ret = (vm0 * vm1) - vs</remarks>
/// <param name="vs">The vector to substract from the intermediate result.</param>
/// <param name="vm0">The first vector to multiply.</param>
/// <param name="vm1">The second vector to multiply.</param>
/// <returns>The <see cref="Vector256{T}"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Vector256<float> MultiplySubstract(
in Vector256<float> vs,
in Vector256<float> vm0,
in Vector256<float> vm1)
{
if (Fma.IsSupported)
{
return Fma.MultiplySubtract(vm1, vm0, vs);
}
else
{
return Avx.Subtract(Avx.Multiply(vm0, vm1), vs);
}
}
/// <summary>
/// <see cref="ByteToNormalizedFloat"/> as many elements as possible, slicing them down (keeping the remainder).
/// </summary>

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
{

4
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.
@ -222,7 +222,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// The number of compressed bytes added to the output, or 0 if either
/// <see cref="IsNeedingInput"/> or <see cref="IsFinished"/> returns true or length is zero.
/// </returns>
public int Deflate(byte[] output, int offset, int length)
public int Deflate(Span<byte> output, int offset, int length)
{
int origLength = length;

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.

43
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
@ -130,9 +130,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// This array contains the part of the uncompressed stream that
/// is of relevance. The current character is indexed by strstart.
/// </summary>
private IManagedByteBuffer windowMemoryOwner;
private IMemoryOwner<byte> windowMemoryOwner;
private MemoryHandle windowMemoryHandle;
private readonly byte[] window;
private readonly Memory<byte> window;
private readonly byte* pinnedWindowPointer;
private int maxChain;
@ -153,19 +153,19 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
// Create pinned pointers to the various buffers to allow indexing
// without bounds checks.
this.windowMemoryOwner = memoryAllocator.AllocateManagedByteBuffer(2 * DeflaterConstants.WSIZE);
this.window = this.windowMemoryOwner.Array;
this.windowMemoryHandle = this.windowMemoryOwner.Memory.Pin();
this.windowMemoryOwner = memoryAllocator.Allocate<byte>(2 * DeflaterConstants.WSIZE);
this.window = this.windowMemoryOwner.Memory;
this.windowMemoryHandle = this.window.Pin();
this.pinnedWindowPointer = (byte*)this.windowMemoryHandle.Pointer;
this.headMemoryOwner = memoryAllocator.Allocate<short>(DeflaterConstants.HASH_SIZE);
this.head = this.headMemoryOwner.Memory;
this.headMemoryHandle = this.headMemoryOwner.Memory.Pin();
this.headMemoryHandle = this.head.Pin();
this.pinnedHeadPointer = (short*)this.headMemoryHandle.Pointer;
this.prevMemoryOwner = memoryAllocator.Allocate<short>(DeflaterConstants.WSIZE);
this.prev = this.prevMemoryOwner.Memory;
this.prevMemoryHandle = this.prevMemoryOwner.Memory.Pin();
this.prevMemoryHandle = this.prev.Pin();
this.pinnedPrevPointer = (short*)this.prevMemoryHandle.Pointer;
// We start at index 1, to avoid an implementation deficiency, that
@ -303,7 +303,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
case DeflaterConstants.DEFLATE_STORED:
if (this.strstart > this.blockStart)
{
this.huffman.FlushStoredBlock(this.window, this.blockStart, this.strstart - this.blockStart, false);
this.huffman.FlushStoredBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, false);
this.blockStart = this.strstart;
}
@ -313,7 +313,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
case DeflaterConstants.DEFLATE_FAST:
if (this.strstart > this.blockStart)
{
this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, false);
this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, false);
this.blockStart = this.strstart;
}
@ -327,7 +327,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
if (this.strstart > this.blockStart)
{
this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, false);
this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, false);
this.blockStart = this.strstart;
}
@ -362,7 +362,10 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
more = this.inputEnd - this.inputOff;
}
Buffer.BlockCopy(this.inputBuf, this.inputOff, this.window, this.strstart + this.lookahead, more);
Unsafe.CopyBlockUnaligned(
ref this.window.Span[this.strstart + this.lookahead],
ref this.inputBuf[this.inputOff],
unchecked((uint)more));
this.inputOff += more;
this.lookahead += more;
@ -426,7 +429,11 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
private void SlideWindow()
{
Unsafe.CopyBlockUnaligned(ref this.window[0], ref this.window[DeflaterConstants.WSIZE], DeflaterConstants.WSIZE);
Unsafe.CopyBlockUnaligned(
ref this.window.Span[0],
ref this.window.Span[DeflaterConstants.WSIZE],
DeflaterConstants.WSIZE);
this.matchStart -= DeflaterConstants.WSIZE;
this.strstart -= DeflaterConstants.WSIZE;
this.blockStart -= DeflaterConstants.WSIZE;
@ -663,7 +670,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
lastBlock = false;
}
this.huffman.FlushStoredBlock(this.window, this.blockStart, storedLength, lastBlock);
this.huffman.FlushStoredBlock(this.window.Span, this.blockStart, storedLength, lastBlock);
this.blockStart += storedLength;
return !(lastBlock || storedLength == 0);
}
@ -683,7 +690,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
if (this.lookahead == 0)
{
// We are flushing everything
this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, finish);
this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, finish);
this.blockStart = this.strstart;
return false;
}
@ -743,7 +750,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
if (this.huffman.IsFull())
{
bool lastBlock = finish && (this.lookahead == 0);
this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, lastBlock);
this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, lastBlock);
this.blockStart = this.strstart;
return !lastBlock;
}
@ -771,7 +778,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.prevAvailable = false;
// We are flushing everything
this.huffman.FlushBlock(this.window, this.blockStart, this.strstart - this.blockStart, finish);
this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, finish);
this.blockStart = this.strstart;
return false;
}
@ -846,7 +853,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
}
bool lastBlock = finish && (this.lookahead == 0) && !this.prevAvailable;
this.huffman.FlushBlock(this.window, this.blockStart, len, lastBlock);
this.huffman.FlushBlock(this.window.Span, this.blockStart, len, lastBlock);
this.blockStart += len;
return !lastBlock;
}

30
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.
@ -41,11 +41,11 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
private Tree blTree;
// Buffer for distances
private readonly IMemoryOwner<short> distanceManagedBuffer;
private readonly IMemoryOwner<short> distanceMemoryOwner;
private readonly short* pinnedDistanceBuffer;
private MemoryHandle distanceBufferHandle;
private readonly IMemoryOwner<short> literalManagedBuffer;
private readonly IMemoryOwner<short> literalMemoryOwner;
private readonly short* pinnedLiteralBuffer;
private MemoryHandle literalBufferHandle;
@ -65,12 +65,12 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.distTree = new Tree(memoryAllocator, DistanceNumber, 1, 15);
this.blTree = new Tree(memoryAllocator, BitLengthNumber, 4, 7);
this.distanceManagedBuffer = memoryAllocator.Allocate<short>(BufferSize);
this.distanceBufferHandle = this.distanceManagedBuffer.Memory.Pin();
this.distanceMemoryOwner = memoryAllocator.Allocate<short>(BufferSize);
this.distanceBufferHandle = this.distanceMemoryOwner.Memory.Pin();
this.pinnedDistanceBuffer = (short*)this.distanceBufferHandle.Pointer;
this.literalManagedBuffer = memoryAllocator.Allocate<short>(BufferSize);
this.literalBufferHandle = this.literalManagedBuffer.Memory.Pin();
this.literalMemoryOwner = memoryAllocator.Allocate<short>(BufferSize);
this.literalBufferHandle = this.literalMemoryOwner.Memory.Pin();
this.pinnedLiteralBuffer = (short*)this.literalBufferHandle.Pointer;
}
@ -239,7 +239,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <param name="storedLength">Count of bytes to write</param>
/// <param name="lastBlock">True if this is the last block</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void FlushStoredBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock)
public void FlushStoredBlock(ReadOnlySpan<byte> stored, int storedOffset, int storedLength, bool lastBlock)
{
this.Pending.WriteBits((DeflaterConstants.STORED_BLOCK << 1) + (lastBlock ? 1 : 0), 3);
this.Pending.AlignToByte();
@ -256,7 +256,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <param name="storedOffset">Index of first byte to flush</param>
/// <param name="storedLength">Count of bytes to flush</param>
/// <param name="lastBlock">True if this is the last block</param>
public void FlushBlock(byte[] stored, int storedOffset, int storedLength, bool lastBlock)
public void FlushBlock(ReadOnlySpan<byte> stored, int storedOffset, int storedLength, bool lastBlock)
{
this.literalTree.Frequencies[EofSymbol]++;
@ -286,13 +286,13 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
+ this.extraBits;
int static_len = this.extraBits;
ref byte staticLLengthRef = ref MemoryMarshal.GetReference<byte>(StaticLLength);
ref byte staticLLengthRef = ref MemoryMarshal.GetReference(StaticLLength);
for (int i = 0; i < LiteralNumber; i++)
{
static_len += this.literalTree.Frequencies[i] * Unsafe.Add(ref staticLLengthRef, i);
}
ref byte staticDLengthRef = ref MemoryMarshal.GetReference<byte>(StaticDLength);
ref byte staticDLengthRef = ref MemoryMarshal.GetReference(StaticDLength);
for (int i = 0; i < DistanceNumber; i++)
{
static_len += this.distTree.Frequencies[i] * Unsafe.Add(ref staticDLengthRef, i);
@ -419,9 +419,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
this.Pending.Dispose();
this.distanceBufferHandle.Dispose();
this.distanceManagedBuffer.Dispose();
this.distanceMemoryOwner.Dispose();
this.literalBufferHandle.Dispose();
this.literalManagedBuffer.Dispose();
this.literalMemoryOwner.Dispose();
this.literalTree.Dispose();
this.blTree.Dispose();
@ -484,7 +484,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
private IMemoryOwner<short> frequenciesMemoryOwner;
private MemoryHandle frequenciesMemoryHandle;
private IManagedByteBuffer lengthsMemoryOwner;
private IMemoryOwner<byte> lengthsMemoryOwner;
private MemoryHandle lengthsMemoryHandle;
public Tree(MemoryAllocator memoryAllocator, int elements, int minCodes, int maxLength)
@ -498,7 +498,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.frequenciesMemoryHandle = this.frequenciesMemoryOwner.Memory.Pin();
this.Frequencies = (short*)this.frequenciesMemoryHandle.Pointer;
this.lengthsMemoryOwner = memoryAllocator.AllocateManagedByteBuffer(elements);
this.lengthsMemoryOwner = memoryAllocator.Allocate<byte>(elements);
this.lengthsMemoryHandle = this.lengthsMemoryOwner.Memory.Pin();
this.Length = (byte*)this.lengthsMemoryHandle.Pointer;

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

@ -2,10 +2,11 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
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
@ -14,8 +15,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
internal sealed class DeflaterOutputStream : Stream
{
private const int BufferLength = 512;
private IManagedByteBuffer memoryOwner;
private readonly byte[] buffer;
private IMemoryOwner<byte> memoryOwner;
private readonly Memory<byte> buffer;
private Deflater deflater;
private readonly Stream rawStream;
private bool isDisposed;
@ -29,8 +30,8 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
public DeflaterOutputStream(MemoryAllocator memoryAllocator, Stream rawStream, int compressionLevel)
{
this.rawStream = rawStream;
this.memoryOwner = memoryAllocator.AllocateManagedByteBuffer(BufferLength);
this.buffer = this.memoryOwner.Array;
this.memoryOwner = memoryAllocator.Allocate<byte>(BufferLength);
this.buffer = this.memoryOwner.Memory;
this.deflater = new Deflater(memoryAllocator, compressionLevel);
}
@ -49,15 +50,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <inheritdoc/>
public override long Position
{
get
{
return this.rawStream.Position;
}
get => this.rawStream.Position;
set
{
throw new NotSupportedException();
}
set => throw new NotSupportedException();
}
/// <inheritdoc/>
@ -93,14 +88,14 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
while (flushing || !this.deflater.IsNeedingInput)
{
int deflateCount = this.deflater.Deflate(this.buffer, 0, BufferLength);
int deflateCount = this.deflater.Deflate(this.buffer.Span, 0, BufferLength);
if (deflateCount <= 0)
{
break;
}
this.rawStream.Write(this.buffer, 0, deflateCount);
this.rawStream.Write(this.buffer.Span.Slice(0, deflateCount));
}
if (!this.deflater.IsNeedingInput)
@ -114,13 +109,13 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
this.deflater.Finish();
while (!this.deflater.IsFinished)
{
int len = this.deflater.Deflate(this.buffer, 0, BufferLength);
int len = this.deflater.Deflate(this.buffer.Span, 0, BufferLength);
if (len <= 0)
{
break;
}
this.rawStream.Write(this.buffer, 0, len);
this.rawStream.Write(this.buffer.Span.Slice(0, len));
}
if (!this.deflater.IsFinished)

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

@ -4,18 +4,19 @@
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
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.
/// </summary>
internal sealed unsafe class DeflaterPendingBuffer : IDisposable
{
private readonly byte[] buffer;
private readonly Memory<byte> buffer;
private readonly byte* pinnedBuffer;
private IManagedByteBuffer bufferMemoryOwner;
private IMemoryOwner<byte> bufferMemoryOwner;
private MemoryHandle bufferMemoryHandle;
private int start;
@ -29,9 +30,9 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <param name="memoryAllocator">The memory allocator to use for buffer allocations.</param>
public DeflaterPendingBuffer(MemoryAllocator memoryAllocator)
{
this.bufferMemoryOwner = memoryAllocator.AllocateManagedByteBuffer(DeflaterConstants.PENDING_BUF_SIZE);
this.buffer = this.bufferMemoryOwner.Array;
this.bufferMemoryHandle = this.bufferMemoryOwner.Memory.Pin();
this.bufferMemoryOwner = memoryAllocator.Allocate<byte>(DeflaterConstants.PENDING_BUF_SIZE);
this.buffer = this.bufferMemoryOwner.Memory;
this.bufferMemoryHandle = this.buffer.Pin();
this.pinnedBuffer = (byte*)this.bufferMemoryHandle.Pointer;
}
@ -70,9 +71,13 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <param name="offset">The offset of first byte to write.</param>
/// <param name="length">The number of bytes to write.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void WriteBlock(byte[] block, int offset, int length)
public void WriteBlock(ReadOnlySpan<byte> block, int offset, int length)
{
Unsafe.CopyBlockUnaligned(ref this.buffer[this.end], ref block[offset], unchecked((uint)length));
Unsafe.CopyBlockUnaligned(
ref this.buffer.Span[this.end],
ref MemoryMarshal.GetReference(block.Slice(offset)),
unchecked((uint)length));
this.end += length;
}
@ -136,7 +141,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
/// <param name="offset">The offset into output array.</param>
/// <param name="length">The maximum number of bytes to store.</param>
/// <returns>The number of bytes flushed.</returns>
public int Flush(byte[] output, int offset, int length)
public int Flush(Span<byte> output, int offset, int length)
{
if (this.BitCount >= 8)
{
@ -149,13 +154,19 @@ namespace SixLabors.ImageSharp.Formats.Png.Zlib
{
length = this.end - this.start;
Unsafe.CopyBlockUnaligned(ref output[offset], ref this.buffer[this.start], unchecked((uint)length));
Unsafe.CopyBlockUnaligned(
ref output[offset],
ref this.buffer.Span[this.start],
unchecked((uint)length));
this.start = 0;
this.end = 0;
}
else
{
Unsafe.CopyBlockUnaligned(ref output[offset], ref this.buffer[this.start], unchecked((uint)length));
Unsafe.CopyBlockUnaligned(
ref output[offset],
ref this.buffer.Span[this.start],
unchecked((uint)length));
this.start += length;
}

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

13
src/ImageSharp/Configuration.cs

@ -10,6 +10,7 @@ using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Processing;
@ -39,7 +40,7 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Initializes a new instance of the <see cref="Configuration" /> class.
/// </summary>
/// <param name="configurationModules">A collection of configuration modules to register</param>
/// <param name="configurationModules">A collection of configuration modules to register.</param>
public Configuration(params IConfigurationModule[] configurationModules)
{
if (configurationModules != null)
@ -77,7 +78,7 @@ namespace SixLabors.ImageSharp
/// <summary>
/// Gets or sets the size of the buffer to use when working with streams.
/// Intitialized with <see cref="DefaultStreamProcessingBufferSize"/> by default.
/// Initialized with <see cref="DefaultStreamProcessingBufferSize"/> by default.
/// </summary>
public int StreamProcessingBufferSize
{
@ -94,9 +95,9 @@ namespace SixLabors.ImageSharp
}
/// <summary>
/// Gets a set of properties for the Congiguration.
/// Gets a set of properties for the Configuration.
/// </summary>
/// <remarks>This can be used for storing global settings and defaults to be accessable to processors.</remarks>
/// <remarks>This can be used for storing global settings and defaults to be accessible to processors.</remarks>
public IDictionary<object, object> Properties { get; } = new ConcurrentDictionary<object, object>();
/// <summary>
@ -180,6 +181,7 @@ namespace SixLabors.ImageSharp
/// <see cref="GifConfigurationModule"/>
/// <see cref="BmpConfigurationModule"/>.
/// <see cref="TgaConfigurationModule"/>.
/// <see cref="TiffConfigurationModule"/>.
/// </summary>
/// <returns>The default configuration of <see cref="Configuration"/>.</returns>
internal static Configuration CreateDefaultInstance()
@ -189,7 +191,8 @@ namespace SixLabors.ImageSharp
new JpegConfigurationModule(),
new GifConfigurationModule(),
new BmpConfigurationModule(),
new TgaConfigurationModule());
new TgaConfigurationModule(),
new TiffConfigurationModule());
}
}
}

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

@ -817,31 +817,29 @@ namespace SixLabors.ImageSharp.Formats.Bmp
padding = 4 - padding;
}
using (IManagedByteBuffer row = this.memoryAllocator.AllocateManagedByteBuffer(arrayWidth + padding, AllocationOptions.Clean))
using IMemoryOwner<byte> row = this.memoryAllocator.Allocate<byte>(arrayWidth + padding, AllocationOptions.Clean);
TPixel color = default;
Span<byte> rowSpan = row.GetSpan();
for (int y = 0; y < height; y++)
{
TPixel color = default;
Span<byte> rowSpan = row.GetSpan();
int newY = Invert(y, height, inverted);
this.stream.Read(rowSpan);
int offset = 0;
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
for (int y = 0; y < height; y++)
for (int x = 0; x < arrayWidth; x++)
{
int newY = Invert(y, height, inverted);
this.stream.Read(row.Array, 0, row.Length());
int offset = 0;
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
for (int x = 0; x < arrayWidth; x++)
int colOffset = x * ppb;
for (int shift = 0, newX = colOffset; shift < ppb && newX < width; shift++, newX++)
{
int colOffset = x * ppb;
for (int shift = 0, newX = colOffset; shift < ppb && newX < width; shift++, newX++)
{
int colorIndex = ((rowSpan[offset] >> (8 - bitsPerPixel - (shift * bitsPerPixel))) & mask) * bytesPerColorMapEntry;
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref colors[colorIndex]));
pixelRow[newX] = color;
}
int colorIndex = ((rowSpan[offset] >> (8 - bitsPerPixel - (shift * bitsPerPixel))) & mask) * bytesPerColorMapEntry;
offset++;
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref colors[colorIndex]));
pixelRow[newX] = color;
}
offset++;
}
}
}
@ -873,29 +871,29 @@ namespace SixLabors.ImageSharp.Formats.Bmp
int greenMaskBits = CountBits((uint)greenMask);
int blueMaskBits = CountBits((uint)blueMask);
using (IManagedByteBuffer buffer = this.memoryAllocator.AllocateManagedByteBuffer(stride))
using IMemoryOwner<byte> buffer = this.memoryAllocator.Allocate<byte>(stride);
Span<byte> bufferSpan = buffer.GetSpan();
for (int y = 0; y < height; y++)
{
for (int y = 0; y < height; y++)
{
this.stream.Read(buffer.Array, 0, stride);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
this.stream.Read(bufferSpan);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
int offset = 0;
for (int x = 0; x < width; x++)
{
short temp = BitConverter.ToInt16(buffer.Array, offset);
int offset = 0;
for (int x = 0; x < width; x++)
{
short temp = BinaryPrimitives.ReadInt16LittleEndian(bufferSpan.Slice(offset));
// Rescale values, so the values range from 0 to 255.
int r = (redMaskBits == 5) ? GetBytesFrom5BitValue((temp & redMask) >> rightShiftRedMask) : GetBytesFrom6BitValue((temp & redMask) >> rightShiftRedMask);
int g = (greenMaskBits == 5) ? GetBytesFrom5BitValue((temp & greenMask) >> rightShiftGreenMask) : GetBytesFrom6BitValue((temp & greenMask) >> rightShiftGreenMask);
int b = (blueMaskBits == 5) ? GetBytesFrom5BitValue((temp & blueMask) >> rightShiftBlueMask) : GetBytesFrom6BitValue((temp & blueMask) >> rightShiftBlueMask);
var rgb = new Rgb24((byte)r, (byte)g, (byte)b);
// Rescale values, so the values range from 0 to 255.
int r = (redMaskBits == 5) ? GetBytesFrom5BitValue((temp & redMask) >> rightShiftRedMask) : GetBytesFrom6BitValue((temp & redMask) >> rightShiftRedMask);
int g = (greenMaskBits == 5) ? GetBytesFrom5BitValue((temp & greenMask) >> rightShiftGreenMask) : GetBytesFrom6BitValue((temp & greenMask) >> rightShiftGreenMask);
int b = (blueMaskBits == 5) ? GetBytesFrom5BitValue((temp & blueMask) >> rightShiftBlueMask) : GetBytesFrom6BitValue((temp & blueMask) >> rightShiftBlueMask);
var rgb = new Rgb24((byte)r, (byte)g, (byte)b);
color.FromRgb24(rgb);
pixelRow[x] = color;
offset += 2;
}
color.FromRgb24(rgb);
pixelRow[x] = color;
offset += 2;
}
}
}
@ -928,20 +926,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp
where TPixel : unmanaged, IPixel<TPixel>
{
int padding = CalculatePadding(width, 3);
using IMemoryOwner<byte> row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, padding);
Span<byte> rowSpan = row.GetSpan();
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, padding))
for (int y = 0; y < height; y++)
{
for (int y = 0; y < height; y++)
{
this.stream.Read(row);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgr24Bytes(
this.Configuration,
row.GetSpan(),
pixelSpan,
width);
}
this.stream.Read(rowSpan);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgr24Bytes(
this.Configuration,
rowSpan,
pixelSpan,
width);
}
}
@ -957,20 +954,19 @@ namespace SixLabors.ImageSharp.Formats.Bmp
where TPixel : unmanaged, IPixel<TPixel>
{
int padding = CalculatePadding(width, 4);
using IMemoryOwner<byte> row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding);
Span<byte> rowSpan = row.GetSpan();
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding))
for (int y = 0; y < height; y++)
{
for (int y = 0; y < height; y++)
{
this.stream.Read(row);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(
this.Configuration,
row.GetSpan(),
pixelSpan,
width);
}
this.stream.Read(rowSpan);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(
this.Configuration,
rowSpan,
pixelSpan,
width);
}
}
@ -987,87 +983,85 @@ namespace SixLabors.ImageSharp.Formats.Bmp
where TPixel : unmanaged, IPixel<TPixel>
{
int padding = CalculatePadding(width, 4);
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding))
using (IMemoryOwner<Bgra32> bgraRow = this.memoryAllocator.Allocate<Bgra32>(width))
using IMemoryOwner<byte> row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, padding);
using IMemoryOwner<Bgra32> bgraRow = this.memoryAllocator.Allocate<Bgra32>(width);
Span<byte> rowSpan = row.GetSpan();
Span<Bgra32> bgraRowSpan = bgraRow.GetSpan();
long currentPosition = this.stream.Position;
bool hasAlpha = false;
// Loop though the rows checking each pixel. We start by assuming it's
// an BGR0 image. If we hit a non-zero alpha value, then we know it's
// actually a BGRA image, and change tactics accordingly.
for (int y = 0; y < height; y++)
{
Span<Bgra32> bgraRowSpan = bgraRow.GetSpan();
long currentPosition = this.stream.Position;
bool hasAlpha = false;
// Loop though the rows checking each pixel. We start by assuming it's
// an BGR0 image. If we hit a non-zero alpha value, then we know it's
// actually a BGRA image, and change tactics accordingly.
for (int y = 0; y < height; y++)
{
this.stream.Read(row);
this.stream.Read(rowSpan);
PixelOperations<Bgra32>.Instance.FromBgra32Bytes(
this.Configuration,
row.GetSpan(),
bgraRowSpan,
width);
// Check each pixel in the row to see if it has an alpha value.
for (int x = 0; x < width; x++)
{
Bgra32 bgra = bgraRowSpan[x];
if (bgra.A > 0)
{
hasAlpha = true;
break;
}
}
PixelOperations<Bgra32>.Instance.FromBgra32Bytes(
this.Configuration,
rowSpan,
bgraRowSpan,
width);
if (hasAlpha)
// Check each pixel in the row to see if it has an alpha value.
for (int x = 0; x < width; x++)
{
Bgra32 bgra = bgraRowSpan[x];
if (bgra.A > 0)
{
hasAlpha = true;
break;
}
}
// Reset our stream for a second pass.
this.stream.Position = currentPosition;
// Process the pixels in bulk taking the raw alpha component value.
if (hasAlpha)
{
for (int y = 0; y < height; y++)
{
this.stream.Read(row);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(
this.Configuration,
row.GetSpan(),
pixelSpan,
width);
}
return;
break;
}
}
// Reset our stream for a second pass.
this.stream.Position = currentPosition;
// Slow path. We need to set each alpha component value to fully opaque.
// Process the pixels in bulk taking the raw alpha component value.
if (hasAlpha)
{
for (int y = 0; y < height; y++)
{
this.stream.Read(row);
PixelOperations<Bgra32>.Instance.FromBgra32Bytes(
this.Configuration,
row.GetSpan(),
bgraRowSpan,
width);
this.stream.Read(rowSpan);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
for (int x = 0; x < width; x++)
{
Bgra32 bgra = bgraRowSpan[x];
bgra.A = byte.MaxValue;
ref TPixel pixel = ref pixelSpan[x];
pixel.FromBgra32(bgra);
}
PixelOperations<TPixel>.Instance.FromBgra32Bytes(
this.Configuration,
rowSpan,
pixelSpan,
width);
}
return;
}
// Slow path. We need to set each alpha component value to fully opaque.
for (int y = 0; y < height; y++)
{
this.stream.Read(rowSpan);
PixelOperations<Bgra32>.Instance.FromBgra32Bytes(
this.Configuration,
rowSpan,
bgraRowSpan,
width);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
for (int x = 0; x < width; x++)
{
Bgra32 bgra = bgraRowSpan[x];
bgra.A = byte.MaxValue;
ref TPixel pixel = ref pixelSpan[x];
pixel.FromBgra32(bgra);
}
}
}
@ -1108,44 +1102,44 @@ namespace SixLabors.ImageSharp.Formats.Bmp
bool unusualBitMask = bitsRedMask > 8 || bitsGreenMask > 8 || bitsBlueMask > 8 || invMaxValueAlpha > 8;
using (IManagedByteBuffer buffer = this.memoryAllocator.AllocateManagedByteBuffer(stride))
using IMemoryOwner<byte> buffer = this.memoryAllocator.Allocate<byte>(stride);
Span<byte> bufferSpan = buffer.GetSpan();
for (int y = 0; y < height; y++)
{
for (int y = 0; y < height; y++)
this.stream.Read(bufferSpan);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
int offset = 0;
for (int x = 0; x < width; x++)
{
this.stream.Read(buffer.Array, 0, stride);
int newY = Invert(y, height, inverted);
Span<TPixel> pixelRow = pixels.GetRowSpan(newY);
uint temp = BinaryPrimitives.ReadUInt32LittleEndian(bufferSpan.Slice(offset));
int offset = 0;
for (int x = 0; x < width; x++)
if (unusualBitMask)
{
uint temp = BitConverter.ToUInt32(buffer.Array, offset);
if (unusualBitMask)
{
uint r = (uint)(temp & redMask) >> rightShiftRedMask;
uint g = (uint)(temp & greenMask) >> rightShiftGreenMask;
uint b = (uint)(temp & blueMask) >> rightShiftBlueMask;
float alpha = alphaMask != 0 ? invMaxValueAlpha * ((uint)(temp & alphaMask) >> rightShiftAlphaMask) : 1.0f;
var vector4 = new Vector4(
r * invMaxValueRed,
g * invMaxValueGreen,
b * invMaxValueBlue,
alpha);
color.FromVector4(vector4);
}
else
{
byte r = (byte)((temp & redMask) >> rightShiftRedMask);
byte g = (byte)((temp & greenMask) >> rightShiftGreenMask);
byte b = (byte)((temp & blueMask) >> rightShiftBlueMask);
byte a = alphaMask != 0 ? (byte)((temp & alphaMask) >> rightShiftAlphaMask) : (byte)255;
color.FromRgba32(new Rgba32(r, g, b, a));
}
pixelRow[x] = color;
offset += 4;
uint r = (uint)(temp & redMask) >> rightShiftRedMask;
uint g = (uint)(temp & greenMask) >> rightShiftGreenMask;
uint b = (uint)(temp & blueMask) >> rightShiftBlueMask;
float alpha = alphaMask != 0 ? invMaxValueAlpha * ((uint)(temp & alphaMask) >> rightShiftAlphaMask) : 1.0f;
var vector4 = new Vector4(
r * invMaxValueRed,
g * invMaxValueGreen,
b * invMaxValueBlue,
alpha);
color.FromVector4(vector4);
}
else
{
byte r = (byte)((temp & redMask) >> rightShiftRedMask);
byte g = (byte)((temp & greenMask) >> rightShiftGreenMask);
byte b = (byte)((temp & blueMask) >> rightShiftBlueMask);
byte a = alphaMask != 0 ? (byte)((temp & alphaMask) >> rightShiftAlphaMask) : (byte)255;
color.FromRgba32(new Rgba32(r, g, b, a));
}
pixelRow[x] = color;
offset += 4;
}
}
}

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

@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.memoryAllocator = memoryAllocator;
this.bitsPerPixel = options.BitsPerPixel;
this.writeV4Header = options.SupportTransparency;
this.quantizer = options.Quantizer ?? KnownQuantizers.Wu;
this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
}
/// <summary>
@ -257,7 +257,8 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
}
private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel) => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding);
private IMemoryOwner<byte> AllocateRow(int width, int bytesPerPixel)
=> this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, this.padding);
/// <summary>
/// Writes the 32bit color palette to the stream.
@ -268,18 +269,18 @@ namespace SixLabors.ImageSharp.Formats.Bmp
private void Write32Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4))
using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 4);
Span<byte> rowSpan = row.GetSpan();
for (int y = pixels.Height - 1; y >= 0; y--)
{
for (int y = pixels.Height - 1; y >= 0; y--)
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra32Bytes(
this.configuration,
pixelSpan,
row.GetSpan(),
pixelSpan.Length);
stream.Write(row.Array, 0, row.Length());
}
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra32Bytes(
this.configuration,
pixelSpan,
rowSpan,
pixelSpan.Length);
stream.Write(rowSpan);
}
}
@ -294,18 +295,18 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
int width = pixels.Width;
int rowBytesWithoutPadding = width * 3;
using (IManagedByteBuffer row = this.AllocateRow(width, 3))
using IMemoryOwner<byte> row = this.AllocateRow(width, 3);
Span<byte> rowSpan = row.GetSpan();
for (int y = pixels.Height - 1; y >= 0; y--)
{
for (int y = pixels.Height - 1; y >= 0; y--)
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgr24Bytes(
this.configuration,
pixelSpan,
row.Slice(0, rowBytesWithoutPadding),
width);
stream.Write(row.Array, 0, row.Length());
}
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgr24Bytes(
this.configuration,
pixelSpan,
row.Slice(0, rowBytesWithoutPadding),
width);
stream.Write(rowSpan);
}
}
@ -320,20 +321,20 @@ namespace SixLabors.ImageSharp.Formats.Bmp
{
int width = pixels.Width;
int rowBytesWithoutPadding = width * 2;
using (IManagedByteBuffer row = this.AllocateRow(width, 2))
using IMemoryOwner<byte> row = this.AllocateRow(width, 2);
Span<byte> rowSpan = row.GetSpan();
for (int y = pixels.Height - 1; y >= 0; y--)
{
for (int y = pixels.Height - 1; y >= 0; y--)
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra5551Bytes(
this.configuration,
pixelSpan,
row.Slice(0, rowBytesWithoutPadding),
pixelSpan.Length);
PixelOperations<TPixel>.Instance.ToBgra5551Bytes(
this.configuration,
pixelSpan,
row.Slice(0, rowBytesWithoutPadding),
pixelSpan.Length);
stream.Write(row.Array, 0, row.Length());
}
stream.Write(rowSpan);
}
}
@ -347,17 +348,16 @@ namespace SixLabors.ImageSharp.Formats.Bmp
where TPixel : unmanaged, IPixel<TPixel>
{
bool isL8 = typeof(TPixel) == typeof(L8);
using (IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize8Bit, AllocationOptions.Clean))
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize8Bit, AllocationOptions.Clean);
Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
if (isL8)
{
Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
if (isL8)
{
this.Write8BitGray(stream, image, colorPalette);
}
else
{
this.Write8BitColor(stream, image, colorPalette);
}
this.Write8BitGray(stream, image, colorPalette);
}
else
{
this.Write8BitColor(stream, image, colorPalette);
}
}
@ -441,7 +441,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
MaxColors = 16
});
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize4Bit, AllocationOptions.Clean);
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize4Bit, AllocationOptions.Clean);
Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span;
@ -485,7 +485,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
MaxColors = 2
});
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(image, image.Bounds());
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.AllocateManagedByteBuffer(ColorPaletteSize1Bit, AllocationOptions.Clean);
using IMemoryOwner<byte> colorPaletteBuffer = this.memoryAllocator.Allocate<byte>(ColorPaletteSize1Bit, AllocationOptions.Clean);
Span<byte> colorPalette = colorPaletteBuffer.GetSpan();
ReadOnlySpan<TPixel> quantizedColorPalette = quantized.Palette.Span;

32
src/ImageSharp/Formats/Gif/GifDecoderCore.cs

@ -2,12 +2,13 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
@ -33,7 +34,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// The global color table.
/// </summary>
private IManagedByteBuffer globalColorTable;
private IMemoryOwner<byte> globalColorTable;
/// <summary>
/// The area to restore.
@ -323,12 +324,12 @@ namespace SixLabors.ImageSharp.Formats.Gif
continue;
}
using (IManagedByteBuffer commentsBuffer = this.MemoryAllocator.AllocateManagedByteBuffer(length))
{
this.stream.Read(commentsBuffer.Array, 0, length);
string commentPart = GifConstants.Encoding.GetString(commentsBuffer.Array, 0, length);
stringBuilder.Append(commentPart);
}
using IMemoryOwner<byte> commentsBuffer = this.MemoryAllocator.Allocate<byte>(length);
Span<byte> commentsSpan = commentsBuffer.GetSpan();
this.stream.Read(commentsSpan);
string commentPart = GifConstants.Encoding.GetString(commentsSpan);
stringBuilder.Append(commentPart);
}
if (stringBuilder.Length > 0)
@ -348,7 +349,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
this.ReadImageDescriptor();
IManagedByteBuffer localColorTable = null;
IMemoryOwner<byte> localColorTable = null;
Buffer2D<byte> indices = null;
try
{
@ -356,8 +357,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
if (this.imageDescriptor.LocalColorTableFlag)
{
int length = this.imageDescriptor.LocalColorTableSize * 3;
localColorTable = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(length, AllocationOptions.Clean);
this.stream.Read(localColorTable.Array, 0, length);
localColorTable = this.Configuration.MemoryAllocator.Allocate<byte>(length, AllocationOptions.Clean);
this.stream.Read(localColorTable.GetSpan());
}
indices = this.Configuration.MemoryAllocator.Allocate2D<byte>(this.imageDescriptor.Width, this.imageDescriptor.Height, AllocationOptions.Clean);
@ -441,6 +442,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
int descriptorRight = descriptorLeft + descriptor.Width;
bool transFlag = this.graphicsControlExtension.TransparencyFlag;
byte transIndex = this.graphicsControlExtension.TransparencyIndex;
int colorTableMaxIdx = colorTable.Length - 1;
for (int y = descriptorTop; y < descriptorBottom && y < imageHeight; y++)
{
@ -487,7 +489,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
// #403 The left + width value can be larger than the image width
for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++)
{
int index = Unsafe.Add(ref indicesRowRef, x - descriptorLeft);
int index = Numerics.Clamp(Unsafe.Add(ref indicesRowRef, x - descriptorLeft), 0, colorTableMaxIdx);
ref TPixel pixel = ref Unsafe.Add(ref rowRef, x);
Rgb24 rgb = colorTable[index];
pixel.FromRgb24(rgb);
@ -497,7 +499,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
{
for (int x = descriptorLeft; x < descriptorRight && x < imageWidth; x++)
{
int index = Unsafe.Add(ref indicesRowRef, x - descriptorLeft);
int index = Numerics.Clamp(Unsafe.Add(ref indicesRowRef, x - descriptorLeft), 0, colorTableMaxIdx);
if (transIndex != index)
{
ref TPixel pixel = ref Unsafe.Add(ref rowRef, x);
@ -621,10 +623,10 @@ namespace SixLabors.ImageSharp.Formats.Gif
int globalColorTableLength = this.logicalScreenDescriptor.GlobalColorTableSize * 3;
this.gifMetadata.GlobalColorTableLength = globalColorTableLength;
this.globalColorTable = this.MemoryAllocator.AllocateManagedByteBuffer(globalColorTableLength, AllocationOptions.Clean);
this.globalColorTable = this.MemoryAllocator.Allocate<byte>(globalColorTableLength, AllocationOptions.Clean);
// Read the global color table data from the stream
stream.Read(this.globalColorTable.Array, 0, globalColorTableLength);
stream.Read(this.globalColorTable.GetSpan());
}
}
}

28
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -7,7 +7,6 @@ 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.Memory;
using SixLabors.ImageSharp.Metadata;
@ -54,7 +53,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <summary>
/// The pixel sampling strategy for global quantization.
/// </summary>
private IPixelSamplingStrategy pixelSamplingStrategy;
private readonly IPixelSamplingStrategy pixelSamplingStrategy;
/// <summary>
/// Initializes a new instance of the <see cref="GifEncoderCore"/> class.
@ -150,8 +149,8 @@ namespace SixLabors.ImageSharp.Formats.Gif
// The palette quantizer can reuse the same pixel map across multiple frames
// since the palette is unchanging. This allows a reduction of memory usage across
// multi frame gifs using a global palette.
EuclideanPixelMap<TPixel> pixelMap = default;
bool pixelMapSet = false;
PaletteQuantizer<TPixel> paletteFrameQuantizer = default;
bool quantizerInitialized = false;
for (int i = 0; i < image.Frames.Count; i++)
{
ImageFrame<TPixel> frame = image.Frames[i];
@ -166,17 +165,18 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
else
{
if (!pixelMapSet)
if (!quantizerInitialized)
{
pixelMapSet = true;
pixelMap = new EuclideanPixelMap<TPixel>(this.configuration, quantized.Palette);
quantizerInitialized = true;
paletteFrameQuantizer = new PaletteQuantizer<TPixel>(this.configuration, this.quantizer.Options, quantized.Palette);
}
using var paletteFrameQuantizer = new PaletteQuantizer<TPixel>(this.configuration, this.quantizer.Options, pixelMap);
using IndexedImageFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds());
this.WriteImageData(paletteQuantized, stream);
}
}
paletteFrameQuantizer.Dispose();
}
private void EncodeLocal<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel> quantized, Stream stream)
@ -305,7 +305,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
}
else
{
ratio = (byte)(((1 / vr) * 64) - 15);
ratio = (byte)((1 / vr * 64) - 15);
}
}
}
@ -349,7 +349,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
return;
}
for (var i = 0; i < metadata.Comments.Count; i++)
for (int i = 0; i < metadata.Comments.Count; i++)
{
string comment = metadata.Comments[i];
this.buffer[0] = GifConstants.ExtensionIntroducer;
@ -470,14 +470,16 @@ namespace SixLabors.ImageSharp.Formats.Gif
// The maximum number of colors for the bit depth
int colorTableLength = ColorNumerics.GetColorCountForBitDepth(this.bitDepth) * Unsafe.SizeOf<Rgb24>();
using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength, AllocationOptions.Clean);
using IMemoryOwner<byte> colorTable = this.memoryAllocator.Allocate<byte>(colorTableLength, AllocationOptions.Clean);
Span<byte> colorTableSpan = colorTable.GetSpan();
PixelOperations<TPixel>.Instance.ToRgb24Bytes(
this.configuration,
image.Palette.Span,
colorTable.GetSpan(),
colorTableSpan,
image.Palette.Length);
stream.Write(colorTable.Array, 0, colorTableLength);
stream.Write(colorTableSpan);
}
/// <summary>

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

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

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

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

228
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs

@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <summary>
/// Represents a Jpeg block with <see cref="float"/> coefficients.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
[StructLayout(LayoutKind.Explicit)]
internal partial struct Block8x8F : IEquatable<Block8x8F>
{
/// <summary>
@ -27,29 +27,69 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
public const int Size = 64;
#pragma warning disable SA1600 // ElementsMustBeDocumented
[FieldOffset(0)]
public Vector4 V0L;
[FieldOffset(16)]
public Vector4 V0R;
[FieldOffset(32)]
public Vector4 V1L;
[FieldOffset(48)]
public Vector4 V1R;
[FieldOffset(64)]
public Vector4 V2L;
[FieldOffset(80)]
public Vector4 V2R;
[FieldOffset(96)]
public Vector4 V3L;
[FieldOffset(112)]
public Vector4 V3R;
[FieldOffset(128)]
public Vector4 V4L;
[FieldOffset(144)]
public Vector4 V4R;
[FieldOffset(160)]
public Vector4 V5L;
[FieldOffset(176)]
public Vector4 V5R;
[FieldOffset(192)]
public Vector4 V6L;
[FieldOffset(208)]
public Vector4 V6R;
[FieldOffset(224)]
public Vector4 V7L;
[FieldOffset(240)]
public Vector4 V7R;
#if SUPPORTS_RUNTIME_INTRINSICS
/// <summary>
/// A number of rows of 8 scalar coefficients each in <see cref="Block8x8F"/>
/// </summary>
public const int RowCount = 8;
[FieldOffset(0)]
public Vector256<float> V0;
[FieldOffset(32)]
public Vector256<float> V1;
[FieldOffset(64)]
public Vector256<float> V2;
[FieldOffset(96)]
public Vector256<float> V3;
[FieldOffset(128)]
public Vector256<float> V4;
[FieldOffset(160)]
public Vector256<float> V5;
[FieldOffset(192)]
public Vector256<float> V6;
[FieldOffset(224)]
public Vector256<float> V7;
#endif
#pragma warning restore SA1600 // ElementsMustBeDocumented
/// <summary>
@ -278,14 +318,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
if (Avx.IsSupported)
{
var valueVec = Vector256.Create(value);
Unsafe.As<Vector4, Vector256<float>>(ref this.V0L) = Avx.Multiply(Unsafe.As<Vector4, Vector256<float>>(ref this.V0L), valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V1L) = Avx.Multiply(Unsafe.As<Vector4, Vector256<float>>(ref this.V1L), valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V2L) = Avx.Multiply(Unsafe.As<Vector4, Vector256<float>>(ref this.V2L), valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V3L) = Avx.Multiply(Unsafe.As<Vector4, Vector256<float>>(ref this.V3L), valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V4L) = Avx.Multiply(Unsafe.As<Vector4, Vector256<float>>(ref this.V4L), valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V5L) = Avx.Multiply(Unsafe.As<Vector4, Vector256<float>>(ref this.V5L), valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V6L) = Avx.Multiply(Unsafe.As<Vector4, Vector256<float>>(ref this.V6L), valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V7L) = Avx.Multiply(Unsafe.As<Vector4, Vector256<float>>(ref this.V7L), valueVec);
this.V0 = Avx.Multiply(this.V0, valueVec);
this.V1 = Avx.Multiply(this.V1, valueVec);
this.V2 = Avx.Multiply(this.V2, valueVec);
this.V3 = Avx.Multiply(this.V3, valueVec);
this.V4 = Avx.Multiply(this.V4, valueVec);
this.V5 = Avx.Multiply(this.V5, valueVec);
this.V6 = Avx.Multiply(this.V6, valueVec);
this.V7 = Avx.Multiply(this.V7, valueVec);
}
else
#endif
@ -319,45 +359,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx.IsSupported)
{
Unsafe.As<Vector4, Vector256<float>>(ref this.V0L)
= Avx.Multiply(
Unsafe.As<Vector4, Vector256<float>>(ref this.V0L),
Unsafe.As<Vector4, Vector256<float>>(ref other.V0L));
Unsafe.As<Vector4, Vector256<float>>(ref this.V1L)
= Avx.Multiply(
Unsafe.As<Vector4, Vector256<float>>(ref this.V1L),
Unsafe.As<Vector4, Vector256<float>>(ref other.V1L));
Unsafe.As<Vector4, Vector256<float>>(ref this.V2L)
= Avx.Multiply(
Unsafe.As<Vector4, Vector256<float>>(ref this.V2L),
Unsafe.As<Vector4, Vector256<float>>(ref other.V2L));
Unsafe.As<Vector4, Vector256<float>>(ref this.V3L)
= Avx.Multiply(
Unsafe.As<Vector4, Vector256<float>>(ref this.V3L),
Unsafe.As<Vector4, Vector256<float>>(ref other.V3L));
Unsafe.As<Vector4, Vector256<float>>(ref this.V4L)
= Avx.Multiply(
Unsafe.As<Vector4, Vector256<float>>(ref this.V4L),
Unsafe.As<Vector4, Vector256<float>>(ref other.V4L));
Unsafe.As<Vector4, Vector256<float>>(ref this.V5L)
= Avx.Multiply(
Unsafe.As<Vector4, Vector256<float>>(ref this.V5L),
Unsafe.As<Vector4, Vector256<float>>(ref other.V5L));
Unsafe.As<Vector4, Vector256<float>>(ref this.V6L)
= Avx.Multiply(
Unsafe.As<Vector4, Vector256<float>>(ref this.V6L),
Unsafe.As<Vector4, Vector256<float>>(ref other.V6L));
Unsafe.As<Vector4, Vector256<float>>(ref this.V7L)
= Avx.Multiply(
Unsafe.As<Vector4, Vector256<float>>(ref this.V7L),
Unsafe.As<Vector4, Vector256<float>>(ref other.V7L));
this.V0 = Avx.Multiply(this.V0, other.V0);
this.V1 = Avx.Multiply(this.V1, other.V1);
this.V2 = Avx.Multiply(this.V2, other.V2);
this.V3 = Avx.Multiply(this.V3, other.V3);
this.V4 = Avx.Multiply(this.V4, other.V4);
this.V5 = Avx.Multiply(this.V5, other.V5);
this.V6 = Avx.Multiply(this.V6, other.V6);
this.V7 = Avx.Multiply(this.V7, other.V7);
}
else
#endif
@ -392,14 +401,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
if (Avx.IsSupported)
{
var valueVec = Vector256.Create(value);
Unsafe.As<Vector4, Vector256<float>>(ref this.V0L) = Avx.Add(Unsafe.As<Vector4, Vector256<float>>(ref this.V0L), valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V1L) = Avx.Add(Unsafe.As<Vector4, Vector256<float>>(ref this.V1L), valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V2L) = Avx.Add(Unsafe.As<Vector4, Vector256<float>>(ref this.V2L), valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V3L) = Avx.Add(Unsafe.As<Vector4, Vector256<float>>(ref this.V3L), valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V4L) = Avx.Add(Unsafe.As<Vector4, Vector256<float>>(ref this.V4L), valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V5L) = Avx.Add(Unsafe.As<Vector4, Vector256<float>>(ref this.V5L), valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V6L) = Avx.Add(Unsafe.As<Vector4, Vector256<float>>(ref this.V6L), valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V7L) = Avx.Add(Unsafe.As<Vector4, Vector256<float>>(ref this.V7L), valueVec);
this.V0 = Avx.Add(this.V0, valueVec);
this.V1 = Avx.Add(this.V1, valueVec);
this.V2 = Avx.Add(this.V2, valueVec);
this.V3 = Avx.Add(this.V3, valueVec);
this.V4 = Avx.Add(this.V4, valueVec);
this.V5 = Avx.Add(this.V5, valueVec);
this.V6 = Avx.Add(this.V6, valueVec);
this.V7 = Avx.Add(this.V7, valueVec);
}
else
#endif
@ -468,81 +477,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
DivideRoundAll(ref dest, ref qt);
}
/// <summary>
/// Scales the 16x16 region represented by the 4 source blocks to the 8x8 DST block.
/// </summary>
/// <param name="destination">The destination block.</param>
/// <param name="source">The source block.</param>
public static unsafe void Scale16X16To8X8(ref Block8x8F destination, ReadOnlySpan<Block8x8F> source)
{
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported)
{
Scale16X16To8X8Vectorized(ref destination, source);
return;
}
#endif
Scale16X16To8X8Scalar(ref destination, source);
}
private static void Scale16X16To8X8Vectorized(ref Block8x8F destination, ReadOnlySpan<Block8x8F> source)
{
#if SUPPORTS_RUNTIME_INTRINSICS
Debug.Assert(Avx2.IsSupported, "AVX2 is required to execute this method");
var f2 = Vector256.Create(2f);
var f025 = Vector256.Create(0.25f);
Vector256<int> switchInnerDoubleWords = Unsafe.As<byte, Vector256<int>>(ref MemoryMarshal.GetReference(SimdUtils.HwIntrinsics.PermuteMaskSwitchInnerDWords8x32));
ref Vector256<float> destRef = ref Unsafe.As<Block8x8F, Vector256<float>>(ref destination);
for (int i = 0; i < 2; i++)
{
ref Vector256<float> in1 = ref Unsafe.As<Block8x8F, Vector256<float>>(ref Unsafe.Add(ref MemoryMarshal.GetReference(source), 2 * i));
ref Vector256<float> in2 = ref Unsafe.As<Block8x8F, Vector256<float>>(ref Unsafe.Add(ref MemoryMarshal.GetReference(source), (2 * i) + 1));
for (int j = 0; j < 8; j += 2)
{
Vector256<float> a = Unsafe.Add(ref in1, j);
Vector256<float> b = Unsafe.Add(ref in1, j + 1);
Vector256<float> c = Unsafe.Add(ref in2, j);
Vector256<float> d = Unsafe.Add(ref in2, j + 1);
Vector256<float> calc1 = Avx.Shuffle(a, c, 0b10_00_10_00);
Vector256<float> calc2 = Avx.Shuffle(a, c, 0b11_01_11_01);
Vector256<float> calc3 = Avx.Shuffle(b, d, 0b10_00_10_00);
Vector256<float> calc4 = Avx.Shuffle(b, d, 0b11_01_11_01);
Vector256<float> sum = Avx.Add(Avx.Add(calc1, calc2), Avx.Add(calc3, calc4));
Vector256<float> add = Avx.Add(sum, f2);
Vector256<float> res = Avx.Multiply(add, f025);
destRef = Avx2.PermuteVar8x32(res, switchInnerDoubleWords);
destRef = ref Unsafe.Add(ref destRef, 1);
}
}
#endif
}
private static unsafe void Scale16X16To8X8Scalar(ref Block8x8F destination, ReadOnlySpan<Block8x8F> source)
{
for (int i = 0; i < 4; i++)
{
int dstOff = ((i & 2) << 4) | ((i & 1) << 2);
Block8x8F iSource = source[i];
for (int y = 0; y < 4; y++)
{
for (int x = 0; x < 4; x++)
{
int j = (16 * y) + (2 * x);
float sum = iSource[j] + iSource[j + 1] + iSource[j + 8] + iSource[j + 9];
destination[(8 * y) + x + dstOff] = (sum + 2) * .25F;
}
}
}
}
[MethodImpl(InliningOptions.ShortMethod)]
private static void DivideRoundAll(ref Block8x8F a, ref Block8x8F b)
{
@ -553,19 +487,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
var vadd = Vector256.Create(.5F);
var vone = Vector256.Create(1f);
ref Vector256<float> aBase = ref Unsafe.AsRef(Unsafe.As<Vector4, Vector256<float>>(ref a.V0L));
ref Vector256<float> bBase = ref Unsafe.AsRef(Unsafe.As<Vector4, Vector256<float>>(ref b.V0L));
ref Vector256<float> aEnd = ref Unsafe.Add(ref aBase, 8);
do
for (int i = 0; i < RowCount; i++)
{
Vector256<float> voff = Avx.Multiply(Avx.Min(Avx.Max(vnegOne, aBase), vone), vadd);
Unsafe.Add(ref aBase, 0) = Avx.Add(Avx.Divide(aBase, bBase), voff);
aBase = ref Unsafe.Add(ref aBase, 1);
bBase = ref Unsafe.Add(ref bBase, 1);
ref Vector256<float> aRow = ref Unsafe.Add(ref a.V0, i);
ref Vector256<float> bRow = ref Unsafe.Add(ref b.V0, i);
Vector256<float> voff = Avx.Multiply(Avx.Min(Avx.Max(vnegOne, aRow), vone), vadd);
aRow = Avx.Add(Avx.Divide(aRow, bRow), voff);
}
while (Unsafe.IsAddressLessThan(ref aBase, ref aEnd));
}
else
#endif
@ -805,26 +733,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
Vector256<float> t0 = Avx.UnpackLow(r0, r1);
Vector256<float> t2 = Avx.UnpackLow(r2, r3);
Vector256<float> v = Avx.Shuffle(t0, t2, 0x4E);
Unsafe.As<Vector4, Vector256<float>>(ref d.V0L) = Avx.Blend(t0, v, 0xCC);
Unsafe.As<Vector4, Vector256<float>>(ref d.V1L) = Avx.Blend(t2, v, 0x33);
d.V0 = Avx.Blend(t0, v, 0xCC);
d.V1 = Avx.Blend(t2, v, 0x33);
Vector256<float> t4 = Avx.UnpackLow(r4, r5);
Vector256<float> t6 = Avx.UnpackLow(r6, r7);
v = Avx.Shuffle(t4, t6, 0x4E);
Unsafe.As<Vector4, Vector256<float>>(ref d.V4L) = Avx.Blend(t4, v, 0xCC);
Unsafe.As<Vector4, Vector256<float>>(ref d.V5L) = Avx.Blend(t6, v, 0x33);
d.V4 = Avx.Blend(t4, v, 0xCC);
d.V5 = Avx.Blend(t6, v, 0x33);
Vector256<float> t1 = Avx.UnpackHigh(r0, r1);
Vector256<float> t3 = Avx.UnpackHigh(r2, r3);
v = Avx.Shuffle(t1, t3, 0x4E);
Unsafe.As<Vector4, Vector256<float>>(ref d.V2L) = Avx.Blend(t1, v, 0xCC);
Unsafe.As<Vector4, Vector256<float>>(ref d.V3L) = Avx.Blend(t3, v, 0x33);
d.V2 = Avx.Blend(t1, v, 0xCC);
d.V3 = Avx.Blend(t3, v, 0x33);
Vector256<float> t5 = Avx.UnpackHigh(r4, r5);
Vector256<float> t7 = Avx.UnpackHigh(r6, r7);
v = Avx.Shuffle(t5, t7, 0x4E);
Unsafe.As<Vector4, Vector256<float>>(ref d.V6L) = Avx.Blend(t5, v, 0xCC);
Unsafe.As<Vector4, Vector256<float>>(ref d.V7L) = Avx.Blend(t7, v, 0x33);
d.V6 = Avx.Blend(t5, v, 0xCC);
d.V7 = Avx.Blend(t7, v, 0x33);
}
else
#endif

16
src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs

@ -1,12 +1,12 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// A compiled look-up table representation of a huffmanSpec.
/// Each value maps to a uint32 of which the 8 most significant bits hold the
/// codeword size in bits and the 24 least significant bits hold the codeword.
/// Each value maps to a int32 of which the 24 most significant bits hold the
/// codeword in bits and the 8 least significant bits hold the codeword size.
/// The maximum codeword size is 16 bits.
/// </summary>
internal readonly struct HuffmanLut
@ -44,17 +44,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
}
}
this.Values = new uint[maxValue + 1];
this.Values = new int[maxValue + 1];
int code = 0;
int k = 0;
for (int i = 0; i < spec.Count.Length; i++)
{
int bits = (i + 1) << 24;
int len = i + 1;
for (int j = 0; j < spec.Count[i]; j++)
{
this.Values[spec.Values[k]] = (uint)(bits | code);
this.Values[spec.Values[k]] = len | (code << 8);
code++;
k++;
}
@ -66,6 +66,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// <summary>
/// Gets the collection of huffman values.
/// </summary>
public uint[] Values { get; }
public int[] Values { get; }
}
}
}

524
src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs

@ -0,0 +1,524 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Runtime.CompilerServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
using System.Threading;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
internal class HuffmanScanEncoder
{
/// <summary>
/// Compiled huffman tree to encode given values.
/// </summary>
/// <remarks>Yields codewords by index consisting of [run length | bitsize].</remarks>
private HuffmanLut[] huffmanTables;
/// <summary>
/// Number of bytes cached before being written to target stream via Stream.Write(byte[], offest, count).
/// </summary>
/// <remarks>
/// This is subject to change, 1024 seems to be the best value in terms of performance.
/// <see cref="Emit(int, int)"/> expects it to be at least 8 (see comments in method body).
/// </remarks>
private const int EmitBufferSizeInBytes = 1024;
/// <summary>
/// A buffer for reducing the number of stream writes when emitting Huffman tables.
/// </summary>
private readonly byte[] emitBuffer = new byte[EmitBufferSizeInBytes];
/// <summary>
/// Number of filled bytes in <see cref="emitBuffer"/> buffer
/// </summary>
private int emitLen = 0;
/// <summary>
/// Emmited bits 'micro buffer' before being transfered to the <see cref="emitBuffer"/>.
/// </summary>
private int accumulatedBits;
/// <summary>
/// Number of jagged bits stored in <see cref="accumulatedBits"/>
/// </summary>
private int bitCount;
private Block8x8F temporalBlock1;
private Block8x8F temporalBlock2;
/// <summary>
/// The output stream. All attempted writes after the first error become no-ops.
/// </summary>
private readonly Stream target;
public HuffmanScanEncoder(Stream outputStream)
{
this.target = outputStream;
}
/// <summary>
/// Encodes the image with no subsampling.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="luminanceQuantTable">Luminance quantization table provided by the callee</param>
/// <param name="chrominanceQuantTable">Chrominance quantization table provided by the callee</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
public void Encode444<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
this.huffmanTables = HuffmanLut.TheHuffmanLut;
var unzig = ZigZag.CreateUnzigTable();
// ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
ImageFrame<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer;
RowOctet<TPixel> currentRows = default;
var pixelConverter = new YCbCrForwardConverter444<TPixel>(frame);
for (int y = 0; y < pixels.Height; y += 8)
{
cancellationToken.ThrowIfCancellationRequested();
currentRows.Update(pixelBuffer, y);
for (int x = 0; x < pixels.Width; x += 8)
{
pixelConverter.Convert(x, y, ref currentRows);
prevDCY = this.WriteBlock(
QuantIndex.Luminance,
prevDCY,
ref pixelConverter.Y,
ref luminanceQuantTable,
ref unzig);
prevDCCb = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCb,
ref pixelConverter.Cb,
ref chrominanceQuantTable,
ref unzig);
prevDCCr = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCr,
ref pixelConverter.Cr,
ref chrominanceQuantTable,
ref unzig);
}
}
this.FlushInternalBuffer();
}
/// <summary>
/// Encodes the image with subsampling. The Cb and Cr components are each subsampled
/// at a factor of 2 both horizontally and vertically.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="luminanceQuantTable">Luminance quantization table provided by the callee</param>
/// <param name="chrominanceQuantTable">Chrominance quantization table provided by the callee</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
public void Encode420<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
this.huffmanTables = HuffmanLut.TheHuffmanLut;
var unzig = ZigZag.CreateUnzigTable();
// ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
ImageFrame<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer;
RowOctet<TPixel> currentRows = default;
var pixelConverter = new YCbCrForwardConverter420<TPixel>(frame);
for (int y = 0; y < pixels.Height; y += 16)
{
cancellationToken.ThrowIfCancellationRequested();
for (int x = 0; x < pixels.Width; x += 16)
{
for (int i = 0; i < 2; i++)
{
int yOff = i * 8;
currentRows.Update(pixelBuffer, y + yOff);
pixelConverter.Convert(x, y, ref currentRows, i);
prevDCY = this.WriteBlock(
QuantIndex.Luminance,
prevDCY,
ref pixelConverter.YLeft,
ref luminanceQuantTable,
ref unzig);
prevDCY = this.WriteBlock(
QuantIndex.Luminance,
prevDCY,
ref pixelConverter.YRight,
ref luminanceQuantTable,
ref unzig);
}
prevDCCb = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCb,
ref pixelConverter.Cb,
ref chrominanceQuantTable,
ref unzig);
prevDCCr = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCr,
ref pixelConverter.Cr,
ref chrominanceQuantTable,
ref unzig);
}
}
this.FlushInternalBuffer();
}
/// <summary>
/// Encodes the image with no chroma, just luminance.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="luminanceQuantTable">Luminance quantization table provided by the callee</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
public void EncodeGrayscale<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
this.huffmanTables = HuffmanLut.TheHuffmanLut;
var unzig = ZigZag.CreateUnzigTable();
// ReSharper disable once InconsistentNaming
int prevDCY = 0;
var pixelConverter = LuminanceForwardConverter<TPixel>.Create();
ImageFrame<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer;
RowOctet<TPixel> currentRows = default;
for (int y = 0; y < pixels.Height; y += 8)
{
cancellationToken.ThrowIfCancellationRequested();
currentRows.Update(pixelBuffer, y);
for (int x = 0; x < pixels.Width; x += 8)
{
pixelConverter.Convert(frame, x, y, ref currentRows);
prevDCY = this.WriteBlock(
QuantIndex.Luminance,
prevDCY,
ref pixelConverter.Y,
ref luminanceQuantTable,
ref unzig);
}
}
this.FlushInternalBuffer();
}
/// <summary>
/// Writes a block of pixel data using the given quantization table,
/// returning the post-quantized DC value of the DCT-transformed block.
/// The block is in natural (not zig-zag) order.
/// </summary>
/// <param name="index">The quantization table index.</param>
/// <param name="prevDC">The previous DC value.</param>
/// <param name="src">Source block</param>
/// <param name="quant">Quantization table</param>
/// <param name="unZig">The 8x8 Unzig block.</param>
/// <returns>The <see cref="int"/>.</returns>
private int WriteBlock(
QuantIndex index,
int prevDC,
ref Block8x8F src,
ref Block8x8F quant,
ref ZigZag unZig)
{
ref Block8x8F refTemp1 = ref this.temporalBlock1;
ref Block8x8F refTemp2 = ref this.temporalBlock2;
FastFloatingPointDCT.TransformFDCT(ref src, ref refTemp1, ref refTemp2);
Block8x8F.Quantize(ref refTemp1, ref refTemp2, ref quant, ref unZig);
// Emit the DC delta.
int dc = (int)refTemp2[0];
this.EmitDirectCurrentTerm(this.huffmanTables[2 * (int)index].Values, dc - prevDC);
// Emit the AC components.
int[] acHuffTable = this.huffmanTables[(2 * (int)index) + 1].Values;
int runLength = 0;
int lastValuableIndex = GetLastValuableElementIndex(ref refTemp2);
for (int zig = 1; zig <= lastValuableIndex; zig++)
{
int ac = (int)refTemp2[zig];
if (ac == 0)
{
runLength++;
}
else
{
while (runLength > 15)
{
this.EmitHuff(acHuffTable, 0xf0);
runLength -= 16;
}
this.EmitHuffRLE(acHuffTable, runLength, ac);
runLength = 0;
}
}
// if mcu block contains trailing zeros - we must write end of block (EOB) value indicating that current block is over
// this can be done for any number of trailing zeros, even when all 63 ac values are zero
// (Block8x8F.Size - 1) == 63 - last index of the mcu elements
if (lastValuableIndex != Block8x8F.Size - 1)
{
this.EmitHuff(acHuffTable, 0x00);
}
return dc;
}
/// <summary>
/// Emits the least significant count of bits to the stream write buffer.
/// The precondition is bits
/// <example>
/// &lt; 1&lt;&lt;nBits &amp;&amp; nBits &lt;= 16
/// </example>
/// .
/// </summary>
/// <param name="bits">The packed bits.</param>
/// <param name="count">The number of bits</param>
[MethodImpl(InliningOptions.ShortMethod)]
private void Emit(int bits, int count)
{
count += this.bitCount;
bits <<= 32 - count;
bits |= this.accumulatedBits;
// Only write if more than 8 bits.
if (count >= 8)
{
// Track length
while (count >= 8)
{
byte b = (byte)(bits >> 24);
this.emitBuffer[this.emitLen++] = b;
// Adding stuff byte
// This is because by JPEG standard scan data can contain JPEG markers (indicated by the 0xFF byte, followed by a non-zero byte)
// Considering this every 0xFF byte must be followed by 0x00 padding byte to signal that this is not a marker
if (b == byte.MaxValue)
{
this.emitBuffer[this.emitLen++] = byte.MinValue;
}
bits <<= 8;
count -= 8;
}
// This can emit 4 times of:
// 1 byte guaranteed
// 1 extra byte.MinValue byte if previous one was byte.MaxValue
// Thus writing (1 + 1) * 4 = 8 bytes max
// So we must check if emit buffer has extra 8 bytes, if not - call stream.Write
if (this.emitLen > EmitBufferSizeInBytes - 8)
{
this.target.Write(this.emitBuffer, 0, this.emitLen);
this.emitLen = 0;
}
}
this.accumulatedBits = bits;
this.bitCount = count;
}
/// <summary>
/// Emits the given value with the given Huffman encoder.
/// </summary>
/// <param name="table">Compiled Huffman spec values.</param>
/// <param name="value">The value to encode.</param>
[MethodImpl(InliningOptions.ShortMethod)]
private void EmitHuff(int[] table, int value)
{
int x = table[value];
this.Emit(x >> 8, x & 0xff);
}
[MethodImpl(InliningOptions.ShortMethod)]
private void EmitDirectCurrentTerm(int[] table, int value)
{
int a = value;
int b = value;
if (a < 0)
{
a = -value;
b = value - 1;
}
int bt = GetHuffmanEncodingLength((uint)a);
this.EmitHuff(table, bt);
if (bt > 0)
{
this.Emit(b & ((1 << bt) - 1), bt);
}
}
/// <summary>
/// Emits a run of runLength copies of value encoded with the given Huffman encoder.
/// </summary>
/// <param name="table">Compiled Huffman spec values.</param>
/// <param name="runLength">The number of copies to encode.</param>
/// <param name="value">The value to encode.</param>
[MethodImpl(InliningOptions.ShortMethod)]
private void EmitHuffRLE(int[] table, int runLength, int value)
{
int a = value;
int b = value;
if (a < 0)
{
a = -value;
b = value - 1;
}
int bt = GetHuffmanEncodingLength((uint)a);
this.EmitHuff(table, (runLength << 4) | bt);
this.Emit(b & ((1 << bt) - 1), bt);
}
/// <summary>
/// Writes remaining bytes from internal buffer to the target stream.
/// </summary>
/// <remarks>Pads last byte with 1's if necessary</remarks>
private void FlushInternalBuffer()
{
// pad last byte with 1's
int padBitsCount = 8 - (this.bitCount % 8);
if (padBitsCount != 0)
{
this.Emit((1 << padBitsCount) - 1, padBitsCount);
this.target.Write(this.emitBuffer, 0, this.emitLen);
}
}
/// <summary>
/// Calculates how many minimum bits needed to store given value for Huffman jpeg encoding.
/// </summary>
/// <remarks>
/// This is an internal operation supposed to be used only in <see cref="HuffmanScanEncoder"/> class for jpeg encoding.
/// </remarks>
/// <param name="value">The value.</param>
[MethodImpl(InliningOptions.ShortMethod)]
internal static int GetHuffmanEncodingLength(uint value)
{
DebugGuard.IsTrue(value <= (1 << 16), "Huffman encoder is supposed to encode a value of 16bit size max");
#if SUPPORTS_BITOPERATIONS
// This should have been implemented as (BitOperations.Log2(value) + 1) as in non-intrinsic implementation
// But internal log2 is implementated like this: (31 - (int)Lzcnt.LeadingZeroCount(value))
// BitOperations.Log2 implementation also checks if input value is zero for the convention 0->0
// Lzcnt would return 32 for input value of 0 - no need to check that with branching
// Fallback code if Lzcnt is not supported still use if-check
// But most modern CPUs support this instruction so this should not be a problem
return 32 - System.Numerics.BitOperations.LeadingZeroCount(value);
#else
// Ideally:
// if 0 - return 0 in this case
// else - return log2(value) + 1
//
// Hack based on input value constaint:
// We know that input values are guaranteed to be maximum 16 bit large for huffman encoding
// We can safely shift input value for one bit -> log2(value << 1)
// Because of the 16 bit value constraint it won't overflow
// With that input value change we no longer need to add 1 before returning
// And this eliminates need to check if input value is zero - it is a standard convention which Log2SoftwareFallback adheres to
return Numerics.Log2(value << 1);
#endif
}
/// <summary>
/// Returns index of the last non-zero element in given mcu block.
/// If all values of the mcu block are zero, this method might return different results depending on the runtime and hardware support.
/// This is jpeg mcu specific code, mcu[0] stores a dc value which will be encoded outside of the loop.
/// This method is guaranteed to return either -1 or 0 if all elements are zero.
/// </summary>
/// <remarks>
/// This is an internal operation supposed to be used only in <see cref="HuffmanScanEncoder"/> class for jpeg encoding.
/// </remarks>
/// <param name="mcu">Mcu block.</param>
/// <returns>Index of the last non-zero element.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
internal static int GetLastValuableElementIndex(ref Block8x8F mcu)
{
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported)
{
const int equalityMask = unchecked((int)0b1111_1111_1111_1111_1111_1111_1111_1111);
Vector256<int> zero8 = Vector256<int>.Zero;
ref Vector256<float> mcuStride = ref mcu.V0;
for (int i = 7; i >= 0; i--)
{
int areEqual = Avx2.MoveMask(Avx2.CompareEqual(Avx.ConvertToVector256Int32(Unsafe.Add(ref mcuStride, i)), zero8).AsByte());
// we do not know for sure if this stride contain all non-zero elements or if it has some trailing zeros
if (areEqual != equalityMask)
{
// last index in the stride, we go from the end to the start of the stride
int startIndex = i * 8;
int index = startIndex + 7;
ref float elemRef = ref Unsafe.As<Block8x8F, float>(ref mcu);
while (index >= startIndex && (int)Unsafe.Add(ref elemRef, index) == 0)
{
index--;
}
// this implementation will return -1 if all ac components are zero and dc are zero
return index;
}
}
return -1;
}
else
#endif
{
int index = Block8x8F.Size - 1;
ref float elemRef = ref Unsafe.As<Block8x8F, float>(ref mcu);
while (index > 0 && (int)Unsafe.Add(ref elemRef, index) == 0)
{
index--;
}
// this implementation will return 0 if all ac components and dc are zero
return index;
}
}
}
}

14
src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanSpec.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.Jpeg.Components.Encoder
@ -24,9 +24,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
0, 0, 0
},
new byte[]
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
}),
{
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
}),
// Luminance AC.
new HuffmanSpec(
new byte[]
{
@ -60,6 +62,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
0xf9, 0xfa
}),
// Chrominance DC.
new HuffmanSpec(
new byte[]
{
@ -132,4 +136,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
this.Values = values;
}
}
}
}

2
src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs

@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
ref Block8x8F yBlock = ref this.Y;
ref L8 l8Start = ref l8Span[0];
for (int i = 0; i < 64; i++)
for (int i = 0; i < Block8x8F.Size; i++)
{
ref L8 c = ref Unsafe.Add(ref l8Start, i);
yBlock[i] = c.PackedValue;

148
src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs

@ -92,48 +92,144 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
return tables;
}
/// <summary>
/// Optimized method to allocates the correct y, cb, and cr values to the DCT blocks from the given r, g, b values.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ConvertPixelInto(
int r,
int g,
int b,
ref Block8x8F yResult,
ref Block8x8F cbResult,
ref Block8x8F crResult,
int i)
private float CalculateY(byte r, byte g, byte b)
{
// float y = (0.299F * r) + (0.587F * g) + (0.114F * b);
yResult[i] = (this.YRTable[r] + this.YGTable[g] + this.YBTable[b]) >> ScaleBits;
return (this.YRTable[r] + this.YGTable[g] + this.YBTable[b]) >> ScaleBits;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private float CalculateCb(byte r, byte g, byte b)
{
// float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b));
cbResult[i] = (this.CbRTable[r] + this.CbGTable[g] + this.CbBTable[b]) >> ScaleBits;
return (this.CbRTable[r] + this.CbGTable[g] + this.CbBTable[b]) >> ScaleBits;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private float CalculateCr(byte r, byte g, byte b)
{
// float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b));
crResult[i] = (this.CbBTable[r] + this.CrGTable[g] + this.CrBTable[b]) >> ScaleBits;
return (this.CbBTable[r] + this.CrGTable[g] + this.CrBTable[b]) >> ScaleBits;
}
public void Convert(Span<Rgb24> rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock)
/// <summary>
/// Converts Rgb24 pixels into YCbCr color space with 4:4:4 subsampling sampling of luminance and chroma.
/// </summary>
/// <param name="rgbSpan">Span of Rgb24 pixel data</param>
/// <param name="yBlock">Resulting Y values block</param>
/// <param name="cbBlock">Resulting Cb values block</param>
/// <param name="crBlock">Resulting Cr values block</param>
public void Convert444(Span<Rgb24> rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock)
{
ref Rgb24 rgbStart = ref rgbSpan[0];
for (int i = 0; i < 64; i++)
for (int i = 0; i < Block8x8F.Size; i++)
{
ref Rgb24 c = ref Unsafe.Add(ref rgbStart, i);
this.ConvertPixelInto(
c.R,
c.G,
c.B,
ref yBlock,
ref cbBlock,
ref crBlock,
i);
Rgb24 c = Unsafe.Add(ref rgbStart, i);
yBlock[i] = this.CalculateY(c.R, c.G, c.B);
cbBlock[i] = this.CalculateCb(c.R, c.G, c.B);
crBlock[i] = this.CalculateCr(c.R, c.G, c.B);
}
}
/// <summary>
/// Converts Rgb24 pixels into YCbCr color space with 4:2:0 subsampling of luminance and chroma.
/// </summary>
/// <remarks>Calculates 2 out of 4 luminance blocks and half of chroma blocks. This method must be called twice per 4x 8x8 DCT blocks with different row param.</remarks>
/// <param name="rgbSpan">Span of Rgb24 pixel data</param>
/// <param name="yBlockLeft">First or "left" resulting Y block</param>
/// <param name="yBlockRight">Second or "right" resulting Y block</param>
/// <param name="cbBlock">Resulting Cb values block</param>
/// <param name="crBlock">Resulting Cr values block</param>
/// <param name="row">Row index of the 16x16 block, 0 or 1</param>
public void Convert420(Span<Rgb24> rgbSpan, ref Block8x8F yBlockLeft, ref Block8x8F yBlockRight, ref Block8x8F cbBlock, ref Block8x8F crBlock, int row)
{
DebugGuard.MustBeBetweenOrEqualTo(row, 0, 1, nameof(row));
ref float yBlockLeftRef = ref Unsafe.As<Block8x8F, float>(ref yBlockLeft);
ref float yBlockRightRef = ref Unsafe.As<Block8x8F, float>(ref yBlockRight);
// 0-31 or 32-63
// upper or lower part
int chromaWriteOffset = row * (Block8x8F.Size / 2);
ref float cbBlockRef = ref Unsafe.Add(ref Unsafe.As<Block8x8F, float>(ref cbBlock), chromaWriteOffset);
ref float crBlockRef = ref Unsafe.Add(ref Unsafe.As<Block8x8F, float>(ref crBlock), chromaWriteOffset);
ref Rgb24 rgbStart = ref rgbSpan[0];
for (int i = 0; i < 8; i += 2)
{
int yBlockWriteOffset = i * 8;
ref Rgb24 stride = ref Unsafe.Add(ref rgbStart, i * 16);
int chromaOffset = 8 * (i / 2);
// left
this.ConvertChunk420(
ref stride,
ref Unsafe.Add(ref yBlockLeftRef, yBlockWriteOffset),
ref Unsafe.Add(ref cbBlockRef, chromaOffset),
ref Unsafe.Add(ref crBlockRef, chromaOffset));
// right
this.ConvertChunk420(
ref Unsafe.Add(ref stride, 8),
ref Unsafe.Add(ref yBlockRightRef, yBlockWriteOffset),
ref Unsafe.Add(ref cbBlockRef, chromaOffset + 4),
ref Unsafe.Add(ref crBlockRef, chromaOffset + 4));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ConvertChunk420(ref Rgb24 stride, ref float yBlock, ref float cbBlock, ref float crBlock)
{
// jpeg 8x8 blocks are processed as 16x16 blocks with 16x8 subpasses (this is done for performance reasons)
// each row is 16 pixels wide thus +16 stride reference offset
// resulting luminance (Y`) are sampled at original resolution thus +8 reference offset
for (int k = 0; k < 8; k += 2)
{
ref float yBlockRef = ref Unsafe.Add(ref yBlock, k);
// top row
Rgb24 px0 = Unsafe.Add(ref stride, k);
Rgb24 px1 = Unsafe.Add(ref stride, k + 1);
yBlockRef = this.CalculateY(px0.R, px0.G, px0.B);
Unsafe.Add(ref yBlockRef, 1) = this.CalculateY(px1.R, px1.G, px1.B);
// bottom row
Rgb24 px2 = Unsafe.Add(ref stride, k + 16);
Rgb24 px3 = Unsafe.Add(ref stride, k + 17);
Unsafe.Add(ref yBlockRef, 8) = this.CalculateY(px2.R, px2.G, px2.B);
Unsafe.Add(ref yBlockRef, 9) = this.CalculateY(px3.R, px3.G, px3.B);
// chroma average for 2x2 pixel block
Unsafe.Add(ref cbBlock, k / 2) = this.CalculateAverageCb(px0, px1, px2, px3);
Unsafe.Add(ref crBlock, k / 2) = this.CalculateAverageCr(px0, px1, px2, px3);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private float CalculateAverageCb(Rgb24 px0, Rgb24 px1, Rgb24 px2, Rgb24 px3)
{
return 0.25f
* (this.CalculateCb(px0.R, px0.G, px0.B)
+ this.CalculateCb(px1.R, px1.G, px1.B)
+ this.CalculateCb(px2.R, px2.G, px2.B)
+ this.CalculateCb(px3.R, px3.G, px3.B));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private float CalculateAverageCr(Rgb24 px0, Rgb24 px1, Rgb24 px2, Rgb24 px3)
{
return 0.25f
* (this.CalculateCr(px0.R, px0.G, px0.B)
+ this.CalculateCr(px1.R, px1.G, px1.B)
+ this.CalculateCr(px2.R, px2.G, px2.B)
+ this.CalculateCr(px3.R, px3.G, px3.B));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Fix(float x)
=> (int)((x * (1L << ScaleBits)) + 0.5F);

195
src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors.
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
@ -27,19 +27,45 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
}
}
public static int AvxCompatibilityPadding
{
// rgb byte matrices contain 8 strides by 8 pixels each, thus 64 pixels total
// Strides are stored sequentially - one big span of 64 * 3 = 192 bytes
// Each stride has exactly 3 * 8 = 24 bytes or 3 * 8 * 8 = 192 bits
// Avx registers are 256 bits so rgb span will be loaded with extra 64 bits from the next stride:
// stride 0 0 - 192 -(+64bits)-> 256
// stride 1 192 - 384 -(+64bits)-> 448
// stride 2 384 - 576 -(+64bits)-> 640
// stride 3 576 - 768 -(+64bits)-> 832
// stride 4 768 - 960 -(+64bits)-> 1024
// stride 5 960 - 1152 -(+64bits)-> 1216
// stride 6 1152 - 1344 -(+64bits)-> 1408
// stride 7 1344 - 1536 -(+64bits)-> 1600 <-- READ ACCESS VIOLATION
//
// Total size of the 64 pixel rgb span: 64 * 3 * 8 = 1536 bits, avx operations require 1600 bits
// This is not permitted - we are reading foreign memory
//
// 8 byte padding to rgb byte span will solve this problem without extra code in converters
get
{
#if SUPPORTS_RUNTIME_INTRINSICS
if (IsSupported)
{
return 8;
}
#endif
return 0;
}
}
#if SUPPORTS_RUNTIME_INTRINSICS
private static ReadOnlySpan<byte> MoveFirst24BytesToSeparateLanes => new byte[]
{
0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0,
3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 7, 0, 0, 0
};
private static ReadOnlySpan<byte> MoveLast24BytesToSeparateLanes => new byte[]
{
2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0,
5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 1, 0, 0, 0
};
private static ReadOnlySpan<byte> ExtractRgb => new byte[]
{
0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF,
@ -47,7 +73,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
};
#endif
public static void Convert(ReadOnlySpan<Rgb24> rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock)
/// <summary>
/// Converts 8x8 Rgb24 pixel matrix to YCbCr pixel matrices with 4:4:4 subsampling
/// </summary>
/// <remarks>Total size of rgb span must be 200 bytes</remarks>
/// <param name="rgbSpan">Span of rgb pixels with size of 64</param>
/// <param name="yBlock">8x8 destination matrix of Luminance(Y) converted data</param>
/// <param name="cbBlock">8x8 destination matrix of Chrominance(Cb) converted data</param>
/// <param name="crBlock">8x8 destination matrix of Chrominance(Cr) converted data</param>
public static void Convert444(ReadOnlySpan<Rgb24> rgbSpan, ref Block8x8F yBlock, ref Block8x8F cbBlock, ref Block8x8F crBlock)
{
Debug.Assert(IsSupported, "AVX2 is required to run this converter");
@ -63,18 +97,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
var f05 = Vector256.Create(0.5f);
var zero = Vector256.Create(0).AsByte();
ref Vector256<byte> inRef = ref Unsafe.As<Rgb24, Vector256<byte>>(ref MemoryMarshal.GetReference(rgbSpan));
ref Vector256<float> destYRef = ref Unsafe.As<Block8x8F, Vector256<float>>(ref yBlock);
ref Vector256<float> destCbRef = ref Unsafe.As<Block8x8F, Vector256<float>>(ref cbBlock);
ref Vector256<float> destCrRef = ref Unsafe.As<Block8x8F, Vector256<float>>(ref crBlock);
ref Vector256<byte> rgbByteSpan = ref Unsafe.As<Rgb24, Vector256<byte>>(ref MemoryMarshal.GetReference(rgbSpan));
ref Vector256<float> destYRef = ref yBlock.V0;
ref Vector256<float> destCbRef = ref cbBlock.V0;
ref Vector256<float> destCrRef = ref crBlock.V0;
var extractToLanesMask = Unsafe.As<byte, Vector256<uint>>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes));
var extractRgbMask = Unsafe.As<byte, Vector256<byte>>(ref MemoryMarshal.GetReference(ExtractRgb));
Vector256<byte> rgb, rg, bx;
Vector256<float> r, g, b;
for (int i = 0; i < 7; i++)
const int bytesPerRgbStride = 24;
for (int i = 0; i < 8; i++)
{
rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref inRef, (IntPtr)(24 * i)).AsUInt32(), extractToLanesMask).AsByte();
rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * i)).AsUInt32(), extractToLanesMask).AsByte();
rgb = Avx2.Shuffle(rgb, extractRgbMask);
@ -94,27 +130,130 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
// 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b))
Unsafe.Add(ref destCrRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r));
}
#endif
}
/// <summary>
/// Converts 16x8 Rgb24 pixels matrix to 2 Y 8x8 matrices with 4:2:0 subsampling
/// </summary>
public static void Convert420(ReadOnlySpan<Rgb24> rgbSpan, ref Block8x8F yBlockLeft, ref Block8x8F yBlockRight, ref Block8x8F cbBlock, ref Block8x8F crBlock, int row)
{
Debug.Assert(IsSupported, "AVX2 is required to run this converter");
#if SUPPORTS_RUNTIME_INTRINSICS
var f0299 = Vector256.Create(0.299f);
var f0587 = Vector256.Create(0.587f);
var f0114 = Vector256.Create(0.114f);
var fn0168736 = Vector256.Create(-0.168736f);
var fn0331264 = Vector256.Create(-0.331264f);
var f128 = Vector256.Create(128f);
var fn0418688 = Vector256.Create(-0.418688f);
var fn0081312F = Vector256.Create(-0.081312F);
var f05 = Vector256.Create(0.5f);
var zero = Vector256.Create(0).AsByte();
ref Vector256<byte> rgbByteSpan = ref Unsafe.As<Rgb24, Vector256<byte>>(ref MemoryMarshal.GetReference(rgbSpan));
int destOffset = row * 4;
extractToLanesMask = Unsafe.As<byte, Vector256<uint>>(ref MemoryMarshal.GetReference(MoveLast24BytesToSeparateLanes));
rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref inRef, (IntPtr)160).AsUInt32(), extractToLanesMask).AsByte();
rgb = Avx2.Shuffle(rgb, extractRgbMask);
ref Vector256<float> destCbRef = ref Unsafe.Add(ref Unsafe.As<Block8x8F, Vector256<float>>(ref cbBlock), destOffset);
ref Vector256<float> destCrRef = ref Unsafe.Add(ref Unsafe.As<Block8x8F, Vector256<float>>(ref crBlock), destOffset);
rg = Avx2.UnpackLow(rgb, zero);
bx = Avx2.UnpackHigh(rgb, zero);
var extractToLanesMask = Unsafe.As<byte, Vector256<uint>>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes));
var extractRgbMask = Unsafe.As<byte, Vector256<byte>>(ref MemoryMarshal.GetReference(ExtractRgb));
Vector256<byte> rgb, rg, bx;
Vector256<float> r, g, b;
Span<Vector256<float>> rDataLanes = stackalloc Vector256<float>[4];
Span<Vector256<float>> gDataLanes = stackalloc Vector256<float>[4];
Span<Vector256<float>> bDataLanes = stackalloc Vector256<float>[4];
const int bytesPerRgbStride = 24;
for (int i = 0; i < 4; i++)
{
// 16x2 => 8x1
// left 8x8 column conversions
for (int j = 0; j < 4; j += 2)
{
rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * ((i * 4) + j))).AsUInt32(), extractToLanesMask).AsByte();
rgb = Avx2.Shuffle(rgb, extractRgbMask);
rg = Avx2.UnpackLow(rgb, zero);
bx = Avx2.UnpackHigh(rgb, zero);
r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32());
g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32());
b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32());
int yBlockVerticalOffset = (i * 2) + ((j & 2) >> 1);
// (0.299F * r) + (0.587F * g) + (0.114F * b);
Unsafe.Add(ref yBlockLeft.V0, yBlockVerticalOffset) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r);
rDataLanes[j] = r;
gDataLanes[j] = g;
bDataLanes[j] = b;
}
// 16x2 => 8x1
// right 8x8 column conversions
for (int j = 1; j < 4; j += 2)
{
rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref rgbByteSpan, (IntPtr)(bytesPerRgbStride * ((i * 4) + j))).AsUInt32(), extractToLanesMask).AsByte();
rgb = Avx2.Shuffle(rgb, extractRgbMask);
rg = Avx2.UnpackLow(rgb, zero);
bx = Avx2.UnpackHigh(rgb, zero);
r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32());
g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32());
b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32());
r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32());
g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32());
b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32());
// (0.299F * r) + (0.587F * g) + (0.114F * b);
Unsafe.Add(ref destYRef, 7) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r);
int yBlockVerticalOffset = (i * 2) + ((j & 2) >> 1);
// 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b))
Unsafe.Add(ref destCbRef, 7) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r));
// (0.299F * r) + (0.587F * g) + (0.114F * b);
Unsafe.Add(ref yBlockRight.V0, yBlockVerticalOffset) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r);
// 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b))
Unsafe.Add(ref destCrRef, 7) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r));
rDataLanes[j] = r;
gDataLanes[j] = g;
bDataLanes[j] = b;
}
r = Scale16x2_8x1(rDataLanes);
g = Scale16x2_8x1(gDataLanes);
b = Scale16x2_8x1(bDataLanes);
// 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b))
Unsafe.Add(ref destCbRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r));
// 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b))
Unsafe.Add(ref destCrRef, i) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, r));
}
#endif
}
#if SUPPORTS_RUNTIME_INTRINSICS
/// <summary>
/// Scales 16x2 matrix to 8x1 using 2x2 average
/// </summary>
/// <param name="v">Input matrix consisting of 4 256bit vectors</param>
/// <returns>256bit vector containing upper and lower scaled parts of the input matrix</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static Vector256<float> Scale16x2_8x1(ReadOnlySpan<Vector256<float>> v)
{
Debug.Assert(Avx2.IsSupported, "AVX2 is required to run this converter");
DebugGuard.IsTrue(v.Length == 4, "Input span must consist of 4 elements");
var f025 = Vector256.Create(0.25f);
Vector256<float> left = Avx.Add(v[0], v[2]);
Vector256<float> right = Avx.Add(v[1], v[3]);
Vector256<float> avg2x2 = Avx.Multiply(Avx.HorizontalAdd(left, right), f025);
return Avx2.Permute4x64(avg2x2.AsDouble(), 0b11_01_10_00).AsSingle();
}
#endif
}
}

121
src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs

@ -0,0 +1,121 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// On-stack worker struct to efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks.
/// </summary>
/// <typeparam name="TPixel">The pixel type to work on</typeparam>
internal ref struct YCbCrForwardConverter420<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Number of pixels processed per single <see cref="Convert(int, int, ref RowOctet{TPixel}, int)"/> call
/// </summary>
private const int PixelsPerSample = 16 * 8;
/// <summary>
/// Total byte size of processed pixels converted from TPixel to <see cref="Rgb24"/>
/// </summary>
private const int RgbSpanByteSize = PixelsPerSample * 3;
/// <summary>
/// <see cref="Size"/> of sampling area from given frame pixel buffer
/// </summary>
private static readonly Size SampleSize = new Size(16, 8);
/// <summary>
/// The left Y component
/// </summary>
public Block8x8F YLeft;
/// <summary>
/// The left Y component
/// </summary>
public Block8x8F YRight;
/// <summary>
/// The Cb component
/// </summary>
public Block8x8F Cb;
/// <summary>
/// The Cr component
/// </summary>
public Block8x8F Cr;
/// <summary>
/// The color conversion tables
/// </summary>
private RgbToYCbCrConverterLut colorTables;
/// <summary>
/// Temporal 16x8 block to hold TPixel data
/// </summary>
private Span<TPixel> pixelSpan;
/// <summary>
/// Temporal RGB block
/// </summary>
private Span<Rgb24> rgbSpan;
/// <summary>
/// Sampled pixel buffer size
/// </summary>
private Size samplingAreaSize;
/// <summary>
/// <see cref="Configuration"/> for internal operations
/// </summary>
private Configuration config;
public YCbCrForwardConverter420(ImageFrame<TPixel> frame)
{
// matrices would be filled during convert calls
this.YLeft = default;
this.YRight = default;
this.Cb = default;
this.Cr = default;
// temporal pixel buffers
this.pixelSpan = new TPixel[PixelsPerSample].AsSpan();
this.rgbSpan = MemoryMarshal.Cast<byte, Rgb24>(new byte[RgbSpanByteSize + RgbToYCbCrConverterVectorized.AvxCompatibilityPadding].AsSpan());
// frame data
this.samplingAreaSize = new Size(frame.Width, frame.Height);
this.config = frame.GetConfiguration();
// conversion vector fallback data
if (!RgbToYCbCrConverterVectorized.IsSupported)
{
this.colorTables = RgbToYCbCrConverterLut.Create();
}
else
{
this.colorTables = default;
}
}
public void Convert(int x, int y, ref RowOctet<TPixel> currentRows, int idx)
{
YCbCrForwardConverter<TPixel>.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize);
PixelOperations<TPixel>.Instance.ToRgb24(this.config, this.pixelSpan, this.rgbSpan);
if (RgbToYCbCrConverterVectorized.IsSupported)
{
RgbToYCbCrConverterVectorized.Convert420(this.rgbSpan, ref this.YLeft, ref this.YRight, ref this.Cb, ref this.Cr, idx);
}
else
{
this.colorTables.Convert420(this.rgbSpan, ref this.YLeft, ref this.YRight, ref this.Cb, ref this.Cr, idx);
}
}
}
}

122
src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs

@ -0,0 +1,122 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// On-stack worker struct to efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks.
/// </summary>
/// <typeparam name="TPixel">The pixel type to work on</typeparam>
internal ref struct YCbCrForwardConverter444<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Number of pixels processed per single <see cref="Convert(int, int, ref RowOctet{TPixel})"/> call
/// </summary>
private const int PixelsPerSample = 8 * 8;
/// <summary>
/// Total byte size of processed pixels converted from TPixel to <see cref="Rgb24"/>
/// </summary>
private const int RgbSpanByteSize = PixelsPerSample * 3;
/// <summary>
/// <see cref="Size"/> of sampling area from given frame pixel buffer
/// </summary>
private static readonly Size SampleSize = new Size(8, 8);
/// <summary>
/// The Y component
/// </summary>
public Block8x8F Y;
/// <summary>
/// The Cb component
/// </summary>
public Block8x8F Cb;
/// <summary>
/// The Cr component
/// </summary>
public Block8x8F Cr;
/// <summary>
/// The color conversion tables
/// </summary>
private RgbToYCbCrConverterLut colorTables;
/// <summary>
/// Temporal 64-byte span to hold unconverted TPixel data
/// </summary>
private Span<TPixel> pixelSpan;
/// <summary>
/// Temporal 64-byte span to hold converted Rgb24 data
/// </summary>
private Span<Rgb24> rgbSpan;
/// <summary>
/// Sampled pixel buffer size
/// </summary>
private Size samplingAreaSize;
/// <summary>
/// <see cref="Configuration"/> for internal operations
/// </summary>
private Configuration config;
public YCbCrForwardConverter444(ImageFrame<TPixel> frame)
{
// matrices would be filled during convert calls
this.Y = default;
this.Cb = default;
this.Cr = default;
// temporal pixel buffers
this.pixelSpan = new TPixel[PixelsPerSample].AsSpan();
this.rgbSpan = MemoryMarshal.Cast<byte, Rgb24>(new byte[RgbSpanByteSize + RgbToYCbCrConverterVectorized.AvxCompatibilityPadding].AsSpan());
// frame data
this.samplingAreaSize = new Size(frame.Width, frame.Height);
this.config = frame.GetConfiguration();
// conversion vector fallback data
if (!RgbToYCbCrConverterVectorized.IsSupported)
{
this.colorTables = RgbToYCbCrConverterLut.Create();
}
else
{
this.colorTables = default;
}
}
/// <summary>
/// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (<see cref="Y"/>, <see cref="Cb"/>, <see cref="Cr"/>)
/// </summary>
public void Convert(int x, int y, ref RowOctet<TPixel> currentRows)
{
YCbCrForwardConverter<TPixel>.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize);
PixelOperations<TPixel>.Instance.ToRgb24(this.config, this.pixelSpan, this.rgbSpan);
ref Block8x8F yBlock = ref this.Y;
ref Block8x8F cbBlock = ref this.Cb;
ref Block8x8F crBlock = ref this.Cr;
if (RgbToYCbCrConverterVectorized.IsSupported)
{
RgbToYCbCrConverterVectorized.Convert444(this.rgbSpan, ref yBlock, ref cbBlock, ref crBlock);
}
else
{
this.colorTables.Convert444(this.rgbSpan, ref yBlock, ref cbBlock, ref crBlock);
}
}
}
}

90
src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs

@ -2,81 +2,59 @@
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Advanced;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// On-stack worker struct to efficiently encapsulate the TPixel -> Rgb24 -> YCbCr conversion chain of 8x8 pixel blocks.
/// </summary>
/// <typeparam name="TPixel">The pixel type to work on</typeparam>
internal ref struct YCbCrForwardConverter<TPixel>
internal static class YCbCrForwardConverter<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// The Y component
/// </summary>
public Block8x8F Y;
/// <summary>
/// The Cb component
/// </summary>
public Block8x8F Cb;
/// <summary>
/// The Cr component
/// </summary>
public Block8x8F Cr;
public static void LoadAndStretchEdges(RowOctet<TPixel> source, Span<TPixel> dest, Point start, Size sampleSize, Size totalSize)
{
DebugGuard.MustBeBetweenOrEqualTo(start.X, 0, totalSize.Width - 1, nameof(start.X));
DebugGuard.MustBeBetweenOrEqualTo(start.Y, 0, totalSize.Height - 1, nameof(start.Y));
/// <summary>
/// The color conversion tables
/// </summary>
private RgbToYCbCrConverterLut colorTables;
int width = Math.Min(sampleSize.Width, totalSize.Width - start.X);
int height = Math.Min(sampleSize.Height, totalSize.Height - start.Y);
/// <summary>
/// Temporal 8x8 block to hold TPixel data
/// </summary>
private GenericBlock8x8<TPixel> pixelBlock;
uint byteWidth = (uint)(width * Unsafe.SizeOf<TPixel>());
int remainderXCount = sampleSize.Width - width;
/// <summary>
/// Temporal RGB block
/// </summary>
private GenericBlock8x8<Rgb24> rgbBlock;
ref byte blockStart = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<TPixel, byte>(dest));
int rowSizeInBytes = sampleSize.Width * Unsafe.SizeOf<TPixel>();
public static YCbCrForwardConverter<TPixel> Create()
{
var result = default(YCbCrForwardConverter<TPixel>);
if (!RgbToYCbCrConverterVectorized.IsSupported)
for (int y = 0; y < height; y++)
{
// Avoid creating lookup tables, when vectorized converter is supported
result.colorTables = RgbToYCbCrConverterLut.Create();
}
Span<TPixel> row = source[y];
return result;
}
ref byte s = ref Unsafe.As<TPixel, byte>(ref row[start.X]);
ref byte d = ref Unsafe.Add(ref blockStart, y * rowSizeInBytes);
/// <summary>
/// Converts a 8x8 image area inside 'pixels' at position (x,y) placing the result members of the structure (<see cref="Y"/>, <see cref="Cb"/>, <see cref="Cr"/>)
/// </summary>
public void Convert(ImageFrame<TPixel> frame, int x, int y, ref RowOctet<TPixel> currentRows)
{
this.pixelBlock.LoadAndStretchEdges(frame.PixelBuffer, x, y, ref currentRows);
Unsafe.CopyBlock(ref d, ref s, byteWidth);
ref TPixel last = ref Unsafe.Add(ref Unsafe.As<byte, TPixel>(ref d), width - 1);
Span<Rgb24> rgbSpan = this.rgbBlock.AsSpanUnsafe();
PixelOperations<TPixel>.Instance.ToRgb24(frame.GetConfiguration(), this.pixelBlock.AsSpanUnsafe(), rgbSpan);
for (int x = 1; x <= remainderXCount; x++)
{
Unsafe.Add(ref last, x) = last;
}
}
ref Block8x8F yBlock = ref this.Y;
ref Block8x8F cbBlock = ref this.Cb;
ref Block8x8F crBlock = ref this.Cr;
int remainderYCount = sampleSize.Height - height;
if (RgbToYCbCrConverterVectorized.IsSupported)
if (remainderYCount == 0)
{
RgbToYCbCrConverterVectorized.Convert(rgbSpan, ref yBlock, ref cbBlock, ref crBlock);
return;
}
else
ref byte lastRowStart = ref Unsafe.Add(ref blockStart, (height - 1) * rowSizeInBytes);
for (int y = 1; y <= remainderYCount; y++)
{
this.colorTables.Convert(rgbSpan, ref yBlock, ref cbBlock, ref crBlock);
ref byte remStart = ref Unsafe.Add(ref lastRowStart, rowSizeInBytes * y);
Unsafe.CopyBlock(ref remStart, ref lastRowStart, (uint)rowSizeInBytes);
}
}
}

463
src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs

@ -1,8 +1,13 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
@ -10,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <summary>
/// Contains inaccurate, but fast forward and inverse DCT implementations.
/// </summary>
internal static class FastFloatingPointDCT
internal static partial class FastFloatingPointDCT
{
#pragma warning disable SA1310 // FieldNamesMustNotContainUnderscore
private const float C_1_175876 = 1.175875602f;
@ -38,147 +43,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
private const float C_0_765367 = 0.765366865f;
private const float C_0_125 = 0.1250f;
#if SUPPORTS_RUNTIME_INTRINSICS
private static readonly Vector256<float> C_V_0_5411 = Vector256.Create(0.541196f);
private static readonly Vector256<float> C_V_1_3065 = Vector256.Create(1.306563f);
private static readonly Vector256<float> C_V_1_1758 = Vector256.Create(1.175876f);
private static readonly Vector256<float> C_V_0_7856 = Vector256.Create(0.785695f);
private static readonly Vector256<float> C_V_1_3870 = Vector256.Create(1.387040f);
private static readonly Vector256<float> C_V_0_2758 = Vector256.Create(0.275899f);
private static readonly Vector256<float> C_V_n1_9615 = Vector256.Create(-1.961570560f);
private static readonly Vector256<float> C_V_n0_3901 = Vector256.Create(-0.390180644f);
private static readonly Vector256<float> C_V_n0_8999 = Vector256.Create(-0.899976223f);
private static readonly Vector256<float> C_V_n2_5629 = Vector256.Create(-2.562915447f);
private static readonly Vector256<float> C_V_0_2986 = Vector256.Create(0.298631336f);
private static readonly Vector256<float> C_V_2_0531 = Vector256.Create(2.053119869f);
private static readonly Vector256<float> C_V_3_0727 = Vector256.Create(3.072711026f);
private static readonly Vector256<float> C_V_1_5013 = Vector256.Create(1.501321110f);
private static readonly Vector256<float> C_V_n1_8477 = Vector256.Create(-1.847759065f);
private static readonly Vector256<float> C_V_0_7653 = Vector256.Create(0.765366865f);
private static readonly Vector256<float> C_V_InvSqrt2 = Vector256.Create(0.707107f);
#endif
#pragma warning restore SA1310 // FieldNamesMustNotContainUnderscore
private static readonly Vector4 InvSqrt2 = new Vector4(0.707107f);
/// <summary>
/// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization).
/// Ported from https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L239
/// </summary>
/// <param name="src">Source</param>
/// <param name="dest">Destination</param>
/// <param name="temp">Temporary block provided by the caller</param>
public static void TransformIDCT(ref Block8x8F src, ref Block8x8F dest, ref Block8x8F temp)
{
src.TransposeInto(ref temp);
IDCT8x4_LeftPart(ref temp, ref dest);
IDCT8x4_RightPart(ref temp, ref dest);
dest.TransposeInto(ref temp);
IDCT8x4_LeftPart(ref temp, ref dest);
IDCT8x4_RightPart(ref temp, ref dest);
// TODO: What if we leave the blocks in a scaled-by-x8 state until final color packing?
dest.MultiplyInPlace(C_0_125);
}
/// <summary>
/// Do IDCT internal operations on the left part of the block. Original src:
/// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261
/// </summary>
/// <param name="s">The source block</param>
/// <param name="d">Destination block</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void IDCT8x4_LeftPart(ref Block8x8F s, ref Block8x8F d)
{
Vector4 my1 = s.V1L;
Vector4 my7 = s.V7L;
Vector4 mz0 = my1 + my7;
Vector4 my3 = s.V3L;
Vector4 mz2 = my3 + my7;
Vector4 my5 = s.V5L;
Vector4 mz1 = my3 + my5;
Vector4 mz3 = my1 + my5;
Vector4 mz4 = (mz0 + mz1) * C_1_175876;
mz2 = (mz2 * C_1_961571) + mz4;
mz3 = (mz3 * C_0_390181) + mz4;
mz0 = mz0 * C_0_899976;
mz1 = mz1 * C_2_562915;
Vector4 mb3 = (my7 * C_0_298631) + mz0 + mz2;
Vector4 mb2 = (my5 * C_2_053120) + mz1 + mz3;
Vector4 mb1 = (my3 * C_3_072711) + mz1 + mz2;
Vector4 mb0 = (my1 * C_1_501321) + mz0 + mz3;
Vector4 my2 = s.V2L;
Vector4 my6 = s.V6L;
mz4 = (my2 + my6) * C_0_541196;
Vector4 my0 = s.V0L;
Vector4 my4 = s.V4L;
mz0 = my0 + my4;
mz1 = my0 - my4;
mz2 = mz4 + (my6 * C_1_847759);
mz3 = mz4 + (my2 * C_0_765367);
my0 = mz0 + mz3;
my3 = mz0 - mz3;
my1 = mz1 + mz2;
my2 = mz1 - mz2;
d.V0L = my0 + mb0;
d.V7L = my0 - mb0;
d.V1L = my1 + mb1;
d.V6L = my1 - mb1;
d.V2L = my2 + mb2;
d.V5L = my2 - mb2;
d.V3L = my3 + mb3;
d.V4L = my3 - mb3;
}
/// <summary>
/// Do IDCT internal operations on the right part of the block.
/// Original src:
/// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261
/// </summary>
/// <param name="s">The source block</param>
/// <param name="d">The destination block</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void IDCT8x4_RightPart(ref Block8x8F s, ref Block8x8F d)
{
Vector4 my1 = s.V1R;
Vector4 my7 = s.V7R;
Vector4 mz0 = my1 + my7;
Vector4 my3 = s.V3R;
Vector4 mz2 = my3 + my7;
Vector4 my5 = s.V5R;
Vector4 mz1 = my3 + my5;
Vector4 mz3 = my1 + my5;
Vector4 mz4 = (mz0 + mz1) * C_1_175876;
mz2 = (mz2 * C_1_961571) + mz4;
mz3 = (mz3 * C_0_390181) + mz4;
mz0 = mz0 * C_0_899976;
mz1 = mz1 * C_2_562915;
Vector4 mb3 = (my7 * C_0_298631) + mz0 + mz2;
Vector4 mb2 = (my5 * C_2_053120) + mz1 + mz3;
Vector4 mb1 = (my3 * C_3_072711) + mz1 + mz2;
Vector4 mb0 = (my1 * C_1_501321) + mz0 + mz3;
Vector4 my2 = s.V2R;
Vector4 my6 = s.V6R;
mz4 = (my2 + my6) * C_0_541196;
Vector4 my0 = s.V0R;
Vector4 my4 = s.V4R;
mz0 = my0 + my4;
mz1 = my0 - my4;
mz2 = mz4 + (my6 * C_1_847759);
mz3 = mz4 + (my2 * C_0_765367);
my0 = mz0 + mz3;
my3 = mz0 - mz3;
my1 = mz1 + mz2;
my2 = mz1 - mz2;
d.V0R = my0 + mb0;
d.V7R = my0 - mb0;
d.V1R = my1 + mb1;
d.V6R = my1 - mb1;
d.V2R = my2 + mb2;
d.V5R = my2 - mb2;
d.V3R = my3 + mb3;
d.V4R = my3 - mb3;
}
/// <summary>
/// Original:
/// <see>
@ -309,11 +198,84 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
}
/// <summary>
/// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization)
/// Combined operation of <see cref="FDCT8x4_LeftPart(ref Block8x8F, ref Block8x8F)"/> and <see cref="FDCT8x4_RightPart(ref Block8x8F, ref Block8x8F)"/>
/// using AVX commands.
/// </summary>
/// <param name="s">Source</param>
/// <param name="d">Destination</param>
public static void FDCT8x8_Avx(ref Block8x8F s, ref Block8x8F d)
{
#if SUPPORTS_RUNTIME_INTRINSICS
Debug.Assert(Avx.IsSupported, "AVX is required to execute this method");
Vector256<float> t0 = Avx.Add(s.V0, s.V7);
Vector256<float> t7 = Avx.Subtract(s.V0, s.V7);
Vector256<float> t1 = Avx.Add(s.V1, s.V6);
Vector256<float> t6 = Avx.Subtract(s.V1, s.V6);
Vector256<float> t2 = Avx.Add(s.V2, s.V5);
Vector256<float> t5 = Avx.Subtract(s.V2, s.V5);
Vector256<float> t3 = Avx.Add(s.V3, s.V4);
Vector256<float> t4 = Avx.Subtract(s.V3, s.V4);
Vector256<float> c0 = Avx.Add(t0, t3);
Vector256<float> c1 = Avx.Add(t1, t2);
// 0 4
d.V0 = Avx.Add(c0, c1);
d.V4 = Avx.Subtract(c0, c1);
Vector256<float> c3 = Avx.Subtract(t0, t3);
Vector256<float> c2 = Avx.Subtract(t1, t2);
// 2 6
d.V2 = SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(c2, C_V_0_5411), c3, C_V_1_3065);
d.V6 = SimdUtils.HwIntrinsics.MultiplySubstract(Avx.Multiply(c2, C_V_1_3065), c3, C_V_0_5411);
c3 = SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(t4, C_V_1_1758), t7, C_V_0_7856);
c0 = SimdUtils.HwIntrinsics.MultiplySubstract(Avx.Multiply(t4, C_V_0_7856), t7, C_V_1_1758);
c2 = SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(t5, C_V_1_3870), C_V_0_2758, t6);
c1 = SimdUtils.HwIntrinsics.MultiplySubstract(Avx.Multiply(C_V_0_2758, t5), t6, C_V_1_3870);
// 3 5
d.V3 = Avx.Subtract(c0, c2);
d.V5 = Avx.Subtract(c3, c1);
c0 = Avx.Multiply(Avx.Add(c0, c2), C_V_InvSqrt2);
c3 = Avx.Multiply(Avx.Add(c3, c1), C_V_InvSqrt2);
// 1 7
d.V1 = Avx.Add(c0, c3);
d.V7 = Avx.Subtract(c0, c3);
#endif
}
/// <summary>
/// Performs 8x8 matrix Forward Discrete Cosine Transform
/// </summary>
/// <param name="s">Source</param>
/// <param name="d">Destination</param>
public static void FDCT8x8(ref Block8x8F s, ref Block8x8F d)
{
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx.IsSupported)
{
FDCT8x8_Avx(ref s, ref d);
}
else
#endif
{
FDCT8x4_LeftPart(ref s, ref d);
FDCT8x4_RightPart(ref s, ref d);
}
}
/// <summary>
/// Apply floating point FDCT from src into dest
/// </summary>
/// <param name="src">Source</param>
/// <param name="dest">Destination</param>
/// <param name="temp">Temporary block provided by the caller</param>
/// <param name="temp">Temporary block provided by the caller for optimization</param>
/// <param name="offsetSourceByNeg128">If true, a constant -128.0 offset is applied for all values before FDCT </param>
public static void TransformFDCT(
ref Block8x8F src,
@ -327,14 +289,225 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
temp.AddInPlace(-128F);
}
FDCT8x4_LeftPart(ref temp, ref dest);
FDCT8x4_RightPart(ref temp, ref dest);
FDCT8x8(ref temp, ref dest);
dest.TransposeInto(ref temp);
FDCT8x4_LeftPart(ref temp, ref dest);
FDCT8x4_RightPart(ref temp, ref dest);
FDCT8x8(ref temp, ref dest);
dest.MultiplyInPlace(C_0_125);
}
/// <summary>
/// Performs 8x8 matrix Inverse Discrete Cosine Transform
/// </summary>
/// <param name="s">Source</param>
/// <param name="d">Destination</param>
public static void IDCT8x8(ref Block8x8F s, ref Block8x8F d)
{
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx.IsSupported)
{
IDCT8x8_Avx(ref s, ref d);
}
else
#endif
{
IDCT8x4_LeftPart(ref s, ref d);
IDCT8x4_RightPart(ref s, ref d);
}
}
/// <summary>
/// Do IDCT internal operations on the left part of the block. Original src:
/// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261
/// </summary>
/// <param name="s">The source block</param>
/// <param name="d">Destination block</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void IDCT8x4_LeftPart(ref Block8x8F s, ref Block8x8F d)
{
Vector4 my1 = s.V1L;
Vector4 my7 = s.V7L;
Vector4 mz0 = my1 + my7;
Vector4 my3 = s.V3L;
Vector4 mz2 = my3 + my7;
Vector4 my5 = s.V5L;
Vector4 mz1 = my3 + my5;
Vector4 mz3 = my1 + my5;
Vector4 mz4 = (mz0 + mz1) * C_1_175876;
mz2 = (mz2 * C_1_961571) + mz4;
mz3 = (mz3 * C_0_390181) + mz4;
mz0 = mz0 * C_0_899976;
mz1 = mz1 * C_2_562915;
Vector4 mb3 = (my7 * C_0_298631) + mz0 + mz2;
Vector4 mb2 = (my5 * C_2_053120) + mz1 + mz3;
Vector4 mb1 = (my3 * C_3_072711) + mz1 + mz2;
Vector4 mb0 = (my1 * C_1_501321) + mz0 + mz3;
Vector4 my2 = s.V2L;
Vector4 my6 = s.V6L;
mz4 = (my2 + my6) * C_0_541196;
Vector4 my0 = s.V0L;
Vector4 my4 = s.V4L;
mz0 = my0 + my4;
mz1 = my0 - my4;
mz2 = mz4 + (my6 * C_1_847759);
mz3 = mz4 + (my2 * C_0_765367);
my0 = mz0 + mz3;
my3 = mz0 - mz3;
my1 = mz1 + mz2;
my2 = mz1 - mz2;
d.V0L = my0 + mb0;
d.V7L = my0 - mb0;
d.V1L = my1 + mb1;
d.V6L = my1 - mb1;
d.V2L = my2 + mb2;
d.V5L = my2 - mb2;
d.V3L = my3 + mb3;
d.V4L = my3 - mb3;
}
/// <summary>
/// Do IDCT internal operations on the right part of the block.
/// Original src:
/// https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L261
/// </summary>
/// <param name="s">The source block</param>
/// <param name="d">The destination block</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void IDCT8x4_RightPart(ref Block8x8F s, ref Block8x8F d)
{
Vector4 my1 = s.V1R;
Vector4 my7 = s.V7R;
Vector4 mz0 = my1 + my7;
Vector4 my3 = s.V3R;
Vector4 mz2 = my3 + my7;
Vector4 my5 = s.V5R;
Vector4 mz1 = my3 + my5;
Vector4 mz3 = my1 + my5;
Vector4 mz4 = (mz0 + mz1) * C_1_175876;
mz2 = (mz2 * C_1_961571) + mz4;
mz3 = (mz3 * C_0_390181) + mz4;
mz0 = mz0 * C_0_899976;
mz1 = mz1 * C_2_562915;
Vector4 mb3 = (my7 * C_0_298631) + mz0 + mz2;
Vector4 mb2 = (my5 * C_2_053120) + mz1 + mz3;
Vector4 mb1 = (my3 * C_3_072711) + mz1 + mz2;
Vector4 mb0 = (my1 * C_1_501321) + mz0 + mz3;
Vector4 my2 = s.V2R;
Vector4 my6 = s.V6R;
mz4 = (my2 + my6) * C_0_541196;
Vector4 my0 = s.V0R;
Vector4 my4 = s.V4R;
mz0 = my0 + my4;
mz1 = my0 - my4;
mz2 = mz4 + (my6 * C_1_847759);
mz3 = mz4 + (my2 * C_0_765367);
my0 = mz0 + mz3;
my3 = mz0 - mz3;
my1 = mz1 + mz2;
my2 = mz1 - mz2;
d.V0R = my0 + mb0;
d.V7R = my0 - mb0;
d.V1R = my1 + mb1;
d.V6R = my1 - mb1;
d.V2R = my2 + mb2;
d.V5R = my2 - mb2;
d.V3R = my3 + mb3;
d.V4R = my3 - mb3;
}
/// <summary>
/// Combined operation of <see cref="IDCT8x4_LeftPart(ref Block8x8F, ref Block8x8F)"/> and <see cref="IDCT8x4_RightPart(ref Block8x8F, ref Block8x8F)"/>
/// using AVX commands.
/// </summary>
/// <param name="s">Source</param>
/// <param name="d">Destination</param>
public static void IDCT8x8_Avx(ref Block8x8F s, ref Block8x8F d)
{
#if SUPPORTS_RUNTIME_INTRINSICS
Debug.Assert(Avx.IsSupported, "AVX is required to execute this method");
Vector256<float> my1 = s.V1;
Vector256<float> my7 = s.V7;
Vector256<float> mz0 = Avx.Add(my1, my7);
Vector256<float> my3 = s.V3;
Vector256<float> mz2 = Avx.Add(my3, my7);
Vector256<float> my5 = s.V5;
Vector256<float> mz1 = Avx.Add(my3, my5);
Vector256<float> mz3 = Avx.Add(my1, my5);
Vector256<float> mz4 = Avx.Multiply(Avx.Add(mz0, mz1), C_V_1_1758);
mz2 = SimdUtils.HwIntrinsics.MultiplyAdd(mz4, mz2, C_V_n1_9615);
mz3 = SimdUtils.HwIntrinsics.MultiplyAdd(mz4, mz3, C_V_n0_3901);
mz0 = Avx.Multiply(mz0, C_V_n0_8999);
mz1 = Avx.Multiply(mz1, C_V_n2_5629);
Vector256<float> mb3 = Avx.Add(SimdUtils.HwIntrinsics.MultiplyAdd(mz0, my7, C_V_0_2986), mz2);
Vector256<float> mb2 = Avx.Add(SimdUtils.HwIntrinsics.MultiplyAdd(mz1, my5, C_V_2_0531), mz3);
Vector256<float> mb1 = Avx.Add(SimdUtils.HwIntrinsics.MultiplyAdd(mz1, my3, C_V_3_0727), mz2);
Vector256<float> mb0 = Avx.Add(SimdUtils.HwIntrinsics.MultiplyAdd(mz0, my1, C_V_1_5013), mz3);
Vector256<float> my2 = s.V2;
Vector256<float> my6 = s.V6;
mz4 = Avx.Multiply(Avx.Add(my2, my6), C_V_0_5411);
Vector256<float> my0 = s.V0;
Vector256<float> my4 = s.V4;
mz0 = Avx.Add(my0, my4);
mz1 = Avx.Subtract(my0, my4);
mz2 = SimdUtils.HwIntrinsics.MultiplyAdd(mz4, my6, C_V_n1_8477);
mz3 = SimdUtils.HwIntrinsics.MultiplyAdd(mz4, my2, C_V_0_7653);
my0 = Avx.Add(mz0, mz3);
my3 = Avx.Subtract(mz0, mz3);
my1 = Avx.Add(mz1, mz2);
my2 = Avx.Subtract(mz1, mz2);
d.V0 = Avx.Add(my0, mb0);
d.V7 = Avx.Subtract(my0, mb0);
d.V1 = Avx.Add(my1, mb1);
d.V6 = Avx.Subtract(my1, mb1);
d.V2 = Avx.Add(my2, mb2);
d.V5 = Avx.Subtract(my2, mb2);
d.V3 = Avx.Add(my3, mb3);
d.V4 = Avx.Subtract(my3, mb3);
#endif
}
/// <summary>
/// Apply floating point IDCT transformation into dest, using a temporary block 'temp' provided by the caller (optimization).
/// Ported from https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L239
/// </summary>
/// <param name="src">Source</param>
/// <param name="dest">Destination</param>
/// <param name="temp">Temporary block provided by the caller</param>
public static void TransformIDCT(ref Block8x8F src, ref Block8x8F dest, ref Block8x8F temp)
{
src.TransposeInto(ref temp);
IDCT8x8(ref temp, ref dest);
dest.TransposeInto(ref temp);
IDCT8x8(ref temp, ref dest);
// TODO: What if we leave the blocks in a scaled-by-x8 state until final color packing?
dest.MultiplyInPlace(C_0_125);
}
}

22
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -928,9 +929,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
int length = remaining;
using (IManagedByteBuffer huffmanData = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(256, AllocationOptions.Clean))
using (IMemoryOwner<byte> huffmanData = this.Configuration.MemoryAllocator.Allocate<byte>(256, AllocationOptions.Clean))
{
ref byte huffmanDataRef = ref MemoryMarshal.GetReference(huffmanData.GetSpan());
Span<byte> huffmanDataSpan = huffmanData.GetSpan();
ref byte huffmanDataRef = ref MemoryMarshal.GetReference(huffmanDataSpan);
for (int i = 2; i < remaining;)
{
byte huffmanTableSpec = (byte)stream.ReadByte();
@ -949,11 +951,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowInvalidImageContentException("Bad Huffman Table index.");
}
stream.Read(huffmanData.Array, 0, 16);
stream.Read(huffmanDataSpan, 0, 16);
using (IManagedByteBuffer codeLengths = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(17, AllocationOptions.Clean))
using (IMemoryOwner<byte> codeLengths = this.Configuration.MemoryAllocator.Allocate<byte>(17, AllocationOptions.Clean))
{
ref byte codeLengthsRef = ref MemoryMarshal.GetReference(codeLengths.GetSpan());
Span<byte> codeLengthsSpan = codeLengths.GetSpan();
ref byte codeLengthsRef = ref MemoryMarshal.GetReference(codeLengthsSpan);
int codeLengthSum = 0;
for (int j = 1; j < 17; j++)
@ -968,17 +971,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowInvalidImageContentException("Huffman table has excessive length.");
}
using (IManagedByteBuffer huffmanValues = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(256, AllocationOptions.Clean))
using (IMemoryOwner<byte> huffmanValues = this.Configuration.MemoryAllocator.Allocate<byte>(256, AllocationOptions.Clean))
{
stream.Read(huffmanValues.Array, 0, codeLengthSum);
Span<byte> huffmanValuesSpan = huffmanValues.GetSpan();
stream.Read(huffmanValuesSpan, 0, codeLengthSum);
i += 17 + codeLengthSum;
this.BuildHuffmanTable(
tableType == 0 ? this.dcHuffmanTables : this.acHuffmanTables,
tableIndex,
codeLengths.GetSpan(),
huffmanValues.GetSpan());
codeLengthsSpan,
huffmanValuesSpan);
}
}
}

606
src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs

@ -5,14 +5,11 @@ using System;
using System.Buffers.Binary;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc;
@ -36,17 +33,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
private readonly byte[] buffer = new byte[20];
/// <summary>
/// A buffer for reducing the number of stream writes when emitting Huffman tables. 64 seems to be enough.
/// </summary>
private readonly byte[] emitBuffer = new byte[64];
/// <summary>
/// A buffer for reducing the number of stream writes when emitting Huffman tables. Max combined table lengths +
/// identifier.
/// </summary>
private readonly byte[] huffmanBuffer = new byte[179];
/// <summary>
/// Gets or sets the subsampling method to use.
/// </summary>
@ -62,26 +48,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
private readonly JpegColorType? colorType;
/// <summary>
/// The accumulated bits to write to the stream.
/// </summary>
private uint accumulatedBits;
/// <summary>
/// The accumulated bit count.
/// </summary>
private uint bitCount;
/// <summary>
/// The scaled chrominance table, in zig-zag order.
/// </summary>
private Block8x8F chrominanceQuantTable;
/// <summary>
/// The scaled luminance table, in zig-zag order.
/// </summary>
private Block8x8F luminanceQuantTable;
/// <summary>
/// The output stream. All attempted writes after the first error become no-ops.
/// </summary>
@ -98,29 +64,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.colorType = options.ColorType;
}
/// <summary>
/// Gets the counts the number of bits needed to hold an integer.
/// </summary>
// The C# compiler emits this as a compile-time constant embedded in the PE file.
// This is effectively compiled down to: return new ReadOnlySpan<byte>(&data, length)
// More details can be found: https://github.com/dotnet/roslyn/pull/24621
private static ReadOnlySpan<byte> BitCountLut => new byte[]
{
0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8,
};
/// <summary>
/// Gets the unscaled quantization tables in zig-zag order. Each
/// encoder copies and scales the tables according to its quality parameter.
@ -131,14 +74,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// This is effectively compiled down to: return new ReadOnlySpan<byte>(&data, length)
// More details can be found: https://github.com/dotnet/roslyn/pull/24621
private static ReadOnlySpan<byte> UnscaledQuant_Luminance => new byte[]
{
// Luminance.
16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24,
40, 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60,
57, 51, 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80,
109, 81, 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112,
100, 120, 92, 101, 103, 99,
};
{
// Luminance.
16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24,
40, 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60,
57, 51, 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80,
109, 81, 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112,
100, 120, 92, 101, 103, 99,
};
/// <summary>
/// Gets the unscaled quantization tables in zig-zag order. Each
@ -150,14 +93,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// This is effectively compiled down to: return new ReadOnlySpan<byte>(&data, length)
// More details can be found: https://github.com/dotnet/roslyn/pull/24621
private static ReadOnlySpan<byte> UnscaledQuant_Chrominance => new byte[]
{
// Chrominance.
17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66,
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
};
{
// Chrominance.
17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66,
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
};
/// <summary>
/// Encode writes the image to the jpeg baseline format with the given options.
@ -171,14 +114,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
Guard.NotNull(image, nameof(image));
Guard.NotNull(stream, nameof(stream));
cancellationToken.ThrowIfCancellationRequested();
const ushort max = JpegConstants.MaxLength;
if (image.Width >= max || image.Height >= max)
if (image.Width >= JpegConstants.MaxLength || image.Height >= JpegConstants.MaxLength)
{
throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}.");
JpegThrowHelper.ThrowDimensionsTooLarge(image.Width, image.Height);
}
cancellationToken.ThrowIfCancellationRequested();
this.outputStream = stream;
ImageMetadata metadata = image.Metadata;
@ -201,10 +144,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
// Initialize the quantization tables.
InitQuantizationTable(0, scale, ref this.luminanceQuantTable);
// TODO: This looks ugly, should we write chrominance table for luminance-only images?
// If not - this can code can be simplified
Block8x8F luminanceQuantTable = default;
Block8x8F chrominanceQuantTable = default;
InitQuantizationTable(0, scale, ref luminanceQuantTable);
if (componentCount > 1)
{
InitQuantizationTable(1, scale, ref this.chrominanceQuantTable);
InitQuantizationTable(1, scale, ref chrominanceQuantTable);
}
// Write the Start Of Image marker.
@ -214,7 +161,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.WriteProfiles(metadata);
// Write the quantization tables.
this.WriteDefineQuantizationTables();
this.WriteDefineQuantizationTables(ref luminanceQuantTable, ref chrominanceQuantTable);
// Write the image dimensions.
this.WriteStartOfFrame(image.Width, image.Height, componentCount);
@ -222,13 +169,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Write the Huffman tables.
this.WriteDefineHuffmanTables(componentCount);
// Write the image data.
// Write the scan header.
this.WriteStartOfScan(image, componentCount, cancellationToken);
// Write the scan compressed data.
var scanEncoder = new HuffmanScanEncoder(stream);
if (this.colorType == JpegColorType.Luminance)
{
scanEncoder.EncodeGrayscale(image, ref luminanceQuantTable, cancellationToken);
}
else
{
switch (this.subsample)
{
case JpegSubsample.Ratio444:
scanEncoder.Encode444(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken);
break;
case JpegSubsample.Ratio420:
scanEncoder.Encode420(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken);
break;
}
}
// Write the End Of Image marker.
this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = JpegConstants.Markers.EOI;
stream.Write(this.buffer, 0, 2);
this.WriteEndOfImageMarker();
stream.Flush();
}
@ -248,248 +213,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
}
/// <summary>
/// Initializes quantization table.
/// </summary>
/// <param name="i">The quantization index.</param>
/// <param name="scale">The scaling factor.</param>
/// <param name="quant">The quantization table.</param>
private static void InitQuantizationTable(int i, int scale, ref Block8x8F quant)
{
DebugGuard.MustBeBetweenOrEqualTo(i, 0, 1, nameof(i));
ReadOnlySpan<byte> unscaledQuant = (i == 0) ? UnscaledQuant_Luminance : UnscaledQuant_Chrominance;
for (int j = 0; j < Block8x8F.Size; j++)
{
int x = unscaledQuant[j];
x = ((x * scale) + 50) / 100;
if (x < 1)
{
x = 1;
}
if (x > 255)
{
x = 255;
}
quant[j] = x;
}
}
/// <summary>
/// Emits the least significant count of bits of bits to the bit-stream.
/// The precondition is bits
/// <example>
/// &lt; 1&lt;&lt;nBits &amp;&amp; nBits &lt;= 16
/// </example>
/// .
/// </summary>
/// <param name="bits">The packed bits.</param>
/// <param name="count">The number of bits</param>
/// <param name="emitBufferBase">The reference to the emitBuffer.</param>
[MethodImpl(InliningOptions.ShortMethod)]
private void Emit(uint bits, uint count, ref byte emitBufferBase)
{
count += this.bitCount;
bits <<= (int)(32 - count);
bits |= this.accumulatedBits;
// Only write if more than 8 bits.
if (count >= 8)
{
// Track length
int len = 0;
while (count >= 8)
{
byte b = (byte)(bits >> 24);
Unsafe.Add(ref emitBufferBase, len++) = b;
if (b == byte.MaxValue)
{
Unsafe.Add(ref emitBufferBase, len++) = byte.MinValue;
}
bits <<= 8;
count -= 8;
}
if (len > 0)
{
this.outputStream.Write(this.emitBuffer, 0, len);
}
}
this.accumulatedBits = bits;
this.bitCount = count;
}
/// <summary>
/// Emits the given value with the given Huffman encoder.
/// </summary>
/// <param name="index">The index of the Huffman encoder</param>
/// <param name="value">The value to encode.</param>
/// <param name="emitBufferBase">The reference to the emit buffer.</param>
[MethodImpl(InliningOptions.ShortMethod)]
private void EmitHuff(HuffIndex index, int value, ref byte emitBufferBase)
{
uint x = HuffmanLut.TheHuffmanLut[(int)index].Values[value];
this.Emit(x & ((1 << 24) - 1), x >> 24, ref emitBufferBase);
}
/// <summary>
/// Emits a run of runLength copies of value encoded with the given Huffman encoder.
/// </summary>
/// <param name="index">The index of the Huffman encoder</param>
/// <param name="runLength">The number of copies to encode.</param>
/// <param name="value">The value to encode.</param>
/// <param name="emitBufferBase">The reference to the emit buffer.</param>
[MethodImpl(InliningOptions.ShortMethod)]
private void EmitHuffRLE(HuffIndex index, int runLength, int value, ref byte emitBufferBase)
{
int a = value;
int b = value;
if (a < 0)
{
a = -value;
b = value - 1;
}
uint bt;
if (a < 0x100)
{
bt = BitCountLut[a];
}
else
{
bt = 8 + (uint)BitCountLut[a >> 8];
}
this.EmitHuff(index, (int)((uint)(runLength << 4) | bt), ref emitBufferBase);
if (bt > 0)
{
this.Emit((uint)b & (uint)((1 << ((int)bt)) - 1), bt, ref emitBufferBase);
}
}
/// <summary>
/// Encodes the image with no subsampling.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
/// <param name="emitBufferBase">The reference to the emit buffer.</param>
private void Encode444<TPixel>(Image<TPixel> pixels, CancellationToken cancellationToken, ref byte emitBufferBase)
where TPixel : unmanaged, IPixel<TPixel>
{
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
// (Partially done with YCbCrForwardConverter<TPixel>)
Block8x8F temp1 = default;
Block8x8F temp2 = default;
Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable;
Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable;
var unzig = ZigZag.CreateUnzigTable();
// ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
var pixelConverter = YCbCrForwardConverter<TPixel>.Create();
ImageFrame<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer;
RowOctet<TPixel> currentRows = default;
for (int y = 0; y < pixels.Height; y += 8)
{
cancellationToken.ThrowIfCancellationRequested();
currentRows.Update(pixelBuffer, y);
for (int x = 0; x < pixels.Width; x += 8)
{
pixelConverter.Convert(frame, x, y, ref currentRows);
prevDCY = this.WriteBlock(
QuantIndex.Luminance,
prevDCY,
ref pixelConverter.Y,
ref temp1,
ref temp2,
ref onStackLuminanceQuantTable,
ref unzig,
ref emitBufferBase);
prevDCCb = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCb,
ref pixelConverter.Cb,
ref temp1,
ref temp2,
ref onStackChrominanceQuantTable,
ref unzig,
ref emitBufferBase);
prevDCCr = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCr,
ref pixelConverter.Cr,
ref temp1,
ref temp2,
ref onStackChrominanceQuantTable,
ref unzig,
ref emitBufferBase);
}
}
}
/// <summary>
/// Encodes the image with no chroma, just luminance.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
/// <param name="emitBufferBase">The reference to the emit buffer.</param>
private void EncodeGrayscale<TPixel>(Image<TPixel> pixels, CancellationToken cancellationToken, ref byte emitBufferBase)
where TPixel : unmanaged, IPixel<TPixel>
{
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
// (Partially done with YCbCrForwardConverter<TPixel>)
Block8x8F temp1 = default;
Block8x8F temp2 = default;
Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable;
var unzig = ZigZag.CreateUnzigTable();
// ReSharper disable once InconsistentNaming
int prevDCY = 0;
var pixelConverter = LuminanceForwardConverter<TPixel>.Create();
ImageFrame<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer;
RowOctet<TPixel> currentRows = default;
for (int y = 0; y < pixels.Height; y += 8)
{
cancellationToken.ThrowIfCancellationRequested();
currentRows.Update(pixelBuffer, y);
for (int x = 0; x < pixels.Width; x += 8)
{
pixelConverter.Convert(frame, x, y, ref currentRows);
prevDCY = this.WriteBlock(
QuantIndex.Luminance,
prevDCY,
ref pixelConverter.Y,
ref temp1,
ref temp2,
ref onStackLuminanceQuantTable,
ref unzig,
ref emitBufferBase);
}
}
}
/// <summary>
/// Writes the application header containing the JFIF identifier plus extra data.
/// </summary>
@ -539,72 +262,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.outputStream.Write(this.buffer, 0, 20);
}
/// <summary>
/// Writes a block of pixel data using the given quantization table,
/// returning the post-quantized DC value of the DCT-transformed block.
/// The block is in natural (not zig-zag) order.
/// </summary>
/// <param name="index">The quantization table index.</param>
/// <param name="prevDC">The previous DC value.</param>
/// <param name="src">Source block</param>
/// <param name="tempDest1">Temporal block to be used as FDCT Destination</param>
/// <param name="tempDest2">Temporal block 2</param>
/// <param name="quant">Quantization table</param>
/// <param name="unZig">The 8x8 Unzig block.</param>
/// <param name="emitBufferBase">The reference to the emit buffer.</param>
/// <returns>The <see cref="int"/>.</returns>
private int WriteBlock(
QuantIndex index,
int prevDC,
ref Block8x8F src,
ref Block8x8F tempDest1,
ref Block8x8F tempDest2,
ref Block8x8F quant,
ref ZigZag unZig,
ref byte emitBufferBase)
{
FastFloatingPointDCT.TransformFDCT(ref src, ref tempDest1, ref tempDest2);
Block8x8F.Quantize(ref tempDest1, ref tempDest2, ref quant, ref unZig);
int dc = (int)tempDest2[0];
// Emit the DC delta.
this.EmitHuffRLE((HuffIndex)((2 * (int)index) + 0), 0, dc - prevDC, ref emitBufferBase);
// Emit the AC components.
var h = (HuffIndex)((2 * (int)index) + 1);
int runLength = 0;
for (int zig = 1; zig < Block8x8F.Size; zig++)
{
int ac = (int)tempDest2[zig];
if (ac == 0)
{
runLength++;
}
else
{
while (runLength > 15)
{
this.EmitHuff(h, 0xf0, ref emitBufferBase);
runLength -= 16;
}
this.EmitHuffRLE(h, runLength, ac, ref emitBufferBase);
runLength = 0;
}
}
if (runLength > 0)
{
this.EmitHuff(h, 0x00, ref emitBufferBase);
}
return dc;
}
/// <summary>
/// Writes the Define Huffman Table marker and tables.
/// </summary>
@ -638,34 +295,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen);
for (int i = 0; i < specs.Length; i++)
{
ref HuffmanSpec spec = ref specs[i];
int len = 0;
fixed (byte* huffman = this.huffmanBuffer)
fixed (byte* count = spec.Count)
fixed (byte* values = spec.Values)
{
huffman[len++] = headers[i];
for (int c = 0; c < spec.Count.Length; c++)
{
huffman[len++] = count[c];
}
for (int v = 0; v < spec.Values.Length; v++)
{
huffman[len++] = values[v];
}
}
this.outputStream.Write(this.huffmanBuffer, 0, len);
this.outputStream.WriteByte(headers[i]);
this.outputStream.Write(specs[i].Count);
this.outputStream.Write(specs[i].Values);
}
}
/// <summary>
/// Writes the Define Quantization Marker and tables.
/// </summary>
private void WriteDefineQuantizationTables()
private void WriteDefineQuantizationTables(ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable)
{
// Marker + quantization table lengths
int markerlen = 2 + (QuantizationTableCount * (1 + Block8x8F.Size));
@ -677,8 +316,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
byte[] dqt = new byte[dqtCount];
int offset = 0;
WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref this.luminanceQuantTable);
WriteDataToDqt(dqt, ref offset, QuantIndex.Chrominance, ref this.chrominanceQuantTable);
WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref luminanceQuantTable);
WriteDataToDqt(dqt, ref offset, QuantIndex.Chrominance, ref chrominanceQuantTable);
this.outputStream.Write(dqt, 0, dqtCount);
}
@ -982,7 +621,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
private void WriteStartOfScan<TPixel>(Image<TPixel> image, int componentCount, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
Span<byte> componentId = stackalloc byte[]
{
0x01,
@ -1024,111 +662,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.buffer[sosSize] = 0x3f; // Se - End of spectral selection.
this.buffer[sosSize + 1] = 0x00; // Ah + Ah (Successive approximation bit position high + low)
this.outputStream.Write(this.buffer, 0, sosSize + 2);
ref byte emitBufferBase = ref MemoryMarshal.GetReference<byte>(this.emitBuffer);
if (this.colorType == JpegColorType.Luminance)
{
this.EncodeGrayscale(image, cancellationToken, ref emitBufferBase);
}
else
{
switch (this.subsample)
{
case JpegSubsample.Ratio444:
this.Encode444(image, cancellationToken, ref emitBufferBase);
break;
case JpegSubsample.Ratio420:
this.Encode420(image, cancellationToken, ref emitBufferBase);
break;
}
}
// Pad the last byte with 1's.
this.Emit(0x7f, 7, ref emitBufferBase);
}
/// <summary>
/// Encodes the image with subsampling. The Cb and Cr components are each subsampled
/// at a factor of 2 both horizontally and vertically.
/// Writes the EndOfImage marker.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
/// <param name="emitBufferBase">The reference to the emit buffer.</param>
private void Encode420<TPixel>(Image<TPixel> pixels, CancellationToken cancellationToken, ref byte emitBufferBase)
where TPixel : unmanaged, IPixel<TPixel>
private void WriteEndOfImageMarker()
{
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.)
Block8x8F b = default;
Span<Block8x8F> cb = stackalloc Block8x8F[4];
Span<Block8x8F> cr = stackalloc Block8x8F[4];
Block8x8F temp1 = default;
Block8x8F temp2 = default;
Block8x8F onStackLuminanceQuantTable = this.luminanceQuantTable;
Block8x8F onStackChrominanceQuantTable = this.chrominanceQuantTable;
var unzig = ZigZag.CreateUnzigTable();
var pixelConverter = YCbCrForwardConverter<TPixel>.Create();
// ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
ImageFrame<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer;
RowOctet<TPixel> currentRows = default;
for (int y = 0; y < pixels.Height; y += 16)
{
cancellationToken.ThrowIfCancellationRequested();
for (int x = 0; x < pixels.Width; x += 16)
{
for (int i = 0; i < 4; i++)
{
int xOff = (i & 1) * 8;
int yOff = (i & 2) * 4;
currentRows.Update(pixelBuffer, y + yOff);
pixelConverter.Convert(frame, x + xOff, y + yOff, ref currentRows);
cb[i] = pixelConverter.Cb;
cr[i] = pixelConverter.Cr;
prevDCY = this.WriteBlock(
QuantIndex.Luminance,
prevDCY,
ref pixelConverter.Y,
ref temp1,
ref temp2,
ref onStackLuminanceQuantTable,
ref unzig,
ref emitBufferBase);
}
Block8x8F.Scale16X16To8X8(ref b, cb);
prevDCCb = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCb,
ref b,
ref temp1,
ref temp2,
ref onStackChrominanceQuantTable,
ref unzig,
ref emitBufferBase);
Block8x8F.Scale16X16To8X8(ref b, cr);
prevDCCr = this.WriteBlock(
QuantIndex.Chrominance,
prevDCCr,
ref b,
ref temp1,
ref temp2,
ref onStackChrominanceQuantTable,
ref unzig,
ref emitBufferBase);
}
}
this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = JpegConstants.Markers.EOI;
this.outputStream.Write(this.buffer, 0, 2);
}
/// <summary>
@ -1145,5 +688,34 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.buffer[3] = (byte)(length & 0xff);
this.outputStream.Write(this.buffer, 0, 4);
}
/// <summary>
/// Initializes quantization table.
/// </summary>
/// <param name="i">The quantization index.</param>
/// <param name="scale">The scaling factor.</param>
/// <param name="quant">The quantization table.</param>
private static void InitQuantizationTable(int i, int scale, ref Block8x8F quant)
{
DebugGuard.MustBeBetweenOrEqualTo(i, 0, 1, nameof(i));
ReadOnlySpan<byte> unscaledQuant = (i == 0) ? UnscaledQuant_Luminance : UnscaledQuant_Chrominance;
for (int j = 0; j < Block8x8F.Size; j++)
{
int x = unscaledQuant[j];
x = ((x * scale) + 50) / 100;
if (x < 1)
{
x = 1;
}
if (x > 255)
{
x = 255;
}
quant[j] = x;
}
}
}
}

3
src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs

@ -46,5 +46,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowInvalidImageDimensions(int width, int height) => throw new InvalidImageContentException($"Invalid image dimensions: {width}x{height}.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowDimensionsTooLarge(int width, int height) => throw new ImageFormatException($"Image is too large to encode at {width}x{height} for JPEG format.");
}
}

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

@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode(Span<byte> scanline, Span<byte> previousScanline, int bytesPerPixel)
{
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSameSized<byte>(scanline, previousScanline, nameof(scanline));
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline);
@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(Span<byte> scanline, Span<byte> previousScanline, Span<byte> result, int bytesPerPixel, out int sum)
public static void Encode(ReadOnlySpan<byte> scanline, ReadOnlySpan<byte> previousScanline, Span<byte> result, int bytesPerPixel, out int sum)
{
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));

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

@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode(Span<byte> scanline, Span<byte> previousScanline, int bytesPerPixel)
{
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSameSized<byte>(scanline, previousScanline, nameof(scanline));
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline);
@ -64,7 +64,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(Span<byte> scanline, Span<byte> previousScanline, Span<byte> result, int bytesPerPixel, out int sum)
public static void Encode(ReadOnlySpan<byte> scanline, ReadOnlySpan<byte> previousScanline, Span<byte> result, int bytesPerPixel, out int sum)
{
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));

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

@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
/// <param name="bytesPerPixel">The bytes per pixel.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(Span<byte> scanline, Span<byte> result, int bytesPerPixel, out int sum)
public static void Encode(ReadOnlySpan<byte> scanline, ReadOnlySpan<byte> result, int bytesPerPixel, out int sum)
{
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));

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

@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Decode(Span<byte> scanline, Span<byte> previousScanline)
{
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSameSized<byte>(scanline, previousScanline, nameof(scanline));
ref byte scanBaseRef = ref MemoryMarshal.GetReference(scanline);
ref byte prevBaseRef = ref MemoryMarshal.GetReference(previousScanline);
@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters
/// <param name="result">The filtered scanline result.</param>
/// <param name="sum">The sum of the total variance of the filtered row</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Encode(Span<byte> scanline, Span<byte> previousScanline, Span<byte> result, out int sum)
public static void Encode(ReadOnlySpan<byte> scanline, ReadOnlySpan<byte> previousScanline, Span<byte> result, out int sum)
{
DebugGuard.MustBeSameSized(scanline, previousScanline, nameof(scanline));
DebugGuard.MustBeSizedAtLeast(result, scanline, nameof(result));

6
src/ImageSharp/Formats/Png/PngChunk.cs

@ -1,7 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Memory;
using System.Buffers;
namespace SixLabors.ImageSharp.Formats.Png
{
@ -10,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
internal readonly struct PngChunk
{
public PngChunk(int length, PngChunkType type, IManagedByteBuffer data = null)
public PngChunk(int length, PngChunkType type, IMemoryOwner<byte> data = null)
{
this.Length = length;
this.Type = type;
@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Gets the data bytes appropriate to the chunk type, if any.
/// This field can be of zero length or null.
/// </summary>
public IManagedByteBuffer Data { get; }
public IMemoryOwner<byte> Data { get; }
/// <summary>
/// Gets a value indicating whether the given chunk is critical to decoding

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>

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

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
@ -10,10 +11,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;
@ -85,12 +85,12 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// Previous scanline processed.
/// </summary>
private IManagedByteBuffer previousScanline;
private IMemoryOwner<byte> previousScanline;
/// <summary>
/// The current scanline that is being processed.
/// </summary>
private IManagedByteBuffer scanline;
private IMemoryOwner<byte> scanline;
/// <summary>
/// The index of the current scanline being processed.
@ -150,7 +150,7 @@ namespace SixLabors.ImageSharp.Formats.Png
switch (chunk.Type)
{
case PngChunkType.Header:
this.ReadHeaderChunk(pngMetadata, chunk.Data.Array);
this.ReadHeaderChunk(pngMetadata, chunk.Data.GetSpan());
break;
case PngChunkType.Physical:
this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan());
@ -169,29 +169,29 @@ namespace SixLabors.ImageSharp.Formats.Png
break;
case PngChunkType.Palette:
var pal = new byte[chunk.Length];
Buffer.BlockCopy(chunk.Data.Array, 0, pal, 0, chunk.Length);
chunk.Data.GetSpan().CopyTo(pal);
this.palette = pal;
break;
case PngChunkType.Transparency:
var alpha = new byte[chunk.Length];
Buffer.BlockCopy(chunk.Data.Array, 0, alpha, 0, chunk.Length);
chunk.Data.GetSpan().CopyTo(alpha);
this.paletteAlpha = alpha;
this.AssignTransparentMarkers(alpha, pngMetadata);
break;
case PngChunkType.Text:
this.ReadTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
this.ReadTextChunk(pngMetadata, chunk.Data.GetSpan());
break;
case PngChunkType.CompressedText:
this.ReadCompressedTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
this.ReadCompressedTextChunk(pngMetadata, chunk.Data.GetSpan());
break;
case PngChunkType.InternationalText:
this.ReadInternationalTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
this.ReadInternationalTextChunk(pngMetadata, chunk.Data.GetSpan());
break;
case PngChunkType.Exif:
if (!this.ignoreMetadata)
{
var exifData = new byte[chunk.Length];
Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length);
chunk.Data.GetSpan().CopyTo(exifData);
metadata.ExifProfile = new ExifProfile(exifData);
}
@ -240,7 +240,7 @@ namespace SixLabors.ImageSharp.Formats.Png
switch (chunk.Type)
{
case PngChunkType.Header:
this.ReadHeaderChunk(pngMetadata, chunk.Data.Array);
this.ReadHeaderChunk(pngMetadata, chunk.Data.GetSpan());
break;
case PngChunkType.Physical:
this.ReadPhysicalChunk(metadata, chunk.Data.GetSpan());
@ -252,19 +252,19 @@ namespace SixLabors.ImageSharp.Formats.Png
this.SkipChunkDataAndCrc(chunk);
break;
case PngChunkType.Text:
this.ReadTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
this.ReadTextChunk(pngMetadata, chunk.Data.GetSpan());
break;
case PngChunkType.CompressedText:
this.ReadCompressedTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
this.ReadCompressedTextChunk(pngMetadata, chunk.Data.GetSpan());
break;
case PngChunkType.InternationalText:
this.ReadInternationalTextChunk(pngMetadata, chunk.Data.Array.AsSpan(0, chunk.Length));
this.ReadInternationalTextChunk(pngMetadata, chunk.Data.GetSpan());
break;
case PngChunkType.Exif:
if (!this.ignoreMetadata)
{
var exifData = new byte[chunk.Length];
Buffer.BlockCopy(chunk.Data.Array, 0, exifData, 0, chunk.Length);
chunk.Data.GetSpan().CopyTo(exifData);
metadata.ExifProfile = new ExifProfile(exifData);
}
@ -313,7 +313,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="bits">The number of bits per value.</param>
/// <param name="buffer">The new array.</param>
/// <returns>The resulting <see cref="ReadOnlySpan{Byte}"/> array.</returns>
private bool TryScaleUpTo8BitArray(ReadOnlySpan<byte> source, int bytesPerScanline, int bits, out IManagedByteBuffer buffer)
private bool TryScaleUpTo8BitArray(ReadOnlySpan<byte> source, int bytesPerScanline, int bits, out IMemoryOwner<byte> buffer)
{
if (bits >= 8)
{
@ -321,9 +321,9 @@ namespace SixLabors.ImageSharp.Formats.Png
return false;
}
buffer = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerScanline * 8 / bits, AllocationOptions.Clean);
buffer = this.memoryAllocator.Allocate<byte>(bytesPerScanline * 8 / bits, AllocationOptions.Clean);
ref byte sourceRef = ref MemoryMarshal.GetReference(source);
ref byte resultRef = ref buffer.Array[0];
ref byte resultRef = ref buffer.GetReference();
int mask = 0xFF >> (8 - bits);
int resultOffset = 0;
@ -393,8 +393,8 @@ namespace SixLabors.ImageSharp.Formats.Png
this.bytesPerSample = this.header.BitDepth / 8;
}
this.previousScanline = this.memoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean);
this.scanline = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(this.bytesPerScanline, AllocationOptions.Clean);
this.previousScanline = this.memoryAllocator.Allocate<byte>(this.bytesPerScanline, AllocationOptions.Clean);
this.scanline = this.Configuration.MemoryAllocator.Allocate<byte>(this.bytesPerScanline, AllocationOptions.Clean);
}
/// <summary>
@ -505,7 +505,8 @@ namespace SixLabors.ImageSharp.Formats.Png
{
while (this.currentRow < this.header.Height)
{
int bytesRead = compressedStream.Read(this.scanline.Array, this.currentRowBytesRead, this.bytesPerScanline - this.currentRowBytesRead);
Span<byte> scanlineSpan = this.scanline.GetSpan();
int bytesRead = compressedStream.Read(scanlineSpan, this.currentRowBytesRead, this.bytesPerScanline - this.currentRowBytesRead);
this.currentRowBytesRead += bytesRead;
if (this.currentRowBytesRead < this.bytesPerScanline)
{
@ -513,7 +514,6 @@ namespace SixLabors.ImageSharp.Formats.Png
}
this.currentRowBytesRead = 0;
Span<byte> scanlineSpan = this.scanline.GetSpan();
switch ((FilterType)scanlineSpan[0])
{
@ -543,7 +543,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.ProcessDefilteredScanline(scanlineSpan, image, pngMetadata);
this.SwapBuffers();
this.SwapScanlineBuffers();
this.currentRow++;
}
}
@ -577,7 +577,7 @@ namespace SixLabors.ImageSharp.Formats.Png
while (this.currentRow < this.header.Height)
{
int bytesRead = compressedStream.Read(this.scanline.Array, this.currentRowBytesRead, bytesPerInterlaceScanline - this.currentRowBytesRead);
int bytesRead = compressedStream.Read(this.scanline.GetSpan(), this.currentRowBytesRead, bytesPerInterlaceScanline - this.currentRowBytesRead);
this.currentRowBytesRead += bytesRead;
if (this.currentRowBytesRead < bytesPerInterlaceScanline)
{
@ -618,7 +618,7 @@ namespace SixLabors.ImageSharp.Formats.Png
Span<TPixel> rowSpan = image.GetPixelRowSpan(this.currentRow);
this.ProcessInterlacedDefilteredScanline(this.scanline.GetSpan(), rowSpan, pngMetadata, Adam7.FirstColumn[pass], Adam7.ColumnIncrement[pass]);
this.SwapBuffers();
this.SwapScanlineBuffers();
this.currentRow += Adam7.RowIncrement[pass];
}
@ -654,70 +654,80 @@ namespace SixLabors.ImageSharp.Formats.Png
ReadOnlySpan<byte> trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1);
// Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent.
ReadOnlySpan<byte> scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline - 1, this.header.BitDepth, out IManagedByteBuffer buffer)
? buffer.GetSpan()
: trimmed;
switch (this.pngColorType)
IMemoryOwner<byte> buffer = null;
try
{
case PngColorType.Grayscale:
PngScanlineProcessor.ProcessGrayscaleScanline(
this.header,
scanlineSpan,
rowSpan,
pngMetadata.HasTransparency,
pngMetadata.TransparentL16.GetValueOrDefault(),
pngMetadata.TransparentL8.GetValueOrDefault());
ReadOnlySpan<byte> scanlineSpan = this.TryScaleUpTo8BitArray(
trimmed,
this.bytesPerScanline - 1,
this.header.BitDepth,
out buffer)
? buffer.GetSpan()
: trimmed;
switch (this.pngColorType)
{
case PngColorType.Grayscale:
PngScanlineProcessor.ProcessGrayscaleScanline(
this.header,
scanlineSpan,
rowSpan,
pngMetadata.HasTransparency,
pngMetadata.TransparentL16.GetValueOrDefault(),
pngMetadata.TransparentL8.GetValueOrDefault());
break;
break;
case PngColorType.GrayscaleWithAlpha:
PngScanlineProcessor.ProcessGrayscaleWithAlphaScanline(
this.header,
scanlineSpan,
rowSpan,
this.bytesPerPixel,
this.bytesPerSample);
case PngColorType.GrayscaleWithAlpha:
PngScanlineProcessor.ProcessGrayscaleWithAlphaScanline(
this.header,
scanlineSpan,
rowSpan,
this.bytesPerPixel,
this.bytesPerSample);
break;
break;
case PngColorType.Palette:
PngScanlineProcessor.ProcessPaletteScanline(
this.header,
scanlineSpan,
rowSpan,
this.palette,
this.paletteAlpha);
case PngColorType.Palette:
PngScanlineProcessor.ProcessPaletteScanline(
this.header,
scanlineSpan,
rowSpan,
this.palette,
this.paletteAlpha);
break;
break;
case PngColorType.Rgb:
PngScanlineProcessor.ProcessRgbScanline(
this.Configuration,
this.header,
scanlineSpan,
rowSpan,
this.bytesPerPixel,
this.bytesPerSample,
pngMetadata.HasTransparency,
pngMetadata.TransparentRgb48.GetValueOrDefault(),
pngMetadata.TransparentRgb24.GetValueOrDefault());
case PngColorType.Rgb:
PngScanlineProcessor.ProcessRgbScanline(
this.Configuration,
this.header,
scanlineSpan,
rowSpan,
this.bytesPerPixel,
this.bytesPerSample,
pngMetadata.HasTransparency,
pngMetadata.TransparentRgb48.GetValueOrDefault(),
pngMetadata.TransparentRgb24.GetValueOrDefault());
break;
break;
case PngColorType.RgbWithAlpha:
PngScanlineProcessor.ProcessRgbaScanline(
this.Configuration,
this.header,
scanlineSpan,
rowSpan,
this.bytesPerPixel,
this.bytesPerSample);
case PngColorType.RgbWithAlpha:
PngScanlineProcessor.ProcessRgbaScanline(
this.Configuration,
this.header,
scanlineSpan,
rowSpan,
this.bytesPerPixel,
this.bytesPerSample);
break;
break;
}
}
finally
{
buffer?.Dispose();
}
buffer?.Dispose();
}
/// <summary>
@ -736,78 +746,88 @@ namespace SixLabors.ImageSharp.Formats.Png
ReadOnlySpan<byte> trimmed = defilteredScanline.Slice(1, defilteredScanline.Length - 1);
// Convert 1, 2, and 4 bit pixel data into the 8 bit equivalent.
ReadOnlySpan<byte> scanlineSpan = this.TryScaleUpTo8BitArray(trimmed, this.bytesPerScanline, this.header.BitDepth, out IManagedByteBuffer buffer)
? buffer.GetSpan()
: trimmed;
switch (this.pngColorType)
IMemoryOwner<byte> buffer = null;
try
{
case PngColorType.Grayscale:
PngScanlineProcessor.ProcessInterlacedGrayscaleScanline(
this.header,
scanlineSpan,
rowSpan,
pixelOffset,
increment,
pngMetadata.HasTransparency,
pngMetadata.TransparentL16.GetValueOrDefault(),
pngMetadata.TransparentL8.GetValueOrDefault());
ReadOnlySpan<byte> scanlineSpan = this.TryScaleUpTo8BitArray(
trimmed,
this.bytesPerScanline,
this.header.BitDepth,
out buffer)
? buffer.GetSpan()
: trimmed;
switch (this.pngColorType)
{
case PngColorType.Grayscale:
PngScanlineProcessor.ProcessInterlacedGrayscaleScanline(
this.header,
scanlineSpan,
rowSpan,
pixelOffset,
increment,
pngMetadata.HasTransparency,
pngMetadata.TransparentL16.GetValueOrDefault(),
pngMetadata.TransparentL8.GetValueOrDefault());
break;
break;
case PngColorType.GrayscaleWithAlpha:
PngScanlineProcessor.ProcessInterlacedGrayscaleWithAlphaScanline(
this.header,
scanlineSpan,
rowSpan,
pixelOffset,
increment,
this.bytesPerPixel,
this.bytesPerSample);
case PngColorType.GrayscaleWithAlpha:
PngScanlineProcessor.ProcessInterlacedGrayscaleWithAlphaScanline(
this.header,
scanlineSpan,
rowSpan,
pixelOffset,
increment,
this.bytesPerPixel,
this.bytesPerSample);
break;
break;
case PngColorType.Palette:
PngScanlineProcessor.ProcessInterlacedPaletteScanline(
this.header,
scanlineSpan,
rowSpan,
pixelOffset,
increment,
this.palette,
this.paletteAlpha);
case PngColorType.Palette:
PngScanlineProcessor.ProcessInterlacedPaletteScanline(
this.header,
scanlineSpan,
rowSpan,
pixelOffset,
increment,
this.palette,
this.paletteAlpha);
break;
break;
case PngColorType.Rgb:
PngScanlineProcessor.ProcessInterlacedRgbScanline(
this.header,
scanlineSpan,
rowSpan,
pixelOffset,
increment,
this.bytesPerPixel,
this.bytesPerSample,
pngMetadata.HasTransparency,
pngMetadata.TransparentRgb48.GetValueOrDefault(),
pngMetadata.TransparentRgb24.GetValueOrDefault());
case PngColorType.Rgb:
PngScanlineProcessor.ProcessInterlacedRgbScanline(
this.header,
scanlineSpan,
rowSpan,
pixelOffset,
increment,
this.bytesPerPixel,
this.bytesPerSample,
pngMetadata.HasTransparency,
pngMetadata.TransparentRgb48.GetValueOrDefault(),
pngMetadata.TransparentRgb24.GetValueOrDefault());
break;
break;
case PngColorType.RgbWithAlpha:
PngScanlineProcessor.ProcessInterlacedRgbaScanline(
this.header,
scanlineSpan,
rowSpan,
pixelOffset,
increment,
this.bytesPerPixel,
this.bytesPerSample);
case PngColorType.RgbWithAlpha:
PngScanlineProcessor.ProcessInterlacedRgbaScanline(
this.header,
scanlineSpan,
rowSpan,
pixelOffset,
increment,
this.bytesPerPixel,
this.bytesPerSample);
break;
break;
}
}
finally
{
buffer?.Dispose();
}
buffer?.Dispose();
}
/// <summary>
@ -1190,12 +1210,12 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
/// <param name="length">The length of the chunk data to read.</param>
[MethodImpl(InliningOptions.ShortMethod)]
private IManagedByteBuffer ReadChunkData(int length)
private IMemoryOwner<byte> ReadChunkData(int length)
{
// We rent the buffer here to return it afterwards in Decode()
IManagedByteBuffer buffer = this.Configuration.MemoryAllocator.AllocateManagedByteBuffer(length, AllocationOptions.Clean);
IMemoryOwner<byte> buffer = this.Configuration.MemoryAllocator.Allocate<byte>(length, AllocationOptions.Clean);
this.currentStream.Read(buffer.Array, 0, length);
this.currentStream.Read(buffer.GetSpan(), 0, length);
return buffer;
}
@ -1273,9 +1293,9 @@ namespace SixLabors.ImageSharp.Formats.Png
return true;
}
private void SwapBuffers()
private void SwapScanlineBuffers()
{
IManagedByteBuffer temp = this.previousScanline;
IMemoryOwner<byte> temp = this.previousScanline;
this.previousScanline = this.scanline;
this.scanline = temp;
}

357
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;
@ -82,32 +80,12 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary>
/// The raw data of previous scanline.
/// </summary>
private IManagedByteBuffer previousScanline;
private IMemoryOwner<byte> previousScanline;
/// <summary>
/// The raw data of current scanline.
/// </summary>
private IManagedByteBuffer currentScanline;
/// <summary>
/// The common buffer for the filters.
/// </summary>
private IManagedByteBuffer filterBuffer;
/// <summary>
/// The ext buffer for the sub filter, <see cref="PngFilterMethod.Adaptive"/>.
/// </summary>
private IManagedByteBuffer subFilter;
/// <summary>
/// The ext buffer for the average filter, <see cref="PngFilterMethod.Adaptive"/>.
/// </summary>
private IManagedByteBuffer averageFilter;
/// <summary>
/// The ext buffer for the Paeth filter, <see cref="PngFilterMethod.Adaptive"/>.
/// </summary>
private IManagedByteBuffer paethFilter;
private IMemoryOwner<byte> currentScanline;
/// <summary>
/// Initializes a new instance of the <see cref="PngEncoderCore" /> class.
@ -175,17 +153,8 @@ namespace SixLabors.ImageSharp.Formats.Png
{
this.previousScanline?.Dispose();
this.currentScanline?.Dispose();
this.subFilter?.Dispose();
this.averageFilter?.Dispose();
this.paethFilter?.Dispose();
this.filterBuffer?.Dispose();
this.previousScanline = null;
this.currentScanline = null;
this.subFilter = null;
this.averageFilter = null;
this.paethFilter = null;
this.filterBuffer = null;
}
/// <summary>
@ -280,21 +249,17 @@ namespace SixLabors.ImageSharp.Formats.Png
else
{
// 1, 2, and 4 bit grayscale
using (IManagedByteBuffer temp = this.memoryAllocator.AllocateManagedByteBuffer(
rowSpan.Length,
AllocationOptions.Clean))
{
int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(this.bitDepth) - 1);
Span<byte> tempSpan = temp.GetSpan();
// We need to first create an array of luminance bytes then scale them down to the correct bit depth.
PixelOperations<TPixel>.Instance.ToL8Bytes(
this.configuration,
rowSpan,
tempSpan,
rowSpan.Length);
PngEncoderHelpers.ScaleDownFrom8BitArray(tempSpan, rawScanlineSpan, this.bitDepth, scaleFactor);
}
using IMemoryOwner<byte> temp = this.memoryAllocator.Allocate<byte>(rowSpan.Length, AllocationOptions.Clean);
int scaleFactor = 255 / (ColorNumerics.GetColorCountForBitDepth(this.bitDepth) - 1);
Span<byte> tempSpan = temp.GetSpan();
// We need to first create an array of luminance bytes then scale them down to the correct bit depth.
PixelOperations<TPixel>.Instance.ToL8Bytes(
this.configuration,
rowSpan,
tempSpan,
rowSpan.Length);
PngEncoderHelpers.ScaleDownFrom8BitArray(tempSpan, rawScanlineSpan, this.bitDepth, scaleFactor);
}
}
}
@ -446,6 +411,8 @@ namespace SixLabors.ImageSharp.Formats.Png
case PngColorType.GrayscaleWithAlpha:
this.CollectGrayscaleBytes(rowSpan);
break;
case PngColorType.Rgb:
case PngColorType.RgbWithAlpha:
default:
this.CollectTPixelBytes(rowSpan);
break;
@ -453,124 +420,127 @@ namespace SixLabors.ImageSharp.Formats.Png
}
/// <summary>
/// Apply filter for the raw scanline.
/// Apply the line filter for the raw scanline to enable better compression.
/// </summary>
private IManagedByteBuffer FilterPixelBytes()
private void FilterPixelBytes(ref Span<byte> filter, ref Span<byte> attempt)
{
switch (this.options.FilterMethod)
{
case PngFilterMethod.None:
NoneFilter.Encode(this.currentScanline.GetSpan(), this.filterBuffer.GetSpan());
return this.filterBuffer;
NoneFilter.Encode(this.currentScanline.GetSpan(), filter);
break;
case PngFilterMethod.Sub:
SubFilter.Encode(this.currentScanline.GetSpan(), this.filterBuffer.GetSpan(), this.bytesPerPixel, out int _);
return this.filterBuffer;
SubFilter.Encode(this.currentScanline.GetSpan(), filter, this.bytesPerPixel, out int _);
break;
case PngFilterMethod.Up:
UpFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), this.filterBuffer.GetSpan(), out int _);
return this.filterBuffer;
UpFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, out int _);
break;
case PngFilterMethod.Average:
AverageFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), this.filterBuffer.GetSpan(), this.bytesPerPixel, out int _);
return this.filterBuffer;
AverageFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, this.bytesPerPixel, out int _);
break;
case PngFilterMethod.Paeth:
PaethFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), this.filterBuffer.GetSpan(), this.bytesPerPixel, out int _);
return this.filterBuffer;
PaethFilter.Encode(this.currentScanline.GetSpan(), this.previousScanline.GetSpan(), filter, this.bytesPerPixel, out int _);
break;
case PngFilterMethod.Adaptive:
default:
return this.GetOptimalFilteredScanline();
this.ApplyOptimalFilteredScanline(ref filter, ref attempt);
break;
}
}
/// <summary>
/// Encodes the pixel data line by line.
/// Each scanline is encoded in the most optimal manner to improve compression.
/// Collects the pixel data line by line for compressing.
/// Each scanline is filtered in the most optimal manner to improve compression.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="rowSpan">The row span.</param>
/// <param name="quantized">The quantized pixels. Can be null.</param>
/// <param name="row">The row.</param>
/// <returns>The <see cref="IManagedByteBuffer"/></returns>
private IManagedByteBuffer EncodePixelRow<TPixel>(ReadOnlySpan<TPixel> rowSpan, IndexedImageFrame<TPixel> quantized, int row)
/// <param name="filter">The filtered buffer.</param>
/// <param name="attempt">Used for attempting optimized filtering.</param>
/// <param name="quantized">The quantized pixels. Can be <see langword="null"/>.</param>
/// <param name="row">The row number.</param>
private void CollectAndFilterPixelRow<TPixel>(
ReadOnlySpan<TPixel> rowSpan,
ref Span<byte> filter,
ref Span<byte> attempt,
IndexedImageFrame<TPixel> quantized,
int row)
where TPixel : unmanaged, IPixel<TPixel>
{
this.CollectPixelBytes(rowSpan, quantized, row);
return this.FilterPixelBytes();
this.FilterPixelBytes(ref filter, ref attempt);
}
/// <summary>
/// Encodes the indexed pixel data (with palette) for Adam7 interlaced mode.
/// </summary>
/// <param name="rowSpan">The row span.</param>
private IManagedByteBuffer EncodeAdam7IndexedPixelRow(ReadOnlySpan<byte> rowSpan)
/// <param name="row">The row span.</param>
/// <param name="filter">The filtered buffer.</param>
/// <param name="attempt">Used for attempting optimized filtering.</param>
private void EncodeAdam7IndexedPixelRow(
ReadOnlySpan<byte> row,
ref Span<byte> filter,
ref Span<byte> attempt)
{
// CollectPixelBytes
if (this.bitDepth < 8)
{
PngEncoderHelpers.ScaleDownFrom8BitArray(rowSpan, this.currentScanline.GetSpan(), this.bitDepth);
PngEncoderHelpers.ScaleDownFrom8BitArray(row, this.currentScanline.GetSpan(), this.bitDepth);
}
else
{
rowSpan.CopyTo(this.currentScanline.GetSpan());
row.CopyTo(this.currentScanline.GetSpan());
}
return this.FilterPixelBytes();
this.FilterPixelBytes(ref filter, ref attempt);
}
/// <summary>
/// Applies all PNG filters to the given scanline and returns the filtered scanline that is deemed
/// to be most compressible, using lowest total variation as proxy for compressibility.
/// </summary>
/// <returns>The <see cref="T:byte[]"/></returns>
private IManagedByteBuffer GetOptimalFilteredScanline()
private void ApplyOptimalFilteredScanline(ref Span<byte> filter, ref Span<byte> attempt)
{
// Palette images don't compress well with adaptive filtering.
if (this.options.ColorType == PngColorType.Palette || this.bitDepth < 8)
// Nor do images comprising a single row.
if (this.options.ColorType == PngColorType.Palette || this.height == 1 || this.bitDepth < 8)
{
NoneFilter.Encode(this.currentScanline.GetSpan(), this.filterBuffer.GetSpan());
return this.filterBuffer;
NoneFilter.Encode(this.currentScanline.GetSpan(), filter);
return;
}
this.AllocateExtBuffers();
Span<byte> scanSpan = this.currentScanline.GetSpan();
Span<byte> prevSpan = this.previousScanline.GetSpan();
// This order, while different to the enumerated order is more likely to produce a smaller sum
// early on which shaves a couple of milliseconds off the processing time.
UpFilter.Encode(scanSpan, prevSpan, this.filterBuffer.GetSpan(), out int currentSum);
Span<byte> current = this.currentScanline.GetSpan();
Span<byte> previous = this.previousScanline.GetSpan();
// TODO: PERF.. We should be breaking out of the encoding for each line as soon as we hit the sum.
// That way the above comment would actually be true. It used to be anyway...
// If we could use SIMD for none branching filters we could really speed it up.
int lowestSum = currentSum;
IManagedByteBuffer actualResult = this.filterBuffer;
PaethFilter.Encode(scanSpan, prevSpan, this.paethFilter.GetSpan(), this.bytesPerPixel, out currentSum);
if (currentSum < lowestSum)
int min = int.MaxValue;
SubFilter.Encode(current, attempt, this.bytesPerPixel, out int sum);
if (sum < min)
{
lowestSum = currentSum;
actualResult = this.paethFilter;
min = sum;
SwapSpans(ref filter, ref attempt);
}
SubFilter.Encode(scanSpan, this.subFilter.GetSpan(), this.bytesPerPixel, out currentSum);
if (currentSum < lowestSum)
UpFilter.Encode(current, previous, attempt, out sum);
if (sum < min)
{
lowestSum = currentSum;
actualResult = this.subFilter;
min = sum;
SwapSpans(ref filter, ref attempt);
}
AverageFilter.Encode(scanSpan, prevSpan, this.averageFilter.GetSpan(), this.bytesPerPixel, out currentSum);
if (currentSum < lowestSum)
AverageFilter.Encode(current, previous, attempt, this.bytesPerPixel, out sum);
if (sum < min)
{
actualResult = this.averageFilter;
min = sum;
SwapSpans(ref filter, ref attempt);
}
return actualResult;
PaethFilter.Encode(current, previous, attempt, this.bytesPerPixel, out sum);
if (sum < min)
{
SwapSpans(ref filter, ref attempt);
}
}
/// <summary>
@ -614,8 +584,8 @@ namespace SixLabors.ImageSharp.Formats.Png
int colorTableLength = paletteLength * Unsafe.SizeOf<Rgb24>();
bool hasAlpha = false;
using IManagedByteBuffer colorTable = this.memoryAllocator.AllocateManagedByteBuffer(colorTableLength);
using IManagedByteBuffer alphaTable = this.memoryAllocator.AllocateManagedByteBuffer(paletteLength);
using IMemoryOwner<byte> colorTable = this.memoryAllocator.Allocate<byte>(colorTableLength);
using IMemoryOwner<byte> alphaTable = this.memoryAllocator.Allocate<byte>(paletteLength);
ref Rgb24 colorTableRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<byte, Rgb24>(colorTable.GetSpan()));
ref byte alphaTableRef = ref MemoryMarshal.GetReference(alphaTable.GetSpan());
@ -642,12 +612,12 @@ namespace SixLabors.ImageSharp.Formats.Png
Unsafe.Add(ref alphaTableRef, i) = alpha;
}
this.WriteChunk(stream, PngChunkType.Palette, colorTable.Array, 0, colorTableLength);
this.WriteChunk(stream, PngChunkType.Palette, colorTable.GetSpan(), 0, colorTableLength);
// Write the transparency data
if (hasAlpha)
{
this.WriteChunk(stream, PngChunkType.Transparency, alphaTable.Array, 0, paletteLength);
this.WriteChunk(stream, PngChunkType.Transparency, alphaTable.GetSpan(), 0, paletteLength);
}
}
@ -926,38 +896,13 @@ namespace SixLabors.ImageSharp.Formats.Png
/// Allocates the buffers for each scanline.
/// </summary>
/// <param name="bytesPerScanline">The bytes per scanline.</param>
/// <param name="resultLength">Length of the result.</param>
private void AllocateBuffers(int bytesPerScanline, int resultLength)
private void AllocateScanlineBuffers(int bytesPerScanline)
{
// Clean up from any potential previous runs.
this.subFilter?.Dispose();
this.averageFilter?.Dispose();
this.paethFilter?.Dispose();
this.subFilter = null;
this.averageFilter = null;
this.paethFilter = null;
this.previousScanline?.Dispose();
this.currentScanline?.Dispose();
this.filterBuffer?.Dispose();
this.previousScanline = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerScanline, AllocationOptions.Clean);
this.currentScanline = this.memoryAllocator.AllocateManagedByteBuffer(bytesPerScanline, AllocationOptions.Clean);
this.filterBuffer = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
}
/// <summary>
/// Allocates the ext buffers for adaptive filter.
/// </summary>
private void AllocateExtBuffers()
{
if (this.subFilter == null)
{
int resultLength = this.filterBuffer.Length();
this.subFilter = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
this.averageFilter = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
this.paethFilter = this.memoryAllocator.AllocateManagedByteBuffer(resultLength, AllocationOptions.Clean);
}
this.previousScanline = this.memoryAllocator.Allocate<byte>(bytesPerScanline, AllocationOptions.Clean);
this.currentScanline = this.memoryAllocator.Allocate<byte>(bytesPerScanline, AllocationOptions.Clean);
}
/// <summary>
@ -971,17 +916,19 @@ namespace SixLabors.ImageSharp.Formats.Png
where TPixel : unmanaged, IPixel<TPixel>
{
int bytesPerScanline = this.CalculateScanlineLength(this.width);
int resultLength = bytesPerScanline + 1;
this.AllocateBuffers(bytesPerScanline, resultLength);
int filterLength = bytesPerScanline + 1;
this.AllocateScanlineBuffers(bytesPerScanline);
using IMemoryOwner<byte> filterBuffer = this.memoryAllocator.Allocate<byte>(filterLength, AllocationOptions.Clean);
using IMemoryOwner<byte> attemptBuffer = this.memoryAllocator.Allocate<byte>(filterLength, AllocationOptions.Clean);
Span<byte> filter = filterBuffer.GetSpan();
Span<byte> attempt = attemptBuffer.GetSpan();
for (int y = 0; y < this.height; y++)
{
IManagedByteBuffer r = this.EncodePixelRow(pixels.GetPixelRowSpan(y), quantized, y);
deflateStream.Write(r.Array, 0, resultLength);
IManagedByteBuffer temp = this.currentScanline;
this.currentScanline = this.previousScanline;
this.previousScanline = temp;
this.CollectAndFilterPixelRow(pixels.GetPixelRowSpan(y), ref filter, ref attempt, quantized, y);
deflateStream.Write(filter);
this.SwapScanlineBuffers();
}
}
@ -1006,36 +953,33 @@ namespace SixLabors.ImageSharp.Formats.Png
? ((blockWidth * this.bitDepth) + 7) / 8
: blockWidth * this.bytesPerPixel;
int resultLength = bytesPerScanline + 1;
int filterLength = bytesPerScanline + 1;
this.AllocateScanlineBuffers(bytesPerScanline);
this.AllocateBuffers(bytesPerScanline, resultLength);
using IMemoryOwner<TPixel> blockBuffer = this.memoryAllocator.Allocate<TPixel>(blockWidth);
using IMemoryOwner<byte> filterBuffer = this.memoryAllocator.Allocate<byte>(filterLength, AllocationOptions.Clean);
using IMemoryOwner<byte> attemptBuffer = this.memoryAllocator.Allocate<byte>(filterLength, AllocationOptions.Clean);
using (IMemoryOwner<TPixel> passData = this.memoryAllocator.Allocate<TPixel>(blockWidth))
Span<TPixel> block = blockBuffer.GetSpan();
Span<byte> filter = filterBuffer.GetSpan();
Span<byte> attempt = attemptBuffer.GetSpan();
for (int row = startRow; row < height; row += Adam7.RowIncrement[pass])
{
Span<TPixel> destSpan = passData.Memory.Span;
for (int row = startRow;
row < height;
row += Adam7.RowIncrement[pass])
// Collect pixel data
Span<TPixel> srcRow = pixels.GetPixelRowSpan(row);
for (int col = startCol, i = 0; col < width; col += Adam7.ColumnIncrement[pass])
{
// collect data
Span<TPixel> srcRow = pixels.GetPixelRowSpan(row);
for (int col = startCol, i = 0;
col < width;
col += Adam7.ColumnIncrement[pass])
{
destSpan[i++] = srcRow[col];
}
block[i++] = srcRow[col];
}
// encode data
// note: quantized parameter not used
// note: row parameter not used
IManagedByteBuffer r = this.EncodePixelRow((ReadOnlySpan<TPixel>)destSpan, null, -1);
deflateStream.Write(r.Array, 0, resultLength);
// Encode data
// Note: quantized parameter not used
// Note: row parameter not used
this.CollectAndFilterPixelRow<TPixel>(block, ref filter, ref attempt, null, -1);
deflateStream.Write(filter);
IManagedByteBuffer temp = this.currentScanline;
this.currentScanline = this.previousScanline;
this.previousScanline = temp;
}
this.SwapScanlineBuffers();
}
}
}
@ -1061,34 +1005,36 @@ namespace SixLabors.ImageSharp.Formats.Png
? ((blockWidth * this.bitDepth) + 7) / 8
: blockWidth * this.bytesPerPixel;
int resultLength = bytesPerScanline + 1;
int filterLength = bytesPerScanline + 1;
this.AllocateBuffers(bytesPerScanline, resultLength);
this.AllocateScanlineBuffers(bytesPerScanline);
using (IMemoryOwner<byte> passData = this.memoryAllocator.Allocate<byte>(blockWidth))
using IMemoryOwner<byte> blockBuffer = this.memoryAllocator.Allocate<byte>(blockWidth);
using IMemoryOwner<byte> filterBuffer = this.memoryAllocator.Allocate<byte>(filterLength, AllocationOptions.Clean);
using IMemoryOwner<byte> attemptBuffer = this.memoryAllocator.Allocate<byte>(filterLength, AllocationOptions.Clean);
Span<byte> block = blockBuffer.GetSpan();
Span<byte> filter = filterBuffer.GetSpan();
Span<byte> attempt = attemptBuffer.GetSpan();
for (int row = startRow;
row < height;
row += Adam7.RowIncrement[pass])
{
Span<byte> destSpan = passData.Memory.Span;
for (int row = startRow;
row < height;
row += Adam7.RowIncrement[pass])
// Collect data
ReadOnlySpan<byte> srcRow = quantized.GetPixelRowSpan(row);
for (int col = startCol, i = 0;
col < width;
col += Adam7.ColumnIncrement[pass])
{
// collect data
ReadOnlySpan<byte> srcRow = quantized.GetPixelRowSpan(row);
for (int col = startCol, i = 0;
col < width;
col += Adam7.ColumnIncrement[pass])
{
destSpan[i++] = srcRow[col];
}
block[i++] = srcRow[col];
}
// encode data
IManagedByteBuffer r = this.EncodeAdam7IndexedPixelRow(destSpan);
deflateStream.Write(r.Array, 0, resultLength);
// Encode data
this.EncodeAdam7IndexedPixelRow(block, ref filter, ref attempt);
deflateStream.Write(filter);
IManagedByteBuffer temp = this.currentScanline;
this.currentScanline = this.previousScanline;
this.previousScanline = temp;
}
this.SwapScanlineBuffers();
}
}
}
@ -1105,7 +1051,8 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="stream">The <see cref="Stream"/> to write to.</param>
/// <param name="type">The type of chunk to write.</param>
/// <param name="data">The <see cref="T:byte[]"/> containing data.</param>
private void WriteChunk(Stream stream, PngChunkType type, byte[] data) => this.WriteChunk(stream, type, data, 0, data?.Length ?? 0);
private void WriteChunk(Stream stream, PngChunkType type, Span<byte> data)
=> this.WriteChunk(stream, type, data, 0, data.Length);
/// <summary>
/// Writes a chunk of a specified length to the stream at the given offset.
@ -1115,7 +1062,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="data">The <see cref="T:byte[]"/> containing data.</param>
/// <param name="offset">The position to offset the data at.</param>
/// <param name="length">The of the data to write.</param>
private void WriteChunk(Stream stream, PngChunkType type, byte[] data, int offset, int length)
private void WriteChunk(Stream stream, PngChunkType type, Span<byte> data, int offset, int length)
{
BinaryPrimitives.WriteInt32BigEndian(this.buffer, length);
BinaryPrimitives.WriteUInt32BigEndian(this.buffer.AsSpan(4, 4), (uint)type);
@ -1128,7 +1075,7 @@ namespace SixLabors.ImageSharp.Formats.Png
{
stream.Write(data, offset, length);
crc = Crc32.Calculate(crc, data.AsSpan(offset, length));
crc = Crc32.Calculate(crc, data.Slice(offset, length));
}
BinaryPrimitives.WriteUInt32BigEndian(this.buffer, crc);
@ -1156,5 +1103,19 @@ namespace SixLabors.ImageSharp.Formats.Png
return scanlineLength / mod;
}
private void SwapScanlineBuffers()
{
IMemoryOwner<byte> temp = this.previousScanline;
this.previousScanline = this.currentScanline;
this.currentScanline = temp;
}
private static void SwapSpans<T>(ref Span<T> a, ref Span<T> b)
{
Span<T> t = b;
b = a;
a = t;
}
}
}

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:

178
src/ImageSharp/Formats/Tga/TgaDecoderCore.cs

@ -114,9 +114,10 @@ namespace SixLabors.ImageSharp.Formats.Tga
int colorMapPixelSizeInBytes = this.fileHeader.CMapDepth / 8;
int colorMapSizeInBytes = this.fileHeader.CMapLength * colorMapPixelSizeInBytes;
using (IManagedByteBuffer palette = this.memoryAllocator.AllocateManagedByteBuffer(colorMapSizeInBytes, AllocationOptions.Clean))
using (IMemoryOwner<byte> palette = this.memoryAllocator.Allocate<byte>(colorMapSizeInBytes, AllocationOptions.Clean))
{
this.currentStream.Read(palette.Array, this.fileHeader.CMapStart, colorMapSizeInBytes);
Span<byte> paletteSpan = palette.GetSpan();
this.currentStream.Read(paletteSpan, this.fileHeader.CMapStart, colorMapSizeInBytes);
if (this.fileHeader.ImageType == TgaImageType.RleColorMapped)
{
@ -124,7 +125,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
this.fileHeader.Width,
this.fileHeader.Height,
pixels,
palette.Array,
paletteSpan,
colorMapPixelSizeInBytes,
origin);
}
@ -134,7 +135,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
this.fileHeader.Width,
this.fileHeader.Height,
pixels,
palette.Array,
paletteSpan,
colorMapPixelSizeInBytes,
origin);
}
@ -224,7 +225,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <param name="palette">The color palette.</param>
/// <param name="colorMapPixelSizeInBytes">Color map size of one entry in bytes.</param>
/// <param name="origin">The image origin.</param>
private void ReadPaletted<TPixel>(int width, int height, Buffer2D<TPixel> pixels, byte[] palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin)
private void ReadPaletted<TPixel>(int width, int height, Buffer2D<TPixel> pixels, Span<byte> palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel<TPixel>
{
TPixel color = default;
@ -304,7 +305,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
/// <param name="palette">The color palette.</param>
/// <param name="colorMapPixelSizeInBytes">Color map size of one entry in bytes.</param>
/// <param name="origin">The image origin.</param>
private void ReadPalettedRle<TPixel>(int width, int height, Buffer2D<TPixel> pixels, byte[] palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin)
private void ReadPalettedRle<TPixel>(int width, int height, Buffer2D<TPixel> pixels, Span<byte> palette, int colorMapPixelSizeInBytes, TgaImageOrigin origin)
where TPixel : unmanaged, IPixel<TPixel>
{
int bytesPerPixel = 1;
@ -373,22 +374,21 @@ namespace SixLabors.ImageSharp.Formats.Tga
return;
}
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0))
using IMemoryOwner<byte> row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 1, 0);
Span<byte> rowSpan = row.GetSpan();
bool invertY = InvertY(origin);
if (invertY)
{
bool invertY = InvertY(origin);
if (invertY)
for (int y = height - 1; y >= 0; y--)
{
for (int y = height - 1; y >= 0; y--)
{
this.ReadL8Row(width, pixels, row, y);
}
this.ReadL8Row(width, pixels, rowSpan, y);
}
else
}
else
{
for (int y = 0; y < height; y++)
{
for (int y = 0; y < height; y++)
{
this.ReadL8Row(width, pixels, row, y);
}
this.ReadL8Row(width, pixels, rowSpan, y);
}
}
}
@ -406,58 +406,57 @@ namespace SixLabors.ImageSharp.Formats.Tga
{
TPixel color = default;
bool invertX = InvertX(origin);
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0))
{
for (int y = 0; y < height; y++)
{
int newY = InvertY(y, height, origin);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
using IMemoryOwner<byte> row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 2, 0);
Span<byte> rowSpan = row.GetSpan();
if (invertX)
{
for (int x = width - 1; x >= 0; x--)
{
this.currentStream.Read(this.scratchBuffer, 0, 2);
if (!this.hasAlpha)
{
this.scratchBuffer[1] |= 1 << 7;
}
if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite)
{
color.FromLa16(Unsafe.As<byte, La16>(ref this.scratchBuffer[0]));
}
else
{
color.FromBgra5551(Unsafe.As<byte, Bgra5551>(ref this.scratchBuffer[0]));
}
for (int y = 0; y < height; y++)
{
int newY = InvertY(y, height, origin);
Span<TPixel> pixelSpan = pixels.GetRowSpan(newY);
pixelSpan[x] = color;
}
}
else
if (invertX)
{
for (int x = width - 1; x >= 0; x--)
{
this.currentStream.Read(row);
Span<byte> rowSpan = row.GetSpan();
this.currentStream.Read(this.scratchBuffer, 0, 2);
if (!this.hasAlpha)
{
// We need to set the alpha component value to fully opaque.
for (int x = 1; x < rowSpan.Length; x += 2)
{
rowSpan[x] |= 1 << 7;
}
this.scratchBuffer[1] |= 1 << 7;
}
if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite)
{
PixelOperations<TPixel>.Instance.FromLa16Bytes(this.Configuration, rowSpan, pixelSpan, width);
color.FromLa16(Unsafe.As<byte, La16>(ref this.scratchBuffer[0]));
}
else
{
PixelOperations<TPixel>.Instance.FromBgra5551Bytes(this.Configuration, rowSpan, pixelSpan, width);
color.FromBgra5551(Unsafe.As<byte, Bgra5551>(ref this.scratchBuffer[0]));
}
pixelSpan[x] = color;
}
}
else
{
this.currentStream.Read(rowSpan);
if (!this.hasAlpha)
{
// We need to set the alpha component value to fully opaque.
for (int x = 1; x < rowSpan.Length; x += 2)
{
rowSpan[x] |= 1 << 7;
}
}
if (this.fileHeader.ImageType == TgaImageType.BlackAndWhite)
{
PixelOperations<TPixel>.Instance.FromLa16Bytes(this.Configuration, rowSpan, pixelSpan, width);
}
else
{
PixelOperations<TPixel>.Instance.FromBgra5551Bytes(this.Configuration, rowSpan, pixelSpan, width);
}
}
}
}
@ -490,23 +489,22 @@ namespace SixLabors.ImageSharp.Formats.Tga
return;
}
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0))
{
bool invertY = InvertY(origin);
using IMemoryOwner<byte> row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 3, 0);
Span<byte> rowSpan = row.GetSpan();
bool invertY = InvertY(origin);
if (invertY)
if (invertY)
{
for (int y = height - 1; y >= 0; y--)
{
for (int y = height - 1; y >= 0; y--)
{
this.ReadBgr24Row(width, pixels, row, y);
}
this.ReadBgr24Row(width, pixels, rowSpan, y);
}
else
}
else
{
for (int y = 0; y < height; y++)
{
for (int y = 0; y < height; y++)
{
this.ReadBgr24Row(width, pixels, row, y);
}
this.ReadBgr24Row(width, pixels, rowSpan, y);
}
}
}
@ -526,21 +524,21 @@ namespace SixLabors.ImageSharp.Formats.Tga
bool invertX = InvertX(origin);
if (this.tgaMetadata.AlphaChannelBits == 8 && !invertX)
{
using (IManagedByteBuffer row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0))
using IMemoryOwner<byte> row = this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, 4, 0);
Span<byte> rowSpan = row.GetSpan();
if (InvertY(origin))
{
if (InvertY(origin))
for (int y = height - 1; y >= 0; y--)
{
for (int y = height - 1; y >= 0; y--)
{
this.ReadBgra32Row(width, pixels, row, y);
}
this.ReadBgra32Row(width, pixels, rowSpan, y);
}
else
}
else
{
for (int y = 0; y < height; y++)
{
for (int y = 0; y < height; y++)
{
this.ReadBgra32Row(width, pixels, row, y);
}
this.ReadBgra32Row(width, pixels, rowSpan, y);
}
}
@ -652,12 +650,12 @@ namespace SixLabors.ImageSharp.Formats.Tga
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadL8Row<TPixel>(int width, Buffer2D<TPixel> pixels, IManagedByteBuffer row, int y)
private void ReadL8Row<TPixel>(int width, Buffer2D<TPixel> pixels, Span<byte> row, int y)
where TPixel : unmanaged, IPixel<TPixel>
{
this.currentStream.Read(row);
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.FromL8Bytes(this.Configuration, row.GetSpan(), pixelSpan, width);
PixelOperations<TPixel>.Instance.FromL8Bytes(this.Configuration, row, pixelSpan, width);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -679,12 +677,12 @@ namespace SixLabors.ImageSharp.Formats.Tga
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadBgr24Row<TPixel>(int width, Buffer2D<TPixel> pixels, IManagedByteBuffer row, int y)
private void ReadBgr24Row<TPixel>(int width, Buffer2D<TPixel> pixels, Span<byte> row, int y)
where TPixel : unmanaged, IPixel<TPixel>
{
this.currentStream.Read(row);
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.FromBgr24Bytes(this.Configuration, row.GetSpan(), pixelSpan, width);
PixelOperations<TPixel>.Instance.FromBgr24Bytes(this.Configuration, row, pixelSpan, width);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -698,16 +696,16 @@ namespace SixLabors.ImageSharp.Formats.Tga
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadBgra32Row<TPixel>(int width, Buffer2D<TPixel> pixels, IManagedByteBuffer row, int y)
private void ReadBgra32Row<TPixel>(int width, Buffer2D<TPixel> pixels, Span<byte> row, int y)
where TPixel : unmanaged, IPixel<TPixel>
{
this.currentStream.Read(row);
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(this.Configuration, row.GetSpan(), pixelSpan, width);
PixelOperations<TPixel>.Instance.FromBgra32Bytes(this.Configuration, row, pixelSpan, width);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadPalettedBgra16Pixel<TPixel>(byte[] palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span<TPixel> pixelRow)
private void ReadPalettedBgra16Pixel<TPixel>(Span<byte> palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span<TPixel> pixelRow)
where TPixel : unmanaged, IPixel<TPixel>
{
int colorIndex = this.currentStream.ReadByte();
@ -716,7 +714,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadPalettedBgra16Pixel<TPixel>(byte[] palette, int index, int colorMapPixelSizeInBytes, ref TPixel color)
private void ReadPalettedBgra16Pixel<TPixel>(Span<byte> palette, int index, int colorMapPixelSizeInBytes, ref TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
Bgra5551 bgra = default;
@ -732,7 +730,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadPalettedBgr24Pixel<TPixel>(byte[] palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span<TPixel> pixelRow)
private void ReadPalettedBgr24Pixel<TPixel>(Span<byte> palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span<TPixel> pixelRow)
where TPixel : unmanaged, IPixel<TPixel>
{
int colorIndex = this.currentStream.ReadByte();
@ -741,7 +739,7 @@ namespace SixLabors.ImageSharp.Formats.Tga
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ReadPalettedBgra32Pixel<TPixel>(byte[] palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span<TPixel> pixelRow)
private void ReadPalettedBgra32Pixel<TPixel>(Span<byte> palette, int colorMapPixelSizeInBytes, int x, TPixel color, Span<TPixel> pixelRow)
where TPixel : unmanaged, IPixel<TPixel>
{
int colorIndex = this.currentStream.ReadByte();

92
src/ImageSharp/Formats/Tga/TgaEncoderCore.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.IO;
using System.Runtime.CompilerServices;
@ -258,7 +259,8 @@ namespace SixLabors.ImageSharp.Formats.Tga
return equalPixelCount;
}
private IManagedByteBuffer AllocateRow(int width, int bytesPerPixel) => this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, 0);
private IMemoryOwner<byte> AllocateRow(int width, int bytesPerPixel)
=> this.memoryAllocator.AllocatePaddedPixelRowBuffer(width, bytesPerPixel, 0);
/// <summary>
/// Writes the 8bit pixels uncompressed to the stream.
@ -269,18 +271,18 @@ namespace SixLabors.ImageSharp.Formats.Tga
private void Write8Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 1))
using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 1);
Span<byte> rowSpan = row.GetSpan();
for (int y = pixels.Height - 1; y >= 0; y--)
{
for (int y = pixels.Height - 1; y >= 0; y--)
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8Bytes(
this.configuration,
pixelSpan,
row.GetSpan(),
pixelSpan.Length);
stream.Write(row.Array, 0, row.Length());
}
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToL8Bytes(
this.configuration,
pixelSpan,
rowSpan,
pixelSpan.Length);
stream.Write(rowSpan);
}
}
@ -293,18 +295,18 @@ namespace SixLabors.ImageSharp.Formats.Tga
private void Write16Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 2))
using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 2);
Span<byte> rowSpan = row.GetSpan();
for (int y = pixels.Height - 1; y >= 0; y--)
{
for (int y = pixels.Height - 1; y >= 0; y--)
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra5551Bytes(
this.configuration,
pixelSpan,
row.GetSpan(),
pixelSpan.Length);
stream.Write(row.Array, 0, row.Length());
}
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra5551Bytes(
this.configuration,
pixelSpan,
rowSpan,
pixelSpan.Length);
stream.Write(rowSpan);
}
}
@ -317,18 +319,18 @@ namespace SixLabors.ImageSharp.Formats.Tga
private void Write24Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 3))
using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 3);
Span<byte> rowSpan = row.GetSpan();
for (int y = pixels.Height - 1; y >= 0; y--)
{
for (int y = pixels.Height - 1; y >= 0; y--)
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgr24Bytes(
this.configuration,
pixelSpan,
row.GetSpan(),
pixelSpan.Length);
stream.Write(row.Array, 0, row.Length());
}
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgr24Bytes(
this.configuration,
pixelSpan,
rowSpan,
pixelSpan.Length);
stream.Write(rowSpan);
}
}
@ -341,18 +343,18 @@ namespace SixLabors.ImageSharp.Formats.Tga
private void Write32Bit<TPixel>(Stream stream, Buffer2D<TPixel> pixels)
where TPixel : unmanaged, IPixel<TPixel>
{
using (IManagedByteBuffer row = this.AllocateRow(pixels.Width, 4))
using IMemoryOwner<byte> row = this.AllocateRow(pixels.Width, 4);
Span<byte> rowSpan = row.GetSpan();
for (int y = pixels.Height - 1; y >= 0; y--)
{
for (int y = pixels.Height - 1; y >= 0; y--)
{
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra32Bytes(
this.configuration,
pixelSpan,
row.GetSpan(),
pixelSpan.Length);
stream.Write(row.Array, 0, row.Length());
}
Span<TPixel> pixelSpan = pixels.GetRowSpan(y);
PixelOperations<TPixel>.Instance.ToBgra32Bytes(
this.configuration,
pixelSpan,
rowSpan,
pixelSpan.Length);
stream.Write(rowSpan);
}
}

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

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

@ -0,0 +1,49 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
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 IMemoryOwner<byte> 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.Allocate<byte>(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;
}
}
}

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

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

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

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

@ -0,0 +1,83 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
namespace SixLabors.ImageSharp.Formats.Tiff.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 Rational and SRational data types
/// </summary>
public const int SizeOfRational = 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 TiffBitsPerSample BitsPerSample1Bit = new TiffBitsPerSample(1, 0, 0);
/// <summary>
/// The bits per sample for images with a 4 color palette.
/// </summary>
public static readonly TiffBitsPerSample BitsPerSample4Bit = new TiffBitsPerSample(4, 0, 0);
/// <summary>
/// The bits per sample for 8 bit images.
/// </summary>
public static readonly TiffBitsPerSample BitsPerSample8Bit = new TiffBitsPerSample(8, 0, 0);
/// <summary>
/// The bits per sample for color images with 8 bits for each color channel.
/// </summary>
public static readonly TiffBitsPerSample BitsPerSampleRgb8Bit = new TiffBitsPerSample(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();
}
}

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

Loading…
Cancel
Save