Browse Source

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

# Conflicts:
#	tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs
pull/1552/head
Brian Popow 5 years ago
parent
commit
437149a69e
  1. 2
      .editorconfig
  2. 11
      Directory.Build.props
  3. 72
      ImageSharp.sln
  4. 2
      shared-infrastructure
  5. 56
      src/ImageSharp/Common/Helpers/Numerics.cs
  6. 25
      src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs
  7. 2
      src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs
  8. 13
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  9. 228
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
  10. 10
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs
  11. 427
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
  12. 2
      src/ImageSharp/Formats/Jpeg/Components/Encoder/LuminanceForwardConverter{TPixel}.cs
  13. 148
      src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterLut.cs
  14. 195
      src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbToYCbCrConverterVectorized.cs
  15. 121
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs
  16. 122
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs
  17. 90
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter{TPixel}.cs
  18. 463
      src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs
  19. 606
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  20. 3
      src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
  21. 4
      src/ImageSharp/Formats/Tiff/Compression/Compressors/T4BitCompressor.cs
  22. 58
      src/ImageSharp/Formats/Tiff/Constants/TiffConstants.cs
  23. 4
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs
  24. 4
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs
  25. 8
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs
  26. 8
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs
  27. 83
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs
  28. 10
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorType.cs
  29. 4
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs
  30. 35
      src/ImageSharp/Formats/Tiff/TiffBitsPerPixel.cs
  31. 124
      src/ImageSharp/Formats/Tiff/TiffBitsPerSample.cs
  32. 111
      src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs
  33. 28
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  34. 73
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
  35. 7
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  36. 16
      src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs
  37. 39
      src/ImageSharp/Formats/Tiff/TiffFrameMetadata.cs
  38. 4
      src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter{TPixel}.cs
  39. 38
      src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs
  40. 19
      src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs
  41. 40
      src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs
  42. 2
      src/ImageSharp/ImageSharp.csproj
  43. 10
      src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs
  44. 16
      src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs
  45. 136
      src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs
  46. 5
      src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs
  47. 134
      src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs
  48. 98
      src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs
  49. 2
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs
  50. 12
      src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs
  51. 44
      src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs
  52. 61
      src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs
  53. 29
      src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs
  54. 4
      src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs
  55. 4
      tests/Directory.Build.targets
  56. 38
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Scale16X16To8X8.cs
  57. 95
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs
  58. 4
      tests/ImageSharp.Benchmarks/Format/Jpeg/Components/Encoder/YCbCrForwardConverterBenchmark.cs
  59. 2
      tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
  60. 2
      tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj
  61. 73
      tests/ImageSharp.Tests/Common/NumericsTests.cs
  62. 2
      tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs
  63. 264
      tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs
  64. 213
      tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs
  65. 6
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColorTests.cs
  66. 11
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/PaletteTiffColorTests.cs
  67. 46
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColorTests.cs
  68. 48
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/RgbTiffColorTests.cs
  69. 6
      tests/ImageSharp.Tests/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColorTests.cs
  70. 43
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  71. 41
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
  72. 2
      tests/ImageSharp.Tests/Formats/Tiff/TiffMetadataTests.cs
  73. 2
      tests/ImageSharp.Tests/ImageSharp.Tests.csproj
  74. 13
      tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs
  75. 9
      tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs
  76. 4
      tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs
  77. 3
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs
  78. 15
      tests/ImageSharp.Tests/TestImages.cs
  79. 2
      tests/ImageSharp.Tests/TestUtilities/ReferenceCodecs/MagickReferenceDecoder.cs
  80. 17
      tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs
  81. 2
      tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp
  82. 2
      tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithWuQuantizer_rgb32.bmp
  83. 4
      tests/Images/External/ReferenceOutput/DitherTests/ApplyDiffusionFilterInBox_Rgba32_CalliphoraPartial.png
  84. 4
      tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png
  85. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png
  86. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png
  87. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png
  88. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png
  89. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Atkinson.png
  90. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Burks.png
  91. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_FloydSteinberg.png
  92. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_JarvisJudiceNinke.png
  93. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra2.png
  94. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra3.png
  95. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_SierraLite.png
  96. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_StevensonArce.png
  97. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Stucki.png
  98. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Atkinson.png
  99. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Burks.png
  100. 4
      tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_FloydSteinberg.png

2
.editorconfig

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

11
Directory.Build.props

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

72
ImageSharp.sln

@ -621,115 +621,43 @@ Global
EndGlobalSection EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Debug-InnerLoop|Any CPU = Debug-InnerLoop|Any CPU 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|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
Release-InnerLoop|Any CPU = Release-InnerLoop|Any CPU Release-InnerLoop|Any CPU = Release-InnerLoop|Any CPU
Release-InnerLoop|x64 = Release-InnerLoop|x64
Release-InnerLoop|x86 = Release-InnerLoop|x86
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution GlobalSection(ProjectConfigurationPlatforms) = postSolution
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {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|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.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|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.ActiveCfg = Release|Any CPU
{2AA31A1F-142C-43F4-8687-09ABCA4B3A26}.Release|Any CPU.Build.0 = 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.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|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.ActiveCfg = Debug|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Debug|Any CPU.Build.0 = 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.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|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.ActiveCfg = Release|Any CPU
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}.Release|Any CPU.Build.0 = 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.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|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.ActiveCfg = Debug|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Debug|Any CPU.Build.0 = 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.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|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.ActiveCfg = Release|Any CPU
{2BF743D8-2A06-412D-96D7-F448F00C5EA5}.Release|Any CPU.Build.0 = 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.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|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.ActiveCfg = Debug|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Debug|Any CPU.Build.0 = 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.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|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.ActiveCfg = Release|Any CPU
{FC527290-2F22-432C-B77B-6E815726B02C}.Release|Any CPU.Build.0 = 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.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|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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

2
shared-infrastructure

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

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

@ -23,6 +23,16 @@ namespace SixLabors.ImageSharp
private const int ShuffleAlphaControl = 0b_11_11_11_11; private const int ShuffleAlphaControl = 0b_11_11_11_11;
#endif #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> /// <summary>
/// Determine the Greatest CommonDivisor (GCD) of two numbers. /// Determine the Greatest CommonDivisor (GCD) of two numbers.
/// </summary> /// </summary>
@ -756,7 +766,7 @@ namespace SixLabors.ImageSharp
/// widening them to 32-bit integers and performing four additions. /// widening them to 32-bit integers and performing four additions.
/// </summary> /// </summary>
/// <remarks> /// <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: /// is widened and added onto <paramref name="accumulator"/> as such:
/// <code> /// <code>
/// accumulator += i32(1, 2, 3, 4); /// accumulator += i32(1, 2, 3, 4);
@ -825,5 +835,49 @@ namespace SixLabors.ImageSharp
return Sse2.ConvertToInt32(vsum); return Sse2.ConvertToInt32(vsum);
} }
#endif #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> /// <summary>
/// Performs a multiplication and an addition of the <see cref="Vector256{T}"/>. /// Performs a multiplication and an addition of the <see cref="Vector256{T}"/>.
/// </summary> /// </summary>
/// <remarks>ret = (vm0 * vm1) + va</remarks>
/// <param name="va">The vector to add to the intermediate result.</param> /// <param name="va">The vector to add to the intermediate result.</param>
/// <param name="vm0">The first vector to multiply.</param> /// <param name="vm0">The first vector to multiply.</param>
/// <param name="vm1">The second 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> /// <summary>
/// <see cref="ByteToNormalizedFloat"/> as many elements as possible, slicing them down (keeping the remainder). /// <see cref="ByteToNormalizedFloat"/> as many elements as possible, slicing them down (keeping the remainder).
/// </summary> /// </summary>

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

@ -98,7 +98,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
this.memoryAllocator = memoryAllocator; this.memoryAllocator = memoryAllocator;
this.bitsPerPixel = options.BitsPerPixel; this.bitsPerPixel = options.BitsPerPixel;
this.writeV4Header = options.SupportTransparency; this.writeV4Header = options.SupportTransparency;
this.quantizer = options.Quantizer ?? KnownQuantizers.Wu; this.quantizer = options.Quantizer ?? KnownQuantizers.Octree;
} }
/// <summary> /// <summary>

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

@ -151,7 +151,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
// since the palette is unchanging. This allows a reduction of memory usage across // since the palette is unchanging. This allows a reduction of memory usage across
// multi frame gifs using a global palette. // multi frame gifs using a global palette.
EuclideanPixelMap<TPixel> pixelMap = default; EuclideanPixelMap<TPixel> pixelMap = default;
bool pixelMapSet = false; bool pixelMapHasValue = false;
for (int i = 0; i < image.Frames.Count; i++) for (int i = 0; i < image.Frames.Count; i++)
{ {
ImageFrame<TPixel> frame = image.Frames[i]; ImageFrame<TPixel> frame = image.Frames[i];
@ -166,17 +166,22 @@ namespace SixLabors.ImageSharp.Formats.Gif
} }
else else
{ {
if (!pixelMapSet) if (!pixelMapHasValue)
{ {
pixelMapSet = true; pixelMapHasValue = true;
pixelMap = new EuclideanPixelMap<TPixel>(this.configuration, quantized.Palette); pixelMap = new EuclideanPixelMap<TPixel>(this.configuration, quantized.Palette);
} }
using var paletteFrameQuantizer = new PaletteQuantizer<TPixel>(this.configuration, this.quantizer.Options, pixelMap); using var paletteFrameQuantizer = new PaletteQuantizer<TPixel>(this.configuration, this.quantizer.Options, pixelMap, true);
using IndexedImageFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds()); using IndexedImageFrame<TPixel> paletteQuantized = paletteFrameQuantizer.QuantizeFrame(frame, frame.Bounds());
this.WriteImageData(paletteQuantized, stream); this.WriteImageData(paletteQuantized, stream);
} }
} }
if (pixelMapHasValue)
{
pixelMap.Dispose();
}
} }
private void EncodeLocal<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel> quantized, Stream stream) private void EncodeLocal<TPixel>(Image<TPixel> image, IndexedImageFrame<TPixel> quantized, Stream stream)

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

@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <summary> /// <summary>
/// Represents a Jpeg block with <see cref="float"/> coefficients. /// Represents a Jpeg block with <see cref="float"/> coefficients.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Explicit)]
internal partial struct Block8x8F : IEquatable<Block8x8F> internal partial struct Block8x8F : IEquatable<Block8x8F>
{ {
/// <summary> /// <summary>
@ -27,29 +27,69 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
public const int Size = 64; public const int Size = 64;
#pragma warning disable SA1600 // ElementsMustBeDocumented #pragma warning disable SA1600 // ElementsMustBeDocumented
[FieldOffset(0)]
public Vector4 V0L; public Vector4 V0L;
[FieldOffset(16)]
public Vector4 V0R; public Vector4 V0R;
[FieldOffset(32)]
public Vector4 V1L; public Vector4 V1L;
[FieldOffset(48)]
public Vector4 V1R; public Vector4 V1R;
[FieldOffset(64)]
public Vector4 V2L; public Vector4 V2L;
[FieldOffset(80)]
public Vector4 V2R; public Vector4 V2R;
[FieldOffset(96)]
public Vector4 V3L; public Vector4 V3L;
[FieldOffset(112)]
public Vector4 V3R; public Vector4 V3R;
[FieldOffset(128)]
public Vector4 V4L; public Vector4 V4L;
[FieldOffset(144)]
public Vector4 V4R; public Vector4 V4R;
[FieldOffset(160)]
public Vector4 V5L; public Vector4 V5L;
[FieldOffset(176)]
public Vector4 V5R; public Vector4 V5R;
[FieldOffset(192)]
public Vector4 V6L; public Vector4 V6L;
[FieldOffset(208)]
public Vector4 V6R; public Vector4 V6R;
[FieldOffset(224)]
public Vector4 V7L; public Vector4 V7L;
[FieldOffset(240)]
public Vector4 V7R; 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 #pragma warning restore SA1600 // ElementsMustBeDocumented
/// <summary> /// <summary>
@ -278,14 +318,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
if (Avx.IsSupported) if (Avx.IsSupported)
{ {
var valueVec = Vector256.Create(value); var valueVec = Vector256.Create(value);
Unsafe.As<Vector4, Vector256<float>>(ref this.V0L) = Avx.Multiply(Unsafe.As<Vector4, Vector256<float>>(ref this.V0L), valueVec); this.V0 = Avx.Multiply(this.V0, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V1L) = Avx.Multiply(Unsafe.As<Vector4, Vector256<float>>(ref this.V1L), valueVec); this.V1 = Avx.Multiply(this.V1, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V2L) = Avx.Multiply(Unsafe.As<Vector4, Vector256<float>>(ref this.V2L), valueVec); this.V2 = Avx.Multiply(this.V2, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V3L) = Avx.Multiply(Unsafe.As<Vector4, Vector256<float>>(ref this.V3L), valueVec); this.V3 = Avx.Multiply(this.V3, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V4L) = Avx.Multiply(Unsafe.As<Vector4, Vector256<float>>(ref this.V4L), valueVec); this.V4 = Avx.Multiply(this.V4, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V5L) = Avx.Multiply(Unsafe.As<Vector4, Vector256<float>>(ref this.V5L), valueVec); this.V5 = Avx.Multiply(this.V5, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V6L) = Avx.Multiply(Unsafe.As<Vector4, Vector256<float>>(ref this.V6L), valueVec); this.V6 = Avx.Multiply(this.V6, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V7L) = Avx.Multiply(Unsafe.As<Vector4, Vector256<float>>(ref this.V7L), valueVec); this.V7 = Avx.Multiply(this.V7, valueVec);
} }
else else
#endif #endif
@ -319,45 +359,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
if (Avx.IsSupported) if (Avx.IsSupported)
{ {
Unsafe.As<Vector4, Vector256<float>>(ref this.V0L) this.V0 = Avx.Multiply(this.V0, other.V0);
= Avx.Multiply( this.V1 = Avx.Multiply(this.V1, other.V1);
Unsafe.As<Vector4, Vector256<float>>(ref this.V0L), this.V2 = Avx.Multiply(this.V2, other.V2);
Unsafe.As<Vector4, Vector256<float>>(ref other.V0L)); this.V3 = Avx.Multiply(this.V3, other.V3);
this.V4 = Avx.Multiply(this.V4, other.V4);
Unsafe.As<Vector4, Vector256<float>>(ref this.V1L) this.V5 = Avx.Multiply(this.V5, other.V5);
= Avx.Multiply( this.V6 = Avx.Multiply(this.V6, other.V6);
Unsafe.As<Vector4, Vector256<float>>(ref this.V1L), this.V7 = Avx.Multiply(this.V7, other.V7);
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));
} }
else else
#endif #endif
@ -392,14 +401,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
if (Avx.IsSupported) if (Avx.IsSupported)
{ {
var valueVec = Vector256.Create(value); var valueVec = Vector256.Create(value);
Unsafe.As<Vector4, Vector256<float>>(ref this.V0L) = Avx.Add(Unsafe.As<Vector4, Vector256<float>>(ref this.V0L), valueVec); this.V0 = Avx.Add(this.V0, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V1L) = Avx.Add(Unsafe.As<Vector4, Vector256<float>>(ref this.V1L), valueVec); this.V1 = Avx.Add(this.V1, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V2L) = Avx.Add(Unsafe.As<Vector4, Vector256<float>>(ref this.V2L), valueVec); this.V2 = Avx.Add(this.V2, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V3L) = Avx.Add(Unsafe.As<Vector4, Vector256<float>>(ref this.V3L), valueVec); this.V3 = Avx.Add(this.V3, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V4L) = Avx.Add(Unsafe.As<Vector4, Vector256<float>>(ref this.V4L), valueVec); this.V4 = Avx.Add(this.V4, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V5L) = Avx.Add(Unsafe.As<Vector4, Vector256<float>>(ref this.V5L), valueVec); this.V5 = Avx.Add(this.V5, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V6L) = Avx.Add(Unsafe.As<Vector4, Vector256<float>>(ref this.V6L), valueVec); this.V6 = Avx.Add(this.V6, valueVec);
Unsafe.As<Vector4, Vector256<float>>(ref this.V7L) = Avx.Add(Unsafe.As<Vector4, Vector256<float>>(ref this.V7L), valueVec); this.V7 = Avx.Add(this.V7, valueVec);
} }
else else
#endif #endif
@ -468,81 +477,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
DivideRoundAll(ref dest, ref qt); 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)] [MethodImpl(InliningOptions.ShortMethod)]
private static void DivideRoundAll(ref Block8x8F a, ref Block8x8F b) 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 vadd = Vector256.Create(.5F);
var vone = Vector256.Create(1f); var vone = Vector256.Create(1f);
ref Vector256<float> aBase = ref Unsafe.AsRef(Unsafe.As<Vector4, Vector256<float>>(ref a.V0L)); for (int i = 0; i < RowCount; i++)
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
{ {
Vector256<float> voff = Avx.Multiply(Avx.Min(Avx.Max(vnegOne, aBase), vone), vadd); ref Vector256<float> aRow = ref Unsafe.Add(ref a.V0, i);
Unsafe.Add(ref aBase, 0) = Avx.Add(Avx.Divide(aBase, bBase), voff); ref Vector256<float> bRow = ref Unsafe.Add(ref b.V0, i);
Vector256<float> voff = Avx.Multiply(Avx.Min(Avx.Max(vnegOne, aRow), vone), vadd);
aBase = ref Unsafe.Add(ref aBase, 1); aRow = Avx.Add(Avx.Divide(aRow, bRow), voff);
bBase = ref Unsafe.Add(ref bBase, 1);
} }
while (Unsafe.IsAddressLessThan(ref aBase, ref aEnd));
} }
else else
#endif #endif
@ -805,26 +733,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
Vector256<float> t0 = Avx.UnpackLow(r0, r1); Vector256<float> t0 = Avx.UnpackLow(r0, r1);
Vector256<float> t2 = Avx.UnpackLow(r2, r3); Vector256<float> t2 = Avx.UnpackLow(r2, r3);
Vector256<float> v = Avx.Shuffle(t0, t2, 0x4E); Vector256<float> v = Avx.Shuffle(t0, t2, 0x4E);
Unsafe.As<Vector4, Vector256<float>>(ref d.V0L) = Avx.Blend(t0, v, 0xCC); d.V0 = Avx.Blend(t0, v, 0xCC);
Unsafe.As<Vector4, Vector256<float>>(ref d.V1L) = Avx.Blend(t2, v, 0x33); d.V1 = Avx.Blend(t2, v, 0x33);
Vector256<float> t4 = Avx.UnpackLow(r4, r5); Vector256<float> t4 = Avx.UnpackLow(r4, r5);
Vector256<float> t6 = Avx.UnpackLow(r6, r7); Vector256<float> t6 = Avx.UnpackLow(r6, r7);
v = Avx.Shuffle(t4, t6, 0x4E); v = Avx.Shuffle(t4, t6, 0x4E);
Unsafe.As<Vector4, Vector256<float>>(ref d.V4L) = Avx.Blend(t4, v, 0xCC); d.V4 = Avx.Blend(t4, v, 0xCC);
Unsafe.As<Vector4, Vector256<float>>(ref d.V5L) = Avx.Blend(t6, v, 0x33); d.V5 = Avx.Blend(t6, v, 0x33);
Vector256<float> t1 = Avx.UnpackHigh(r0, r1); Vector256<float> t1 = Avx.UnpackHigh(r0, r1);
Vector256<float> t3 = Avx.UnpackHigh(r2, r3); Vector256<float> t3 = Avx.UnpackHigh(r2, r3);
v = Avx.Shuffle(t1, t3, 0x4E); v = Avx.Shuffle(t1, t3, 0x4E);
Unsafe.As<Vector4, Vector256<float>>(ref d.V2L) = Avx.Blend(t1, v, 0xCC); d.V2 = Avx.Blend(t1, v, 0xCC);
Unsafe.As<Vector4, Vector256<float>>(ref d.V3L) = Avx.Blend(t3, v, 0x33); d.V3 = Avx.Blend(t3, v, 0x33);
Vector256<float> t5 = Avx.UnpackHigh(r4, r5); Vector256<float> t5 = Avx.UnpackHigh(r4, r5);
Vector256<float> t7 = Avx.UnpackHigh(r6, r7); Vector256<float> t7 = Avx.UnpackHigh(r6, r7);
v = Avx.Shuffle(t5, t7, 0x4E); v = Avx.Shuffle(t5, t7, 0x4E);
Unsafe.As<Vector4, Vector256<float>>(ref d.V6L) = Avx.Blend(t5, v, 0xCC); d.V6 = Avx.Blend(t5, v, 0xCC);
Unsafe.As<Vector4, Vector256<float>>(ref d.V7L) = Avx.Blend(t7, v, 0x33); d.V7 = Avx.Blend(t7, v, 0x33);
} }
else else
#endif #endif

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

@ -1,4 +1,4 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
} }
} }
this.Values = new uint[maxValue + 1]; this.Values = new int[maxValue + 1];
int code = 0; int code = 0;
int k = 0; int k = 0;
@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
int bits = (i + 1) << 24; int bits = (i + 1) << 24;
for (int j = 0; j < spec.Count[i]; j++) for (int j = 0; j < spec.Count[i]; j++)
{ {
this.Values[spec.Values[k]] = (uint)(bits | code); this.Values[spec.Values[k]] = bits | code;
code++; code++;
k++; k++;
} }
@ -66,6 +66,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// <summary> /// <summary>
/// Gets the collection of huffman values. /// Gets the collection of huffman values.
/// </summary> /// </summary>
public uint[] Values { get; } public int[] Values { get; }
} }
} }

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

@ -0,0 +1,427 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
internal class HuffmanScanEncoder
{
/// <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>
{
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>
{
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>
{
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);
int dc = (int)refTemp2[0];
// Emit the DC delta.
this.EmitHuffRLE((2 * (int)index) + 0, 0, dc - prevDC);
// Emit the AC components.
int h = (2 * (int)index) + 1;
int runLength = 0;
for (int zig = 1; zig < Block8x8F.Size; zig++)
{
int ac = (int)refTemp2[zig];
if (ac == 0)
{
runLength++;
}
else
{
while (runLength > 15)
{
this.EmitHuff(h, 0xf0);
runLength -= 16;
}
this.EmitHuffRLE(h, runLength, ac);
runLength = 0;
}
}
if (runLength > 0)
{
this.EmitHuff(h, 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;
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="index">The index of the Huffman encoder</param>
/// <param name="value">The value to encode.</param>
[MethodImpl(InliningOptions.ShortMethod)]
private void EmitHuff(int index, int value)
{
int x = HuffmanLut.TheHuffmanLut[index].Values[value];
this.Emit(x & ((1 << 24) - 1), x >> 24);
}
/// <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>
[MethodImpl(InliningOptions.ShortMethod)]
private void EmitHuffRLE(int index, 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(index, (runLength << 4) | bt);
if (bt > 0)
{
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);
}
// flush remaining bytes
if (this.emitLen != 0)
{
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 method returns 0 for input value 0. This is done specificaly for huffman encoding
/// </remarks>
/// <param name="value">The value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private 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
}
}
}

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 Block8x8F yBlock = ref this.Y;
ref L8 l8Start = ref l8Span[0]; 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); ref L8 c = ref Unsafe.Add(ref l8Start, i);
yBlock[i] = c.PackedValue; 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; 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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ConvertPixelInto( private float CalculateY(byte r, byte g, byte b)
int r,
int g,
int b,
ref Block8x8F yResult,
ref Block8x8F cbResult,
ref Block8x8F crResult,
int i)
{ {
// float y = (0.299F * r) + (0.587F * g) + (0.114F * 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)); // 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)); // 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]; 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); Rgb24 c = Unsafe.Add(ref rgbStart, i);
this.ConvertPixelInto( yBlock[i] = this.CalculateY(c.R, c.G, c.B);
c.R, cbBlock[i] = this.CalculateCb(c.R, c.G, c.B);
c.G, crBlock[i] = this.CalculateCr(c.R, c.G, c.B);
c.B,
ref yBlock,
ref cbBlock,
ref crBlock,
i);
} }
} }
/// <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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Fix(float x) private static int Fix(float x)
=> (int)((x * (1L << ScaleBits)) + 0.5F); => (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. // Licensed under the Apache License, Version 2.0.
using System; 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 #if SUPPORTS_RUNTIME_INTRINSICS
private static ReadOnlySpan<byte> MoveFirst24BytesToSeparateLanes => new byte[] private static ReadOnlySpan<byte> MoveFirst24BytesToSeparateLanes => new byte[]
{ {
0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0, 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 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[] private static ReadOnlySpan<byte> ExtractRgb => new byte[]
{ {
0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, 0xFF, 0xFF, 0xFF, 0xFF, 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 #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"); 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 f05 = Vector256.Create(0.5f);
var zero = Vector256.Create(0).AsByte(); var zero = Vector256.Create(0).AsByte();
ref Vector256<byte> inRef = ref Unsafe.As<Rgb24, Vector256<byte>>(ref MemoryMarshal.GetReference(rgbSpan)); ref Vector256<byte> rgbByteSpan = 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> destYRef = ref yBlock.V0;
ref Vector256<float> destCbRef = ref Unsafe.As<Block8x8F, Vector256<float>>(ref cbBlock); ref Vector256<float> destCbRef = ref cbBlock.V0;
ref Vector256<float> destCrRef = ref Unsafe.As<Block8x8F, Vector256<float>>(ref crBlock); ref Vector256<float> destCrRef = ref crBlock.V0;
var extractToLanesMask = Unsafe.As<byte, Vector256<uint>>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes)); var extractToLanesMask = Unsafe.As<byte, Vector256<uint>>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes));
var extractRgbMask = Unsafe.As<byte, Vector256<byte>>(ref MemoryMarshal.GetReference(ExtractRgb)); var extractRgbMask = Unsafe.As<byte, Vector256<byte>>(ref MemoryMarshal.GetReference(ExtractRgb));
Vector256<byte> rgb, rg, bx; Vector256<byte> rgb, rg, bx;
Vector256<float> r, g, b; 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); 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)) // 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)); 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)); ref Vector256<float> destCbRef = ref Unsafe.Add(ref Unsafe.As<Block8x8F, Vector256<float>>(ref cbBlock), destOffset);
rgb = Avx2.PermuteVar8x32(Unsafe.AddByteOffset(ref inRef, (IntPtr)160).AsUInt32(), extractToLanesMask).AsByte(); ref Vector256<float> destCrRef = ref Unsafe.Add(ref Unsafe.As<Block8x8F, Vector256<float>>(ref crBlock), destOffset);
rgb = Avx2.Shuffle(rgb, extractRgbMask);
rg = Avx2.UnpackLow(rgb, zero); var extractToLanesMask = Unsafe.As<byte, Vector256<uint>>(ref MemoryMarshal.GetReference(MoveFirst24BytesToSeparateLanes));
bx = Avx2.UnpackHigh(rgb, zero); 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()); r = Avx.ConvertToVector256Single(Avx2.UnpackLow(rg, zero).AsInt32());
g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32()); g = Avx.ConvertToVector256Single(Avx2.UnpackHigh(rg, zero).AsInt32());
b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32()); b = Avx.ConvertToVector256Single(Avx2.UnpackLow(bx, zero).AsInt32());
// (0.299F * r) + (0.587F * g) + (0.114F * b); int yBlockVerticalOffset = (i * 2) + ((j & 2) >> 1);
Unsafe.Add(ref destYRef, 7) = SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f0114, b), f0587, g), f0299, r);
// 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)) // (0.299F * r) + (0.587F * g) + (0.114F * b);
Unsafe.Add(ref destCbRef, 7) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(f05, b), fn0331264, g), fn0168736, r)); 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)) rDataLanes[j] = r;
Unsafe.Add(ref destCrRef, 7) = Avx.Add(f128, SimdUtils.HwIntrinsics.MultiplyAdd(SimdUtils.HwIntrinsics.MultiplyAdd(Avx.Multiply(fn0081312F, b), fn0418688, g), f05, 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 #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. // Licensed under the Apache License, Version 2.0.
using System; using System;
using SixLabors.ImageSharp.Advanced; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{ {
/// <summary> internal static class YCbCrForwardConverter<TPixel>
/// 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>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
/// <summary> public static void LoadAndStretchEdges(RowOctet<TPixel> source, Span<TPixel> dest, Point start, Size sampleSize, Size totalSize)
/// The Y component {
/// </summary> DebugGuard.MustBeBetweenOrEqualTo(start.X, 1, totalSize.Width - 1, nameof(start.X));
public Block8x8F Y; DebugGuard.MustBeBetweenOrEqualTo(start.Y, 1, totalSize.Height - 1, nameof(start.Y));
/// <summary>
/// The Cb component
/// </summary>
public Block8x8F Cb;
/// <summary>
/// The Cr component
/// </summary>
public Block8x8F Cr;
/// <summary> int width = Math.Min(sampleSize.Width, totalSize.Width - start.X);
/// The color conversion tables int height = Math.Min(sampleSize.Height, totalSize.Height - start.Y);
/// </summary>
private RgbToYCbCrConverterLut colorTables;
/// <summary> uint byteWidth = (uint)(width * Unsafe.SizeOf<TPixel>());
/// Temporal 8x8 block to hold TPixel data int remainderXCount = sampleSize.Width - width;
/// </summary>
private GenericBlock8x8<TPixel> pixelBlock;
/// <summary> ref byte blockStart = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<TPixel, byte>(dest));
/// Temporal RGB block int rowSizeInBytes = sampleSize.Width * Unsafe.SizeOf<TPixel>();
/// </summary>
private GenericBlock8x8<Rgb24> rgbBlock;
public static YCbCrForwardConverter<TPixel> Create() for (int y = 0; y < height; y++)
{
var result = default(YCbCrForwardConverter<TPixel>);
if (!RgbToYCbCrConverterVectorized.IsSupported)
{ {
// Avoid creating lookup tables, when vectorized converter is supported Span<TPixel> row = source[y];
result.colorTables = RgbToYCbCrConverterLut.Create();
}
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> Unsafe.CopyBlock(ref d, ref s, byteWidth);
/// 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> ref TPixel last = ref Unsafe.Add(ref Unsafe.As<byte, TPixel>(ref d), width - 1);
public void Convert(ImageFrame<TPixel> frame, int x, int y, ref RowOctet<TPixel> currentRows)
{
this.pixelBlock.LoadAndStretchEdges(frame.PixelBuffer, x, y, ref currentRows);
Span<Rgb24> rgbSpan = this.rgbBlock.AsSpanUnsafe(); for (int x = 1; x <= remainderXCount; x++)
PixelOperations<TPixel>.Instance.ToRgb24(frame.GetConfiguration(), this.pixelBlock.AsSpanUnsafe(), rgbSpan); {
Unsafe.Add(ref last, x) = last;
}
}
ref Block8x8F yBlock = ref this.Y; int remainderYCount = sampleSize.Height - height;
ref Block8x8F cbBlock = ref this.Cb;
ref Block8x8F crBlock = ref this.Cr;
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. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Diagnostics;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Formats.Jpeg.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components
@ -10,7 +15,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// <summary> /// <summary>
/// Contains inaccurate, but fast forward and inverse DCT implementations. /// Contains inaccurate, but fast forward and inverse DCT implementations.
/// </summary> /// </summary>
internal static class FastFloatingPointDCT internal static partial class FastFloatingPointDCT
{ {
#pragma warning disable SA1310 // FieldNamesMustNotContainUnderscore #pragma warning disable SA1310 // FieldNamesMustNotContainUnderscore
private const float C_1_175876 = 1.175875602f; 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_765367 = 0.765366865f;
private const float C_0_125 = 0.1250f; 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 #pragma warning restore SA1310 // FieldNamesMustNotContainUnderscore
private static readonly Vector4 InvSqrt2 = new Vector4(0.707107f); 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> /// <summary>
/// Original: /// Original:
/// <see> /// <see>
@ -309,11 +198,84 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
} }
/// <summary> /// <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> /// </summary>
/// <param name="src">Source</param> /// <param name="src">Source</param>
/// <param name="dest">Destination</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> /// <param name="offsetSourceByNeg128">If true, a constant -128.0 offset is applied for all values before FDCT </param>
public static void TransformFDCT( public static void TransformFDCT(
ref Block8x8F src, ref Block8x8F src,
@ -327,14 +289,225 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
temp.AddInPlace(-128F); temp.AddInPlace(-128F);
} }
FDCT8x4_LeftPart(ref temp, ref dest); FDCT8x8(ref temp, ref dest);
FDCT8x4_RightPart(ref temp, ref dest);
dest.TransposeInto(ref temp); dest.TransposeInto(ref temp);
FDCT8x4_LeftPart(ref temp, ref dest); FDCT8x8(ref temp, ref dest);
FDCT8x4_RightPart(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); dest.MultiplyInPlace(C_0_125);
} }
} }

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

@ -5,14 +5,11 @@ using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
using SixLabors.ImageSharp.Common.Helpers; using SixLabors.ImageSharp.Common.Helpers;
using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Exif;
using SixLabors.ImageSharp.Metadata.Profiles.Icc; using SixLabors.ImageSharp.Metadata.Profiles.Icc;
@ -36,17 +33,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
private readonly byte[] buffer = new byte[20]; 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> /// <summary>
/// Gets or sets the subsampling method to use. /// Gets or sets the subsampling method to use.
/// </summary> /// </summary>
@ -62,26 +48,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
private readonly JpegColorType? colorType; 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> /// <summary>
/// The output stream. All attempted writes after the first error become no-ops. /// The output stream. All attempted writes after the first error become no-ops.
/// </summary> /// </summary>
@ -98,29 +64,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.colorType = options.ColorType; 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> /// <summary>
/// Gets the unscaled quantization tables in zig-zag order. Each /// Gets the unscaled quantization tables in zig-zag order. Each
/// encoder copies and scales the tables according to its quality parameter. /// 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) // This is effectively compiled down to: return new ReadOnlySpan<byte>(&data, length)
// More details can be found: https://github.com/dotnet/roslyn/pull/24621 // More details can be found: https://github.com/dotnet/roslyn/pull/24621
private static ReadOnlySpan<byte> UnscaledQuant_Luminance => new byte[] private static ReadOnlySpan<byte> UnscaledQuant_Luminance => new byte[]
{ {
// Luminance. // Luminance.
16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24, 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, 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, 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, 109, 81, 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112,
100, 120, 92, 101, 103, 99, 100, 120, 92, 101, 103, 99,
}; };
/// <summary> /// <summary>
/// Gets the unscaled quantization tables in zig-zag order. Each /// 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) // This is effectively compiled down to: return new ReadOnlySpan<byte>(&data, length)
// More details can be found: https://github.com/dotnet/roslyn/pull/24621 // More details can be found: https://github.com/dotnet/roslyn/pull/24621
private static ReadOnlySpan<byte> UnscaledQuant_Chrominance => new byte[] private static ReadOnlySpan<byte> UnscaledQuant_Chrominance => new byte[]
{ {
// Chrominance. // Chrominance.
17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66, 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, 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> /// <summary>
/// Encode writes the image to the jpeg baseline format with the given options. /// 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(image, nameof(image));
Guard.NotNull(stream, nameof(stream)); Guard.NotNull(stream, nameof(stream));
cancellationToken.ThrowIfCancellationRequested();
const ushort max = JpegConstants.MaxLength; if (image.Width >= JpegConstants.MaxLength || image.Height >= JpegConstants.MaxLength)
if (image.Width >= max || image.Height >= max)
{ {
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; this.outputStream = stream;
ImageMetadata metadata = image.Metadata; ImageMetadata metadata = image.Metadata;
@ -201,10 +144,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
// Initialize the quantization tables. // 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) if (componentCount > 1)
{ {
InitQuantizationTable(1, scale, ref this.chrominanceQuantTable); InitQuantizationTable(1, scale, ref chrominanceQuantTable);
} }
// Write the Start Of Image marker. // Write the Start Of Image marker.
@ -214,7 +161,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.WriteProfiles(metadata); this.WriteProfiles(metadata);
// Write the quantization tables. // Write the quantization tables.
this.WriteDefineQuantizationTables(); this.WriteDefineQuantizationTables(ref luminanceQuantTable, ref chrominanceQuantTable);
// Write the image dimensions. // Write the image dimensions.
this.WriteStartOfFrame(image.Width, image.Height, componentCount); this.WriteStartOfFrame(image.Width, image.Height, componentCount);
@ -222,13 +169,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Write the Huffman tables. // Write the Huffman tables.
this.WriteDefineHuffmanTables(componentCount); this.WriteDefineHuffmanTables(componentCount);
// Write the image data. // Write the scan header.
this.WriteStartOfScan(image, componentCount, cancellationToken); 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. // Write the End Of Image marker.
this.buffer[0] = JpegConstants.Markers.XFF; this.WriteEndOfImageMarker();
this.buffer[1] = JpegConstants.Markers.EOI;
stream.Write(this.buffer, 0, 2);
stream.Flush(); 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> /// <summary>
/// Writes the application header containing the JFIF identifier plus extra data. /// Writes the application header containing the JFIF identifier plus extra data.
/// </summary> /// </summary>
@ -539,72 +262,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.outputStream.Write(this.buffer, 0, 20); 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> /// <summary>
/// Writes the Define Huffman Table marker and tables. /// Writes the Define Huffman Table marker and tables.
/// </summary> /// </summary>
@ -638,34 +295,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen); this.WriteMarkerHeader(JpegConstants.Markers.DHT, markerlen);
for (int i = 0; i < specs.Length; i++) for (int i = 0; i < specs.Length; i++)
{ {
ref HuffmanSpec spec = ref specs[i]; this.outputStream.WriteByte(headers[i]);
int len = 0; this.outputStream.Write(specs[i].Count);
this.outputStream.Write(specs[i].Values);
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);
} }
} }
/// <summary> /// <summary>
/// Writes the Define Quantization Marker and tables. /// Writes the Define Quantization Marker and tables.
/// </summary> /// </summary>
private void WriteDefineQuantizationTables() private void WriteDefineQuantizationTables(ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable)
{ {
// Marker + quantization table lengths // Marker + quantization table lengths
int markerlen = 2 + (QuantizationTableCount * (1 + Block8x8F.Size)); int markerlen = 2 + (QuantizationTableCount * (1 + Block8x8F.Size));
@ -677,8 +316,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
byte[] dqt = new byte[dqtCount]; byte[] dqt = new byte[dqtCount];
int offset = 0; int offset = 0;
WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref this.luminanceQuantTable); WriteDataToDqt(dqt, ref offset, QuantIndex.Luminance, ref luminanceQuantTable);
WriteDataToDqt(dqt, ref offset, QuantIndex.Chrominance, ref this.chrominanceQuantTable); WriteDataToDqt(dqt, ref offset, QuantIndex.Chrominance, ref chrominanceQuantTable);
this.outputStream.Write(dqt, 0, dqtCount); 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) private void WriteStartOfScan<TPixel>(Image<TPixel> image, int componentCount, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> 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[] Span<byte> componentId = stackalloc byte[]
{ {
0x01, 0x01,
@ -1024,111 +662,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.buffer[sosSize] = 0x3f; // Se - End of spectral selection. this.buffer[sosSize] = 0x3f; // Se - End of spectral selection.
this.buffer[sosSize + 1] = 0x00; // Ah + Ah (Successive approximation bit position high + low) this.buffer[sosSize + 1] = 0x00; // Ah + Ah (Successive approximation bit position high + low)
this.outputStream.Write(this.buffer, 0, sosSize + 2); 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> /// <summary>
/// Encodes the image with subsampling. The Cb and Cr components are each subsampled /// Writes the EndOfImage marker.
/// at a factor of 2 both horizontally and vertically.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> private void WriteEndOfImageMarker()
/// <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>
{ {
// TODO: Need a JpegScanEncoder<TPixel> class or struct that encapsulates the scan-encoding implementation. (Similar to JpegScanDecoder.) this.buffer[0] = JpegConstants.Markers.XFF;
Block8x8F b = default; this.buffer[1] = JpegConstants.Markers.EOI;
Span<Block8x8F> cb = stackalloc Block8x8F[4]; this.outputStream.Write(this.buffer, 0, 2);
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);
}
}
} }
/// <summary> /// <summary>
@ -1145,5 +688,34 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.buffer[3] = (byte)(length & 0xff); this.buffer[3] = (byte)(length & 0xff);
this.outputStream.Write(this.buffer, 0, 4); 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)] [MethodImpl(InliningOptions.ColdPath)]
public static void ThrowInvalidImageDimensions(int width, int height) => throw new InvalidImageContentException($"Invalid image dimensions: {width}x{height}."); 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/Tiff/Compression/Compressors/T4BitCompressor.cs

@ -213,7 +213,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
this.compressedDataBuffer = this.Allocator.Allocate<byte>(maxNeededBytes); this.compressedDataBuffer = this.Allocator.Allocate<byte>(maxNeededBytes);
} }
/// <summary>Writes a image compressed with CCITT T4 to the stream.</summary> /// <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="pixelsAsGray">The pixels as 8-bit gray array.</param>
/// <param name="height">The strip height.</param> /// <param name="height">The strip height.</param>
public override void CompressStrip(Span<byte> pixelsAsGray, int height) public override void CompressStrip(Span<byte> pixelsAsGray, int height)

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

@ -40,41 +40,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants
/// </summary> /// </summary>
public const int RowsPerStripInfinity = 2147483647; public const int RowsPerStripInfinity = 2147483647;
/// <summary>
/// Size (in bytes) of the TIFF file header.
/// </summary>
public const int SizeOfTiffHeader = 8;
/// <summary>
/// Size (in bytes) of each individual TIFF IFD entry
/// </summary>
public const int SizeOfIfdEntry = 12;
/// <summary>
/// Size (in bytes) of the Short and SShort data types
/// </summary>
public const int SizeOfShort = 2;
/// <summary>
/// Size (in bytes) of the Long and SLong data types
/// </summary>
public const int SizeOfLong = 4;
/// <summary> /// <summary>
/// Size (in bytes) of the Rational and SRational data types /// Size (in bytes) of the Rational and SRational data types
/// </summary> /// </summary>
public const int SizeOfRational = 8; public const int SizeOfRational = 8;
/// <summary>
/// Size (in bytes) of the Float data type
/// </summary>
public const int SizeOfFloat = 4;
/// <summary>
/// Size (in bytes) of the Double data type
/// </summary>
public const int SizeOfDouble = 8;
/// <summary> /// <summary>
/// The default strip size is 8k. /// The default strip size is 8k.
/// </summary> /// </summary>
@ -83,42 +53,22 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants
/// <summary> /// <summary>
/// The bits per sample for 1 bit bicolor images. /// The bits per sample for 1 bit bicolor images.
/// </summary> /// </summary>
public static readonly ushort[] BitsPerSample1Bit = { 1 }; public static readonly TiffBitsPerSample BitsPerSample1Bit = new TiffBitsPerSample(1, 0, 0);
/// <summary> /// <summary>
/// The bits per sample for images with a 4 color palette. /// The bits per sample for images with a 4 color palette.
/// </summary> /// </summary>
public static readonly ushort[] BitsPerSample4Bit = { 4 }; public static readonly TiffBitsPerSample BitsPerSample4Bit = new TiffBitsPerSample(4, 0, 0);
/// <summary> /// <summary>
/// The bits per sample for 8 bit images. /// The bits per sample for 8 bit images.
/// </summary> /// </summary>
public static readonly ushort[] BitsPerSample8Bit = { 8 }; public static readonly TiffBitsPerSample BitsPerSample8Bit = new TiffBitsPerSample(8, 0, 0);
/// <summary>
/// The bits per sample for color images with 2 bits for each color channel.
/// </summary>
public static readonly ushort[] BitsPerSampleRgb2Bit = { 2, 2, 2 };
/// <summary>
/// The bits per sample for color images with 4 bits for each color channel.
/// </summary>
public static readonly ushort[] BitsPerSampleRgb4Bit = { 4, 4, 4 };
/// <summary> /// <summary>
/// The bits per sample for color images with 8 bits for each color channel. /// The bits per sample for color images with 8 bits for each color channel.
/// </summary> /// </summary>
public static readonly ushort[] BitsPerSampleRgb8Bit = { 8, 8, 8 }; public static readonly TiffBitsPerSample BitsPerSampleRgb8Bit = new TiffBitsPerSample(8, 8, 8);
/// <summary>
/// The bits per sample for color images with 10 bits for each color channel.
/// </summary>
public static readonly ushort[] BitsPerSampleRgb10Bit = { 10, 10, 10 };
/// <summary>
/// The bits per sample for color images with 14 bits for each color channel.
/// </summary>
public static readonly ushort[] BitsPerSampleRgb14Bit = { 14, 14, 14 };
/// <summary> /// <summary>
/// The list of mimetypes that equate to a tiff. /// The list of mimetypes that equate to a tiff.

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

@ -19,9 +19,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
private readonly float factor; private readonly float factor;
public BlackIsZeroTiffColor(ushort[] bitsPerSample) public BlackIsZeroTiffColor(TiffBitsPerSample bitsPerSample)
{ {
this.bitsPerSample0 = bitsPerSample[0]; this.bitsPerSample0 = bitsPerSample.Channel0;
this.factor = (1 << this.bitsPerSample0) - 1.0f; this.factor = (1 << this.bitsPerSample0) - 1.0f;
} }

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

@ -21,9 +21,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// <param name="bitsPerSample">The number of bits per sample for each pixel.</param> /// <param name="bitsPerSample">The number of bits per sample for each pixel.</param>
/// <param name="colorMap">The RGB color lookup table to use for decoding the image.</param> /// <param name="colorMap">The RGB color lookup table to use for decoding the image.</param>
public PaletteTiffColor(ushort[] bitsPerSample, ushort[] colorMap) public PaletteTiffColor(TiffBitsPerSample bitsPerSample, ushort[] colorMap)
{ {
this.bitsPerSample0 = bitsPerSample[0]; this.bitsPerSample0 = bitsPerSample.Channel0;
int colorCount = 1 << this.bitsPerSample0; int colorCount = 1 << this.bitsPerSample0;
this.palette = GeneratePalette(colorMap, colorCount); this.palette = GeneratePalette(colorMap, colorCount);
} }

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

@ -26,11 +26,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
private readonly ushort bitsPerSampleB; private readonly ushort bitsPerSampleB;
public RgbPlanarTiffColor(ushort[] bitsPerSample) public RgbPlanarTiffColor(TiffBitsPerSample bitsPerSample)
{ {
this.bitsPerSampleR = bitsPerSample[0]; this.bitsPerSampleR = bitsPerSample.Channel0;
this.bitsPerSampleG = bitsPerSample[1]; this.bitsPerSampleG = bitsPerSample.Channel1;
this.bitsPerSampleB = bitsPerSample[2]; this.bitsPerSampleB = bitsPerSample.Channel2;
this.rFactor = (1 << this.bitsPerSampleR) - 1.0f; this.rFactor = (1 << this.bitsPerSampleR) - 1.0f;
this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; this.gFactor = (1 << this.bitsPerSampleG) - 1.0f;

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

@ -27,11 +27,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
private readonly ushort bitsPerSampleB; private readonly ushort bitsPerSampleB;
public RgbTiffColor(ushort[] bitsPerSample) public RgbTiffColor(TiffBitsPerSample bitsPerSample)
{ {
this.bitsPerSampleR = bitsPerSample[0]; this.bitsPerSampleR = bitsPerSample.Channel0;
this.bitsPerSampleG = bitsPerSample[1]; this.bitsPerSampleG = bitsPerSample.Channel1;
this.bitsPerSampleB = bitsPerSample[2]; this.bitsPerSampleB = bitsPerSample.Channel2;
this.rFactor = (1 << this.bitsPerSampleR) - 1.0f; this.rFactor = (1 << this.bitsPerSampleR) - 1.0f;
this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; this.gFactor = (1 << this.bitsPerSampleG) - 1.0f;

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

@ -8,107 +8,125 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
internal static class TiffColorDecoderFactory<TPixel> internal static class TiffColorDecoderFactory<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
public static TiffBaseColorDecoder<TPixel> Create(TiffColorType colorType, ushort[] bitsPerSample, ushort[] colorMap) public static TiffBaseColorDecoder<TPixel> Create(TiffColorType colorType, TiffBitsPerSample bitsPerSample, ushort[] colorMap)
{ {
switch (colorType) switch (colorType)
{ {
case TiffColorType.WhiteIsZero: case TiffColorType.WhiteIsZero:
DebugGuard.IsTrue(bitsPerSample.Length == 1, "bitsPerSample"); DebugGuard.IsTrue(bitsPerSample.Channels == 1, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new WhiteIsZeroTiffColor<TPixel>(bitsPerSample); return new WhiteIsZeroTiffColor<TPixel>(bitsPerSample);
case TiffColorType.WhiteIsZero1: case TiffColorType.WhiteIsZero1:
DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 1, "bitsPerSample"); DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 1, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new WhiteIsZero1TiffColor<TPixel>(); return new WhiteIsZero1TiffColor<TPixel>();
case TiffColorType.WhiteIsZero4: case TiffColorType.WhiteIsZero4:
DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 4, "bitsPerSample"); DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 4, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new WhiteIsZero4TiffColor<TPixel>(); return new WhiteIsZero4TiffColor<TPixel>();
case TiffColorType.WhiteIsZero8: case TiffColorType.WhiteIsZero8:
DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 8, "bitsPerSample"); DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 8, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new WhiteIsZero8TiffColor<TPixel>(); return new WhiteIsZero8TiffColor<TPixel>();
case TiffColorType.BlackIsZero: case TiffColorType.BlackIsZero:
DebugGuard.IsTrue(bitsPerSample.Length == 1, "bitsPerSample"); DebugGuard.IsTrue(bitsPerSample.Channels == 1, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new BlackIsZeroTiffColor<TPixel>(bitsPerSample); return new BlackIsZeroTiffColor<TPixel>(bitsPerSample);
case TiffColorType.BlackIsZero1: case TiffColorType.BlackIsZero1:
DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 1, "bitsPerSample"); DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 1, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new BlackIsZero1TiffColor<TPixel>(); return new BlackIsZero1TiffColor<TPixel>();
case TiffColorType.BlackIsZero4: case TiffColorType.BlackIsZero4:
DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 4, "bitsPerSample"); DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 4, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new BlackIsZero4TiffColor<TPixel>(); return new BlackIsZero4TiffColor<TPixel>();
case TiffColorType.BlackIsZero8: case TiffColorType.BlackIsZero8:
DebugGuard.IsTrue(bitsPerSample.Length == 1 && bitsPerSample[0] == 8, "bitsPerSample"); DebugGuard.IsTrue(bitsPerSample.Channels == 1 && bitsPerSample.Channel0 == 8, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new BlackIsZero8TiffColor<TPixel>(); return new BlackIsZero8TiffColor<TPixel>();
case TiffColorType.Rgb: case TiffColorType.Rgb:
DebugGuard.NotNull(bitsPerSample, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbTiffColor<TPixel>(bitsPerSample); return new RgbTiffColor<TPixel>(bitsPerSample);
case TiffColorType.Rgb222: case TiffColorType.Rgb222:
DebugGuard.IsTrue( DebugGuard.IsTrue(
bitsPerSample.Length == 3 bitsPerSample.Channels == 3
&& bitsPerSample[2] == 2 && bitsPerSample.Channel2 == 2
&& bitsPerSample[1] == 2 && bitsPerSample.Channel1 == 2
&& bitsPerSample[0] == 2, && bitsPerSample.Channel0 == 2,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbTiffColor<TPixel>(bitsPerSample); return new RgbTiffColor<TPixel>(bitsPerSample);
case TiffColorType.Rgb444: case TiffColorType.Rgb444:
DebugGuard.IsTrue( DebugGuard.IsTrue(
bitsPerSample.Length == 3 bitsPerSample.Channels == 3
&& bitsPerSample[2] == 4 && bitsPerSample.Channel2 == 4
&& bitsPerSample[1] == 4 && bitsPerSample.Channel1 == 4
&& bitsPerSample[0] == 4, && bitsPerSample.Channel0 == 4,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgb444TiffColor<TPixel>(); return new Rgb444TiffColor<TPixel>();
case TiffColorType.Rgb888: case TiffColorType.Rgb888:
DebugGuard.IsTrue( DebugGuard.IsTrue(
bitsPerSample.Length == 3 bitsPerSample.Channels == 3
&& bitsPerSample[2] == 8 && bitsPerSample.Channel2 == 8
&& bitsPerSample[1] == 8 && bitsPerSample.Channel1 == 8
&& bitsPerSample[0] == 8, && bitsPerSample.Channel0 == 8,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgb888TiffColor<TPixel>(); return new Rgb888TiffColor<TPixel>();
case TiffColorType.Rgb101010: case TiffColorType.Rgb101010:
DebugGuard.IsTrue( DebugGuard.IsTrue(
bitsPerSample.Length == 3 bitsPerSample.Channels == 3
&& bitsPerSample[2] == 10 && bitsPerSample.Channel2 == 10
&& bitsPerSample[1] == 10 && bitsPerSample.Channel1 == 10
&& bitsPerSample[0] == 10, && bitsPerSample.Channel0 == 10,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbTiffColor<TPixel>(bitsPerSample);
case TiffColorType.Rgb121212:
DebugGuard.IsTrue(
bitsPerSample.Channels == 3
&& bitsPerSample.Channel2 == 12
&& bitsPerSample.Channel1 == 12
&& bitsPerSample.Channel0 == 12,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbTiffColor<TPixel>(bitsPerSample); return new RgbTiffColor<TPixel>(bitsPerSample);
case TiffColorType.Rgb141414: case TiffColorType.Rgb141414:
DebugGuard.IsTrue( DebugGuard.IsTrue(
bitsPerSample.Length == 3 bitsPerSample.Channels == 3
&& bitsPerSample[2] == 14 && bitsPerSample.Channel2 == 14
&& bitsPerSample[1] == 14 && bitsPerSample.Channel1 == 14
&& bitsPerSample[0] == 14, && bitsPerSample.Channel0 == 14,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbTiffColor<TPixel>(bitsPerSample);
case TiffColorType.Rgb161616:
DebugGuard.IsTrue(
bitsPerSample.Channels == 3
&& bitsPerSample.Channel2 == 16
&& bitsPerSample.Channel1 == 16
&& bitsPerSample.Channel0 == 16,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbTiffColor<TPixel>(bitsPerSample); return new RgbTiffColor<TPixel>(bitsPerSample);
case TiffColorType.PaletteColor: case TiffColorType.PaletteColor:
DebugGuard.NotNull(bitsPerSample, "bitsPerSample");
DebugGuard.NotNull(colorMap, "colorMap"); DebugGuard.NotNull(colorMap, "colorMap");
return new PaletteTiffColor<TPixel>(bitsPerSample, colorMap); return new PaletteTiffColor<TPixel>(bitsPerSample, colorMap);
@ -117,12 +135,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
} }
} }
public static RgbPlanarTiffColor<TPixel> CreatePlanar(TiffColorType colorType, ushort[] bitsPerSample, ushort[] colorMap) public static RgbPlanarTiffColor<TPixel> CreatePlanar(TiffColorType colorType, TiffBitsPerSample bitsPerSample, ushort[] colorMap)
{ {
switch (colorType) switch (colorType)
{ {
case TiffColorType.RgbPlanar: case TiffColorType.RgbPlanar:
DebugGuard.NotNull(bitsPerSample, "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbPlanarTiffColor<TPixel>(bitsPerSample); return new RgbPlanarTiffColor<TPixel>(bitsPerSample);

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

@ -78,11 +78,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
/// </summary> /// </summary>
Rgb101010, Rgb101010,
/// <summary>
/// RGB color image with 12 bits for each channel.
/// </summary>
Rgb121212,
/// <summary> /// <summary>
/// RGB color image with 14 bits for each channel. /// RGB color image with 14 bits for each channel.
/// </summary> /// </summary>
Rgb141414, Rgb141414,
/// <summary>
/// RGB color image with 16 bits for each channel.
/// </summary>
Rgb161616,
/// <summary> /// <summary>
/// RGB Full Color. Planar configuration of data. /// RGB Full Color. Planar configuration of data.
/// </summary> /// </summary>

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

@ -19,9 +19,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
private readonly float factor; private readonly float factor;
public WhiteIsZeroTiffColor(ushort[] bitsPerSample) public WhiteIsZeroTiffColor(TiffBitsPerSample bitsPerSample)
{ {
this.bitsPerSample0 = bitsPerSample[0]; this.bitsPerSample0 = bitsPerSample.Channel0;
this.factor = (float)Math.Pow(2, this.bitsPerSample0) - 1.0f; this.factor = (float)Math.Pow(2, this.bitsPerSample0) - 1.0f;
} }

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

@ -30,6 +30,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary> /// </summary>
Bit8 = 8, Bit8 = 8,
/// <summary>
/// 10 bits per pixel, for gray images.
///
/// Note: The TiffEncoder does not yet support 10 bits per pixel and will default to 24 bits per pixel instead.
/// </summary>
Bit10 = 10,
/// <summary> /// <summary>
/// 12 bits per pixel. 4 bit for each color channel. /// 12 bits per pixel. 4 bit for each color channel.
/// ///
@ -37,6 +44,20 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary> /// </summary>
Bit12 = 12, Bit12 = 12,
/// <summary>
/// 14 bits per pixel, for gray images.
///
/// Note: The TiffEncoder does not yet support 14 bits per pixel images and will default to 24 bits per pixel instead.
/// </summary>
Bit14 = 14,
/// <summary>
/// 16 bits per pixel, for gray images.
///
/// Note: The TiffEncoder does not yet support 16 bits per color channel and will default to 24 bits per pixel instead.
/// </summary>
Bit16 = 16,
/// <summary> /// <summary>
/// 24 bits per pixel. One byte for each color channel. /// 24 bits per pixel. One byte for each color channel.
/// </summary> /// </summary>
@ -49,11 +70,25 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary> /// </summary>
Bit30 = 30, Bit30 = 30,
/// <summary>
/// 36 bits per pixel. 12 bit for each color channel.
///
/// Note: The TiffEncoder does not yet support 12 bits per color channel and will default to 24 bits per pixel instead.
/// </summary>
Bit36 = 36,
/// <summary> /// <summary>
/// 42 bits per pixel. 14 bit for each color channel. /// 42 bits per pixel. 14 bit for each color channel.
/// ///
/// Note: The TiffEncoder does not yet support 14 bits per color channel and will default to 24 bits per pixel instead. /// Note: The TiffEncoder does not yet support 14 bits per color channel and will default to 24 bits per pixel instead.
/// </summary> /// </summary>
Bit42 = 42, Bit42 = 42,
/// <summary>
/// 48 bits per pixel. 16 bit for each color channel.
///
/// Note: The TiffEncoder does not yet support 16 bits per color channel and will default to 24 bits per pixel instead.
/// </summary>
Bit48 = 48,
} }
} }

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

@ -1,56 +1,138 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.Tiff namespace SixLabors.ImageSharp.Formats.Tiff
{ {
/// <summary> /// <summary>
/// The number of bits per component. /// The number of bits per component.
/// </summary> /// </summary>
public enum TiffBitsPerSample public readonly struct TiffBitsPerSample : IEquatable<TiffBitsPerSample>
{ {
/// <summary> /// <summary>
/// The Bits per samples is not known. /// The bits for the channel 0.
/// </summary> /// </summary>
Unknown = 0, public readonly ushort Channel0;
/// <summary> /// <summary>
/// One bit per sample for bicolor images. /// The bits for the channel 1.
/// </summary> /// </summary>
Bit1 = 1, public readonly ushort Channel1;
/// <summary> /// <summary>
/// Four bits per sample for grayscale images with 16 different levels of gray or paletted images with a palette of 16 colors. /// The bits for the channel 2.
/// </summary> /// </summary>
Bit4 = 4, public readonly ushort Channel2;
/// <summary> /// <summary>
/// Eight bits per sample for grayscale images with 256 different levels of gray or paletted images with a palette of 256 colors. /// The number of channels.
/// </summary> /// </summary>
Bit8 = 8, public readonly byte Channels;
/// <summary> /// <summary>
/// Six bits per sample, each channel has 2 bits. /// Initializes a new instance of the <see cref="TiffBitsPerSample"/> struct.
/// </summary> /// </summary>
Bit6 = 6, /// <param name="channel0">The bits for the channel 0.</param>
/// <param name="channel1">The bits for the channel 1.</param>
/// <param name="channel2">The bits for the channel 2.</param>
public TiffBitsPerSample(ushort channel0, ushort channel1, ushort channel2)
{
this.Channel0 = (ushort)Numerics.Clamp(channel0, 0, 32);
this.Channel1 = (ushort)Numerics.Clamp(channel1, 0, 32);
this.Channel2 = (ushort)Numerics.Clamp(channel2, 0, 32);
/// <summary> this.Channels = 0;
/// Twelve bits per sample, each channel has 4 bits. this.Channels += (byte)(this.Channel0 != 0 ? 1 : 0);
/// </summary> this.Channels += (byte)(this.Channel1 != 0 ? 1 : 0);
Bit12 = 12, this.Channels += (byte)(this.Channel2 != 0 ? 1 : 0);
}
/// <summary> /// <summary>
/// 24 bits per sample, each color channel has 8 Bits. /// Tries to parse a ushort array and convert it into a TiffBitsPerSample struct.
/// </summary> /// </summary>
Bit24 = 24, /// <param name="value">The value to parse.</param>
/// <param name="sample">The tiff bits per sample.</param>
/// <returns>True, if the value could be parsed.</returns>
public static bool TryParse(ushort[] value, out TiffBitsPerSample sample)
{
if (value is null || value.Length == 0)
{
sample = default;
return false;
}
ushort c2;
ushort c1;
ushort c0;
switch (value.Length)
{
case 3:
c2 = value[2];
c1 = value[1];
c0 = value[0];
break;
case 2:
c2 = 0;
c1 = value[1];
c0 = value[0];
break;
default:
c2 = 0;
c1 = 0;
c0 = value[0];
break;
}
sample = new TiffBitsPerSample(c0, c1, c2);
return true;
}
/// <inheritdoc/>
public override bool Equals(object obj)
=> obj is TiffBitsPerSample sample && this.Equals(sample);
/// <inheritdoc/>
public bool Equals(TiffBitsPerSample other)
=> this.Channel0 == other.Channel0
&& this.Channel1 == other.Channel1
&& this.Channel2 == other.Channel2;
/// <inheritdoc/>
public override int GetHashCode()
=> HashCode.Combine(this.Channel0, this.Channel1, this.Channel2);
/// <summary> /// <summary>
/// Thirty bits per sample, each channel has 10 bits. /// Converts the bits per sample struct to an ushort array.
/// </summary> /// </summary>
Bit30 = 30, /// <returns>Bits per sample as ushort array.</returns>
public ushort[] ToArray()
{
if (this.Channel1 == 0)
{
return new[] { this.Channel0 };
}
if (this.Channel2 == 0)
{
return new[] { this.Channel0, this.Channel1 };
}
return new[] { this.Channel0, this.Channel1, this.Channel2 };
}
/// <summary> /// <summary>
/// Forty two bits per sample, each channel has 14 bits. /// Gets the bits per pixel for the given bits per sample.
/// </summary> /// </summary>
Bit42 = 42, /// <returns>Bits per pixel.</returns>
public TiffBitsPerPixel BitsPerPixel()
{
int bitsPerPixel = this.Channel0 + this.Channel1 + this.Channel2;
return (TiffBitsPerPixel)bitsPerPixel;
}
/// <inheritdoc/>
public override string ToString()
=> $"TiffBitsPerSample({this.Channel0}, {this.Channel1}, {this.Channel2})";
} }
} }

111
src/ImageSharp/Formats/Tiff/TiffBitsPerSampleExtensions.cs

@ -1,111 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
namespace SixLabors.ImageSharp.Formats.Tiff
{
internal static class TiffBitsPerSampleExtensions
{
/// <summary>
/// Gets the bits per channel array for a given BitsPerSample value, e,g, for RGB888: [8, 8, 8]
/// </summary>
/// <param name="tiffBitsPerSample">The tiff bits per sample.</param>
/// <returns>Bits per sample array.</returns>
public static ushort[] Bits(this TiffBitsPerSample tiffBitsPerSample)
{
switch (tiffBitsPerSample)
{
case TiffBitsPerSample.Bit1:
return TiffConstants.BitsPerSample1Bit;
case TiffBitsPerSample.Bit4:
return TiffConstants.BitsPerSample4Bit;
case TiffBitsPerSample.Bit6:
return TiffConstants.BitsPerSampleRgb2Bit;
case TiffBitsPerSample.Bit8:
return TiffConstants.BitsPerSample8Bit;
case TiffBitsPerSample.Bit12:
return TiffConstants.BitsPerSampleRgb4Bit;
case TiffBitsPerSample.Bit24:
return TiffConstants.BitsPerSampleRgb8Bit;
case TiffBitsPerSample.Bit30:
return TiffConstants.BitsPerSampleRgb10Bit;
case TiffBitsPerSample.Bit42:
return TiffConstants.BitsPerSampleRgb14Bit;
default:
return Array.Empty<ushort>();
}
}
/// <summary>
/// Maps an array of bits per sample to a concrete enum value.
/// </summary>
/// <param name="bitsPerSample">The bits per sample array.</param>
/// <returns>TiffBitsPerSample enum value.</returns>
public static TiffBitsPerSample GetBitsPerSample(this ushort[] bitsPerSample)
{
switch (bitsPerSample.Length)
{
case 3:
if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb14Bit[2] &&
bitsPerSample[1] == TiffConstants.BitsPerSampleRgb14Bit[1] &&
bitsPerSample[0] == TiffConstants.BitsPerSampleRgb14Bit[0])
{
return TiffBitsPerSample.Bit42;
}
if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb10Bit[2] &&
bitsPerSample[1] == TiffConstants.BitsPerSampleRgb10Bit[1] &&
bitsPerSample[0] == TiffConstants.BitsPerSampleRgb10Bit[0])
{
return TiffBitsPerSample.Bit30;
}
if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb8Bit[2] &&
bitsPerSample[1] == TiffConstants.BitsPerSampleRgb8Bit[1] &&
bitsPerSample[0] == TiffConstants.BitsPerSampleRgb8Bit[0])
{
return TiffBitsPerSample.Bit24;
}
if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb4Bit[2] &&
bitsPerSample[1] == TiffConstants.BitsPerSampleRgb4Bit[1] &&
bitsPerSample[0] == TiffConstants.BitsPerSampleRgb4Bit[0])
{
return TiffBitsPerSample.Bit12;
}
if (bitsPerSample[2] == TiffConstants.BitsPerSampleRgb2Bit[2] &&
bitsPerSample[1] == TiffConstants.BitsPerSampleRgb2Bit[1] &&
bitsPerSample[0] == TiffConstants.BitsPerSampleRgb2Bit[0])
{
return TiffBitsPerSample.Bit6;
}
break;
case 1:
if (bitsPerSample[0] == TiffConstants.BitsPerSample1Bit[0])
{
return TiffBitsPerSample.Bit1;
}
if (bitsPerSample[0] == TiffConstants.BitsPerSample4Bit[0])
{
return TiffBitsPerSample.Bit4;
}
if (bitsPerSample[0] == TiffConstants.BitsPerSample8Bit[0])
{
return TiffBitsPerSample.Bit8;
}
break;
}
return TiffBitsPerSample.Unknown;
}
}
}

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

@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
} }
/// <summary> /// <summary>
/// Gets or sets the number of bits per component of the pixel format used to decode the image. /// Gets or sets the bits per sample.
/// </summary> /// </summary>
public TiffBitsPerSample BitsPerSample { get; set; } public TiffBitsPerSample BitsPerSample { get; set; }
@ -198,7 +198,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// <returns>The size (in bytes) of the required pixel buffer.</returns> /// <returns>The size (in bytes) of the required pixel buffer.</returns>
private int CalculateStripBufferSize(int width, int height, int plane = -1) private int CalculateStripBufferSize(int width, int height, int plane = -1)
{ {
int bitsPerPixel; DebugGuard.MustBeLessThanOrEqualTo(plane, 3, nameof(plane));
int bitsPerPixel = 0;
if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky) if (this.PlanarConfiguration == TiffPlanarConfiguration.Chunky)
{ {
@ -207,7 +209,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff
} }
else else
{ {
bitsPerPixel = this.BitsPerSample.Bits()[plane]; switch (plane)
{
case 0:
bitsPerPixel = this.BitsPerSample.Channel0;
break;
case 1:
bitsPerPixel = this.BitsPerSample.Channel1;
break;
case 2:
bitsPerPixel = this.BitsPerSample.Channel2;
break;
default:
TiffThrowHelper.ThrowNotSupported("More then 3 color channels are not supported");
break;
}
} }
int bytesPerRow = ((width * bitsPerPixel) + 7) / 8; int bytesPerRow = ((width * bitsPerPixel) + 7) / 8;
@ -225,7 +241,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
private void DecodeStripsPlanar<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, Number[] stripOffsets, Number[] stripByteCounts) private void DecodeStripsPlanar<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, Number[] stripOffsets, Number[] stripByteCounts)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int stripsPerPixel = this.BitsPerSample.Bits().Length; int stripsPerPixel = this.BitsPerSample.Channels;
int stripsPerPlane = stripOffsets.Length / stripsPerPixel; int stripsPerPlane = stripOffsets.Length / stripsPerPixel;
int bitsPerPixel = this.BitsPerPixel; int bitsPerPixel = this.BitsPerPixel;
@ -243,7 +259,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions); using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions);
RgbPlanarTiffColor<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(this.ColorType, this.BitsPerSample.Bits(), this.ColorMap); RgbPlanarTiffColor<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(this.ColorType, this.BitsPerSample, this.ColorMap);
for (int i = 0; i < stripsPerPlane; i++) for (int i = 0; i < stripsPerPlane; i++)
{ {
@ -286,7 +302,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions); using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(this.CompressionType, this.memoryAllocator, this.PhotometricInterpretation, frame.Width, bitsPerPixel, this.Predictor, this.FaxCompressionOptions);
TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(this.ColorType, this.BitsPerSample.Bits(), this.ColorMap); TiffBaseColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.Create(this.ColorType, this.BitsPerSample, this.ColorMap);
for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++) for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++)
{ {

73
src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs

@ -1,7 +1,6 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Linq; using System.Linq;
using SixLabors.ImageSharp.Formats.Tiff.Compression; using SixLabors.ImageSharp.Formats.Tiff.Compression;
using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.Constants;
@ -69,7 +68,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
options.Predictor = frameMetadata.Predictor ?? TiffPredictor.None; options.Predictor = frameMetadata.Predictor ?? TiffPredictor.None;
options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation ?? TiffPhotometricInterpretation.Rgb; options.PhotometricInterpretation = frameMetadata.PhotometricInterpretation ?? TiffPhotometricInterpretation.Rgb;
options.BitsPerPixel = frameMetadata.BitsPerPixel != null ? (int)frameMetadata.BitsPerPixel.Value : (int)TiffBitsPerPixel.Bit24; options.BitsPerPixel = frameMetadata.BitsPerPixel != null ? (int)frameMetadata.BitsPerPixel.Value : (int)TiffBitsPerPixel.Bit24;
options.BitsPerSample = GetBitsPerSample(frameMetadata.BitsPerPixel); options.BitsPerSample = frameMetadata.BitsPerSample ?? new TiffBitsPerSample(0, 0, 0);
options.ParseColorType(exifProfile); options.ParseColorType(exifProfile);
options.ParseCompression(frameMetadata.Compression, exifProfile); options.ParseCompression(frameMetadata.Compression, exifProfile);
@ -99,26 +98,32 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{ {
case TiffPhotometricInterpretation.WhiteIsZero: case TiffPhotometricInterpretation.WhiteIsZero:
{ {
if (options.BitsPerSample.Bits().Length != 1) if (options.BitsPerSample.Channels != 1)
{ {
TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported.");
} }
switch (options.BitsPerSample) ushort bitsPerChannel = options.BitsPerSample.Channel0;
if (bitsPerChannel > 16)
{ {
case TiffBitsPerSample.Bit8: TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported.");
}
switch (bitsPerChannel)
{
case 8:
{ {
options.ColorType = TiffColorType.WhiteIsZero8; options.ColorType = TiffColorType.WhiteIsZero8;
break; break;
} }
case TiffBitsPerSample.Bit4: case 4:
{ {
options.ColorType = TiffColorType.WhiteIsZero4; options.ColorType = TiffColorType.WhiteIsZero4;
break; break;
} }
case TiffBitsPerSample.Bit1: case 1:
{ {
options.ColorType = TiffColorType.WhiteIsZero1; options.ColorType = TiffColorType.WhiteIsZero1;
break; break;
@ -136,26 +141,32 @@ namespace SixLabors.ImageSharp.Formats.Tiff
case TiffPhotometricInterpretation.BlackIsZero: case TiffPhotometricInterpretation.BlackIsZero:
{ {
if (options.BitsPerSample.Bits().Length != 1) if (options.BitsPerSample.Channels != 1)
{ {
TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported.");
} }
switch (options.BitsPerSample) ushort bitsPerChannel = options.BitsPerSample.Channel0;
if (bitsPerChannel > 16)
{
TiffThrowHelper.ThrowNotSupported("Bits per sample is not supported.");
}
switch (bitsPerChannel)
{ {
case TiffBitsPerSample.Bit8: case 8:
{ {
options.ColorType = TiffColorType.BlackIsZero8; options.ColorType = TiffColorType.BlackIsZero8;
break; break;
} }
case TiffBitsPerSample.Bit4: case 4:
{ {
options.ColorType = TiffColorType.BlackIsZero4; options.ColorType = TiffColorType.BlackIsZero4;
break; break;
} }
case TiffBitsPerSample.Bit1: case 1:
{ {
options.ColorType = TiffColorType.BlackIsZero1; options.ColorType = TiffColorType.BlackIsZero1;
break; break;
@ -173,30 +184,39 @@ namespace SixLabors.ImageSharp.Formats.Tiff
case TiffPhotometricInterpretation.Rgb: case TiffPhotometricInterpretation.Rgb:
{ {
if (options.BitsPerSample.Bits().Length != 3) if (options.BitsPerSample.Channels != 3)
{ {
TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported.");
} }
if (options.PlanarConfiguration == TiffPlanarConfiguration.Chunky) if (options.PlanarConfiguration == TiffPlanarConfiguration.Chunky)
{ {
switch (options.BitsPerSample) ushort bitsPerChannel = options.BitsPerSample.Channel0;
switch (bitsPerChannel)
{ {
case TiffBitsPerSample.Bit42: case 16:
options.ColorType = TiffColorType.Rgb161616;
break;
case 14:
options.ColorType = TiffColorType.Rgb141414; options.ColorType = TiffColorType.Rgb141414;
break; break;
case TiffBitsPerSample.Bit30: case 12:
options.ColorType = TiffColorType.Rgb121212;
break;
case 10:
options.ColorType = TiffColorType.Rgb101010; options.ColorType = TiffColorType.Rgb101010;
break; break;
case TiffBitsPerSample.Bit24: case 8:
options.ColorType = TiffColorType.Rgb888; options.ColorType = TiffColorType.Rgb888;
break; break;
case TiffBitsPerSample.Bit12: case 4:
options.ColorType = TiffColorType.Rgb444; options.ColorType = TiffColorType.Rgb444;
break; break;
case TiffBitsPerSample.Bit6: case 2:
options.ColorType = TiffColorType.Rgb222; options.ColorType = TiffColorType.Rgb222;
break; break;
default: default:
@ -217,7 +237,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
options.ColorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value; options.ColorMap = exifProfile.GetValue(ExifTag.ColorMap)?.Value;
if (options.ColorMap != null) if (options.ColorMap != null)
{ {
if (options.BitsPerSample.Bits().Length != 1) if (options.BitsPerSample.Channels != 1)
{ {
TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported."); TiffThrowHelper.ThrowNotSupported("The number of samples in the TIFF BitsPerSample entry is not supported.");
} }
@ -291,18 +311,5 @@ namespace SixLabors.ImageSharp.Formats.Tiff
} }
} }
} }
private static TiffBitsPerSample GetBitsPerSample(TiffBitsPerPixel? bitsPerPixel) => bitsPerPixel switch
{
TiffBitsPerPixel.Bit1 => TiffBitsPerSample.Bit1,
TiffBitsPerPixel.Bit4 => TiffBitsPerSample.Bit4,
TiffBitsPerPixel.Bit6 => TiffBitsPerSample.Bit6,
TiffBitsPerPixel.Bit8 => TiffBitsPerSample.Bit8,
TiffBitsPerPixel.Bit12 => TiffBitsPerSample.Bit12,
TiffBitsPerPixel.Bit24 => TiffBitsPerSample.Bit24,
TiffBitsPerPixel.Bit30 => TiffBitsPerSample.Bit30,
TiffBitsPerPixel.Bit42 => TiffBitsPerSample.Bit42,
_ => throw new NotSupportedException("The bits per pixel are not supported"),
};
} }
} }

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

@ -320,10 +320,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.SetEncoderOptions(bitsPerPixel, photometricInterpretation ?? TiffPhotometricInterpretation.BlackIsZero, compression, predictor); this.SetEncoderOptions(bitsPerPixel, photometricInterpretation ?? TiffPhotometricInterpretation.BlackIsZero, compression, predictor);
break; break;
case TiffBitsPerPixel.Bit6: case TiffBitsPerPixel.Bit6:
case TiffBitsPerPixel.Bit10:
case TiffBitsPerPixel.Bit12: case TiffBitsPerPixel.Bit12:
case TiffBitsPerPixel.Bit14:
case TiffBitsPerPixel.Bit16:
case TiffBitsPerPixel.Bit30: case TiffBitsPerPixel.Bit30:
case TiffBitsPerPixel.Bit36:
case TiffBitsPerPixel.Bit42: case TiffBitsPerPixel.Bit42:
// Encoding 42, 30, 12 and 6 bits per pixel is not yet supported. Default to 24 bits. case TiffBitsPerPixel.Bit48:
// Encoding not yet supported bits per pixel will default to 24 bits.
this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, TiffPredictor.None); this.SetEncoderOptions(TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, compression, TiffPredictor.None);
break; break;
default: default:

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

@ -318,34 +318,34 @@ namespace SixLabors.ImageSharp.Formats.Tiff
case TiffPhotometricInterpretation.PaletteColor: case TiffPhotometricInterpretation.PaletteColor:
if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit4) if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit4)
{ {
return TiffConstants.BitsPerSample4Bit; return TiffConstants.BitsPerSample4Bit.ToArray();
} }
else else
{ {
return TiffConstants.BitsPerSample8Bit; return TiffConstants.BitsPerSample8Bit.ToArray();
} }
case TiffPhotometricInterpretation.Rgb: case TiffPhotometricInterpretation.Rgb:
return TiffConstants.BitsPerSampleRgb8Bit; return TiffConstants.BitsPerSampleRgb8Bit.ToArray();
case TiffPhotometricInterpretation.WhiteIsZero: case TiffPhotometricInterpretation.WhiteIsZero:
if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit1) if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit1)
{ {
return TiffConstants.BitsPerSample1Bit; return TiffConstants.BitsPerSample1Bit.ToArray();
} }
return TiffConstants.BitsPerSample8Bit; return TiffConstants.BitsPerSample8Bit.ToArray();
case TiffPhotometricInterpretation.BlackIsZero: case TiffPhotometricInterpretation.BlackIsZero:
if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit1) if (encoder.BitsPerPixel == TiffBitsPerPixel.Bit1)
{ {
return TiffConstants.BitsPerSample1Bit; return TiffConstants.BitsPerSample1Bit.ToArray();
} }
return TiffConstants.BitsPerSample8Bit; return TiffConstants.BitsPerSample8Bit.ToArray();
default: default:
return TiffConstants.BitsPerSampleRgb8Bit; return TiffConstants.BitsPerSampleRgb8Bit.ToArray();
} }
} }

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

@ -35,6 +35,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary> /// </summary>
public TiffBitsPerPixel? BitsPerPixel { get; set; } public TiffBitsPerPixel? BitsPerPixel { get; set; }
/// <summary>
/// Gets or sets number of bits per component.
/// </summary>
public TiffBitsPerSample? BitsPerSample { get; set; }
/// <summary> /// <summary>
/// Gets or sets the compression scheme used on the image data. /// Gets or sets the compression scheme used on the image data.
/// </summary> /// </summary>
@ -64,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
} }
/// <summary> /// <summary>
/// Parses the given Exif profile to populate the properties of the tiff frame meta data.. /// Parses the given Exif profile to populate the properties of the tiff frame meta data.
/// </summary> /// </summary>
/// <param name="meta">The tiff frame meta data.</param> /// <param name="meta">The tiff frame meta data.</param>
/// <param name="profile">The Exif profile containing tiff frame directory tags.</param> /// <param name="profile">The Exif profile containing tiff frame directory tags.</param>
@ -72,11 +77,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff
{ {
if (profile != null) if (profile != null)
{ {
ushort[] bitsPerSample = profile.GetValue(ExifTag.BitsPerSample)?.Value; if (TiffBitsPerSample.TryParse(profile.GetValue(ExifTag.BitsPerSample)?.Value, out TiffBitsPerSample bitsPerSample))
meta.BitsPerPixel = BitsPerPixelFromBitsPerSample(bitsPerSample); {
meta.BitsPerSample = bitsPerSample;
}
meta.BitsPerPixel = meta.BitsPerSample?.BitsPerPixel();
meta.Compression = (TiffCompression?)profile.GetValue(ExifTag.Compression)?.Value; meta.Compression = (TiffCompression?)profile.GetValue(ExifTag.Compression)?.Value;
meta.PhotometricInterpretation = meta.PhotometricInterpretation = (TiffPhotometricInterpretation?)profile.GetValue(ExifTag.PhotometricInterpretation)?.Value;
(TiffPhotometricInterpretation?)profile.GetValue(ExifTag.PhotometricInterpretation)?.Value;
meta.Predictor = (TiffPredictor?)profile.GetValue(ExifTag.Predictor)?.Value; meta.Predictor = (TiffPredictor?)profile.GetValue(ExifTag.Predictor)?.Value;
profile.RemoveValue(ExifTag.BitsPerSample); profile.RemoveValue(ExifTag.BitsPerSample);
@ -86,27 +94,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff
} }
} }
/// <summary>
/// Gets the bits per pixel for the given bits per sample.
/// </summary>
/// <param name="bitsPerSample">The tiff bits per sample.</param>
/// <returns>Bits per pixel.</returns>
private static TiffBitsPerPixel? BitsPerPixelFromBitsPerSample(ushort[] bitsPerSample)
{
if (bitsPerSample == null)
{
return null;
}
int bitsPerPixel = 0;
foreach (ushort bits in bitsPerSample)
{
bitsPerPixel += bits;
}
return (TiffBitsPerPixel)bitsPerPixel;
}
/// <inheritdoc/> /// <inheritdoc/>
public IDeepCloneable DeepClone() => new TiffFrameMetadata(this); public IDeepCloneable DeepClone() => new TiffFrameMetadata(this);
} }

4
src/ImageSharp/Formats/Tiff/Writers/TiffBaseColorWriter{TPixel}.cs

@ -79,10 +79,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers
this.Dispose(true); this.Dispose(true);
} }
protected static Span<T> GetStripPixels<T>(Buffer2D<T> buffer2D, int y, int height)
where T : struct
=> buffer2D.GetSingleSpan().Slice(y * buffer2D.Width, height * buffer2D.Width);
protected abstract void EncodeStrip(int y, int height, TiffBaseCompressor compressor); protected abstract void EncodeStrip(int y, int height, TiffBaseCompressor compressor);
/// <summary> /// <summary>

38
src/ImageSharp/Formats/Tiff/Writers/TiffBiColorWriter{TPixel}.cs

@ -36,38 +36,50 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers
/// <inheritdoc/> /// <inheritdoc/>
protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor)
{ {
this.pixelsAsGray ??= this.MemoryAllocator.Allocate<byte>(height * this.Image.Width); int width = this.Image.Width;
Span<byte> pixelAsGraySpan = this.pixelsAsGray.Slice(0, height * this.Image.Width);
Span<TPixel> pixelsBlackWhite = GetStripPixels(this.imageBlackWhite.GetRootFramePixelBuffer(), y, height);
PixelOperations<TPixel>.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhite, pixelAsGraySpan, pixelsBlackWhite.Length);
if (compressor.Method == TiffCompression.CcittGroup3Fax || compressor.Method == TiffCompression.Ccitt1D) if (compressor.Method == TiffCompression.CcittGroup3Fax || compressor.Method == TiffCompression.Ccitt1D)
{ {
// Special case for T4BitCompressor. // Special case for T4BitCompressor.
compressor.CompressStrip(pixelAsGraySpan, height); int stripPixels = width * height;
this.pixelsAsGray ??= this.MemoryAllocator.Allocate<byte>(stripPixels);
Span<byte> pixelAsGraySpan = this.pixelsAsGray.GetSpan();
int lastRow = y + height;
int grayRowIdx = 0;
for (int row = y; row < lastRow; row++)
{
Span<TPixel> pixelsBlackWhiteRow = this.imageBlackWhite.GetPixelRowSpan(row);
Span<byte> pixelAsGrayRow = pixelAsGraySpan.Slice(grayRowIdx * width, width);
PixelOperations<TPixel>.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGrayRow, width);
grayRowIdx++;
}
compressor.CompressStrip(pixelAsGraySpan.Slice(0, stripPixels), height);
} }
else else
{ {
// Write uncompressed image. // Write uncompressed image.
int bytesPerStrip = this.BytesPerRow * height; int bytesPerStrip = this.BytesPerRow * height;
this.bitStrip ??= this.MemoryAllocator.AllocateManagedByteBuffer(bytesPerStrip); this.bitStrip ??= this.MemoryAllocator.AllocateManagedByteBuffer(bytesPerStrip);
this.pixelsAsGray ??= this.MemoryAllocator.Allocate<byte>(width);
Span<byte> pixelAsGraySpan = this.pixelsAsGray.GetSpan();
Span<byte> rows = this.bitStrip.Slice(0, bytesPerStrip); Span<byte> rows = this.bitStrip.Slice(0, bytesPerStrip);
rows.Clear(); rows.Clear();
int grayPixelIndex = 0; int outputRowIdx = 0;
for (int s = 0; s < height; s++) int lastRow = y + height;
for (int row = y; row < lastRow; row++)
{ {
int bitIndex = 0; int bitIndex = 0;
int byteIndex = 0; int byteIndex = 0;
Span<byte> outputRow = rows.Slice(s * this.BytesPerRow); Span<byte> outputRow = rows.Slice(outputRowIdx * this.BytesPerRow);
Span<TPixel> pixelsBlackWhiteRow = this.imageBlackWhite.GetPixelRowSpan(row);
PixelOperations<TPixel>.Instance.ToL8Bytes(this.Configuration, pixelsBlackWhiteRow, pixelAsGraySpan, width);
for (int x = 0; x < this.Image.Width; x++) for (int x = 0; x < this.Image.Width; x++)
{ {
int shift = 7 - bitIndex; int shift = 7 - bitIndex;
if (pixelAsGraySpan[grayPixelIndex++] == 255) if (pixelAsGraySpan[x] == 255)
{ {
outputRow[byteIndex] |= (byte)(1 << shift); outputRow[byteIndex] |= (byte)(1 << shift);
} }
@ -79,6 +91,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers
bitIndex = 0; bitIndex = 0;
} }
} }
outputRowIdx++;
} }
compressor.CompressStrip(rows, height); compressor.CompressStrip(rows, height);

19
src/ImageSharp/Formats/Tiff/Writers/TiffCompositeColorWriter{TPixel}.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using SixLabors.ImageSharp.Formats.Tiff.Compression; using SixLabors.ImageSharp.Formats.Tiff.Compression;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -30,12 +31,22 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers
this.rowBuffer.Clear(); this.rowBuffer.Clear();
Span<byte> rowSpan = this.rowBuffer.GetSpan().Slice(0, this.BytesPerRow * height); Span<byte> outputRowSpan = this.rowBuffer.GetSpan().Slice(0, this.BytesPerRow * height);
Span<TPixel> pixels = GetStripPixels(this.Image.PixelBuffer, y, height); int width = this.Image.Width;
using IMemoryOwner<TPixel> stripPixelBuffer = this.MemoryAllocator.Allocate<TPixel>(height * width);
Span<TPixel> stripPixels = stripPixelBuffer.GetSpan();
int lastRow = y + height;
int stripPixelsRowIdx = 0;
for (int row = y; row < lastRow; row++)
{
Span<TPixel> stripPixelsRow = this.Image.PixelBuffer.GetRowSpan(row);
stripPixelsRow.CopyTo(stripPixels.Slice(stripPixelsRowIdx * width, width));
stripPixelsRowIdx++;
}
this.EncodePixels(pixels, rowSpan); this.EncodePixels(stripPixels, outputRowSpan);
compressor.CompressStrip(rowSpan, height); compressor.CompressStrip(outputRowSpan, height);
} }
protected abstract void EncodePixels(Span<TPixel> pixels, Span<byte> buffer); protected abstract void EncodePixels(Span<TPixel> pixels, Span<byte> buffer);

40
src/ImageSharp/Formats/Tiff/Writers/TiffPaletteWriter{TPixel}.cs

@ -5,7 +5,6 @@ using System;
using System.Buffers; using System.Buffers;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Formats.Tiff.Compression; using SixLabors.ImageSharp.Formats.Tiff.Compression;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata.Profiles.Exif; using SixLabors.ImageSharp.Metadata.Profiles.Exif;
@ -21,6 +20,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers
private readonly int colorPaletteSize; private readonly int colorPaletteSize;
private readonly int colorPaletteBytes; private readonly int colorPaletteBytes;
private readonly IndexedImageFrame<TPixel> quantizedImage; private readonly IndexedImageFrame<TPixel> quantizedImage;
private IMemoryOwner<byte> indexedPixelsBuffer;
public TiffPaletteWriter( public TiffPaletteWriter(
ImageFrame<TPixel> image, ImageFrame<TPixel> image,
@ -55,22 +55,24 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers
/// <inheritdoc /> /// <inheritdoc />
protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor) protected override void EncodeStrip(int y, int height, TiffBaseCompressor compressor)
{ {
Span<byte> indexedPixels = GetStripPixels(((IPixelSource)this.quantizedImage).PixelBuffer, y, height); int width = this.Image.Width;
if (this.BitsPerPixel == 4) if (this.BitsPerPixel == 4)
{ {
int width = this.Image.Width;
int halfWidth = width >> 1; int halfWidth = width >> 1;
int excess = (width & 1) * height; // (width % 2) * height int excess = (width & 1) * height; // (width % 2) * height
int rows4BitBufferLength = (halfWidth * height) + excess; int rows4BitBufferLength = (halfWidth * height) + excess;
using IMemoryOwner<byte> rows4bitBuffer = this.MemoryAllocator.Allocate<byte>(rows4BitBufferLength); this.indexedPixelsBuffer ??= this.MemoryAllocator.Allocate<byte>(rows4BitBufferLength);
Span<byte> rows4bit = rows4bitBuffer.GetSpan(); Span<byte> rows4bit = this.indexedPixelsBuffer.GetSpan();
int idxPixels = 0;
int idx4bitRows = 0; int idx4bitRows = 0;
for (int row = 0; row < height; row++) int lastRow = y + height;
for (int row = y; row < lastRow; row++)
{ {
ReadOnlySpan<byte> indexedPixelRow = this.quantizedImage.GetPixelRowSpan(row);
int idxPixels = 0;
for (int x = 0; x < halfWidth; x++) for (int x = 0; x < halfWidth; x++)
{ {
rows4bit[idx4bitRows] = (byte)((indexedPixels[idxPixels] << 4) | (indexedPixels[idxPixels + 1] & 0xF)); rows4bit[idx4bitRows] = (byte)((indexedPixelRow[idxPixels] << 4) | (indexedPixelRow[idxPixels + 1] & 0xF));
idxPixels += 2; idxPixels += 2;
idx4bitRows++; idx4bitRows++;
} }
@ -78,7 +80,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers
// Make sure rows are byte-aligned. // Make sure rows are byte-aligned.
if (width % 2 != 0) if (width % 2 != 0)
{ {
rows4bit[idx4bitRows++] = (byte)(indexedPixels[idxPixels++] << 4); rows4bit[idx4bitRows++] = (byte)(indexedPixelRow[idxPixels] << 4);
} }
} }
@ -86,12 +88,28 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Writers
} }
else else
{ {
compressor.CompressStrip(indexedPixels, height); int stripPixels = width * height;
this.indexedPixelsBuffer ??= this.MemoryAllocator.AllocateManagedByteBuffer(stripPixels);
Span<byte> indexedPixels = this.indexedPixelsBuffer.GetSpan();
int lastRow = y + height;
int indexedPixelsRowIdx = 0;
for (int row = y; row < lastRow; row++)
{
ReadOnlySpan<byte> indexedPixelRow = this.quantizedImage.GetPixelRowSpan(row);
indexedPixelRow.CopyTo(indexedPixels.Slice(indexedPixelsRowIdx * width, width));
indexedPixelsRowIdx++;
}
compressor.CompressStrip(indexedPixels.Slice(0, stripPixels), height);
} }
} }
/// <inheritdoc /> /// <inheritdoc />
protected override void Dispose(bool disposing) => this.quantizedImage?.Dispose(); protected override void Dispose(bool disposing)
{
this.quantizedImage?.Dispose();
this.indexedPixelsBuffer?.Dispose();
}
private void AddColorMapTag() private void AddColorMapTag()
{ {

2
src/ImageSharp/ImageSharp.csproj

@ -12,7 +12,7 @@
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl> <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
<PackageTags>Image Resize Crop Gif Jpg Jpeg Bitmap Png Tga NetCore</PackageTags> <PackageTags>Image Resize Crop Gif Jpg Jpeg Bitmap Png Tga NetCore</PackageTags>
<Description>A new, fully featured, fully managed, cross-platform, 2D graphics API for .NET</Description> <Description>A new, fully featured, fully managed, cross-platform, 2D graphics API for .NET</Description>
<Configurations>Debug;Release;Release-InnerLoop;Debug-InnerLoop</Configurations> <Configurations>Debug;Release;Debug-InnerLoop;Release-InnerLoop</Configurations>
</PropertyGroup> </PropertyGroup>
<Choose> <Choose>

10
src/ImageSharp/Memory/Allocators/ArrayPoolMemoryAllocator.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
@ -131,10 +131,6 @@ namespace SixLabors.ImageSharp.Memory
Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length)); Guard.MustBeGreaterThanOrEqualTo(length, 0, nameof(length));
int itemSizeBytes = Unsafe.SizeOf<T>(); int itemSizeBytes = Unsafe.SizeOf<T>();
int bufferSizeInBytes = length * itemSizeBytes; int bufferSizeInBytes = length * itemSizeBytes;
if (bufferSizeInBytes < 0 || bufferSizeInBytes > this.BufferCapacityInBytes)
{
ThrowInvalidAllocationException<T>(length);
}
ArrayPool<byte> pool = this.GetArrayPool(bufferSizeInBytes); ArrayPool<byte> pool = this.GetArrayPool(bufferSizeInBytes);
byte[] byteArray = pool.Rent(bufferSizeInBytes); byte[] byteArray = pool.Rent(bufferSizeInBytes);
@ -171,9 +167,9 @@ namespace SixLabors.ImageSharp.Memory
} }
[MethodImpl(InliningOptions.ColdPath)] [MethodImpl(InliningOptions.ColdPath)]
private static void ThrowInvalidAllocationException<T>(int length) => private static void ThrowInvalidAllocationException<T>(int length, int max) =>
throw new InvalidMemoryOperationException( throw new InvalidMemoryOperationException(
$"Requested allocation: {length} elements of {typeof(T).Name} is over the capacity of the MemoryAllocator."); $"Requested allocation: '{length}' elements of '{typeof(T).Name}' is over the capacity in bytes '{max}' of the MemoryAllocator.");
private ArrayPool<byte> GetArrayPool(int bufferSizeInBytes) private ArrayPool<byte> GetArrayPool(int bufferSizeInBytes)
{ {

16
src/ImageSharp/Processing/Extensions/Dithering/DitherExtensions.cs

@ -21,7 +21,7 @@ namespace SixLabors.ImageSharp.Processing
Dither(source, KnownDitherings.Bayer8x8); Dither(source, KnownDitherings.Bayer8x8);
/// <summary> /// <summary>
/// Dithers the image reducing it to a web-safe palette using ordered dithering. /// Dithers the image reducing it to a web-safe palette.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param> /// <param name="dither">The ordered ditherer.</param>
@ -32,7 +32,7 @@ namespace SixLabors.ImageSharp.Processing
source.ApplyProcessor(new PaletteDitherProcessor(dither)); source.ApplyProcessor(new PaletteDitherProcessor(dither));
/// <summary> /// <summary>
/// Dithers the image reducing it to a web-safe palette using ordered dithering. /// Dithers the image reducing it to a web-safe palette.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param> /// <param name="dither">The ordered ditherer.</param>
@ -45,7 +45,7 @@ namespace SixLabors.ImageSharp.Processing
source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale)); source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale));
/// <summary> /// <summary>
/// Dithers the image reducing it to the given palette using ordered dithering. /// Dithers the image reducing it to the given palette.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param> /// <param name="dither">The ordered ditherer.</param>
@ -58,7 +58,7 @@ namespace SixLabors.ImageSharp.Processing
source.ApplyProcessor(new PaletteDitherProcessor(dither, palette)); source.ApplyProcessor(new PaletteDitherProcessor(dither, palette));
/// <summary> /// <summary>
/// Dithers the image reducing it to the given palette using ordered dithering. /// Dithers the image reducing it to the given palette.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param> /// <param name="dither">The ordered ditherer.</param>
@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Processing
Dither(source, KnownDitherings.Bayer8x8, rectangle); Dither(source, KnownDitherings.Bayer8x8, rectangle);
/// <summary> /// <summary>
/// Dithers the image reducing it to a web-safe palette using ordered dithering. /// Dithers the image reducing it to a web-safe palette.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param> /// <param name="dither">The ordered ditherer.</param>
@ -99,7 +99,7 @@ namespace SixLabors.ImageSharp.Processing
source.ApplyProcessor(new PaletteDitherProcessor(dither), rectangle); source.ApplyProcessor(new PaletteDitherProcessor(dither), rectangle);
/// <summary> /// <summary>
/// Dithers the image reducing it to a web-safe palette using ordered dithering. /// Dithers the image reducing it to a web-safe palette.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param> /// <param name="dither">The ordered ditherer.</param>
@ -116,7 +116,7 @@ namespace SixLabors.ImageSharp.Processing
source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale), rectangle); source.ApplyProcessor(new PaletteDitherProcessor(dither, ditherScale), rectangle);
/// <summary> /// <summary>
/// Dithers the image reducing it to the given palette using ordered dithering. /// Dithers the image reducing it to the given palette.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param> /// <param name="dither">The ordered ditherer.</param>
@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp.Processing
source.ApplyProcessor(new PaletteDitherProcessor(dither, palette), rectangle); source.ApplyProcessor(new PaletteDitherProcessor(dither, palette), rectangle);
/// <summary> /// <summary>
/// Dithers the image reducing it to the given palette using ordered dithering. /// Dithers the image reducing it to the given palette.
/// </summary> /// </summary>
/// <param name="source">The image this method extends.</param> /// <param name="source">The image this method extends.</param>
/// <param name="dither">The ordered ditherer.</param> /// <param name="dither">The ordered ditherer.</param>

136
src/ImageSharp/Processing/Processors/Dithering/OrderedDither.cs

@ -3,7 +3,6 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Quantization; using SixLabors.ImageSharp.Processing.Processors.Quantization;
@ -110,17 +109,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
where TFrameQuantizer : struct, IQuantizer<TPixel> where TFrameQuantizer : struct, IQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var ditherOperation = new QuantizeDitherRowOperation<TFrameQuantizer, TPixel>( int spread = CalculatePaletteSpread(destination.Palette.Length);
ref quantizer, float scale = quantizer.Options.DitherScale;
in Unsafe.AsRef(this),
source,
destination,
bounds);
ParallelRowIterator.IterateRows( for (int y = bounds.Top; y < bounds.Bottom; y++)
quantizer.Configuration, {
bounds, ReadOnlySpan<TPixel> sourceRow = source.GetPixelRowSpan(y).Slice(bounds.X, bounds.Width);
in ditherOperation); Span<byte> destRow = destination.GetWritablePixelRowSpanUnsafe(y - bounds.Y).Slice(0, sourceRow.Length);
for (int x = 0; x < sourceRow.Length; x++)
{
TPixel dithered = this.Dither(sourceRow[x], x, y, spread, scale);
destRow[x] = quantizer.GetQuantizedColor(dithered, out TPixel _);
}
}
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -132,16 +134,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor<TPixel> where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var ditherOperation = new PaletteDitherRowOperation<TPaletteDitherImageProcessor, TPixel>( int spread = CalculatePaletteSpread(processor.Palette.Length);
in processor, float scale = processor.DitherScale;
in Unsafe.AsRef(this),
source,
bounds);
ParallelRowIterator.IterateRows( for (int y = bounds.Top; y < bounds.Bottom; y++)
processor.Configuration, {
bounds, Span<TPixel> row = source.GetPixelRowSpan(y).Slice(bounds.X, bounds.Width);
in ditherOperation);
for (int x = 0; x < row.Length; x++)
{
ref TPixel sourcePixel = ref row[x];
TPixel dithered = this.Dither(sourcePixel, x, y, spread, scale);
sourcePixel = processor.GetPaletteColor(dithered);
}
}
} }
// Spread assumes an even colorspace distribution and precision. // Spread assumes an even colorspace distribution and precision.
@ -195,95 +201,5 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public override int GetHashCode() public override int GetHashCode()
=> HashCode.Combine(this.thresholdMatrix, this.modulusX, this.modulusY); => HashCode.Combine(this.thresholdMatrix, this.modulusX, this.modulusY);
private readonly struct QuantizeDitherRowOperation<TFrameQuantizer, TPixel> : IRowOperation
where TFrameQuantizer : struct, IQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly TFrameQuantizer quantizer;
private readonly OrderedDither dither;
private readonly ImageFrame<TPixel> source;
private readonly IndexedImageFrame<TPixel> destination;
private readonly Rectangle bounds;
private readonly int spread;
[MethodImpl(InliningOptions.ShortMethod)]
public QuantizeDitherRowOperation(
ref TFrameQuantizer quantizer,
in OrderedDither dither,
ImageFrame<TPixel> source,
IndexedImageFrame<TPixel> destination,
Rectangle bounds)
{
this.quantizer = quantizer;
this.dither = dither;
this.source = source;
this.destination = destination;
this.bounds = bounds;
this.spread = CalculatePaletteSpread(destination.Palette.Length);
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y)
{
ref TFrameQuantizer quantizer = ref Unsafe.AsRef(this.quantizer);
int spread = this.spread;
float scale = this.quantizer.Options.DitherScale;
ReadOnlySpan<TPixel> sourceRow = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width);
Span<byte> destRow =
this.destination.GetWritablePixelRowSpanUnsafe(y - this.bounds.Y).Slice(0, sourceRow.Length);
for (int x = 0; x < sourceRow.Length; x++)
{
TPixel dithered = this.dither.Dither(sourceRow[x], x, y, spread, scale);
destRow[x] = quantizer.GetQuantizedColor(dithered, out TPixel _);
}
}
}
private readonly struct PaletteDitherRowOperation<TPaletteDitherImageProcessor, TPixel> : IRowOperation
where TPaletteDitherImageProcessor : struct, IPaletteDitherImageProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly TPaletteDitherImageProcessor processor;
private readonly OrderedDither dither;
private readonly ImageFrame<TPixel> source;
private readonly Rectangle bounds;
private readonly float scale;
private readonly int spread;
[MethodImpl(InliningOptions.ShortMethod)]
public PaletteDitherRowOperation(
in TPaletteDitherImageProcessor processor,
in OrderedDither dither,
ImageFrame<TPixel> source,
Rectangle bounds)
{
this.processor = processor;
this.dither = dither;
this.source = source;
this.bounds = bounds;
this.scale = processor.DitherScale;
this.spread = CalculatePaletteSpread(processor.Palette.Length);
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(int y)
{
ref TPaletteDitherImageProcessor processor = ref Unsafe.AsRef(this.processor);
int spread = this.spread;
float scale = this.scale;
Span<TPixel> row = this.source.GetPixelRowSpan(y).Slice(this.bounds.X, this.bounds.Width);
for (int x = 0; x < row.Length; x++)
{
ref TPixel sourcePixel = ref row[x];
TPixel dithered = this.dither.Dither(sourcePixel, x, y, spread, scale);
sourcePixel = processor.GetPaletteColor(dithered);
}
}
}
} }
} }

5
src/ImageSharp/Processing/Processors/Dithering/PaletteDitherProcessor{TPixel}.cs

@ -62,6 +62,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
if (disposing) if (disposing)
{ {
this.paletteOwner.Dispose(); this.paletteOwner.Dispose();
this.ditherProcessor.Dispose();
} }
this.paletteOwner = null; this.paletteOwner = null;
@ -73,7 +74,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
/// <see cref="IPaletteDitherImageProcessor{TPixel}.GetPaletteColor(TPixel)"/>. /// <see cref="IPaletteDitherImageProcessor{TPixel}.GetPaletteColor(TPixel)"/>.
/// </summary> /// </summary>
/// <remarks>Internal for AOT</remarks> /// <remarks>Internal for AOT</remarks>
internal readonly struct DitherProcessor : IPaletteDitherImageProcessor<TPixel> internal readonly struct DitherProcessor : IPaletteDitherImageProcessor<TPixel>, IDisposable
{ {
private readonly EuclideanPixelMap<TPixel> pixelMap; private readonly EuclideanPixelMap<TPixel> pixelMap;
@ -101,6 +102,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Dithering
this.pixelMap.GetClosestColor(color, out TPixel match); this.pixelMap.GetClosestColor(color, out TPixel match);
return match; return match;
} }
public void Dispose() => this.pixelMap.Dispose();
} }
} }
} }

134
src/ImageSharp/Processing/Processors/Quantization/EuclideanPixelMap{TPixel}.cs

@ -2,10 +2,10 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Collections.Concurrent; using System.Buffers;
using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Processing.Processors.Quantization namespace SixLabors.ImageSharp.Processing.Processors.Quantization
@ -14,11 +14,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// Gets the closest color to the supplied color based upon the Euclidean distance. /// Gets the closest color to the supplied color based upon the Euclidean distance.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
internal readonly struct EuclideanPixelMap<TPixel> /// <para>
/// This class is not threadsafe and should not be accessed in parallel.
/// Doing so will result in non-idempotent results.
/// </para>
internal readonly struct EuclideanPixelMap<TPixel> : IDisposable
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private readonly Vector4[] vectorCache; private readonly Rgba32[] rgbaPalette;
private readonly ConcurrentDictionary<TPixel, int> distanceCache; private readonly ColorDistanceCache cache;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="EuclideanPixelMap{TPixel}"/> struct. /// Initializes a new instance of the <see cref="EuclideanPixelMap{TPixel}"/> struct.
@ -29,11 +33,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory<TPixel> palette) public EuclideanPixelMap(Configuration configuration, ReadOnlyMemory<TPixel> palette)
{ {
this.Palette = palette; this.Palette = palette;
this.vectorCache = new Vector4[palette.Length]; this.rgbaPalette = new Rgba32[palette.Length];
this.cache = new ColorDistanceCache(configuration.MemoryAllocator);
// Use the same rules across all target frameworks. PixelOperations<TPixel>.Instance.ToRgba32(configuration, this.Palette.Span, this.rgbaPalette);
this.distanceCache = new ConcurrentDictionary<TPixel, int>(Environment.ProcessorCount, 31);
PixelOperations<TPixel>.Instance.ToVector4(configuration, this.Palette.Span, this.vectorCache);
} }
/// <summary> /// <summary>
@ -57,11 +59,13 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
public int GetClosestColor(TPixel color, out TPixel match) public int GetClosestColor(TPixel color, out TPixel match)
{ {
ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.Palette.Span); ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.Palette.Span);
Unsafe.SkipInit(out Rgba32 rgba);
color.ToRgba32(ref rgba);
// Check if the color is in the lookup table // Check if the color is in the lookup table
if (!this.distanceCache.TryGetValue(color, out int index)) if (!this.cache.TryGetValue(rgba, out short index))
{ {
return this.GetClosestColorSlow(color, ref paletteRef, out match); return this.GetClosestColorSlow(rgba, ref paletteRef, out match);
} }
match = Unsafe.Add(ref paletteRef, index); match = Unsafe.Add(ref paletteRef, index);
@ -69,17 +73,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private int GetClosestColorSlow(TPixel color, ref TPixel paletteRef, out TPixel match) private int GetClosestColorSlow(Rgba32 rgba, ref TPixel paletteRef, out TPixel match)
{ {
// Loop through the palette and find the nearest match. // Loop through the palette and find the nearest match.
int index = 0; int index = 0;
float leastDistance = float.MaxValue; float leastDistance = float.MaxValue;
var vector = color.ToVector4(); for (int i = 0; i < this.rgbaPalette.Length; i++)
ref Vector4 vectorCacheRef = ref MemoryMarshal.GetReference<Vector4>(this.vectorCache);
for (int i = 0; i < this.Palette.Length; i++)
{ {
Vector4 candidate = Unsafe.Add(ref vectorCacheRef, i); Rgba32 candidate = this.rgbaPalette[i];
float distance = Vector4.DistanceSquared(vector, candidate); int distance = DistanceSquared(rgba, candidate);
// If it's an exact match, exit the loop // If it's an exact match, exit the loop
if (distance == 0) if (distance == 0)
@ -97,9 +99,103 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
} }
// Now I have the index, pop it into the cache for next time // Now I have the index, pop it into the cache for next time
this.distanceCache[color] = index; this.cache.Add(rgba, (byte)index);
match = Unsafe.Add(ref paletteRef, index); match = Unsafe.Add(ref paletteRef, index);
return index; return index;
} }
/// <summary>
/// Returns the Euclidean distance squared between two specified points.
/// </summary>
/// <param name="a">The first point.</param>
/// <param name="b">The second point.</param>
/// <returns>The distance squared.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
private static int DistanceSquared(Rgba32 a, Rgba32 b)
{
int deltaR = a.R - b.R;
int deltaG = a.G - b.G;
int deltaB = a.B - b.B;
int deltaA = a.A - b.A;
return (deltaR * deltaR) + (deltaG * deltaG) + (deltaB * deltaB) + (deltaA * deltaA);
}
public void Dispose() => this.cache.Dispose();
/// <summary>
/// A cache for storing color distance matching results.
/// </summary>
/// <remarks>
/// <para>
/// The granularity of the cache has been determined based upon the current
/// suite of test images and provides the lowest possible memory usage while
/// providing enough match accuracy.
/// Entry count is currently limited to 1185921 entries (2371842 bytes ~2.26MB).
/// </para>
/// </remarks>
private unsafe struct ColorDistanceCache : IDisposable
{
private const int IndexBits = 5;
private const int IndexAlphaBits = 5;
private const int IndexCount = (1 << IndexBits) + 1;
private const int IndexAlphaCount = (1 << IndexAlphaBits) + 1;
private const int RgbShift = 8 - IndexBits;
private const int AlphaShift = 8 - IndexAlphaBits;
private const int Entries = IndexCount * IndexCount * IndexCount * IndexAlphaCount;
private MemoryHandle tableHandle;
private readonly IMemoryOwner<short> table;
private readonly short* tablePointer;
public ColorDistanceCache(MemoryAllocator allocator)
{
this.table = allocator.Allocate<short>(Entries);
this.table.GetSpan().Fill(-1);
this.tableHandle = this.table.Memory.Pin();
this.tablePointer = (short*)this.tableHandle.Pointer;
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Add(Rgba32 rgba, byte index)
{
int r = rgba.R >> RgbShift;
int g = rgba.G >> RgbShift;
int b = rgba.B >> RgbShift;
int a = rgba.A >> AlphaShift;
int idx = GetPaletteIndex(r, g, b, a);
this.tablePointer[idx] = index;
}
[MethodImpl(InliningOptions.ShortMethod)]
public bool TryGetValue(Rgba32 rgba, out short match)
{
int r = rgba.R >> RgbShift;
int g = rgba.G >> RgbShift;
int b = rgba.B >> RgbShift;
int a = rgba.A >> AlphaShift;
int idx = GetPaletteIndex(r, g, b, a);
match = this.tablePointer[idx];
return match > -1;
}
[MethodImpl(InliningOptions.ShortMethod)]
private static int GetPaletteIndex(int r, int g, int b, int a)
=> (r << ((IndexBits << 1) + IndexAlphaBits))
+ (r << (IndexBits + IndexAlphaBits + 1))
+ (g << (IndexBits + IndexAlphaBits))
+ (r << (IndexBits << 1))
+ (r << (IndexBits + 1))
+ (g << IndexBits)
+ ((r + g + b) << IndexAlphaBits)
+ r + g + b + a;
public void Dispose()
{
if (this.table != null)
{
this.tableHandle.Dispose();
this.table.Dispose();
}
}
}
} }
} }

98
src/ImageSharp/Processing/Processors/Quantization/OctreeQuantizer{TPixel}.cs

@ -20,10 +20,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private readonly int maxColors; private readonly int maxColors;
private readonly int bitDepth;
private readonly Octree octree; private readonly Octree octree;
private IMemoryOwner<TPixel> paletteOwner; private IMemoryOwner<TPixel> paletteOwner;
private ReadOnlyMemory<TPixel> palette; private ReadOnlyMemory<TPixel> palette;
private EuclideanPixelMap<TPixel> pixelMap; private EuclideanPixelMap<TPixel> pixelMap;
private bool pixelMapHasValue;
private readonly bool isDithering; private readonly bool isDithering;
private bool isDisposed; private bool isDisposed;
@ -42,10 +44,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.Options = options; this.Options = options;
this.maxColors = this.Options.MaxColors; this.maxColors = this.Options.MaxColors;
this.octree = new Octree(Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.maxColors), 1, 8)); this.bitDepth = Numerics.Clamp(ColorNumerics.GetBitsNeededForColorDepth(this.maxColors), 1, 8);
this.octree = new Octree(this.bitDepth);
this.paletteOwner = configuration.MemoryAllocator.Allocate<TPixel>(this.maxColors, AllocationOptions.Clean); this.paletteOwner = configuration.MemoryAllocator.Allocate<TPixel>(this.maxColors, AllocationOptions.Clean);
this.palette = default;
this.pixelMap = default; this.pixelMap = default;
this.pixelMapHasValue = false;
this.palette = default;
this.isDithering = !(this.Options.Dither is null); this.isDithering = !(this.Options.Dither is null);
this.isDisposed = false; this.isDisposed = false;
} }
@ -67,37 +71,56 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
} }
/// <inheritdoc/> /// <inheritdoc/>
[MethodImpl(InliningOptions.ShortMethod)]
public void AddPaletteColors(Buffer2DRegion<TPixel> pixelRegion) public void AddPaletteColors(Buffer2DRegion<TPixel> pixelRegion)
{ {
Rectangle bounds = pixelRegion.Rectangle; Rectangle bounds = pixelRegion.Rectangle;
Buffer2D<TPixel> source = pixelRegion.Buffer; Buffer2D<TPixel> source = pixelRegion.Buffer;
using IMemoryOwner<Rgba32> buffer = this.Configuration.MemoryAllocator.Allocate<Rgba32>(bounds.Width); using (IMemoryOwner<Rgba32> buffer = this.Configuration.MemoryAllocator.Allocate<Rgba32>(bounds.Width))
Span<Rgba32> bufferSpan = buffer.GetSpan();
// Loop through each row
for (int y = bounds.Top; y < bounds.Bottom; y++)
{ {
Span<TPixel> row = source.GetRowSpan(y).Slice(bounds.Left, bounds.Width); Span<Rgba32> bufferSpan = buffer.GetSpan();
PixelOperations<TPixel>.Instance.ToRgba32(this.Configuration, row, bufferSpan);
for (int x = 0; x < bufferSpan.Length; x++) // Loop through each row
for (int y = bounds.Top; y < bounds.Bottom; y++)
{ {
Rgba32 rgba = bufferSpan[x]; Span<TPixel> row = source.GetRowSpan(y).Slice(bounds.Left, bounds.Width);
PixelOperations<TPixel>.Instance.ToRgba32(this.Configuration, row, bufferSpan);
for (int x = 0; x < bufferSpan.Length; x++)
{
Rgba32 rgba = bufferSpan[x];
// Add the color to the Octree // Add the color to the Octree
this.octree.AddColor(rgba); this.octree.AddColor(rgba);
}
} }
} }
Span<TPixel> paletteSpan = this.paletteOwner.GetSpan();
int paletteIndex = 0; int paletteIndex = 0;
this.octree.Palletize(paletteSpan, this.maxColors, ref paletteIndex); Span<TPixel> paletteSpan = this.paletteOwner.GetSpan();
// Length of reduced palette + transparency. // On very rare occasions, (blur.png), the quantizer does not preserve a
ReadOnlyMemory<TPixel> result = this.paletteOwner.Memory.Slice(0, Math.Min(paletteIndex + 2, this.maxColors)); // transparent entry when palletizing the captured colors.
this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, result); // To workaround this we ensure the palette ends with the default color
// for higher bit depths. Lower bit depths will correctly reduce the palette.
// TODO: Investigate more evenly reduced palette reduction.
int max = this.maxColors;
if (this.bitDepth == 8)
{
max--;
}
this.octree.Palletize(paletteSpan, max, ref paletteIndex);
ReadOnlyMemory<TPixel> result = this.paletteOwner.Memory.Slice(0, paletteSpan.Length);
// When called by QuantizerUtilities.BuildPalette this prevents
// mutiple instances of the map being created but not disposed.
if (this.pixelMapHasValue)
{
this.pixelMap.Dispose();
}
this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, result);
this.pixelMapHasValue = true;
this.palette = result; this.palette = result;
} }
@ -118,8 +141,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
return (byte)this.pixelMap.GetClosestColor(color, out match); return (byte)this.pixelMap.GetClosestColor(color, out match);
} }
ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.pixelMap.Palette.Span); ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.palette.Span);
var index = (byte)this.octree.GetPaletteIndex(color); byte index = (byte)this.octree.GetPaletteIndex(color);
match = Unsafe.Add(ref paletteRef, index); match = Unsafe.Add(ref paletteRef, index);
return index; return index;
} }
@ -132,6 +155,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.isDisposed = true; this.isDisposed = true;
this.paletteOwner.Dispose(); this.paletteOwner.Dispose();
this.paletteOwner = null; this.paletteOwner = null;
this.pixelMap.Dispose();
} }
} }
@ -176,21 +200,6 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.previousNode = null; this.previousNode = null;
} }
/// <summary>
/// Gets the mask used when getting the appropriate pixels for a given node.
/// </summary>
private static ReadOnlySpan<byte> Mask => new byte[]
{
0b10000000,
0b1000000,
0b100000,
0b10000,
0b1000,
0b100,
0b10,
0b1
};
/// <summary> /// <summary>
/// Gets or sets the number of leaves in the tree /// Gets or sets the number of leaves in the tree
/// </summary> /// </summary>
@ -251,7 +260,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void Palletize(Span<TPixel> palette, int colorCount, ref int paletteIndex) public void Palletize(Span<TPixel> palette, int colorCount, ref int paletteIndex)
{ {
while (this.Leaves > colorCount - 1) while (this.Leaves > colorCount)
{ {
this.Reduce(); this.Reduce();
} }
@ -269,7 +278,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public int GetPaletteIndex(TPixel color) public int GetPaletteIndex(TPixel color)
{ {
Rgba32 rgba = default; Unsafe.SkipInit(out Rgba32 rgba);
color.ToRgba32(ref rgba); color.ToRgba32(ref rgba);
return this.root.GetPaletteIndex(ref rgba, 0); return this.root.GetPaletteIndex(ref rgba, 0);
} }
@ -468,7 +477,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
Vector3.Zero, Vector3.Zero,
new Vector3(255)); new Vector3(255));
TPixel pixel = default; Unsafe.SkipInit(out TPixel pixel);
pixel.FromRgba32(new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, byte.MaxValue)); pixel.FromRgba32(new Rgba32((byte)vector.X, (byte)vector.Y, (byte)vector.Z, byte.MaxValue));
palette[index] = pixel; palette[index] = pixel;
@ -517,7 +526,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
child = this.children[i]; child = this.children[i];
if (child != null) if (child != null)
{ {
var childIndex = child.GetPaletteIndex(ref pixel, level + 1); int childIndex = child.GetPaletteIndex(ref pixel, level + 1);
if (childIndex != 0) if (childIndex != 0)
{ {
return childIndex; return childIndex;
@ -538,15 +547,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private static int GetColorIndex(ref Rgba32 color, int level) private static int GetColorIndex(ref Rgba32 color, int level)
{ {
DebugGuard.MustBeLessThan(level, Mask.Length, nameof(level));
int shift = 7 - level; int shift = 7 - level;
ref byte maskRef = ref MemoryMarshal.GetReference(Mask); byte mask = (byte)(1 << shift);
byte mask = Unsafe.Add(ref maskRef, level);
return ((color.R & mask) >> shift) return ((color.R & mask) >> shift)
| ((color.G & mask) >> (shift - 1)) | (((color.G & mask) >> shift) << 1)
| ((color.B & mask) >> (shift - 2)); | (((color.B & mask) >> shift) << 2);
} }
/// <summary> /// <summary>

2
src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer.cs

@ -60,7 +60,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
Color.ToPixel(configuration, this.colorPalette.Span, palette.AsSpan()); Color.ToPixel(configuration, this.colorPalette.Span, palette.AsSpan());
var pixelMap = new EuclideanPixelMap<TPixel>(configuration, palette); var pixelMap = new EuclideanPixelMap<TPixel>(configuration, palette);
return new PaletteQuantizer<TPixel>(configuration, options, pixelMap); return new PaletteQuantizer<TPixel>(configuration, options, pixelMap, false);
} }
} }
} }

12
src/ImageSharp/Processing/Processors/Quantization/PaletteQuantizer{TPixel}.cs

@ -17,6 +17,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private readonly EuclideanPixelMap<TPixel> pixelMap; private readonly EuclideanPixelMap<TPixel> pixelMap;
private readonly bool leaveMap;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PaletteQuantizer{TPixel}"/> struct. /// Initializes a new instance of the <see cref="PaletteQuantizer{TPixel}"/> struct.
@ -24,11 +25,15 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param> /// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="options">The quantizer options defining quantization rules.</param> /// <param name="options">The quantizer options defining quantization rules.</param>
/// <param name="pixelMap">The pixel map for looking up color matches from a predefined palette.</param> /// <param name="pixelMap">The pixel map for looking up color matches from a predefined palette.</param>
/// <param name="leaveMap">
/// <see langword="true"/> to leave the pixel map undisposed after disposing the <see cref="PaletteQuantizer{TPixel}"/> object; otherwise, <see langword="false"/>.
/// </param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public PaletteQuantizer( public PaletteQuantizer(
Configuration configuration, Configuration configuration,
QuantizerOptions options, QuantizerOptions options,
EuclideanPixelMap<TPixel> pixelMap) EuclideanPixelMap<TPixel> pixelMap,
bool leaveMap)
{ {
Guard.NotNull(configuration, nameof(configuration)); Guard.NotNull(configuration, nameof(configuration));
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
@ -36,6 +41,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.Configuration = configuration; this.Configuration = configuration;
this.Options = options; this.Options = options;
this.pixelMap = pixelMap; this.pixelMap = pixelMap;
this.leaveMap = leaveMap;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -66,6 +72,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
{ {
if (!this.leaveMap)
{
this.pixelMap.Dispose();
}
} }
} }
} }

44
src/ImageSharp/Processing/Processors/Quantization/QuantizeProcessor{TPixel}.cs

@ -41,46 +41,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(configuration); using IQuantizer<TPixel> frameQuantizer = this.quantizer.CreatePixelSpecificQuantizer<TPixel>(configuration);
using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(source, interest); using IndexedImageFrame<TPixel> quantized = frameQuantizer.BuildPaletteAndQuantizeFrame(source, interest);
var operation = new RowIntervalOperation(this.SourceRectangle, source, quantized); ReadOnlySpan<TPixel> paletteSpan = quantized.Palette.Span;
ParallelRowIterator.IterateRowIntervals( int offsetY = interest.Top;
configuration, int offsetX = interest.Left;
interest,
in operation);
}
private readonly struct RowIntervalOperation : IRowIntervalOperation
{
private readonly Rectangle bounds;
private readonly ImageFrame<TPixel> source;
private readonly IndexedImageFrame<TPixel> quantized;
[MethodImpl(InliningOptions.ShortMethod)] for (int y = interest.Y; y < interest.Height; y++)
public RowIntervalOperation(
Rectangle bounds,
ImageFrame<TPixel> source,
IndexedImageFrame<TPixel> quantized)
{ {
this.bounds = bounds; Span<TPixel> row = source.GetPixelRowSpan(y);
this.source = source; ReadOnlySpan<byte> quantizedRow = quantized.GetPixelRowSpan(y - offsetY);
this.quantized = quantized;
}
[MethodImpl(InliningOptions.ShortMethod)] for (int x = interest.Left; x < interest.Right; x++)
public void Invoke(in RowInterval rows)
{
ReadOnlySpan<TPixel> paletteSpan = this.quantized.Palette.Span;
int offsetY = this.bounds.Top;
int offsetX = this.bounds.Left;
for (int y = rows.Min; y < rows.Max; y++)
{ {
Span<TPixel> row = this.source.GetPixelRowSpan(y); row[x] = paletteSpan[quantizedRow[x - offsetX]];
ReadOnlySpan<byte> quantizedRow = this.quantized.GetPixelRowSpan(y - offsetY);
for (int x = this.bounds.Left; x < this.bounds.Right; x++)
{
row[x] = paletteSpan[quantizedRow[x - offsetX]];
}
} }
} }
} }

61
src/ImageSharp/Processing/Processors/Quantization/QuantizerUtilities.cs

@ -3,7 +3,6 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Processing.Processors.Dithering;
@ -126,62 +125,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
if (dither is null) if (dither is null)
{ {
var operation = new RowIntervalOperation<TFrameQuantizer, TPixel>( int offsetY = bounds.Top;
ref quantizer, int offsetX = bounds.Left;
source,
destination,
bounds);
ParallelRowIterator.IterateRowIntervals( for (int y = bounds.Y; y < bounds.Height; y++)
quantizer.Configuration,
bounds,
in operation);
return;
}
dither.ApplyQuantizationDither(ref quantizer, source, destination, bounds);
}
private readonly struct RowIntervalOperation<TFrameQuantizer, TPixel> : IRowIntervalOperation
where TFrameQuantizer : struct, IQuantizer<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly TFrameQuantizer quantizer;
private readonly ImageFrame<TPixel> source;
private readonly IndexedImageFrame<TPixel> destination;
private readonly Rectangle bounds;
[MethodImpl(InliningOptions.ShortMethod)]
public RowIntervalOperation(
ref TFrameQuantizer quantizer,
ImageFrame<TPixel> source,
IndexedImageFrame<TPixel> destination,
Rectangle bounds)
{
this.quantizer = quantizer;
this.source = source;
this.destination = destination;
this.bounds = bounds;
}
[MethodImpl(InliningOptions.ShortMethod)]
public void Invoke(in RowInterval rows)
{
int offsetY = this.bounds.Top;
int offsetX = this.bounds.Left;
for (int y = rows.Min; y < rows.Max; y++)
{ {
Span<TPixel> sourceRow = this.source.GetPixelRowSpan(y); Span<TPixel> sourceRow = source.GetPixelRowSpan(y);
Span<byte> destinationRow = this.destination.GetWritablePixelRowSpanUnsafe(y - offsetY); Span<byte> destinationRow = destination.GetWritablePixelRowSpanUnsafe(y - offsetY);
for (int x = this.bounds.Left; x < this.bounds.Right; x++) for (int x = bounds.Left; x < bounds.Right; x++)
{ {
destinationRow[x - offsetX] = Unsafe.AsRef(this.quantizer).GetQuantizedColor(sourceRow[x], out TPixel _); destinationRow[x - offsetX] = Unsafe.AsRef(quantizer).GetQuantizedColor(sourceRow[x], out TPixel _);
} }
} }
return;
} }
dither.ApplyQuantizationDither(ref quantizer, source, destination, bounds);
} }
} }
} }

29
src/ImageSharp/Processing/Processors/Quantization/WuQuantizer{TPixel}.cs

@ -6,7 +6,6 @@ using System.Buffers;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -73,6 +72,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
private int maxColors; private int maxColors;
private readonly Box[] colorCube; private readonly Box[] colorCube;
private EuclideanPixelMap<TPixel> pixelMap; private EuclideanPixelMap<TPixel> pixelMap;
private bool pixelMapHasValue;
private readonly bool isDithering; private readonly bool isDithering;
private bool isDisposed; private bool isDisposed;
@ -94,10 +94,11 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.momentsOwner = this.memoryAllocator.Allocate<Moment>(TableLength, AllocationOptions.Clean); this.momentsOwner = this.memoryAllocator.Allocate<Moment>(TableLength, AllocationOptions.Clean);
this.tagsOwner = this.memoryAllocator.Allocate<byte>(TableLength, AllocationOptions.Clean); this.tagsOwner = this.memoryAllocator.Allocate<byte>(TableLength, AllocationOptions.Clean);
this.paletteOwner = this.memoryAllocator.Allocate<TPixel>(this.maxColors, AllocationOptions.Clean); this.paletteOwner = this.memoryAllocator.Allocate<TPixel>(this.maxColors, AllocationOptions.Clean);
this.palette = default;
this.colorCube = new Box[this.maxColors]; this.colorCube = new Box[this.maxColors];
this.isDisposed = false; this.isDisposed = false;
this.pixelMap = default; this.pixelMap = default;
this.pixelMapHasValue = false;
this.palette = default;
this.isDithering = this.isDithering = !(this.Options.Dither is null); this.isDithering = this.isDithering = !(this.Options.Dither is null);
} }
@ -127,9 +128,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.Get3DMoments(this.memoryAllocator); this.Get3DMoments(this.memoryAllocator);
this.BuildCube(); this.BuildCube();
// Slice again since maxColors has been updated since the buffer was created.
Span<TPixel> paletteSpan = this.paletteOwner.GetSpan().Slice(0, this.maxColors);
ReadOnlySpan<Moment> momentsSpan = this.momentsOwner.GetSpan(); ReadOnlySpan<Moment> momentsSpan = this.momentsOwner.GetSpan();
Span<TPixel> paletteSpan = this.paletteOwner.GetSpan(); for (int k = 0; k < paletteSpan.Length; k++)
for (int k = 0; k < this.maxColors; k++)
{ {
this.Mark(ref this.colorCube[k], (byte)k); this.Mark(ref this.colorCube[k], (byte)k);
@ -142,8 +144,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
} }
} }
ReadOnlyMemory<TPixel> result = this.paletteOwner.Memory.Slice(0, this.maxColors); ReadOnlyMemory<TPixel> result = this.paletteOwner.Memory.Slice(0, paletteSpan.Length);
this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, result); if (this.isDithering)
{
// When called by QuantizerUtilities.BuildPalette this prevents
// mutiple instances of the map being created but not disposed.
if (this.pixelMapHasValue)
{
this.pixelMap.Dispose();
}
this.pixelMap = new EuclideanPixelMap<TPixel>(this.Configuration, result);
this.pixelMapHasValue = true;
}
this.palette = result; this.palette = result;
} }
@ -170,7 +184,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
ReadOnlySpan<byte> tagSpan = this.tagsOwner.GetSpan(); ReadOnlySpan<byte> tagSpan = this.tagsOwner.GetSpan();
byte index = tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)]; byte index = tagSpan[GetPaletteIndex(r + 1, g + 1, b + 1, a + 1)];
ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.pixelMap.Palette.Span); ref TPixel paletteRef = ref MemoryMarshal.GetReference(this.palette.Span);
match = Unsafe.Add(ref paletteRef, index); match = Unsafe.Add(ref paletteRef, index);
return index; return index;
} }
@ -187,6 +201,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Quantization
this.momentsOwner = null; this.momentsOwner = null;
this.tagsOwner = null; this.tagsOwner = null;
this.paletteOwner = null; this.paletteOwner = null;
this.pixelMap.Dispose();
} }
} }

4
src/ImageSharp/Processing/Processors/Transforms/Resize/ResizeKernelMap.cs

@ -130,9 +130,9 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
int radius = (int)TolerantMath.Ceiling(scale * sampler.Radius); int radius = (int)TolerantMath.Ceiling(scale * sampler.Radius);
// 'ratio' is a rational number. // 'ratio' is a rational number.
// Multiplying it by LCM(sourceSize, destSize)/sourceSize will result in a whole number "again". // Multiplying it by destSize/GCD(sourceSize, destSize) will result in a whole number "again".
// This value is determining the length of the periods in repeating kernel map rows. // This value is determining the length of the periods in repeating kernel map rows.
int period = Numerics.LeastCommonMultiple(sourceSize, destinationSize) / sourceSize; int period = destinationSize / Numerics.GreatestCommonDivisor(sourceSize, destinationSize);
// the center position at i == 0: // the center position at i == 0:
double center0 = (ratio - 1) * 0.5; double center0 = (ratio - 1) * 0.5;

4
tests/Directory.Build.targets

@ -22,8 +22,8 @@
<PackageReference Update="BenchmarkDotNet.Diagnostics.Windows" Version="0.12.1" Condition="'$(IsWindows)'=='true'" /> <PackageReference Update="BenchmarkDotNet.Diagnostics.Windows" Version="0.12.1" Condition="'$(IsWindows)'=='true'" />
<PackageReference Update="Colourful" Version="2.0.5" /> <PackageReference Update="Colourful" Version="2.0.5" />
<PackageReference Update="Magick.NET-Q16-AnyCPU" Version="7.23.2.1" /> <PackageReference Update="Magick.NET-Q16-AnyCPU" Version="7.23.2.1" />
<PackageReference Update="Microsoft.DotNet.RemoteExecutor" Version="6.0.0-beta.20513.1" /> <PackageReference Update="Microsoft.DotNet.RemoteExecutor" Version="6.0.0-beta.21311.3" />
<PackageReference Update="Microsoft.DotNet.XUnitExtensions" Version="6.0.0-beta.20513.1" /> <PackageReference Update="Microsoft.DotNet.XUnitExtensions" Version="6.0.0-beta.21311.3" />
<PackageReference Update="Moq" Version="4.14.6" /> <PackageReference Update="Moq" Version="4.14.6" />
<PackageReference Update="Pfim" Version="0.9.1" /> <PackageReference Update="Pfim" Version="0.9.1" />
<PackageReference Include="runtime.osx.10.10-x64.CoreCompat.System.Drawing" Version="5.8.64" Condition="'$(IsOSX)'=='true'" /> <PackageReference Include="runtime.osx.10.10-x64.CoreCompat.System.Drawing" Version="5.8.64" Condition="'$(IsOSX)'=='true'" />

38
tests/ImageSharp.Benchmarks/Codecs/Jpeg/BlockOperations/Block8x8F_Scale16X16To8X8.cs

@ -1,38 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg.Components;
namespace SixLabors.ImageSharp.Benchmarks.Format.Jpeg.Components
{
[Config(typeof(Config.HwIntrinsics_SSE_AVX))]
public class Block8x8F_Scale16X16To8X8
{
private Block8x8F source;
private readonly Block8x8F[] target = new Block8x8F[4];
[GlobalSetup]
public void Setup()
{
var random = new Random();
float[] f = new float[8 * 8];
for (int i = 0; i < f.Length; i++)
{
f[i] = (float)random.NextDouble();
}
for (int i = 0; i < 4; i++)
{
this.target[i] = Block8x8F.Load(f);
}
this.source = Block8x8F.Load(f);
}
[Benchmark]
public void Scale16X16To8X8() => Block8x8F.Scale16X16To8X8(ref this.source, this.target);
}
}

95
tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs

@ -4,6 +4,7 @@
using System.Drawing.Imaging; using System.Drawing.Imaging;
using System.IO; using System.IO;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Tests; using SixLabors.ImageSharp.Tests;
using SDImage = System.Drawing.Image; using SDImage = System.Drawing.Image;
@ -12,10 +13,22 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
public class EncodeJpeg public class EncodeJpeg
{ {
// System.Drawing needs this. [Params(75, 90, 100)]
private Stream bmpStream; public int Quality;
private const string TestImage = TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr;
// System.Drawing
private SDImage bmpDrawing; private SDImage bmpDrawing;
private Stream bmpStream;
private ImageCodecInfo jpegCodec;
private EncoderParameters encoderParameters;
// ImageSharp
private Image<Rgba32> bmpCore; private Image<Rgba32> bmpCore;
private JpegEncoder encoder420;
private JpegEncoder encoder444;
private MemoryStream destinationStream; private MemoryStream destinationStream;
[GlobalSetup] [GlobalSetup]
@ -23,12 +36,23 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
if (this.bmpStream == null) if (this.bmpStream == null)
{ {
const string TestImage = TestImages.Jpeg.BenchmarkSuite.Jpeg420Exif_MidSizeYCbCr;
this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage)); this.bmpStream = File.OpenRead(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImage));
this.bmpCore = Image.Load<Rgba32>(this.bmpStream); this.bmpCore = Image.Load<Rgba32>(this.bmpStream);
this.bmpCore.Metadata.ExifProfile = null; this.bmpCore.Metadata.ExifProfile = null;
this.encoder420 = new JpegEncoder { Quality = this.Quality, Subsample = JpegSubsample.Ratio420 };
this.encoder444 = new JpegEncoder { Quality = this.Quality, Subsample = JpegSubsample.Ratio444 };
this.bmpStream.Position = 0; this.bmpStream.Position = 0;
this.bmpDrawing = SDImage.FromStream(this.bmpStream); this.bmpDrawing = SDImage.FromStream(this.bmpStream);
this.jpegCodec = GetEncoder(ImageFormat.Jpeg);
this.encoderParameters = new EncoderParameters(1);
// Quality cast to long is necessary
#pragma warning disable IDE0004 // Remove Unnecessary Cast
this.encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, (long)this.Quality);
#pragma warning restore IDE0004 // Remove Unnecessary Cast
this.destinationStream = new MemoryStream(); this.destinationStream = new MemoryStream();
} }
} }
@ -38,36 +62,73 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
{ {
this.bmpStream.Dispose(); this.bmpStream.Dispose();
this.bmpStream = null; this.bmpStream = null;
this.destinationStream.Dispose();
this.destinationStream = null;
this.bmpCore.Dispose(); this.bmpCore.Dispose();
this.bmpDrawing.Dispose(); this.bmpDrawing.Dispose();
this.encoderParameters.Dispose();
} }
[Benchmark(Baseline = true, Description = "System.Drawing Jpeg")] [Benchmark(Baseline = true, Description = "System.Drawing Jpeg 4:2:0")]
public void JpegSystemDrawing() public void JpegSystemDrawing()
{ {
this.bmpDrawing.Save(this.destinationStream, ImageFormat.Jpeg); this.bmpDrawing.Save(this.destinationStream, this.jpegCodec, this.encoderParameters);
this.destinationStream.Seek(0, SeekOrigin.Begin);
}
[Benchmark(Description = "ImageSharp Jpeg 4:2:0")]
public void JpegCore420()
{
this.bmpCore.SaveAsJpeg(this.destinationStream, this.encoder420);
this.destinationStream.Seek(0, SeekOrigin.Begin); this.destinationStream.Seek(0, SeekOrigin.Begin);
} }
[Benchmark(Description = "ImageSharp Jpeg")] [Benchmark(Description = "ImageSharp Jpeg 4:4:4")]
public void JpegCore() public void JpegCore444()
{ {
this.bmpCore.SaveAsJpeg(this.destinationStream); this.bmpCore.SaveAsJpeg(this.destinationStream, this.encoder444);
this.destinationStream.Seek(0, SeekOrigin.Begin); this.destinationStream.Seek(0, SeekOrigin.Begin);
} }
// https://docs.microsoft.com/en-us/dotnet/api/system.drawing.imaging.encoderparameter?redirectedfrom=MSDN&view=net-5.0
private static ImageCodecInfo GetEncoder(ImageFormat format)
{
ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
foreach (ImageCodecInfo codec in codecs)
{
if (codec.FormatID == format.Guid)
{
return codec;
}
}
return null;
}
} }
} }
/* /*
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.959 (1909/November2018Update/19H2) BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
Intel Core i7-8650U CPU 1.90GHz (Kaby Lake R), 1 CPU, 8 logical and 4 physical cores Intel Core i7-6700K CPU 4.00GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.302 .NET Core SDK=6.0.100-preview.3.21202.5
[Host] : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT [Host] : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT [AttachedDebugger]
DefaultJob : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT DefaultJob : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT
| Method | Mean | Error | StdDev | Ratio | RatioSD | | Method | Quality | Mean | Error | StdDev | Ratio | RatioSD |
|---------------------- |---------:|----------:|----------:|------:|--------:| |---------------------------- |-------- |---------:|---------:|---------:|------:|--------:|
| 'System.Drawing Jpeg' | 4.297 ms | 0.0244 ms | 0.0228 ms | 1.00 | 0.00 | | 'System.Drawing Jpeg 4:2:0' | 75 | 30.60 ms | 0.496 ms | 0.464 ms | 1.00 | 0.00 |
| 'ImageSharp Jpeg' | 5.286 ms | 0.1034 ms | 0.0967 ms | 1.23 | 0.02 | | 'ImageSharp Jpeg 4:2:0' | 75 | 29.86 ms | 0.350 ms | 0.311 ms | 0.98 | 0.02 |
| 'ImageSharp Jpeg 4:4:4' | 75 | 45.36 ms | 0.899 ms | 1.036 ms | 1.48 | 0.05 |
| | | | | | | |
| 'System.Drawing Jpeg 4:2:0' | 90 | 34.05 ms | 0.669 ms | 0.687 ms | 1.00 | 0.00 |
| 'ImageSharp Jpeg 4:2:0' | 90 | 37.26 ms | 0.706 ms | 0.660 ms | 1.10 | 0.03 |
| 'ImageSharp Jpeg 4:4:4' | 90 | 52.54 ms | 0.579 ms | 0.514 ms | 1.55 | 0.04 |
| | | | | | | |
| 'System.Drawing Jpeg 4:2:0' | 100 | 39.36 ms | 0.267 ms | 0.237 ms | 1.00 | 0.00 |
| 'ImageSharp Jpeg 4:2:0' | 100 | 42.44 ms | 0.410 ms | 0.383 ms | 1.08 | 0.01 |
| 'ImageSharp Jpeg 4:4:4' | 100 | 70.88 ms | 0.508 ms | 0.450 ms | 1.80 | 0.02 |
*/ */

4
tests/ImageSharp.Benchmarks/Format/Jpeg/Components/Encoder/YCbCrForwardConverterBenchmark.cs

@ -37,7 +37,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Format.Jpeg.Components.Encoder
Block8x8F cb = default; Block8x8F cb = default;
Block8x8F cr = default; Block8x8F cr = default;
this.converter.Convert(this.data.AsSpan(), ref y, ref cb, ref cr); this.converter.Convert444(this.data.AsSpan(), ref y, ref cb, ref cr);
} }
[Benchmark] [Benchmark]
@ -49,7 +49,7 @@ namespace SixLabors.ImageSharp.Benchmarks.Format.Jpeg.Components.Encoder
if (RgbToYCbCrConverterVectorized.IsSupported) if (RgbToYCbCrConverterVectorized.IsSupported)
{ {
RgbToYCbCrConverterVectorized.Convert(this.data.AsSpan(), ref y, ref cb, ref cr); RgbToYCbCrConverterVectorized.Convert444(this.data.AsSpan(), ref y, ref cb, ref cr);
} }
} }
} }

2
tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj

@ -8,7 +8,7 @@
<GenerateProgramFile>false</GenerateProgramFile> <GenerateProgramFile>false</GenerateProgramFile>
<!--Used to hide test project from dotnet test--> <!--Used to hide test project from dotnet test-->
<IsTestProject>false</IsTestProject> <IsTestProject>false</IsTestProject>
<Configurations>Debug;Release;Release-InnerLoop;Debug-InnerLoop</Configurations> <Configurations>Debug;Release;Debug-InnerLoop;Release-InnerLoop</Configurations>
<!-- Uncomment this to run benchmarks depending on Colorful or Pfim (colorspaces and TGA): --> <!-- Uncomment this to run benchmarks depending on Colorful or Pfim (colorspaces and TGA): -->
<!--<SignAssembly>false</SignAssembly>--> <!--<SignAssembly>false</SignAssembly>-->

2
tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj

@ -12,7 +12,7 @@
<!--Used to hide test project from dotnet test--> <!--Used to hide test project from dotnet test-->
<IsTestProject>false</IsTestProject> <IsTestProject>false</IsTestProject>
<EnsureNETCoreAppRuntime>false</EnsureNETCoreAppRuntime> <EnsureNETCoreAppRuntime>false</EnsureNETCoreAppRuntime>
<Configurations>Debug;Release;Release-InnerLoop;Debug-InnerLoop</Configurations> <Configurations>Debug;Release;Debug-InnerLoop;Release-InnerLoop</Configurations>
<ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained> <ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
</PropertyGroup> </PropertyGroup>

73
tests/ImageSharp.Tests/Common/NumericsTests.cs

@ -0,0 +1,73 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using Xunit;
using Xunit.Abstractions;
namespace SixLabors.ImageSharp.Tests.Common
{
public class NumericsTests
{
private ITestOutputHelper Output { get; }
public NumericsTests(ITestOutputHelper output)
{
this.Output = output;
}
private static int Log2_ReferenceImplementation(uint value)
{
int n = 0;
while ((value >>= 1) != 0)
{
++n;
}
return n;
}
[Fact]
public void Log2_ZeroConvention()
{
uint value = 0;
int expected = 0;
int actual = Numerics.Log2(value);
Assert.True(expected == actual, $"Expected: {expected}, Actual: {actual}");
}
[Fact]
public void Log2_PowersOfTwo()
{
for (int i = 0; i < sizeof(int) * 8; i++)
{
// from 2^0 to 2^32
uint value = (uint)(1 << i);
int expected = i;
int actual = Numerics.Log2(value);
Assert.True(expected == actual, $"Expected: {expected}, Actual: {actual}");
}
}
[Theory]
[InlineData(1, 100)]
[InlineData(2, 100)]
public void Log2_RandomValues(int seed, int count)
{
var rng = new Random(seed);
byte[] bytes = new byte[4];
for (int i = 0; i < count; i++)
{
rng.NextBytes(bytes);
uint value = BitConverter.ToUInt32(bytes, 0);
int expected = Log2_ReferenceImplementation(value);
int actual = Numerics.Log2(value);
Assert.True(expected == actual, $"Expected: {expected}, Actual: {actual}");
}
}
}
}

2
tests/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs

@ -343,7 +343,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Bmp
{ {
BitsPerPixel = bitsPerPixel, BitsPerPixel = bitsPerPixel,
SupportTransparency = supportTransparency, SupportTransparency = supportTransparency,
Quantizer = quantizer ?? KnownQuantizers.Wu Quantizer = quantizer ?? KnownQuantizers.Octree
}; };
// Does DebugSave & load reference CompareToReferenceInput(): // Does DebugSave & load reference CompareToReferenceInput():

264
tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs

@ -2,10 +2,12 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics.X86;
#endif
using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils; using SixLabors.ImageSharp.Tests.Formats.Jpg.Utils;
using SixLabors.ImageSharp.Tests.TestUtilities;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
@ -22,94 +24,180 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{ {
} }
[Fact] // Reference tests
public void IDCT2D8x4_LeftPart() [Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public void LLM_TransformIDCT_CompareToNonOptimized(int seed)
{ {
float[] sourceArray = Create8x8FloatData(); float[] sourceArray = Create8x8RoundedRandomFloatData(-1000, 1000, seed);
var expectedDestArray = new float[64];
var source = Block8x8F.Load(sourceArray);
ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D8x4_32f(sourceArray, expectedDestArray); Block8x8F expected = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformIDCT(ref source);
var temp = default(Block8x8F);
var actual = default(Block8x8F);
FastFloatingPointDCT.TransformIDCT(ref source, ref actual, ref temp);
var source = default(Block8x8F); this.CompareBlocks(expected, actual, 1f);
source.LoadFrom(sourceArray); }
var dest = default(Block8x8F); [Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public void LLM_TransformIDCT_CompareToAccurate(int seed)
{
float[] sourceArray = Create8x8RoundedRandomFloatData(-1000, 1000, seed);
FastFloatingPointDCT.IDCT8x4_LeftPart(ref source, ref dest); var source = Block8x8F.Load(sourceArray);
var actualDestArray = new float[64]; Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref source);
dest.ScaledCopyTo(actualDestArray);
this.Print8x8Data(expectedDestArray); var temp = default(Block8x8F);
this.Output.WriteLine("**************"); var actual = default(Block8x8F);
this.Print8x8Data(actualDestArray); FastFloatingPointDCT.TransformIDCT(ref source, ref actual, ref temp);
Assert.Equal(expectedDestArray, actualDestArray); this.CompareBlocks(expected, actual, 1f);
} }
[Fact] // Inverse transform
public void IDCT2D8x4_RightPart() [Theory]
[InlineData(1)]
[InlineData(2)]
public void IDCT8x4_LeftPart(int seed)
{ {
float[] sourceArray = Create8x8FloatData(); Span<float> src = Create8x8RoundedRandomFloatData(-200, 200, seed);
var expectedDestArray = new float[64]; var srcBlock = default(Block8x8F);
srcBlock.LoadFrom(src);
ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D8x4_32f(sourceArray.AsSpan(4), expectedDestArray.AsSpan(4));
var source = default(Block8x8F); var destBlock = default(Block8x8F);
source.LoadFrom(sourceArray);
var dest = default(Block8x8F); var expectedDest = new float[64];
FastFloatingPointDCT.IDCT8x4_RightPart(ref source, ref dest); // reference
ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D8x4_32f(src, expectedDest);
var actualDestArray = new float[64]; // testee
dest.ScaledCopyTo(actualDestArray); FastFloatingPointDCT.IDCT8x4_LeftPart(ref srcBlock, ref destBlock);
this.Print8x8Data(expectedDestArray); var actualDest = new float[64];
this.Output.WriteLine("**************"); destBlock.ScaledCopyTo(actualDest);
this.Print8x8Data(actualDestArray);
Assert.Equal(expectedDestArray, actualDestArray); Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f));
} }
[Theory] [Theory]
[InlineData(1)] [InlineData(1)]
[InlineData(2)] [InlineData(2)]
[InlineData(3)] public void IDCT8x4_RightPart(int seed)
public void LLM_TransformIDCT_CompareToNonOptimized(int seed)
{ {
float[] sourceArray = Create8x8RoundedRandomFloatData(-1000, 1000, seed); Span<float> src = Create8x8RoundedRandomFloatData(-200, 200, seed);
var srcBlock = default(Block8x8F);
srcBlock.LoadFrom(src);
var source = Block8x8F.Load(sourceArray); var destBlock = default(Block8x8F);
Block8x8F expected = ReferenceImplementations.LLM_FloatingPoint_DCT.TransformIDCT(ref source); var expectedDest = new float[64];
var temp = default(Block8x8F); // reference
var actual = default(Block8x8F); ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D8x4_32f(src.Slice(4), expectedDest.AsSpan(4));
FastFloatingPointDCT.TransformIDCT(ref source, ref actual, ref temp);
this.CompareBlocks(expected, actual, 1f); // testee
FastFloatingPointDCT.IDCT8x4_RightPart(ref srcBlock, ref destBlock);
var actualDest = new float[64];
destBlock.ScaledCopyTo(actualDest);
Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f));
} }
[Theory] [Theory]
[InlineData(1)] [InlineData(1)]
[InlineData(2)] [InlineData(2)]
[InlineData(3)] public void IDCT8x8_Avx(int seed)
public void LLM_TransformIDCT_CompareToAccurate(int seed)
{ {
float[] sourceArray = Create8x8RoundedRandomFloatData(-1000, 1000, seed); #if SUPPORTS_RUNTIME_INTRINSICS
var skip = !Avx.IsSupported;
#else
var skip = true;
#endif
if (skip)
{
this.Output.WriteLine("No AVX present, skipping test!");
return;
}
var source = Block8x8F.Load(sourceArray); Span<float> src = Create8x8RoundedRandomFloatData(-200, 200, seed);
var srcBlock = default(Block8x8F);
srcBlock.LoadFrom(src);
Block8x8F expected = ReferenceImplementations.AccurateDCT.TransformIDCT(ref source); var destBlock = default(Block8x8F);
var temp = default(Block8x8F); var expectedDest = new float[64];
var actual = default(Block8x8F);
FastFloatingPointDCT.TransformIDCT(ref source, ref actual, ref temp);
this.CompareBlocks(expected, actual, 1f); // reference, left part
ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D8x4_32f(src, expectedDest);
// reference, right part
ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D8x4_32f(src.Slice(4), expectedDest.AsSpan(4));
// testee, whole 8x8
FastFloatingPointDCT.IDCT8x8_Avx(ref srcBlock, ref destBlock);
var actualDest = new float[64];
destBlock.ScaledCopyTo(actualDest);
Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f));
} }
[Theory]
[InlineData(1)]
[InlineData(2)]
public void TransformIDCT(int seed)
{
static void RunTest(string serialized)
{
int seed = FeatureTestRunner.Deserialize<int>(serialized);
Span<float> src = Create8x8RoundedRandomFloatData(-200, 200, seed);
var srcBlock = default(Block8x8F);
srcBlock.LoadFrom(src);
var destBlock = default(Block8x8F);
var expectedDest = new float[64];
var temp1 = new float[64];
var temp2 = default(Block8x8F);
// reference
ReferenceImplementations.LLM_FloatingPoint_DCT.IDCT2D_llm(src, expectedDest, temp1);
// testee
FastFloatingPointDCT.TransformIDCT(ref srcBlock, ref destBlock, ref temp2);
var actualDest = new float[64];
destBlock.ScaledCopyTo(actualDest);
Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f));
}
// 3 paths:
// 1. AllowAll - call avx/fma implementation
// 2. DisableFMA - call avx implementation without fma acceleration
// 3. DisableAvx - call fallback code of Vector4 implementation
//
// DisableSSE isn't needed because fallback Vector4 code will compile to either sse or fallback code with same result
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
seed,
HwIntrinsics.AllowAll | HwIntrinsics.DisableFMA | HwIntrinsics.DisableAVX);
}
// Forward transform
[Theory] [Theory]
[InlineData(1)] [InlineData(1)]
[InlineData(2)] [InlineData(2)]
@ -123,7 +211,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var expectedDest = new float[64]; var expectedDest = new float[64];
// reference
ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D8x4_32f(src, expectedDest); ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D8x4_32f(src, expectedDest);
// testee
FastFloatingPointDCT.FDCT8x4_LeftPart(ref srcBlock, ref destBlock); FastFloatingPointDCT.FDCT8x4_LeftPart(ref srcBlock, ref destBlock);
var actualDest = new float[64]; var actualDest = new float[64];
@ -145,7 +236,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var expectedDest = new float[64]; var expectedDest = new float[64];
// reference
ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D8x4_32f(src.Slice(4), expectedDest.AsSpan(4)); ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D8x4_32f(src.Slice(4), expectedDest.AsSpan(4));
// testee
FastFloatingPointDCT.FDCT8x4_RightPart(ref srcBlock, ref destBlock); FastFloatingPointDCT.FDCT8x4_RightPart(ref srcBlock, ref destBlock);
var actualDest = new float[64]; var actualDest = new float[64];
@ -157,8 +251,19 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory] [Theory]
[InlineData(1)] [InlineData(1)]
[InlineData(2)] [InlineData(2)]
public void TransformFDCT(int seed) public void FDCT8x8_Avx(int seed)
{ {
#if SUPPORTS_RUNTIME_INTRINSICS
var skip = !Avx.IsSupported;
#else
var skip = true;
#endif
if (skip)
{
this.Output.WriteLine("No AVX present, skipping test!");
return;
}
Span<float> src = Create8x8RoundedRandomFloatData(-200, 200, seed); Span<float> src = Create8x8RoundedRandomFloatData(-200, 200, seed);
var srcBlock = default(Block8x8F); var srcBlock = default(Block8x8F);
srcBlock.LoadFrom(src); srcBlock.LoadFrom(src);
@ -166,17 +271,64 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var destBlock = default(Block8x8F); var destBlock = default(Block8x8F);
var expectedDest = new float[64]; var expectedDest = new float[64];
var temp1 = new float[64];
var temp2 = default(Block8x8F);
ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D_llm(src, expectedDest, temp1, downscaleBy8: true); // reference, left part
FastFloatingPointDCT.TransformFDCT(ref srcBlock, ref destBlock, ref temp2, false); ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D8x4_32f(src, expectedDest);
// reference, right part
ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D8x4_32f(src.Slice(4), expectedDest.AsSpan(4));
// testee, whole 8x8
FastFloatingPointDCT.FDCT8x8_Avx(ref srcBlock, ref destBlock);
var actualDest = new float[64]; var actualDest = new float[64];
destBlock.ScaledCopyTo(actualDest); destBlock.ScaledCopyTo(actualDest);
Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f)); Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f));
} }
[Theory]
[InlineData(1)]
[InlineData(2)]
public void TransformFDCT(int seed)
{
static void RunTest(string serialized)
{
int seed = FeatureTestRunner.Deserialize<int>(serialized);
Span<float> src = Create8x8RoundedRandomFloatData(-200, 200, seed);
var srcBlock = default(Block8x8F);
srcBlock.LoadFrom(src);
var destBlock = default(Block8x8F);
var expectedDest = new float[64];
var temp1 = new float[64];
var temp2 = default(Block8x8F);
// reference
ReferenceImplementations.LLM_FloatingPoint_DCT.FDCT2D_llm(src, expectedDest, temp1, downscaleBy8: true);
// testee
FastFloatingPointDCT.TransformFDCT(ref srcBlock, ref destBlock, ref temp2, false);
var actualDest = new float[64];
destBlock.ScaledCopyTo(actualDest);
Assert.Equal(actualDest, expectedDest, new ApproximateFloatComparer(1f));
}
// 3 paths:
// 1. AllowAll - call avx/fma implementation
// 2. DisableFMA - call avx implementation without fma acceleration
// 3. DisableAvx - call fallback code of Vector4 implementation
//
// DisableSSE isn't needed because fallback Vector4 code will compile to either sse or fallback code with same result
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
seed,
HwIntrinsics.AllowAll | HwIntrinsics.DisableFMA | HwIntrinsics.DisableAVX);
}
} }
} }
} }

213
tests/ImageSharp.Tests/Formats/Jpg/RgbToYCbCrConverterTests.cs

@ -1,7 +1,13 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
using SixLabors.ImageSharp.ColorSpaces; using SixLabors.ImageSharp.ColorSpaces;
using SixLabors.ImageSharp.Formats.Jpeg.Components; using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder; using SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder;
@ -23,22 +29,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
private ITestOutputHelper Output { get; } private ITestOutputHelper Output { get; }
[Fact] [Fact]
public void TestLutConverter() public void TestConverterLut444()
{ {
Rgb24[] data = CreateTestData(); int dataSize = 8 * 8;
Rgb24[] data = CreateTestData(dataSize);
var target = RgbToYCbCrConverterLut.Create(); var target = RgbToYCbCrConverterLut.Create();
Block8x8F y = default; Block8x8F y = default;
Block8x8F cb = default; Block8x8F cb = default;
Block8x8F cr = default; Block8x8F cr = default;
target.Convert(data.AsSpan(), ref y, ref cb, ref cr); target.Convert444(data.AsSpan(), ref y, ref cb, ref cr);
Verify(data, ref y, ref cb, ref cr, new ApproximateColorSpaceComparer(1F)); Verify444(data, ref y, ref cb, ref cr, new ApproximateColorSpaceComparer(1F));
} }
[Fact] [Fact]
public void TestVectorizedConverter() public void TestConverterVectorized444()
{ {
if (!RgbToYCbCrConverterVectorized.IsSupported) if (!RgbToYCbCrConverterVectorized.IsSupported)
{ {
@ -46,18 +53,186 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
return; return;
} }
Rgb24[] data = CreateTestData(); int dataSize = 8 * 8;
Rgb24[] data = CreateTestData(dataSize);
Block8x8F y = default; Block8x8F y = default;
Block8x8F cb = default; Block8x8F cb = default;
Block8x8F cr = default; Block8x8F cr = default;
RgbToYCbCrConverterVectorized.Convert(data.AsSpan(), ref y, ref cb, ref cr); RgbToYCbCrConverterVectorized.Convert444(data.AsSpan(), ref y, ref cb, ref cr);
Verify(data, ref y, ref cb, ref cr, new ApproximateColorSpaceComparer(0.0001F)); Verify444(data, ref y, ref cb, ref cr, new ApproximateColorSpaceComparer(0.0001F));
} }
private static void Verify(ReadOnlySpan<Rgb24> data, ref Block8x8F yResult, ref Block8x8F cbResult, ref Block8x8F crResult, ApproximateColorSpaceComparer comparer) [Fact]
public void TestConverterLut420()
{
int dataSize = 16 * 16;
Span<Rgb24> data = CreateTestData(dataSize).AsSpan();
var target = RgbToYCbCrConverterLut.Create();
var yBlocks = new Block8x8F[4];
var cb = default(Block8x8F);
var cr = default(Block8x8F);
target.Convert420(data, ref yBlocks[0], ref yBlocks[1], ref cb, ref cr, 0);
target.Convert420(data.Slice(16 * 8), ref yBlocks[2], ref yBlocks[3], ref cb, ref cr, 1);
Verify420(data, yBlocks, ref cb, ref cr, new ApproximateFloatComparer(1F));
}
[Fact]
public void TestConverterVectorized420()
{
if (!RgbToYCbCrConverterVectorized.IsSupported)
{
this.Output.WriteLine("No AVX and/or FMA present, skipping test!");
return;
}
int dataSize = 16 * 16;
Span<Rgb24> data = CreateTestData(dataSize).AsSpan();
var yBlocks = new Block8x8F[4];
var cb = default(Block8x8F);
var cr = default(Block8x8F);
RgbToYCbCrConverterVectorized.Convert420(data, ref yBlocks[0], ref yBlocks[1], ref cb, ref cr, 0);
RgbToYCbCrConverterVectorized.Convert420(data.Slice(16 * 8), ref yBlocks[2], ref yBlocks[3], ref cb, ref cr, 1);
Verify420(data, yBlocks, ref cb, ref cr, new ApproximateFloatComparer(1F));
}
#if SUPPORTS_RUNTIME_INTRINSICS
[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public void Scale16x2_8x1(int seed)
{
if (!Avx2.IsSupported)
{
return;
}
Span<float> data = new Random(seed).GenerateRandomFloatArray(Vector256<float>.Count * 4, -1000, 1000);
// Act:
Vector256<float> resultVector = RgbToYCbCrConverterVectorized.Scale16x2_8x1(MemoryMarshal.Cast<float, Vector256<float>>(data));
ref float result = ref Unsafe.As<Vector256<float>, float>(ref resultVector);
// Assert:
// Comparison epsilon is tricky but 10^(-4) is good enough (?)
var comparer = new ApproximateFloatComparer(0.0001f);
for (int i = 0; i < Vector256<float>.Count; i++)
{
float actual = Unsafe.Add(ref result, i);
float expected = CalculateAverage16x2_8x1(data, i);
Assert.True(comparer.Equals(actual, expected), $"Pos {i}, Expected: {expected}, Actual: {actual}");
}
static float CalculateAverage16x2_8x1(Span<float> data, int index)
{
int upIdx = index * 2;
int lowIdx = (index + 8) * 2;
return 0.25f * (data[upIdx] + data[upIdx + 1] + data[lowIdx] + data[lowIdx + 1]);
}
}
#endif
private static void Verify444(
ReadOnlySpan<Rgb24> data,
ref Block8x8F yResult,
ref Block8x8F cbResult,
ref Block8x8F crResult,
ApproximateColorSpaceComparer comparer)
{
Block8x8F y = default;
Block8x8F cb = default;
Block8x8F cr = default;
RgbToYCbCr(data, ref y, ref cb, ref cr);
for (int i = 0; i < Block8x8F.Size; i++)
{
Assert.True(comparer.Equals(new YCbCr(y[i], cb[i], cr[i]), new YCbCr(yResult[i], cbResult[i], crResult[i])), $"Pos {i}, Expected {y[i]} == {yResult[i]}, {cb[i]} == {cbResult[i]}, {cr[i]} == {crResult[i]}");
}
}
private static void Verify420(
ReadOnlySpan<Rgb24> data,
Block8x8F[] yResult,
ref Block8x8F cbResult,
ref Block8x8F crResult,
ApproximateFloatComparer comparer)
{
var trueBlock = default(Block8x8F);
var cbTrue = new Block8x8F[4];
var crTrue = new Block8x8F[4];
Span<Rgb24> tempData = new Rgb24[8 * 8].AsSpan();
// top left
Copy8x8(data, tempData);
RgbToYCbCr(tempData, ref trueBlock, ref cbTrue[0], ref crTrue[0]);
VerifyBlock(ref yResult[0], ref trueBlock, comparer);
// top right
Copy8x8(data.Slice(8), tempData);
RgbToYCbCr(tempData, ref trueBlock, ref cbTrue[1], ref crTrue[1]);
VerifyBlock(ref yResult[1], ref trueBlock, comparer);
// bottom left
Copy8x8(data.Slice(8 * 16), tempData);
RgbToYCbCr(tempData, ref trueBlock, ref cbTrue[2], ref crTrue[2]);
VerifyBlock(ref yResult[2], ref trueBlock, comparer);
// bottom right
Copy8x8(data.Slice((8 * 16) + 8), tempData);
RgbToYCbCr(tempData, ref trueBlock, ref cbTrue[3], ref crTrue[3]);
VerifyBlock(ref yResult[3], ref trueBlock, comparer);
// verify Cb
Scale16X16To8X8(ref trueBlock, cbTrue);
VerifyBlock(ref cbResult, ref trueBlock, comparer);
// verify Cr
Scale16X16To8X8(ref trueBlock, crTrue);
VerifyBlock(ref crResult, ref trueBlock, comparer);
// extracts 8x8 blocks from 16x8 memory region
static void Copy8x8(ReadOnlySpan<Rgb24> source, Span<Rgb24> dest)
{
for (int i = 0; i < 8; i++)
{
source.Slice(i * 16, 8).CopyTo(dest.Slice(i * 8));
}
}
// scales 16x16 to 8x8, used in chroma subsampling tests
static void Scale16X16To8X8(ref Block8x8F dest, 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];
dest[(8 * y) + x + dstOff] = (sum + 2) * .25F;
}
}
}
}
}
private static void RgbToYCbCr(ReadOnlySpan<Rgb24> data, ref Block8x8F y, ref Block8x8F cb, ref Block8x8F cr)
{ {
for (int i = 0; i < data.Length; i++) for (int i = 0; i < data.Length; i++)
{ {
@ -65,17 +240,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
int g = data[i].G; int g = data[i].G;
int b = data[i].B; int b = data[i].B;
float y = (0.299F * r) + (0.587F * g) + (0.114F * b); y[i] = (0.299F * r) + (0.587F * g) + (0.114F * b);
float cb = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b)); cb[i] = 128F + ((-0.168736F * r) - (0.331264F * g) + (0.5F * b));
float cr = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b)); cr[i] = 128F + ((0.5F * r) - (0.418688F * g) - (0.081312F * b));
}
}
Assert.True(comparer.Equals(new YCbCr(y, cb, cr), new YCbCr(yResult[i], cbResult[i], crResult[i])), $"Pos {i}, Expected {y} == {yResult[i]}, {cb} == {cbResult[i]}, {cr} == {crResult[i]}"); private static void VerifyBlock(ref Block8x8F res, ref Block8x8F target, ApproximateFloatComparer comparer)
{
for (int i = 0; i < Block8x8F.Size; i++)
{
Assert.True(comparer.Equals(res[i], target[i]), $"Pos {i}, Expected: {target[i]}, Got: {res[i]}");
} }
} }
private static Rgb24[] CreateTestData() private static Rgb24[] CreateTestData(int size)
{ {
var data = new Rgb24[64]; var data = new Rgb24[size];
var r = new Random(); var r = new Random();
var random = new byte[3]; var random = new byte[3];

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

@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Collections.Generic; using System.Collections.Generic;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -154,11 +154,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation
[MemberData(nameof(BilevelData))] [MemberData(nameof(BilevelData))]
[MemberData(nameof(Grayscale4_Data))] [MemberData(nameof(Grayscale4_Data))]
[MemberData(nameof(Grayscale8_Data))] [MemberData(nameof(Grayscale8_Data))]
public void Decode_WritesPixelData(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) public void Decode_WritesPixelData(byte[] inputData, ushort bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
{ {
AssertDecode(expectedResult, pixels => AssertDecode(expectedResult, pixels =>
{ {
new BlackIsZeroTiffColor<Rgba32>(new[] { (ushort)bitsPerSample }).Decode(inputData, pixels, left, top, width, height); new BlackIsZeroTiffColor<Rgba32>(new TiffBitsPerSample(bitsPerSample, 0, 0)).Decode(inputData, pixels, left, top, width, height);
}); });
} }

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

@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Collections.Generic; using System.Collections.Generic;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -83,10 +83,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation
[Theory] [Theory]
[MemberData(nameof(Palette4Data))] [MemberData(nameof(Palette4Data))]
[MemberData(nameof(Palette8Data))] [MemberData(nameof(Palette8Data))]
public void Decode_WritesPixelData(byte[] inputData, ushort bitsPerSample, ushort[] colorMap, int left, int top, int width, int height, Rgba32[][] expectedResult) => AssertDecode(expectedResult, pixels => public void Decode_WritesPixelData(byte[] inputData, ushort bitsPerSample, ushort[] colorMap, int left, int top, int width, int height, Rgba32[][] expectedResult)
{ => AssertDecode(expectedResult, pixels =>
new PaletteTiffColor<Rgba32>(new[] { bitsPerSample }, colorMap).Decode(inputData, pixels, left, top, width, height); {
}); new PaletteTiffColor<Rgba32>(new TiffBitsPerSample(bitsPerSample, 0, 0), colorMap).Decode(inputData, pixels, left, top, width, height);
});
private static uint[][] GeneratePalette(int count) private static uint[][] GeneratePalette(int count)
{ {

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

@ -3,7 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -101,17 +101,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation
{ {
get get
{ {
yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 0, 0, 4, 4, Rgb4Result4X4 }; yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 4, 4, Rgb4Result4X4 };
yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 0, 0, 4, 4, Offset(Rgb4Result4X4, 0, 0, 6, 6) }; yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 4, 4, Offset(Rgb4Result4X4, 0, 0, 6, 6) };
yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 1, 0, 4, 4, Offset(Rgb4Result4X4, 1, 0, 6, 6) }; yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 1, 0, 4, 4, Offset(Rgb4Result4X4, 1, 0, 6, 6) };
yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 0, 1, 4, 4, Offset(Rgb4Result4X4, 0, 1, 6, 6) }; yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 1, 4, 4, Offset(Rgb4Result4X4, 0, 1, 6, 6) };
yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 1, 1, 4, 4, Offset(Rgb4Result4X4, 1, 1, 6, 6) }; yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 1, 1, 4, 4, Offset(Rgb4Result4X4, 1, 1, 6, 6) };
yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 0, 0, 3, 4, Rgb4Result3X4 }; yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 3, 4, Rgb4Result3X4 };
yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 0, 0, 3, 4, Offset(Rgb4Result3X4, 0, 0, 6, 6) }; yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 3, 4, Offset(Rgb4Result3X4, 0, 0, 6, 6) };
yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 1, 0, 3, 4, Offset(Rgb4Result3X4, 1, 0, 6, 6) }; yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 1, 0, 3, 4, Offset(Rgb4Result3X4, 1, 0, 6, 6) };
yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 0, 1, 3, 4, Offset(Rgb4Result3X4, 0, 1, 6, 6) }; yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 1, 3, 4, Offset(Rgb4Result3X4, 0, 1, 6, 6) };
yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 1, 1, 3, 4, Offset(Rgb4Result3X4, 1, 1, 6, 6) }; yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 1, 1, 3, 4, Offset(Rgb4Result3X4, 1, 1, 6, 6) };
} }
} }
@ -170,11 +170,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation
{ {
get get
{ {
yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 0, 0, 4, 4, Rgb8Result4X4 }; yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 0, 4, 4, Rgb8Result4X4 };
yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 0, 0, 4, 4, Offset(Rgb8Result4X4, 0, 0, 6, 6) }; yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 0, 4, 4, Offset(Rgb8Result4X4, 0, 0, 6, 6) };
yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 1, 0, 4, 4, Offset(Rgb8Result4X4, 1, 0, 6, 6) }; yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 1, 0, 4, 4, Offset(Rgb8Result4X4, 1, 0, 6, 6) };
yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 0, 1, 4, 4, Offset(Rgb8Result4X4, 0, 1, 6, 6) }; yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 1, 4, 4, Offset(Rgb8Result4X4, 0, 1, 6, 6) };
yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 1, 1, 4, 4, Offset(Rgb8Result4X4, 1, 1, 6, 6) }; yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 1, 1, 4, 4, Offset(Rgb8Result4X4, 1, 1, 6, 6) };
} }
} }
@ -230,11 +230,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation
{ {
get get
{ {
yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 0, 0, 4, 4, Rgb484Result4X4 }; yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 0, 4, 4, Rgb484Result4X4 };
yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 0, 0, 4, 4, Offset(Rgb484Result4X4, 0, 0, 6, 6) }; yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 0, 4, 4, Offset(Rgb484Result4X4, 0, 0, 6, 6) };
yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 1, 0, 4, 4, Offset(Rgb484Result4X4, 1, 0, 6, 6) }; yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 1, 0, 4, 4, Offset(Rgb484Result4X4, 1, 0, 6, 6) };
yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 0, 1, 4, 4, Offset(Rgb484Result4X4, 0, 1, 6, 6) }; yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 1, 4, 4, Offset(Rgb484Result4X4, 0, 1, 6, 6) };
yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 1, 1, 4, 4, Offset(Rgb484Result4X4, 1, 1, 6, 6) }; yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 1, 1, 4, 4, Offset(Rgb484Result4X4, 1, 1, 6, 6) };
} }
} }
@ -242,7 +242,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation
[MemberData(nameof(Rgb4Data))] [MemberData(nameof(Rgb4Data))]
[MemberData(nameof(Rgb8Data))] [MemberData(nameof(Rgb8Data))]
[MemberData(nameof(Rgb484_Data))] [MemberData(nameof(Rgb484_Data))]
public void Decode_WritesPixelData(byte[][] inputData, ushort[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) public void Decode_WritesPixelData(byte[][] inputData, TiffBitsPerSample bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
{ {
AssertDecode(expectedResult, pixels => AssertDecode(expectedResult, pixels =>
{ {

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

@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Collections.Generic; using System.Collections.Generic;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -63,17 +63,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation
{ {
get get
{ {
yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 0, 0, 4, 4, Rgb4Result4X4 }; yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 4, 4, Rgb4Result4X4 };
yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 0, 0, 4, 4, Offset(Rgb4Result4X4, 0, 0, 6, 6) }; yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 4, 4, Offset(Rgb4Result4X4, 0, 0, 6, 6) };
yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 1, 0, 4, 4, Offset(Rgb4Result4X4, 1, 0, 6, 6) }; yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 1, 0, 4, 4, Offset(Rgb4Result4X4, 1, 0, 6, 6) };
yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 0, 1, 4, 4, Offset(Rgb4Result4X4, 0, 1, 6, 6) }; yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 0, 1, 4, 4, Offset(Rgb4Result4X4, 0, 1, 6, 6) };
yield return new object[] { Rgb4Bytes4X4, new ushort[] { 4, 4, 4 }, 1, 1, 4, 4, Offset(Rgb4Result4X4, 1, 1, 6, 6) }; yield return new object[] { Rgb4Bytes4X4, new TiffBitsPerSample(4, 4, 4), 1, 1, 4, 4, Offset(Rgb4Result4X4, 1, 1, 6, 6) };
yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 0, 0, 3, 4, Rgb4Result3X4 }; yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 3, 4, Rgb4Result3X4 };
yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 0, 0, 3, 4, Offset(Rgb4Result3X4, 0, 0, 6, 6) }; yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 0, 3, 4, Offset(Rgb4Result3X4, 0, 0, 6, 6) };
yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 1, 0, 3, 4, Offset(Rgb4Result3X4, 1, 0, 6, 6) }; yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 1, 0, 3, 4, Offset(Rgb4Result3X4, 1, 0, 6, 6) };
yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 0, 1, 3, 4, Offset(Rgb4Result3X4, 0, 1, 6, 6) }; yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 0, 1, 3, 4, Offset(Rgb4Result3X4, 0, 1, 6, 6) };
yield return new object[] { Rgb4Bytes3X4, new ushort[] { 4, 4, 4 }, 1, 1, 3, 4, Offset(Rgb4Result3X4, 1, 1, 6, 6) }; yield return new object[] { Rgb4Bytes3X4, new TiffBitsPerSample(4, 4, 4), 1, 1, 3, 4, Offset(Rgb4Result3X4, 1, 1, 6, 6) };
} }
} }
@ -111,11 +111,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation
{ {
get get
{ {
yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 0, 0, 4, 4, Rgb8Result4X4 }; yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 0, 4, 4, Rgb8Result4X4 };
yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 0, 0, 4, 4, Offset(Rgb8Result4X4, 0, 0, 6, 6) }; yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 0, 4, 4, Offset(Rgb8Result4X4, 0, 0, 6, 6) };
yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 1, 0, 4, 4, Offset(Rgb8Result4X4, 1, 0, 6, 6) }; yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 1, 0, 4, 4, Offset(Rgb8Result4X4, 1, 0, 6, 6) };
yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 0, 1, 4, 4, Offset(Rgb8Result4X4, 0, 1, 6, 6) }; yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 0, 1, 4, 4, Offset(Rgb8Result4X4, 0, 1, 6, 6) };
yield return new object[] { Rgb8Bytes4X4, new ushort[] { 8, 8, 8 }, 1, 1, 4, 4, Offset(Rgb8Result4X4, 1, 1, 6, 6) }; yield return new object[] { Rgb8Bytes4X4, new TiffBitsPerSample(8, 8, 8), 1, 1, 4, 4, Offset(Rgb8Result4X4, 1, 1, 6, 6) };
} }
} }
@ -153,11 +153,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation
{ {
get get
{ {
yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 0, 0, 4, 4, Rgb484Result4X4 }; yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 0, 4, 4, Rgb484Result4X4 };
yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 0, 0, 4, 4, Offset(Rgb484Result4X4, 0, 0, 6, 6) }; yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 0, 4, 4, Offset(Rgb484Result4X4, 0, 0, 6, 6) };
yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 1, 0, 4, 4, Offset(Rgb484Result4X4, 1, 0, 6, 6) }; yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 1, 0, 4, 4, Offset(Rgb484Result4X4, 1, 0, 6, 6) };
yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 0, 1, 4, 4, Offset(Rgb484Result4X4, 0, 1, 6, 6) }; yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 0, 1, 4, 4, Offset(Rgb484Result4X4, 0, 1, 6, 6) };
yield return new object[] { Rgb484Bytes4X4, new ushort[] { 4, 8, 4 }, 1, 1, 4, 4, Offset(Rgb484Result4X4, 1, 1, 6, 6) }; yield return new object[] { Rgb484Bytes4X4, new TiffBitsPerSample(4, 8, 4), 1, 1, 4, 4, Offset(Rgb484Result4X4, 1, 1, 6, 6) };
} }
} }
@ -165,7 +165,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation
[MemberData(nameof(Rgb4Data))] [MemberData(nameof(Rgb4Data))]
[MemberData(nameof(Rgb8Data))] [MemberData(nameof(Rgb8Data))]
[MemberData(nameof(Rgb484Data))] [MemberData(nameof(Rgb484Data))]
public void Decode_WritesPixelData(byte[] inputData, ushort[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) public void Decode_WritesPixelData(byte[] inputData, TiffBitsPerSample bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
{ {
AssertDecode(expectedResult, pixels => AssertDecode(expectedResult, pixels =>
{ {
@ -175,7 +175,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation
[Theory] [Theory]
[MemberData(nameof(Rgb8Data))] [MemberData(nameof(Rgb8Data))]
public void Decode_WritesPixelData_8Bit(byte[] inputData, ushort[] bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) public void Decode_WritesPixelData_8Bit(byte[] inputData, TiffBitsPerSample bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
{ {
AssertDecode(expectedResult, pixels => AssertDecode(expectedResult, pixels =>
{ {

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

@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Collections.Generic; using System.Collections.Generic;
using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation; using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -154,11 +154,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.PhotometricInterpretation
[MemberData(nameof(BilevelData))] [MemberData(nameof(BilevelData))]
[MemberData(nameof(Grayscale4Data))] [MemberData(nameof(Grayscale4Data))]
[MemberData(nameof(Grayscale8Data))] [MemberData(nameof(Grayscale8Data))]
public void Decode_WritesPixelData(byte[] inputData, int bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult) public void Decode_WritesPixelData(byte[] inputData, ushort bitsPerSample, int left, int top, int width, int height, Rgba32[][] expectedResult)
{ {
AssertDecode(expectedResult, pixels => AssertDecode(expectedResult, pixels =>
{ {
new WhiteIsZeroTiffColor<Rgba32>(new[] { (ushort)bitsPerSample }).Decode(inputData, pixels, left, top, width, height); new WhiteIsZeroTiffColor<Rgba32>(new TiffBitsPerSample(bitsPerSample, 0, 0)).Decode(inputData, pixels, left, top, width, height);
}); });
} }

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

@ -97,30 +97,73 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_4Bit_WithPalette<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_4Bit_WithPalette<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider, ReferenceDecoder, useExactComparer: false, 0.01f); where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider, ReferenceDecoder, useExactComparer: false, 0.01f);
[Theory]
[WithFile(Flower2BitPalette, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_2Bit_WithPalette<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider, ReferenceDecoder, useExactComparer: false, 0.01f);
[Theory]
[WithFile(Flower2BitGray, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_2Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory] [Theory]
[WithFile(FlowerRgb222Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb222Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerRgb222Planar, PixelTypes.Rgba32)] [WithFile(FlowerRgb222Planar, PixelTypes.Rgba32)]
[WithFile(Flower6BitGray, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_6Bit<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_6Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider); where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Flower8BitGray, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_8Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Flower10BitGray, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_10Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory] [Theory]
[WithFile(FlowerRgb444Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb444Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerRgb444Planar, PixelTypes.Rgba32)] [WithFile(FlowerRgb444Planar, PixelTypes.Rgba32)]
[WithFile(Flower12BitGray, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_12Bit<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_12Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider); where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Flower14BitGray, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_14Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Flower16BitGray, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_16Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory] [Theory]
[WithFile(FlowerRgb101010Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb101010Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerRgb101010Planar, PixelTypes.Rgba32)] [WithFile(FlowerRgb101010Planar, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_30Bit<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_30Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider); where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(FlowerRgb121212Contiguous, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_36Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory] [Theory]
[WithFile(FlowerRgb141414Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb141414Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerRgb141414Planar, PixelTypes.Rgba32)] [WithFile(FlowerRgb141414Planar, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_42Bit<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_42Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider); where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(FlowerRgb161616Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerRgb161616Planar, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_48Bit<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory] [Theory]
[WithFile(GrayscaleDeflateMultistrip, PixelTypes.Rgba32)] [WithFile(GrayscaleDeflateMultistrip, PixelTypes.Rgba32)]
[WithFile(RgbDeflateMultistrip, PixelTypes.Rgba32)] [WithFile(RgbDeflateMultistrip, PixelTypes.Rgba32)]

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

@ -78,9 +78,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
} }
[Theory] [Theory]
[InlineData(TiffBitsPerPixel.Bit48)]
[InlineData(TiffBitsPerPixel.Bit42)] [InlineData(TiffBitsPerPixel.Bit42)]
[InlineData(TiffBitsPerPixel.Bit36)]
[InlineData(TiffBitsPerPixel.Bit30)] [InlineData(TiffBitsPerPixel.Bit30)]
[InlineData(TiffBitsPerPixel.Bit12)] [InlineData(TiffBitsPerPixel.Bit12)]
[InlineData(TiffBitsPerPixel.Bit10)]
[InlineData(TiffBitsPerPixel.Bit6)] [InlineData(TiffBitsPerPixel.Bit6)]
public void EncoderOptions_UnsupportedBitPerPixel_DefaultTo24Bits(TiffBitsPerPixel bitsPerPixel) public void EncoderOptions_UnsupportedBitPerPixel_DefaultTo24Bits(TiffBitsPerPixel bitsPerPixel)
{ {
@ -413,7 +416,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[Theory] [Theory]
[WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits)] [WithFile(GrayscaleUncompressed, PixelTypes.L8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.PackBits)]
[WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw)]
[WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate)] [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffPhotometricInterpretation.Rgb, TiffCompression.Deflate)]
[WithFile(RgbUncompressed, PixelTypes.Rgb24, TiffPhotometricInterpretation.Rgb, TiffCompression.None)] [WithFile(RgbUncompressed, PixelTypes.Rgb24, TiffPhotometricInterpretation.Rgb, TiffCompression.None)]
[WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffPhotometricInterpretation.Rgb, TiffCompression.None)] [WithFile(RgbUncompressed, PixelTypes.Rgba32, TiffPhotometricInterpretation.Rgb, TiffCompression.None)]
@ -422,6 +424,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
where TPixel : unmanaged, IPixel<TPixel> => where TPixel : unmanaged, IPixel<TPixel> =>
TestStripLength(provider, photometricInterpretation, compression); TestStripLength(provider, photometricInterpretation, compression);
[Theory]
[WithFile(PaletteDeflateMultistrip, PixelTypes.L8, TiffPhotometricInterpretation.PaletteColor, TiffCompression.Lzw)]
public void TiffEncoder_StripLength_WithPalette<TPixel>(TestImageProvider<TPixel> provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression)
where TPixel : unmanaged, IPixel<TPixel> =>
TestStripLength(provider, photometricInterpretation, compression, false, 0.01f);
[Theory] [Theory]
[WithFile(Calliphora_BiColorUncompressed, PixelTypes.L8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax)] [WithFile(Calliphora_BiColorUncompressed, PixelTypes.L8, TiffPhotometricInterpretation.BlackIsZero, TiffCompression.CcittGroup3Fax)]
public void TiffEncoder_StripLength_OutOfBounds<TPixel>(TestImageProvider<TPixel> provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression) public void TiffEncoder_StripLength_OutOfBounds<TPixel>(TestImageProvider<TPixel> provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression)
@ -429,7 +437,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
//// CcittGroup3Fax compressed data length can be larger than the original length. //// CcittGroup3Fax compressed data length can be larger than the original length.
Assert.Throws<Xunit.Sdk.TrueException>(() => TestStripLength(provider, photometricInterpretation, compression)); Assert.Throws<Xunit.Sdk.TrueException>(() => TestStripLength(provider, photometricInterpretation, compression));
private static void TestStripLength<TPixel>(TestImageProvider<TPixel> provider, TiffPhotometricInterpretation photometricInterpretation, TiffCompression compression) [Theory]
[WithTestPatternImages(287, 321, PixelTypes.Rgba32, TiffPhotometricInterpretation.Rgb)]
[WithTestPatternImages(287, 321, PixelTypes.Rgba32, TiffPhotometricInterpretation.PaletteColor)]
[WithTestPatternImages(287, 321, PixelTypes.Rgba32, TiffPhotometricInterpretation.BlackIsZero)]
public void TiffEncode_WorksWithDiscontiguousBuffers<TPixel>(TestImageProvider<TPixel> provider, TiffPhotometricInterpretation photometricInterpretation)
where TPixel : unmanaged, IPixel<TPixel>
{
provider.LimitAllocatorBufferCapacity().InPixelsSqrt(200);
using Image<TPixel> image = provider.GetImage();
var encoder = new TiffEncoder { PhotometricInterpretation = photometricInterpretation };
image.DebugSave(provider, encoder);
}
private static void TestStripLength<TPixel>(
TestImageProvider<TPixel> provider,
TiffPhotometricInterpretation photometricInterpretation,
TiffCompression compression,
bool useExactComparer = true,
float compareTolerance = 0.01f)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// arrange // arrange
@ -477,10 +504,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
// Compare with reference. // Compare with reference.
TestTiffEncoderCore( TestTiffEncoderCore(
provider, provider,
inputMeta.BitsPerPixel, inputMeta.BitsPerPixel,
photometricInterpretation, photometricInterpretation,
inputCompression); inputCompression,
useExactComparer: useExactComparer,
compareTolerance: compareTolerance);
} }
private static void TestTiffEncoderCore<TPixel>( private static void TestTiffEncoderCore<TPixel>(

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

@ -66,7 +66,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{ {
Assert.NotNull(frameMetaData); Assert.NotNull(frameMetaData);
Assert.NotNull(frameMetaData.BitsPerPixel); Assert.NotNull(frameMetaData.BitsPerPixel);
Assert.Equal(TiffBitsPerSample.Bit4, (TiffBitsPerSample)frameMetaData.BitsPerPixel); Assert.Equal(TiffBitsPerPixel.Bit4, frameMetaData.BitsPerPixel);
Assert.Equal(TiffCompression.Lzw, frameMetaData.Compression); Assert.Equal(TiffCompression.Lzw, frameMetaData.Compression);
Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frameMetaData.PhotometricInterpretation); Assert.Equal(TiffPhotometricInterpretation.PaletteColor, frameMetaData.PhotometricInterpretation);
Assert.Equal(TiffPredictor.None, frameMetaData.Predictor); Assert.Equal(TiffPredictor.None, frameMetaData.Predictor);

2
tests/ImageSharp.Tests/ImageSharp.Tests.csproj

@ -6,7 +6,7 @@
<AssemblyName>SixLabors.ImageSharp.Tests</AssemblyName> <AssemblyName>SixLabors.ImageSharp.Tests</AssemblyName>
<Platforms>AnyCPU;x64;x86</Platforms> <Platforms>AnyCPU;x64;x86</Platforms>
<RootNamespace>SixLabors.ImageSharp.Tests</RootNamespace> <RootNamespace>SixLabors.ImageSharp.Tests</RootNamespace>
<Configurations>Debug;Release;Release-InnerLoop;Debug-InnerLoop</Configurations> <Configurations>Debug;Release;Debug-InnerLoop;Release-InnerLoop</Configurations>
</PropertyGroup> </PropertyGroup>
<Choose> <Choose>

13
tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs

@ -223,19 +223,6 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators
Assert.Equal(0, buffer.Memory.Length); Assert.Equal(0, buffer.Memory.Length);
} }
[Theory]
[InlineData(101)]
[InlineData((int.MaxValue / SizeOfLargeStruct) - 1)]
[InlineData(int.MaxValue / SizeOfLargeStruct)]
[InlineData((int.MaxValue / SizeOfLargeStruct) + 1)]
[InlineData((int.MaxValue / SizeOfLargeStruct) + 137)]
public void Allocate_OverCapacity_Throws_InvalidMemoryOperationException(int length)
{
this.LocalFixture.MemoryAllocator.BufferCapacityInBytes = 100 * SizeOfLargeStruct;
Assert.Throws<InvalidMemoryOperationException>(() =>
this.LocalFixture.MemoryAllocator.Allocate<LargeStruct>(length));
}
[Theory] [Theory]
[InlineData(-1)] [InlineData(-1)]
public void AllocateManagedByteBuffer_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length) public void AllocateManagedByteBuffer_IncorrectAmount_ThrowsCorrect_ArgumentOutOfRangeException(int length)

9
tests/ImageSharp.Tests/Processing/Processors/Convolution/BokehBlurTest.cs

@ -11,6 +11,7 @@ using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Convolution; using SixLabors.ImageSharp.Processing.Processors.Convolution;
using SixLabors.ImageSharp.Tests.TestUtilities; using SixLabors.ImageSharp.Tests.TestUtilities;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using Xunit; using Xunit;
using Xunit.Abstractions; using Xunit.Abstractions;
@ -154,8 +155,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
appendSourceFileOrDescription: false); appendSourceFileOrDescription: false);
[Theory] [Theory]
[WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32)] [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32, HwIntrinsics.AllowAll)]
public void BokehBlurFilterProcessor_Bounded(TestImageProvider<Rgba32> provider, BokehBlurInfo value) [WithFileCollection(nameof(TestFiles), nameof(BokehBlurValues), PixelTypes.Rgba32, HwIntrinsics.DisableSSE41)]
public void BokehBlurFilterProcessor_Bounded(TestImageProvider<Rgba32> provider, BokehBlurInfo value, HwIntrinsics intrinsicsFilter)
{ {
static void RunTest(string arg1, string arg2) static void RunTest(string arg1, string arg2)
{ {
@ -173,12 +175,13 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
x.BokehBlur(value.Radius, value.Components, value.Gamma, bounds); x.BokehBlur(value.Radius, value.Components, value.Gamma, bounds);
}, },
testOutputDetails: value.ToString(), testOutputDetails: value.ToString(),
ImageComparer.TolerantPercentage(0.05f),
appendPixelTypeToFileName: false); appendPixelTypeToFileName: false);
} }
FeatureTestRunner.RunWithHwIntrinsicsFeature( FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest, RunTest,
HwIntrinsics.DisableSSE41, intrinsicsFilter,
provider, provider,
value); value);
} }

4
tests/ImageSharp.Tests/Processing/Processors/Dithering/DitherTests.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Dithering; using SixLabors.ImageSharp.Processing.Processors.Dithering;
@ -172,8 +173,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Dithering
provider.RunBufferCapacityLimitProcessorTest( provider.RunBufferCapacityLimitProcessorTest(
41, 41,
c => c.Dither(dither), c => c.Dither(dither),
name, name);
ImageComparer.TolerantPercentage(0.001f));
} }
} }
} }

3
tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeKernelMapTests.cs

@ -80,6 +80,9 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
{ KnownResamplers.Bicubic, 1680, 1200 }, { KnownResamplers.Bicubic, 1680, 1200 },
{ KnownResamplers.Box, 13, 299 }, { KnownResamplers.Box, 13, 299 },
{ KnownResamplers.Lanczos5, 3032, 600 }, { KnownResamplers.Lanczos5, 3032, 600 },
// Large number. https://github.com/SixLabors/ImageSharp/issues/1616
{ KnownResamplers.Bicubic, 207773, 51943 }
}; };
public static TheoryData<string, int, int> GeneratedImageResizeData = public static TheoryData<string, int, int> GeneratedImageResizeData =

15
tests/ImageSharp.Tests/TestImages.cs

@ -735,16 +735,27 @@ namespace SixLabors.ImageSharp.Tests
public const string RgbPalette = "Tiff/rgb_palette.tiff"; public const string RgbPalette = "Tiff/rgb_palette.tiff";
public const string Rgb4BitPalette = "Tiff/bike_colorpalette_4bit.tiff"; public const string Rgb4BitPalette = "Tiff/bike_colorpalette_4bit.tiff";
public const string RgbPaletteDeflate = "Tiff/rgb_palette_deflate.tiff"; public const string RgbPaletteDeflate = "Tiff/rgb_palette_deflate.tiff";
public const string Flower4BitPalette = "Tiff/flower-palette-04.tiff"; public const string FlowerRgb161616Contiguous = "Tiff/flower-rgb-contig-16.tiff";
public const string Flower4BitPaletteGray = "Tiff/flower-minisblack-04.tiff"; public const string FlowerRgb161616Planar = "Tiff/flower-rgb-planar-16.tiff";
public const string FlowerRgb141414Contiguous = "Tiff/flower-rgb-contig-14.tiff"; public const string FlowerRgb141414Contiguous = "Tiff/flower-rgb-contig-14.tiff";
public const string FlowerRgb141414Planar = "Tiff/flower-rgb-planar-14.tiff"; public const string FlowerRgb141414Planar = "Tiff/flower-rgb-planar-14.tiff";
public const string FlowerRgb101010Contiguous = "Tiff/flower-rgb-contig-10.tiff"; public const string FlowerRgb101010Contiguous = "Tiff/flower-rgb-contig-10.tiff";
public const string FlowerRgb101010Planar = "Tiff/flower-rgb-planar-10.tiff"; public const string FlowerRgb101010Planar = "Tiff/flower-rgb-planar-10.tiff";
public const string FlowerRgb121212Contiguous = "Tiff/flower-rgb-contig-12.tiff";
public const string FlowerRgb444Contiguous = "Tiff/flower-rgb-contig-04.tiff"; public const string FlowerRgb444Contiguous = "Tiff/flower-rgb-contig-04.tiff";
public const string FlowerRgb444Planar = "Tiff/flower-rgb-planar-04.tiff"; public const string FlowerRgb444Planar = "Tiff/flower-rgb-planar-04.tiff";
public const string FlowerRgb222Contiguous = "Tiff/flower-rgb-contig-02.tiff"; public const string FlowerRgb222Contiguous = "Tiff/flower-rgb-contig-02.tiff";
public const string FlowerRgb222Planar = "Tiff/flower-rgb-planar-02.tiff"; public const string FlowerRgb222Planar = "Tiff/flower-rgb-planar-02.tiff";
public const string Flower2BitGray = "Tiff/flower-minisblack-02.tiff";
public const string Flower2BitPalette = "Tiff/flower-palette-02.tiff";
public const string Flower4BitPalette = "Tiff/flower-palette-04.tiff";
public const string Flower4BitPaletteGray = "Tiff/flower-minisblack-04.tiff";
public const string Flower6BitGray = "Tiff/flower-minisblack-06.tiff";
public const string Flower8BitGray = "Tiff/flower-minisblack-08.tiff";
public const string Flower10BitGray = "Tiff/flower-minisblack-10.tiff";
public const string Flower12BitGray = "Tiff/flower-minisblack-12.tiff";
public const string Flower14BitGray = "Tiff/flower-minisblack-14.tiff";
public const string Flower16BitGray = "Tiff/flower-minisblack-16.tiff";
public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff"; public const string SmallRgbDeflate = "Tiff/rgb_small_deflate.tiff";
public const string SmallRgbLzw = "Tiff/rgb_small_lzw.tiff"; public const string SmallRgbLzw = "Tiff/rgb_small_lzw.tiff";

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

@ -84,7 +84,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ReferenceCodecs
MemoryGroup<TPixel> framePixels = frame.PixelBuffer.FastMemoryGroup; MemoryGroup<TPixel> framePixels = frame.PixelBuffer.FastMemoryGroup;
using IUnsafePixelCollection<ushort> pixels = magicFrame.GetPixelsUnsafe(); using IUnsafePixelCollection<ushort> pixels = magicFrame.GetPixelsUnsafe();
if (magicFrame.Depth == 8 || magicFrame.Depth == 4 || magicFrame.Depth == 2 || magicFrame.Depth == 1 || magicFrame.Depth == 10) if (magicFrame.Depth == 8 || magicFrame.Depth == 6 || magicFrame.Depth == 4 || magicFrame.Depth == 2 || magicFrame.Depth == 1 || magicFrame.Depth == 10 || magicFrame.Depth == 12)
{ {
byte[] data = pixels.ToByteArray(PixelMapping.RGBA); byte[] data = pixels.ToByteArray(PixelMapping.RGBA);

17
tests/ImageSharp.Tests/TestUtilities/Tests/TestEnvironmentTests.cs

@ -3,6 +3,8 @@
using System; using System;
using System.IO; using System.IO;
using Microsoft.DotNet.RemoteExecutor;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Formats.Bmp; using SixLabors.ImageSharp.Formats.Bmp;
using SixLabors.ImageSharp.Formats.Gif; using SixLabors.ImageSharp.Formats.Gif;
@ -117,5 +119,20 @@ namespace SixLabors.ImageSharp.Tests
IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName); IImageDecoder decoder = TestEnvironment.GetReferenceDecoder(fileName);
Assert.IsType(expectedDecoderType, decoder); Assert.IsType(expectedDecoderType, decoder);
} }
// RemoteExecutor does not work with "dotnet xunit" used to run tests on 32 bit .NET Framework:
// https://github.com/SixLabors/ImageSharp/blob/381dff8640b721a34b1227c970fcf6ad6c5e3e72/ci-test.ps1#L30
public static bool IsNot32BitNetFramework = !TestEnvironment.IsFramework || TestEnvironment.Is64BitProcess;
[ConditionalFact(nameof(IsNot32BitNetFramework))]
public void RemoteExecutor_FailingRemoteTestShouldFailLocalTest()
{
static void FailingCode()
{
Assert.False(true);
}
Assert.ThrowsAny<RemoteExecutionException>(() => RemoteExecutor.Invoke(FailingCode).Dispose());
}
} }
} }

2
tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithOctreeQuantizer_rgb32.bmp

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:6dba331639d724f198d7d11af971156d34076b57bba0f2d0d45e699104a3a674 oid sha256:11375b15df083d98335f4a4baf0717e7fdd6b21ab2132a6815cadc787ac17e7d
size 9270 size 9270

2
tests/Images/External/ReferenceOutput/BmpEncoderTests/Encode_8BitColor_WithWuQuantizer_rgb32.bmp

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:9213e188b3a2f715ae21a5ab2fb2acedc23397207820c0999b06fa60e7052b85 oid sha256:e063e97cd8a000de6830adcc3961a7dc41785d40cd4d83af10ca38d96e071362
size 9270 size 9270

4
tests/Images/External/ReferenceOutput/DitherTests/ApplyDiffusionFilterInBox_Rgba32_CalliphoraPartial.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:1aa62e798c085eb7b0e8e5ce5e4cb2cccfe925dd8ac3e29659f9afd53fca977c oid sha256:cafc426ac8e8d02a87f67c90e8c1976c5fae0e12b49deae52ad08476f7ed49a4
size 329912 size 266391

4
tests/Images/External/ReferenceOutput/DitherTests/ApplyDitherFilterInBox_Rgba32_CalliphoraPartial.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:d0c8ccdfbf6b1c961f6531ae61207a7f89507f469c875677f1755ea3d6c8d900 oid sha256:4d88eb2e50ca9dbed0e8dfe4ad278cd88ddb9d3408b30d9dfe59102b167f570b
size 326504 size 262887

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Bgra32_filter0.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:0216f1684430087035b387ab02d33b043c526bd8c7d78343c31e1bc410581bfb oid sha256:0369747820c86bb692fc7b75f3519095c9b2a58a885ebd37c871c103d08405a0
size 727 size 720

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgb24_filter0.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:0216f1684430087035b387ab02d33b043c526bd8c7d78343c31e1bc410581bfb oid sha256:0369747820c86bb692fc7b75f3519095c9b2a58a885ebd37c871c103d08405a0
size 727 size 720

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_Rgba32_filter0.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:0216f1684430087035b387ab02d33b043c526bd8c7d78343c31e1bc410581bfb oid sha256:0369747820c86bb692fc7b75f3519095c9b2a58a885ebd37c871c103d08405a0
size 727 size 720

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_ShouldNotDependOnSinglePixelType_RgbaVector_filter0.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:24191da3ce18438edefa7a189d9beadaa3057b5e4d4c550254e3a81ed159c0f8 oid sha256:f63aebed17504ef50d96ac7e58dc41f5227a83a38810359ed8e9cecda137183b
size 723 size 720

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Atkinson.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:087148425a048f33c6ae063064cfe374f7fb88f075d767e62c73675ec52a3e0a oid sha256:471eaf2e532b40592c86dc816709d3ae4bbd64892006e00fd611ef6869d3b934
size 100066 size 52070

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Burks.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:b2a90c8463632606b40461ad91d80d44826f7b468ba5f1a905acfc85ad0344c9 oid sha256:91fb9966a4b3eaefd5533ddf0b98ec08fbf8cbc263e4ebd438895e6d4129dd03
size 114413 size 61447

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_FloydSteinberg.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:192c742bfd53f3a74d96c79e92443a922ac60c354b73d7abf292f30d10131307 oid sha256:d74faa8d188a2915739de64ba9d71b2132b53c8d154db22510c524ae757578a5
size 114842 size 61183

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_JarvisJudiceNinke.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:50c12659dc05b3ce8a6692cdbc72971bbc691cc7fd26c34df65b4bd71d190e5b oid sha256:080cc89d1d6568a2c9b707bf05428ab5febd2951e37223f96e349cc6646d32aa
size 108799 size 56070

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra2.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:6a1c408687899b57b96e9f01ea889bc6f16d9a479386346c6bd9babc45ef99d0 oid sha256:c7589986c1a762d52fe8ffc252e9938ff0e3a9e00b91ea7f5e36d4335b2b7870
size 109095 size 58502

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Sierra3.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:d3d8aead7f9f69dac7eec1b2d8e2200331bf28218c98b7fa3435c9610ff88264 oid sha256:934042746c3a9b652069da26b479e2be7cbdb17ab20e41c5e271013a76e96e46
size 110221 size 58480

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_SierraLite.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:f1fed9d8f58e38cfa938d7735cbdfcbac0aa02f58eda0dddfe29a5ebed0e74eb oid sha256:03d5d5cbf1b2c0be736aa2bf726ad4bb04fca77aff393edb9663a7915a794264
size 117802 size 62418

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_StevensonArce.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:dbeeda3f4e505c46e1ec218001f0f1aade4eb64c3932e2c374a74c4b0702d7a8 oid sha256:19a0d8667bfd01e18adbfca778e868ea7a6c43d427f9ae40eb4281d438ef509c
size 103735 size 54464

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_Bike_Stucki.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:8003014c90f6c3a722c75e9cafb397d1be3818bb84c3484e28ee79ae273d7d0b oid sha256:11c1056e013292e0543598f5690625b9bac0420a15fd1f37f6484daa3b8326fa
size 109707 size 60074

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Atkinson.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:f02a9465aaaa62b6fc0e9e0578362ccf65ce57bf7a8e1e2899f254863e72807f oid sha256:3fcf9b7e4ee34e80e8811f94940aff09a5392c21019fc86b145d16fd9c6b1cd2
size 100060 size 57501

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_Burks.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:f50c240c062cb66e820e8f632107e63bc0de85013143a81975e56f0b72499d8f oid sha256:4f0d9a43d8a47e00f6e5932b57f99565370a7239496fdbe162fb774497c4ef2a
size 102871 size 59377

4
tests/Images/External/ReferenceOutput/DitherTests/DiffusionFilter_WorksWithAllErrorDiffusers_CalliphoraPartial_FloydSteinberg.png

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:aa6c2bb78faaf689cf979dd87b3a24b6405720755ce16c028cafab690ac7b318 oid sha256:d4a64da29f144d4d4c525ea45e56819e02a46030ae09542be01fdd8ffc85a295
size 104334 size 60377

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

Loading…
Cancel
Save