Browse Source

Merge branch 'master' into icc-color-conversion

pull/1567/head
James Jackson-South 5 years ago
parent
commit
4cde4ef976
  1. 141
      .github/workflows/build-and-test.yml
  2. 4
      .gitignore
  3. 5
      Directory.Build.props
  4. 167
      ImageSharp.sln
  5. 2
      shared-infrastructure
  6. 5
      src/ImageSharp/Advanced/AotCompilerTools.cs
  7. 1
      src/ImageSharp/Advanced/ParallelExecutionSettings.cs
  8. 151
      src/ImageSharp/Color/Color.Conversions.cs
  9. 106
      src/ImageSharp/Color/Color.cs
  10. 2
      src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.cs
  11. 21
      src/ImageSharp/Common/Helpers/ExifResolutionValues.cs
  12. 4
      src/ImageSharp/Common/Helpers/InliningOptions.cs
  13. 8
      src/ImageSharp/Common/Helpers/Numerics.cs
  14. 145
      src/ImageSharp/Common/Helpers/SimdUtils.HwIntrinsics.cs
  15. 2
      src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs
  16. 25
      src/ImageSharp/Common/Helpers/UnitConverter.cs
  17. 170
      src/ImageSharp/Compression/Zlib/Adler32.cs
  18. 180
      src/ImageSharp/Compression/Zlib/Crc32.cs
  19. 33
      src/ImageSharp/Configuration.cs
  20. 1
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  21. 1
      src/ImageSharp/Formats/Gif/LzwEncoder.cs
  22. 104
      src/ImageSharp/Formats/ImageExtensions.Save.cs
  23. 1
      src/ImageSharp/Formats/ImageExtensions.Save.tt
  24. 193
      src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs
  25. 149
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Intrinsic.cs
  26. 2
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.cs
  27. 412
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
  28. 47
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx2.cs
  29. 45
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykBasic.cs
  30. 34
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector8.cs
  31. 32
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx2.cs
  32. 38
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleBasic.cs
  33. 40
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx2.cs
  34. 33
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbBasic.cs
  35. 40
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs
  36. 50
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx2.cs
  37. 37
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs
  38. 52
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector4.cs
  39. 47
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector8.cs
  40. 64
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx2.cs
  41. 43
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKBasic.cs
  42. 60
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector8.cs
  43. 15
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.VectorizedJpegColorConverter.cs
  44. 156
      src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs
  45. 6
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs
  46. 231
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
  47. 17
      src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs
  48. 32
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs
  49. 33
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs
  50. 64
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs
  51. 91
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs
  52. 181
      src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs
  53. 144
      src/ImageSharp/Formats/Jpeg/Components/Decoder/QualityEvaluator.cs
  54. 44
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs
  55. 172
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
  56. 21
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanLut.cs
  57. 561
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
  58. 8
      src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs
  59. 114
      src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs
  60. 8
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs
  61. 8
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs
  62. 161
      src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.Intrinsic.cs
  63. 561
      src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.cs
  64. 199
      src/ImageSharp/Formats/Jpeg/Components/Quantization.cs
  65. 300
      src/ImageSharp/Formats/Jpeg/Components/ZigZag.Intrinsic.cs
  66. 79
      src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs
  67. 14
      src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs
  68. 49
      src/ImageSharp/Formats/Jpeg/JpegColorType.cs
  69. 55
      src/ImageSharp/Formats/Jpeg/JpegConstants.cs
  70. 4
      src/ImageSharp/Formats/Jpeg/JpegDecoder.cs
  71. 605
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  72. 40
      src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
  73. 430
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  74. 81
      src/ImageSharp/Formats/Jpeg/JpegMetadata.cs
  75. 23
      src/ImageSharp/Formats/Jpeg/JpegSubsample.cs
  76. 12
      src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
  77. 26
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  78. 38
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  79. 8
      src/ImageSharp/Formats/Png/PngEncoderOptions.cs
  80. 12
      src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs
  81. 2
      src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsWriter.cs
  82. 49
      src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs
  83. 154
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittReferenceScanline.cs
  84. 27
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCode.cs
  85. 73
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCodeType.cs
  86. 32
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs
  87. 94
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs
  88. 17
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs
  89. 73
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs
  90. 38
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs
  91. 6
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs
  92. 5
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs
  93. 33
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs
  94. 394
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs
  95. 64
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs
  96. 159
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs
  97. 254
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs
  98. 282
      src/ImageSharp/Formats/Tiff/Compression/HorizontalPredictor.cs
  99. 20
      src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs
  100. 6
      src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs

141
.github/workflows/build-and-test.yml

@ -1,19 +1,37 @@
name: Build name: Build
on: on:
push: push:
branches: branches:
- master - master
tags: tags:
- "v*" - "v*"
pull_request: pull_request:
branches: branches:
- master - master
jobs: jobs:
Build: Build:
strategy: strategy:
matrix: matrix:
options: options:
- os: ubuntu-latest
framework: net6.0
sdk: 6.0.x
sdk-preview: true
runtime: -x64
codecov: false
- os: macos-latest
framework: net6.0
sdk: 6.0.x
sdk-preview: true
runtime: -x64
codecov: false
- os: windows-latest
framework: net6.0
sdk: 6.0.x
sdk-preview: true
runtime: -x64
codecov: false
- os: ubuntu-latest - os: ubuntu-latest
framework: net5.0 framework: net5.0
runtime: -x64 runtime: -x64
@ -52,37 +70,38 @@ jobs:
codecov: false codecov: false
runs-on: ${{matrix.options.os}} runs-on: ${{matrix.options.os}}
if: "!contains(github.event.head_commit.message, '[skip ci]')"
steps: steps:
- uses: actions/checkout@v2 - name: Git Config
shell: bash
run: |
git config --global core.autocrlf false
git config --global core.longpaths true
- name: Git Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: recursive
# See https://github.com/actions/checkout/issues/165#issuecomment-657673315 # See https://github.com/actions/checkout/issues/165#issuecomment-657673315
- name: Create LFS file list - name: Git Create LFS FileList
run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id
- name: Restore LFS cache - name: Git Setup LFS Cache
uses: actions/cache@v2 uses: actions/cache@v2
id: lfs-cache id: lfs-cache
with: with:
path: .git/lfs path: .git/lfs
key: ${{ runner.os }}-lfs-${{ hashFiles('.lfs-assets-id') }}-v1 key: ${{ runner.os }}-lfs-${{ hashFiles('.lfs-assets-id') }}-v1
- name: Git LFS Pull - name: Git Pull LFS
run: git lfs pull run: git lfs pull
- name: Install NuGet - name: NuGet Install
uses: NuGet/setup-nuget@v1 uses: NuGet/setup-nuget@v1
- name: Setup Git - name: NuGet Setup Cache
shell: bash
run: |
git config --global core.autocrlf false
git config --global core.longpaths true
git fetch --prune --unshallow
git submodule -q update --init --recursive
- name: Setup NuGet Cache
uses: actions/cache@v2 uses: actions/cache@v2
id: nuget-cache id: nuget-cache
with: with:
@ -90,60 +109,94 @@ jobs:
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }}
restore-keys: ${{ runner.os }}-nuget- restore-keys: ${{ runner.os }}-nuget-
- name: Build - name: DotNet Setup Preview
if: ${{ matrix.options.sdk-preview == true }}
uses: actions/setup-dotnet@v1
with:
dotnet-version: ${{ matrix.options.sdk }}
include-prerelease: true
- name: DotNet Build
if: ${{ matrix.options.sdk-preview != true }}
shell: pwsh shell: pwsh
run: ./ci-build.ps1 "${{matrix.options.framework}}" run: ./ci-build.ps1 "${{matrix.options.framework}}"
env: env:
SIXLABORS_TESTING: True SIXLABORS_TESTING: True
- name: Test - name: DotNet Build Preview
if: ${{ matrix.options.sdk-preview == true }}
shell: pwsh
run: ./ci-build.ps1 "${{matrix.options.framework}}"
env:
SIXLABORS_TESTING_PREVIEW: True
- name: DotNet Test
if: ${{ matrix.options.sdk-preview != true }}
shell: pwsh shell: pwsh
run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}" run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}"
env: env:
SIXLABORS_TESTING: True SIXLABORS_TESTING: True
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit
- name: DotNet Test Preview
if: ${{ matrix.options.sdk-preview == true }}
shell: pwsh
run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}"
env:
SIXLABORS_TESTING_PREVIEW: True
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit
- name: Export Failed Output - name: Export Failed Output
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
if: failure() if: failure()
with: with:
name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip
path: tests/Images/ActualOutput/ path: tests/Images/ActualOutput/
- name: Update Codecov - name: Codecov Update
uses: codecov/codecov-action@v1 uses: codecov/codecov-action@v1
if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors') if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors')
with: with:
flags: unittests flags: unittests
Publish: Publish:
needs: [Build] needs: [Build]
runs-on: windows-latest runs-on: ubuntu-latest
if: (github.event_name == 'push') if: (github.event_name == 'push')
steps: steps:
- uses: actions/checkout@v2 - name: Git Config
- name: Install NuGet
uses: NuGet/setup-nuget@v1
- name: Setup Git
shell: bash shell: bash
run: | run: |
git config --global core.autocrlf false git config --global core.autocrlf false
git config --global core.longpaths true git config --global core.longpaths true
git fetch --prune --unshallow
git submodule -q update --init --recursive
- name: Pack - name: Git Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: recursive
- name: NuGet Install
uses: NuGet/setup-nuget@v1
- name: NuGet Setup Cache
uses: actions/cache@v2
id: nuget-cache
with:
path: ~/.nuget
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }}
restore-keys: ${{ runner.os }}-nuget-
- name: DotNet Pack
shell: pwsh shell: pwsh
run: ./ci-pack.ps1 run: ./ci-pack.ps1
- name: Publish to MyGet - name: MyGet Publish
shell: pwsh shell: pwsh
run: | run: |
nuget.exe push .\artifacts\*.nupkg ${{secrets.MYGET_TOKEN}} -Source https://www.myget.org/F/sixlabors/api/v2/package dotnet nuget push .\artifacts\*.nupkg -k ${{secrets.MYGET_TOKEN}} -s https://www.myget.org/F/sixlabors/api/v2/package
nuget.exe push .\artifacts\*.snupkg ${{secrets.MYGET_TOKEN}} -Source https://www.myget.org/F/sixlabors/api/v3/index.json dotnet nuget push .\artifacts\*.snupkg -k ${{secrets.MYGET_TOKEN}} -s https://www.myget.org/F/sixlabors/api/v3/index.json
# TODO: If github.ref starts with 'refs/tags' then it was tag push and we can optionally push out package to nuget.org # TODO: If github.ref starts with 'refs/tags' then it was tag push and we can optionally push out package to nuget.org

4
.gitignore

@ -223,3 +223,7 @@ artifacts/
**/Images/ReferenceOutput **/Images/ReferenceOutput
**/Images/Input/MemoryStress **/Images/Input/MemoryStress
.DS_Store .DS_Store
#lfs
hooks/**
lfs/**

5
Directory.Build.props

@ -18,6 +18,11 @@
<!-- 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" />
<PropertyGroup Condition="$(SIXLABORS_TESTING_PREVIEW) == true">
<!-- Workaround various issues bound to implicit language features. -->
<LangVersion>preview</LangVersion>
</PropertyGroup>
<!-- <!--
Ensure all custom build configurations based upon "Release" are optimized. Ensure all custom build configurations based upon "Release" are optimized.
This is easier than setting each project individually. This is easier than setting each project individually.

167
ImageSharp.sln

@ -1,3 +1,4 @@

Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16 # Visual Studio Version 16
VisualStudioVersion = 16.0.28902.138 VisualStudioVersion = 16.0.28902.138
@ -378,6 +379,170 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Png", "Png", "{E1C42A6F-913
tests\Images\Input\Png\zlib-ztxt-bad-header.png = tests\Images\Input\Png\zlib-ztxt-bad-header.png tests\Images\Input\Png\zlib-ztxt-bad-header.png = tests\Images\Input\Png\zlib-ztxt-bad-header.png
EndProjectSection EndProjectSection
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Webp", "Webp", "{983A31E2-5E26-4058-BD6E-03B4922D4BBF}"
ProjectSection(SolutionItems) = preProject
tests\Images\Input\Webp\1602311202.webp = tests\Images\Input\Webp\1602311202.webp
tests\Images\Input\Webp\alpha_color_cache.webp = tests\Images\Input\Webp\alpha_color_cache.webp
tests\Images\Input\Webp\alpha_filter_0_method_0.webp = tests\Images\Input\Webp\alpha_filter_0_method_0.webp
tests\Images\Input\Webp\alpha_filter_0_method_1.webp = tests\Images\Input\Webp\alpha_filter_0_method_1.webp
tests\Images\Input\Webp\alpha_filter_1.webp = tests\Images\Input\Webp\alpha_filter_1.webp
tests\Images\Input\Webp\alpha_filter_1_method_0.webp = tests\Images\Input\Webp\alpha_filter_1_method_0.webp
tests\Images\Input\Webp\alpha_filter_1_method_1.webp = tests\Images\Input\Webp\alpha_filter_1_method_1.webp
tests\Images\Input\Webp\alpha_filter_2.webp = tests\Images\Input\Webp\alpha_filter_2.webp
tests\Images\Input\Webp\alpha_filter_2_method_0.webp = tests\Images\Input\Webp\alpha_filter_2_method_0.webp
tests\Images\Input\Webp\alpha_filter_2_method_1.webp = tests\Images\Input\Webp\alpha_filter_2_method_1.webp
tests\Images\Input\Webp\alpha_filter_3.webp = tests\Images\Input\Webp\alpha_filter_3.webp
tests\Images\Input\Webp\alpha_filter_3_method_0.webp = tests\Images\Input\Webp\alpha_filter_3_method_0.webp
tests\Images\Input\Webp\alpha_filter_3_method_1.webp = tests\Images\Input\Webp\alpha_filter_3_method_1.webp
tests\Images\Input\Webp\alpha_no_compression.webp = tests\Images\Input\Webp\alpha_no_compression.webp
tests\Images\Input\Webp\animated-webp.webp = tests\Images\Input\Webp\animated-webp.webp
tests\Images\Input\Webp\animated2.webp = tests\Images\Input\Webp\animated2.webp
tests\Images\Input\Webp\animated3.webp = tests\Images\Input\Webp\animated3.webp
tests\Images\Input\Webp\animated_lossy.webp = tests\Images\Input\Webp\animated_lossy.webp
tests\Images\Input\Webp\bad_palette_index.webp = tests\Images\Input\Webp\bad_palette_index.webp
tests\Images\Input\Webp\big_endian_bug_393.webp = tests\Images\Input\Webp\big_endian_bug_393.webp
tests\Images\Input\Webp\bike_lossless.webp = tests\Images\Input\Webp\bike_lossless.webp
tests\Images\Input\Webp\bike_lossless_small.webp = tests\Images\Input\Webp\bike_lossless_small.webp
tests\Images\Input\Webp\bike_lossy.webp = tests\Images\Input\Webp\bike_lossy.webp
tests\Images\Input\Webp\bike_lossy_complex_filter.webp = tests\Images\Input\Webp\bike_lossy_complex_filter.webp
tests\Images\Input\Webp\bryce.webp = tests\Images\Input\Webp\bryce.webp
tests\Images\Input\Webp\bug3.webp = tests\Images\Input\Webp\bug3.webp
tests\Images\Input\Webp\color_cache_bits_11.webp = tests\Images\Input\Webp\color_cache_bits_11.webp
tests\Images\Input\Webp\earth_lossless.webp = tests\Images\Input\Webp\earth_lossless.webp
tests\Images\Input\Webp\earth_lossy.webp = tests\Images\Input\Webp\earth_lossy.webp
tests\Images\Input\Webp\exif_lossless.webp = tests\Images\Input\Webp\exif_lossless.webp
tests\Images\Input\Webp\exif_lossy.webp = tests\Images\Input\Webp\exif_lossy.webp
tests\Images\Input\Webp\flag_of_germany.png = tests\Images\Input\Webp\flag_of_germany.png
tests\Images\Input\Webp\lossless1.webp = tests\Images\Input\Webp\lossless1.webp
tests\Images\Input\Webp\lossless2.webp = tests\Images\Input\Webp\lossless2.webp
tests\Images\Input\Webp\lossless3.webp = tests\Images\Input\Webp\lossless3.webp
tests\Images\Input\Webp\lossless4.webp = tests\Images\Input\Webp\lossless4.webp
tests\Images\Input\Webp\lossless_alpha_small.webp = tests\Images\Input\Webp\lossless_alpha_small.webp
tests\Images\Input\Webp\lossless_big_random_alpha.webp = tests\Images\Input\Webp\lossless_big_random_alpha.webp
tests\Images\Input\Webp\lossless_color_transform.bmp = tests\Images\Input\Webp\lossless_color_transform.bmp
tests\Images\Input\Webp\lossless_color_transform.pam = tests\Images\Input\Webp\lossless_color_transform.pam
tests\Images\Input\Webp\lossless_color_transform.pgm = tests\Images\Input\Webp\lossless_color_transform.pgm
tests\Images\Input\Webp\lossless_color_transform.ppm = tests\Images\Input\Webp\lossless_color_transform.ppm
tests\Images\Input\Webp\lossless_color_transform.tiff = tests\Images\Input\Webp\lossless_color_transform.tiff
tests\Images\Input\Webp\lossless_color_transform.webp = tests\Images\Input\Webp\lossless_color_transform.webp
tests\Images\Input\Webp\lossless_vec_1_0.webp = tests\Images\Input\Webp\lossless_vec_1_0.webp
tests\Images\Input\Webp\lossless_vec_1_1.webp = tests\Images\Input\Webp\lossless_vec_1_1.webp
tests\Images\Input\Webp\lossless_vec_1_10.webp = tests\Images\Input\Webp\lossless_vec_1_10.webp
tests\Images\Input\Webp\lossless_vec_1_11.webp = tests\Images\Input\Webp\lossless_vec_1_11.webp
tests\Images\Input\Webp\lossless_vec_1_12.webp = tests\Images\Input\Webp\lossless_vec_1_12.webp
tests\Images\Input\Webp\lossless_vec_1_13.webp = tests\Images\Input\Webp\lossless_vec_1_13.webp
tests\Images\Input\Webp\lossless_vec_1_14.webp = tests\Images\Input\Webp\lossless_vec_1_14.webp
tests\Images\Input\Webp\lossless_vec_1_15.webp = tests\Images\Input\Webp\lossless_vec_1_15.webp
tests\Images\Input\Webp\lossless_vec_1_2.webp = tests\Images\Input\Webp\lossless_vec_1_2.webp
tests\Images\Input\Webp\lossless_vec_1_3.webp = tests\Images\Input\Webp\lossless_vec_1_3.webp
tests\Images\Input\Webp\lossless_vec_1_4.webp = tests\Images\Input\Webp\lossless_vec_1_4.webp
tests\Images\Input\Webp\lossless_vec_1_5.webp = tests\Images\Input\Webp\lossless_vec_1_5.webp
tests\Images\Input\Webp\lossless_vec_1_6.webp = tests\Images\Input\Webp\lossless_vec_1_6.webp
tests\Images\Input\Webp\lossless_vec_1_7.webp = tests\Images\Input\Webp\lossless_vec_1_7.webp
tests\Images\Input\Webp\lossless_vec_1_8.webp = tests\Images\Input\Webp\lossless_vec_1_8.webp
tests\Images\Input\Webp\lossless_vec_1_9.webp = tests\Images\Input\Webp\lossless_vec_1_9.webp
tests\Images\Input\Webp\lossless_vec_2_0.webp = tests\Images\Input\Webp\lossless_vec_2_0.webp
tests\Images\Input\Webp\lossless_vec_2_1.webp = tests\Images\Input\Webp\lossless_vec_2_1.webp
tests\Images\Input\Webp\lossless_vec_2_10.webp = tests\Images\Input\Webp\lossless_vec_2_10.webp
tests\Images\Input\Webp\lossless_vec_2_11.webp = tests\Images\Input\Webp\lossless_vec_2_11.webp
tests\Images\Input\Webp\lossless_vec_2_12.webp = tests\Images\Input\Webp\lossless_vec_2_12.webp
tests\Images\Input\Webp\lossless_vec_2_13.webp = tests\Images\Input\Webp\lossless_vec_2_13.webp
tests\Images\Input\Webp\lossless_vec_2_14.webp = tests\Images\Input\Webp\lossless_vec_2_14.webp
tests\Images\Input\Webp\lossless_vec_2_15.webp = tests\Images\Input\Webp\lossless_vec_2_15.webp
tests\Images\Input\Webp\lossless_vec_2_2.webp = tests\Images\Input\Webp\lossless_vec_2_2.webp
tests\Images\Input\Webp\lossless_vec_2_3.webp = tests\Images\Input\Webp\lossless_vec_2_3.webp
tests\Images\Input\Webp\lossless_vec_2_4.webp = tests\Images\Input\Webp\lossless_vec_2_4.webp
tests\Images\Input\Webp\lossless_vec_2_5.webp = tests\Images\Input\Webp\lossless_vec_2_5.webp
tests\Images\Input\Webp\lossless_vec_2_6.webp = tests\Images\Input\Webp\lossless_vec_2_6.webp
tests\Images\Input\Webp\lossless_vec_2_7.webp = tests\Images\Input\Webp\lossless_vec_2_7.webp
tests\Images\Input\Webp\lossless_vec_2_8.webp = tests\Images\Input\Webp\lossless_vec_2_8.webp
tests\Images\Input\Webp\lossless_vec_2_9.webp = tests\Images\Input\Webp\lossless_vec_2_9.webp
tests\Images\Input\Webp\lossless_vec_list.txt = tests\Images\Input\Webp\lossless_vec_list.txt
tests\Images\Input\Webp\lossless_with_iccp.webp = tests\Images\Input\Webp\lossless_with_iccp.webp
tests\Images\Input\Webp\lossy_alpha1.webp = tests\Images\Input\Webp\lossy_alpha1.webp
tests\Images\Input\Webp\lossy_alpha2.webp = tests\Images\Input\Webp\lossy_alpha2.webp
tests\Images\Input\Webp\lossy_alpha3.webp = tests\Images\Input\Webp\lossy_alpha3.webp
tests\Images\Input\Webp\lossy_alpha4.webp = tests\Images\Input\Webp\lossy_alpha4.webp
tests\Images\Input\Webp\lossy_extreme_probabilities.webp = tests\Images\Input\Webp\lossy_extreme_probabilities.webp
tests\Images\Input\Webp\lossy_q0_f100.webp = tests\Images\Input\Webp\lossy_q0_f100.webp
tests\Images\Input\Webp\lossy_with_iccp.webp = tests\Images\Input\Webp\lossy_with_iccp.webp
tests\Images\Input\Webp\near_lossless_75.webp = tests\Images\Input\Webp\near_lossless_75.webp
tests\Images\Input\Webp\peak.png = tests\Images\Input\Webp\peak.png
tests\Images\Input\Webp\rgb_pattern_100x100.png = tests\Images\Input\Webp\rgb_pattern_100x100.png
tests\Images\Input\Webp\rgb_pattern_63x63.png = tests\Images\Input\Webp\rgb_pattern_63x63.png
tests\Images\Input\Webp\rgb_pattern_80x80.png = tests\Images\Input\Webp\rgb_pattern_80x80.png
tests\Images\Input\Webp\segment01.webp = tests\Images\Input\Webp\segment01.webp
tests\Images\Input\Webp\segment02.webp = tests\Images\Input\Webp\segment02.webp
tests\Images\Input\Webp\segment03.webp = tests\Images\Input\Webp\segment03.webp
tests\Images\Input\Webp\small_13x1.webp = tests\Images\Input\Webp\small_13x1.webp
tests\Images\Input\Webp\small_1x1.webp = tests\Images\Input\Webp\small_1x1.webp
tests\Images\Input\Webp\small_1x13.webp = tests\Images\Input\Webp\small_1x13.webp
tests\Images\Input\Webp\small_31x13.webp = tests\Images\Input\Webp\small_31x13.webp
tests\Images\Input\Webp\sticker.webp = tests\Images\Input\Webp\sticker.webp
tests\Images\Input\Webp\test-nostrong.webp = tests\Images\Input\Webp\test-nostrong.webp
tests\Images\Input\Webp\test.webp = tests\Images\Input\Webp\test.webp
tests\Images\Input\Webp\testpattern_opaque.png = tests\Images\Input\Webp\testpattern_opaque.png
tests\Images\Input\Webp\testpattern_opaque_small.png = tests\Images\Input\Webp\testpattern_opaque_small.png
tests\Images\Input\Webp\very_short.webp = tests\Images\Input\Webp\very_short.webp
tests\Images\Input\Webp\vp80-00-comprehensive-001.webp = tests\Images\Input\Webp\vp80-00-comprehensive-001.webp
tests\Images\Input\Webp\vp80-00-comprehensive-002.webp = tests\Images\Input\Webp\vp80-00-comprehensive-002.webp
tests\Images\Input\Webp\vp80-00-comprehensive-003.webp = tests\Images\Input\Webp\vp80-00-comprehensive-003.webp
tests\Images\Input\Webp\vp80-00-comprehensive-004.webp = tests\Images\Input\Webp\vp80-00-comprehensive-004.webp
tests\Images\Input\Webp\vp80-00-comprehensive-005.webp = tests\Images\Input\Webp\vp80-00-comprehensive-005.webp
tests\Images\Input\Webp\vp80-00-comprehensive-006.webp = tests\Images\Input\Webp\vp80-00-comprehensive-006.webp
tests\Images\Input\Webp\vp80-00-comprehensive-007.webp = tests\Images\Input\Webp\vp80-00-comprehensive-007.webp
tests\Images\Input\Webp\vp80-00-comprehensive-008.webp = tests\Images\Input\Webp\vp80-00-comprehensive-008.webp
tests\Images\Input\Webp\vp80-00-comprehensive-009.webp = tests\Images\Input\Webp\vp80-00-comprehensive-009.webp
tests\Images\Input\Webp\vp80-00-comprehensive-010.webp = tests\Images\Input\Webp\vp80-00-comprehensive-010.webp
tests\Images\Input\Webp\vp80-00-comprehensive-011.webp = tests\Images\Input\Webp\vp80-00-comprehensive-011.webp
tests\Images\Input\Webp\vp80-00-comprehensive-012.webp = tests\Images\Input\Webp\vp80-00-comprehensive-012.webp
tests\Images\Input\Webp\vp80-00-comprehensive-013.webp = tests\Images\Input\Webp\vp80-00-comprehensive-013.webp
tests\Images\Input\Webp\vp80-00-comprehensive-014.webp = tests\Images\Input\Webp\vp80-00-comprehensive-014.webp
tests\Images\Input\Webp\vp80-00-comprehensive-015.webp = tests\Images\Input\Webp\vp80-00-comprehensive-015.webp
tests\Images\Input\Webp\vp80-00-comprehensive-016.webp = tests\Images\Input\Webp\vp80-00-comprehensive-016.webp
tests\Images\Input\Webp\vp80-00-comprehensive-017.webp = tests\Images\Input\Webp\vp80-00-comprehensive-017.webp
tests\Images\Input\Webp\vp80-01-intra-1400.webp = tests\Images\Input\Webp\vp80-01-intra-1400.webp
tests\Images\Input\Webp\vp80-01-intra-1411.webp = tests\Images\Input\Webp\vp80-01-intra-1411.webp
tests\Images\Input\Webp\vp80-01-intra-1416.webp = tests\Images\Input\Webp\vp80-01-intra-1416.webp
tests\Images\Input\Webp\vp80-01-intra-1417.webp = tests\Images\Input\Webp\vp80-01-intra-1417.webp
tests\Images\Input\Webp\vp80-02-inter-1402.webp = tests\Images\Input\Webp\vp80-02-inter-1402.webp
tests\Images\Input\Webp\vp80-02-inter-1412.webp = tests\Images\Input\Webp\vp80-02-inter-1412.webp
tests\Images\Input\Webp\vp80-02-inter-1418.webp = tests\Images\Input\Webp\vp80-02-inter-1418.webp
tests\Images\Input\Webp\vp80-02-inter-1424.webp = tests\Images\Input\Webp\vp80-02-inter-1424.webp
tests\Images\Input\Webp\vp80-03-segmentation-1401.webp = tests\Images\Input\Webp\vp80-03-segmentation-1401.webp
tests\Images\Input\Webp\vp80-03-segmentation-1403.webp = tests\Images\Input\Webp\vp80-03-segmentation-1403.webp
tests\Images\Input\Webp\vp80-03-segmentation-1407.webp = tests\Images\Input\Webp\vp80-03-segmentation-1407.webp
tests\Images\Input\Webp\vp80-03-segmentation-1408.webp = tests\Images\Input\Webp\vp80-03-segmentation-1408.webp
tests\Images\Input\Webp\vp80-03-segmentation-1409.webp = tests\Images\Input\Webp\vp80-03-segmentation-1409.webp
tests\Images\Input\Webp\vp80-03-segmentation-1410.webp = tests\Images\Input\Webp\vp80-03-segmentation-1410.webp
tests\Images\Input\Webp\vp80-03-segmentation-1413.webp = tests\Images\Input\Webp\vp80-03-segmentation-1413.webp
tests\Images\Input\Webp\vp80-03-segmentation-1414.webp = tests\Images\Input\Webp\vp80-03-segmentation-1414.webp
tests\Images\Input\Webp\vp80-03-segmentation-1415.webp = tests\Images\Input\Webp\vp80-03-segmentation-1415.webp
tests\Images\Input\Webp\vp80-03-segmentation-1425.webp = tests\Images\Input\Webp\vp80-03-segmentation-1425.webp
tests\Images\Input\Webp\vp80-03-segmentation-1426.webp = tests\Images\Input\Webp\vp80-03-segmentation-1426.webp
tests\Images\Input\Webp\vp80-03-segmentation-1427.webp = tests\Images\Input\Webp\vp80-03-segmentation-1427.webp
tests\Images\Input\Webp\vp80-03-segmentation-1432.webp = tests\Images\Input\Webp\vp80-03-segmentation-1432.webp
tests\Images\Input\Webp\vp80-03-segmentation-1435.webp = tests\Images\Input\Webp\vp80-03-segmentation-1435.webp
tests\Images\Input\Webp\vp80-03-segmentation-1436.webp = tests\Images\Input\Webp\vp80-03-segmentation-1436.webp
tests\Images\Input\Webp\vp80-03-segmentation-1437.webp = tests\Images\Input\Webp\vp80-03-segmentation-1437.webp
tests\Images\Input\Webp\vp80-03-segmentation-1441.webp = tests\Images\Input\Webp\vp80-03-segmentation-1441.webp
tests\Images\Input\Webp\vp80-03-segmentation-1442.webp = tests\Images\Input\Webp\vp80-03-segmentation-1442.webp
tests\Images\Input\Webp\vp80-04-partitions-1404.webp = tests\Images\Input\Webp\vp80-04-partitions-1404.webp
tests\Images\Input\Webp\vp80-04-partitions-1405.webp = tests\Images\Input\Webp\vp80-04-partitions-1405.webp
tests\Images\Input\Webp\vp80-04-partitions-1406.webp = tests\Images\Input\Webp\vp80-04-partitions-1406.webp
tests\Images\Input\Webp\vp80-05-sharpness-1428.webp = tests\Images\Input\Webp\vp80-05-sharpness-1428.webp
tests\Images\Input\Webp\vp80-05-sharpness-1429.webp = tests\Images\Input\Webp\vp80-05-sharpness-1429.webp
tests\Images\Input\Webp\vp80-05-sharpness-1430.webp = tests\Images\Input\Webp\vp80-05-sharpness-1430.webp
tests\Images\Input\Webp\vp80-05-sharpness-1431.webp = tests\Images\Input\Webp\vp80-05-sharpness-1431.webp
tests\Images\Input\Webp\vp80-05-sharpness-1433.webp = tests\Images\Input\Webp\vp80-05-sharpness-1433.webp
tests\Images\Input\Webp\vp80-05-sharpness-1434.webp = tests\Images\Input\Webp\vp80-05-sharpness-1434.webp
tests\Images\Input\Webp\vp80-05-sharpness-1438.webp = tests\Images\Input\Webp\vp80-05-sharpness-1438.webp
tests\Images\Input\Webp\vp80-05-sharpness-1439.webp = tests\Images\Input\Webp\vp80-05-sharpness-1439.webp
tests\Images\Input\Webp\vp80-05-sharpness-1440.webp = tests\Images\Input\Webp\vp80-05-sharpness-1440.webp
tests\Images\Input\Webp\vp80-05-sharpness-1443.webp = tests\Images\Input\Webp\vp80-05-sharpness-1443.webp
tests\Images\Input\Webp\yuv_test.png = tests\Images\Input\Webp\yuv_test.png
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests", "tests\ImageSharp.Tests\ImageSharp.Tests.csproj", "{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Tests", "tests\ImageSharp.Tests\ImageSharp.Tests.csproj", "{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Benchmarks", "tests\ImageSharp.Benchmarks\ImageSharp.Benchmarks.csproj", "{2BF743D8-2A06-412D-96D7-F448F00C5EA5}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Benchmarks", "tests\ImageSharp.Benchmarks\ImageSharp.Benchmarks.csproj", "{2BF743D8-2A06-412D-96D7-F448F00C5EA5}"
@ -403,6 +568,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "issues", "issues", "{670DD4
tests\Images\Input\Png\issues\Issue_1127.png = tests\Images\Input\Png\issues\Issue_1127.png tests\Images\Input\Png\issues\Issue_1127.png = tests\Images\Input\Png\issues\Issue_1127.png
tests\Images\Input\Png\issues\Issue_1177_1.png = tests\Images\Input\Png\issues\Issue_1177_1.png tests\Images\Input\Png\issues\Issue_1177_1.png = tests\Images\Input\Png\issues\Issue_1177_1.png
tests\Images\Input\Png\issues\Issue_1177_2.png = tests\Images\Input\Png\issues\Issue_1177_2.png tests\Images\Input\Png\issues\Issue_1177_2.png = tests\Images\Input\Png\issues\Issue_1177_2.png
tests\Images\Input\Png\issues\Issue_1765_Net6DeflateStreamRead.png = tests\Images\Input\Png\issues\Issue_1765_Net6DeflateStreamRead.png
tests\Images\Input\Png\issues\Issue_410.png = tests\Images\Input\Png\issues\Issue_410.png tests\Images\Input\Png\issues\Issue_410.png = tests\Images\Input\Png\issues\Issue_410.png
tests\Images\Input\Png\issues\Issue_935.png = tests\Images\Input\Png\issues\Issue_935.png tests\Images\Input\Png\issues\Issue_935.png = tests\Images\Input\Png\issues\Issue_935.png
EndProjectSection EndProjectSection
@ -538,6 +704,7 @@ Global
{6458AFCB-A159-47D5-8F2B-50C95C0915E0} = {DB21FED7-E8CB-4B00-9EB2-9144D32A590A} {6458AFCB-A159-47D5-8F2B-50C95C0915E0} = {DB21FED7-E8CB-4B00-9EB2-9144D32A590A}
{39F5197B-CF6C-41A5-9739-7F97E78BB104} = {6458AFCB-A159-47D5-8F2B-50C95C0915E0} {39F5197B-CF6C-41A5-9739-7F97E78BB104} = {6458AFCB-A159-47D5-8F2B-50C95C0915E0}
{E1C42A6F-913B-4A7B-B1A8-2BB62843B254} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66} {E1C42A6F-913B-4A7B-B1A8-2BB62843B254} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
{983A31E2-5E26-4058-BD6E-03B4922D4BBF} = {9DA226A1-8656-49A8-A58A-A8B5C081AD66}
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
{2BF743D8-2A06-412D-96D7-F448F00C5EA5} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC} {2BF743D8-2A06-412D-96D7-F448F00C5EA5} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
{C0D7754B-5277-438E-ABEB-2BA34401B5A7} = {1799C43E-5C54-4A8F-8D64-B1475241DB0D} {C0D7754B-5277-438E-ABEB-2BA34401B5A7} = {1799C43E-5C54-4A8F-8D64-B1475241DB0D}

2
shared-infrastructure

@ -1 +1 @@
Subproject commit 9b94ebc4be9b7a8d7620c257e6ee485455973332 Subproject commit a042aba176cdb840d800c6ed4cfe41a54fb7b1e3

5
src/ImageSharp/Advanced/AotCompilerTools.cs

@ -13,6 +13,7 @@ using SixLabors.ImageSharp.Formats.Jpeg.Components;
using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
@ -195,6 +196,7 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileImageEncoderInternals<TPixel>() private static void AotCompileImageEncoderInternals<TPixel>()
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
default(WebpEncoderCore).Encode<TPixel>(default, default, default);
default(BmpEncoderCore).Encode<TPixel>(default, default, default); default(BmpEncoderCore).Encode<TPixel>(default, default, default);
default(GifEncoderCore).Encode<TPixel>(default, default, default); default(GifEncoderCore).Encode<TPixel>(default, default, default);
default(JpegEncoderCore).Encode<TPixel>(default, default, default); default(JpegEncoderCore).Encode<TPixel>(default, default, default);
@ -211,6 +213,7 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileImageDecoderInternals<TPixel>() private static void AotCompileImageDecoderInternals<TPixel>()
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
default(WebpDecoderCore).Decode<TPixel>(default, default, default);
default(BmpDecoderCore).Decode<TPixel>(default, default, default); default(BmpDecoderCore).Decode<TPixel>(default, default, default);
default(GifDecoderCore).Decode<TPixel>(default, default, default); default(GifDecoderCore).Decode<TPixel>(default, default, default);
default(JpegDecoderCore).Decode<TPixel>(default, default, default); default(JpegDecoderCore).Decode<TPixel>(default, default, default);
@ -227,6 +230,7 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileImageEncoders<TPixel>() private static void AotCompileImageEncoders<TPixel>()
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
AotCompileImageEncoder<TPixel, WebpEncoder>();
AotCompileImageEncoder<TPixel, BmpEncoder>(); AotCompileImageEncoder<TPixel, BmpEncoder>();
AotCompileImageEncoder<TPixel, GifEncoder>(); AotCompileImageEncoder<TPixel, GifEncoder>();
AotCompileImageEncoder<TPixel, JpegEncoder>(); AotCompileImageEncoder<TPixel, JpegEncoder>();
@ -243,6 +247,7 @@ namespace SixLabors.ImageSharp.Advanced
private static void AotCompileImageDecoders<TPixel>() private static void AotCompileImageDecoders<TPixel>()
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
AotCompileImageDecoder<TPixel, WebpDecoder>();
AotCompileImageDecoder<TPixel, BmpDecoder>(); AotCompileImageDecoder<TPixel, BmpDecoder>();
AotCompileImageDecoder<TPixel, GifDecoder>(); AotCompileImageDecoder<TPixel, GifDecoder>();
AotCompileImageDecoder<TPixel, JpegDecoder>(); AotCompileImageDecoder<TPixel, JpegDecoder>();

1
src/ImageSharp/Advanced/ParallelExecutionSettings.cs

@ -3,7 +3,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Advanced namespace SixLabors.ImageSharp.Advanced

151
src/ImageSharp/Color/Color.Conversions.cs

@ -3,7 +3,6 @@
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp namespace SixLabors.ImageSharp
@ -18,56 +17,118 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="pixel">The <see cref="Rgba64"/> containing the color information.</param> /// <param name="pixel">The <see cref="Rgba64"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public Color(Rgba64 pixel) => this.data = pixel; public Color(Rgba64 pixel)
{
this.data = pixel;
this.boxedHighPrecisionPixel = null;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="pixel">The <see cref="Rgb48"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public Color(Rgb48 pixel)
{
this.data = new Rgba64(pixel.R, pixel.G, pixel.B, ushort.MaxValue);
this.boxedHighPrecisionPixel = null;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="pixel">The <see cref="La32"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public Color(La32 pixel)
{
this.data = new Rgba64(pixel.L, pixel.L, pixel.L, pixel.A);
this.boxedHighPrecisionPixel = null;
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="pixel">The <see cref="L16"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public Color(L16 pixel)
{
this.data = new Rgba64(pixel.PackedValue, pixel.PackedValue, pixel.PackedValue, ushort.MaxValue);
this.boxedHighPrecisionPixel = null;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct. /// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary> /// </summary>
/// <param name="pixel">The <see cref="Rgba32"/> containing the color information.</param> /// <param name="pixel">The <see cref="Rgba32"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public Color(Rgba32 pixel) => this.data = new Rgba64(pixel); public Color(Rgba32 pixel)
{
this.data = new Rgba64(pixel);
this.boxedHighPrecisionPixel = null;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct. /// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary> /// </summary>
/// <param name="pixel">The <see cref="Argb32"/> containing the color information.</param> /// <param name="pixel">The <see cref="Argb32"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public Color(Argb32 pixel) => this.data = new Rgba64(pixel); public Color(Argb32 pixel)
{
this.data = new Rgba64(pixel);
this.boxedHighPrecisionPixel = null;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct. /// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary> /// </summary>
/// <param name="pixel">The <see cref="Bgra32"/> containing the color information.</param> /// <param name="pixel">The <see cref="Bgra32"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public Color(Bgra32 pixel) => this.data = new Rgba64(pixel); public Color(Bgra32 pixel)
{
this.data = new Rgba64(pixel);
this.boxedHighPrecisionPixel = null;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct. /// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary> /// </summary>
/// <param name="pixel">The <see cref="Rgb24"/> containing the color information.</param> /// <param name="pixel">The <see cref="Rgb24"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public Color(Rgb24 pixel) => this.data = new Rgba64(pixel); public Color(Rgb24 pixel)
{
this.data = new Rgba64(pixel);
this.boxedHighPrecisionPixel = null;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct. /// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary> /// </summary>
/// <param name="pixel">The <see cref="Bgr24"/> containing the color information.</param> /// <param name="pixel">The <see cref="Bgr24"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public Color(Bgr24 pixel) => this.data = new Rgba64(pixel); public Color(Bgr24 pixel)
{
this.data = new Rgba64(pixel);
this.boxedHighPrecisionPixel = null;
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct. /// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary> /// </summary>
/// <param name="vector">The <see cref="Vector4"/> containing the color information.</param> /// <param name="vector">The <see cref="Vector4"/> containing the color information.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public Color(Vector4 vector) => this.data = new Rgba64(vector); public Color(Vector4 vector)
{
vector = Numerics.Clamp(vector, Vector4.Zero, Vector4.One);
this.boxedHighPrecisionPixel = new RgbaVector(vector.X, vector.Y, vector.Z, vector.W);
this.data = default;
}
/// <summary> /// <summary>
/// Converts a <see cref="Color"/> to <see cref="Vector4"/>. /// Converts a <see cref="Color"/> to <see cref="Vector4"/>.
/// </summary> /// </summary>
/// <param name="color">The <see cref="Color"/>.</param> /// <param name="color">The <see cref="Color"/>.</param>
/// <returns>The <see cref="Vector4"/>.</returns> /// <returns>The <see cref="Vector4"/>.</returns>
public static explicit operator Vector4(Color color) => color.data.ToVector4(); public static explicit operator Vector4(Color color) => color.ToVector4();
/// <summary> /// <summary>
/// Converts an <see cref="Vector4"/> to <see cref="Color"/>. /// Converts an <see cref="Vector4"/> to <see cref="Color"/>.
@ -75,24 +136,82 @@ namespace SixLabors.ImageSharp
/// <param name="source">The <see cref="Vector4"/>.</param> /// <param name="source">The <see cref="Vector4"/>.</param>
/// <returns>The <see cref="Color"/>.</returns> /// <returns>The <see cref="Color"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static explicit operator Color(Vector4 source) => new Color(source); public static explicit operator Color(Vector4 source) => new(source);
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
internal Rgba32 ToRgba32() => this.data.ToRgba32(); internal Rgba32 ToRgba32()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToRgba32();
}
Rgba32 value = default;
this.boxedHighPrecisionPixel.ToRgba32(ref value);
return value;
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
internal Bgra32 ToBgra32() => this.data.ToBgra32(); internal Bgra32 ToBgra32()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToBgra32();
}
Bgra32 value = default;
value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
return value;
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
internal Argb32 ToArgb32() => this.data.ToArgb32(); internal Argb32 ToArgb32()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToArgb32();
}
Argb32 value = default;
value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
return value;
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
internal Rgb24 ToRgb24() => this.data.ToRgb24(); internal Rgb24 ToRgb24()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToRgb24();
}
Rgb24 value = default;
value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
return value;
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
internal Bgr24 ToBgr24() => this.data.ToBgr24(); internal Bgr24 ToBgr24()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToBgr24();
}
Bgr24 value = default;
value.FromScaledVector4(this.boxedHighPrecisionPixel.ToScaledVector4());
return value;
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
internal Vector4 ToVector4() => this.data.ToVector4(); internal Vector4 ToVector4()
{
if (this.boxedHighPrecisionPixel is null)
{
return this.data.ToScaledVector4();
}
return this.boxedHighPrecisionPixel.ToScaledVector4();
}
} }
} }

106
src/ImageSharp/Color/Color.cs

@ -4,8 +4,6 @@
using System; using System;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp namespace SixLabors.ImageSharp
@ -22,6 +20,7 @@ namespace SixLabors.ImageSharp
public readonly partial struct Color : IEquatable<Color> public readonly partial struct Color : IEquatable<Color>
{ {
private readonly Rgba64 data; private readonly Rgba64 data;
private readonly IPixel boxedHighPrecisionPixel;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private Color(byte r, byte g, byte b, byte a) private Color(byte r, byte g, byte b, byte a)
@ -31,6 +30,8 @@ namespace SixLabors.ImageSharp
ColorNumerics.UpscaleFrom8BitTo16Bit(g), ColorNumerics.UpscaleFrom8BitTo16Bit(g),
ColorNumerics.UpscaleFrom8BitTo16Bit(b), ColorNumerics.UpscaleFrom8BitTo16Bit(b),
ColorNumerics.UpscaleFrom8BitTo16Bit(a)); ColorNumerics.UpscaleFrom8BitTo16Bit(a));
this.boxedHighPrecisionPixel = null;
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
@ -41,6 +42,15 @@ namespace SixLabors.ImageSharp
ColorNumerics.UpscaleFrom8BitTo16Bit(g), ColorNumerics.UpscaleFrom8BitTo16Bit(g),
ColorNumerics.UpscaleFrom8BitTo16Bit(b), ColorNumerics.UpscaleFrom8BitTo16Bit(b),
ushort.MaxValue); ushort.MaxValue);
this.boxedHighPrecisionPixel = null;
}
[MethodImpl(InliningOptions.ShortMethod)]
private Color(IPixel pixel)
{
this.boxedHighPrecisionPixel = pixel;
this.data = default;
} }
/// <summary> /// <summary>
@ -53,13 +63,10 @@ namespace SixLabors.ImageSharp
/// otherwise, false. /// otherwise, false.
/// </returns> /// </returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static bool operator ==(Color left, Color right) public static bool operator ==(Color left, Color right) => left.Equals(right);
{
return left.Equals(right);
}
/// <summary> /// <summary>
/// Checks whether two <see cref="Color"/> structures are equal. /// Checks whether two <see cref="Color"/> structures are not equal.
/// </summary> /// </summary>
/// <param name="left">The left hand <see cref="Color"/> operand.</param> /// <param name="left">The left hand <see cref="Color"/> operand.</param>
/// <param name="right">The right hand <see cref="Color"/> operand.</param> /// <param name="right">The right hand <see cref="Color"/> operand.</param>
@ -68,10 +75,7 @@ namespace SixLabors.ImageSharp
/// otherwise, false. /// otherwise, false.
/// </returns> /// </returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static bool operator !=(Color left, Color right) public static bool operator !=(Color left, Color right) => !left.Equals(right);
{
return !left.Equals(right);
}
/// <summary> /// <summary>
/// Creates a <see cref="Color"/> from RGBA bytes. /// Creates a <see cref="Color"/> from RGBA bytes.
@ -82,7 +86,7 @@ namespace SixLabors.ImageSharp
/// <param name="a">The alpha component (0-255).</param> /// <param name="a">The alpha component (0-255).</param>
/// <returns>The <see cref="Color"/>.</returns> /// <returns>The <see cref="Color"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static Color FromRgba(byte r, byte g, byte b, byte a) => new Color(r, g, b, a); public static Color FromRgba(byte r, byte g, byte b, byte a) => new(r, g, b, a);
/// <summary> /// <summary>
/// Creates a <see cref="Color"/> from RGB bytes. /// Creates a <see cref="Color"/> from RGB bytes.
@ -92,7 +96,46 @@ namespace SixLabors.ImageSharp
/// <param name="b">The blue component (0-255).</param> /// <param name="b">The blue component (0-255).</param>
/// <returns>The <see cref="Color"/>.</returns> /// <returns>The <see cref="Color"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static Color FromRgb(byte r, byte g, byte b) => new Color(r, g, b); public static Color FromRgb(byte r, byte g, byte b) => new(r, g, b);
/// <summary>
/// Creates a <see cref="Color"/> from the given <typeparamref name="TPixel"/>.
/// </summary>
/// <param name="pixel">The pixel to convert from.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="Color"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)]
public static Color FromPixel<TPixel>(TPixel pixel)
where TPixel : unmanaged, IPixel<TPixel>
{
// Avoid boxing in case we can convert to Rgba64 safely and efficently
if (typeof(TPixel) == typeof(Rgba64))
{
return new((Rgba64)(object)pixel);
}
else if (typeof(TPixel) == typeof(Rgb48))
{
return new((Rgb48)(object)pixel);
}
else if (typeof(TPixel) == typeof(La32))
{
return new((La32)(object)pixel);
}
else if (typeof(TPixel) == typeof(L16))
{
return new((L16)(object)pixel);
}
else if (Unsafe.SizeOf<TPixel>() <= Unsafe.SizeOf<Rgba32>())
{
Rgba32 p = default;
pixel.ToRgba32(ref p);
return new(p);
}
else
{
return new(pixel);
}
}
/// <summary> /// <summary>
/// Creates a new instance of the <see cref="Color"/> struct /// Creates a new instance of the <see cref="Color"/> struct
@ -214,7 +257,7 @@ namespace SixLabors.ImageSharp
public override string ToString() => this.ToHex(); public override string ToString() => this.ToHex();
/// <summary> /// <summary>
/// Converts the color instance to a specified <see cref="IPixel{TSelf}"/> type. /// Converts the color instance to a specified <typeparamref name="TPixel"/> type.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel type to convert to.</typeparam> /// <typeparam name="TPixel">The pixel type to convert to.</typeparam>
/// <returns>The pixel value.</returns> /// <returns>The pixel value.</returns>
@ -222,13 +265,18 @@ namespace SixLabors.ImageSharp
public TPixel ToPixel<TPixel>() public TPixel ToPixel<TPixel>()
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
TPixel pixel = default; if (this.boxedHighPrecisionPixel is TPixel pixel)
{
return pixel;
}
pixel = default;
pixel.FromRgba64(this.data); pixel.FromRgba64(this.data);
return pixel; return pixel;
} }
/// <summary> /// <summary>
/// Bulk converts a span of <see cref="Color"/> to a span of a specified <see cref="IPixel{TSelf}"/> type. /// Bulk converts a span of <see cref="Color"/> to a span of a specified <typeparamref name="TPixel"/> type.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel type to convert to.</typeparam> /// <typeparam name="TPixel">The pixel type to convert to.</typeparam>
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
@ -241,28 +289,38 @@ namespace SixLabors.ImageSharp
Span<TPixel> destination) Span<TPixel> destination)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
ReadOnlySpan<Rgba64> rgba64Span = MemoryMarshal.Cast<Color, Rgba64>(source); Guard.DestinationShouldNotBeTooShort(source, destination, nameof(destination));
PixelOperations<TPixel>.Instance.FromRgba64(configuration, rgba64Span, destination); for (int i = 0; i < source.Length; i++)
{
destination[i] = source[i].ToPixel<TPixel>();
}
} }
/// <inheritdoc /> /// <inheritdoc />
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public bool Equals(Color other) public bool Equals(Color other)
{ {
return this.data.PackedValue == other.data.PackedValue; if (this.boxedHighPrecisionPixel is null && other.boxedHighPrecisionPixel is null)
{
return this.data.PackedValue == other.data.PackedValue;
}
return this.boxedHighPrecisionPixel?.Equals(other.boxedHighPrecisionPixel) == true;
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool Equals(object obj) public override bool Equals(object obj) => obj is Color other && this.Equals(other);
{
return obj is Color other && this.Equals(other);
}
/// <inheritdoc /> /// <inheritdoc />
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public override int GetHashCode() public override int GetHashCode()
{ {
return this.data.PackedValue.GetHashCode(); if (this.boxedHighPrecisionPixel is null)
{
return this.data.PackedValue.GetHashCode();
}
return this.boxedHighPrecisionPixel.GetHashCode();
} }
} }
} }

2
src/ImageSharp/ColorSpaces/Conversion/ColorSpaceConverter.HunterLab.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;

21
src/ImageSharp/Common/Helpers/ExifResolutionValues.cs

@ -0,0 +1,21 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Common.Helpers
{
internal readonly struct ExifResolutionValues
{
public ExifResolutionValues(ushort resolutionUnit, double? horizontalResolution, double? verticalResolution)
{
this.ResolutionUnit = resolutionUnit;
this.HorizontalResolution = horizontalResolution;
this.VerticalResolution = verticalResolution;
}
public ushort ResolutionUnit { get; }
public double? HorizontalResolution { get; }
public double? VerticalResolution { get; }
}
}

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

@ -12,6 +12,10 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
internal static class InliningOptions internal static class InliningOptions
{ {
/// <summary>
/// <see cref="MethodImplOptions.AggressiveInlining"/> regardless of the build conditions.
/// </summary>
public const MethodImplOptions AlwaysInline = MethodImplOptions.AggressiveInlining;
#if PROFILING #if PROFILING
public const MethodImplOptions HotPath = MethodImplOptions.NoInlining; public const MethodImplOptions HotPath = MethodImplOptions.NoInlining;
public const MethodImplOptions ShortMethod = MethodImplOptions.NoInlining; public const MethodImplOptions ShortMethod = MethodImplOptions.NoInlining;

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

@ -879,5 +879,13 @@ namespace SixLabors.ImageSharp
(IntPtr)(int)((value * 0x07C4ACDDu) >> 27)); // uint|long -> IntPtr cast on 32-bit platforms does expensive overflow checks not needed here (IntPtr)(int)((value * 0x07C4ACDDu) >> 27)); // uint|long -> IntPtr cast on 32-bit platforms does expensive overflow checks not needed here
} }
#endif #endif
/// <summary>
/// Fast division with ceiling for <see cref="uint"/> numbers.
/// </summary>
/// <param name="value">Divident value.</param>
/// <param name="divisor">Divisor value.</param>
/// <returns>Ceiled division result.</returns>
public static uint DivideCeil(uint value, uint divisor) => (value + divisor - 1) / divisor;
} }
} }

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

@ -537,7 +537,7 @@ namespace SixLabors.ImageSharp
/// <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>
/// <returns>The <see cref="Vector256{T}"/>.</returns> /// <returns>The <see cref="Vector256{T}"/>.</returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.AlwaysInline)]
public static Vector256<float> MultiplyAdd( public static Vector256<float> MultiplyAdd(
in Vector256<float> va, in Vector256<float> va,
in Vector256<float> vm0, in Vector256<float> vm0,
@ -622,90 +622,89 @@ namespace SixLabors.ImageSharp
ReadOnlySpan<byte> source, ReadOnlySpan<byte> source,
Span<float> dest) Span<float> dest)
{ {
if (Avx2.IsSupported) fixed (byte* sourceBase = source)
{ {
VerifySpanInput(source, dest, Vector256<byte>.Count); if (Avx2.IsSupported)
{
int n = dest.Length / Vector256<byte>.Count; VerifySpanInput(source, dest, Vector256<byte>.Count);
byte* sourceBase = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(source)); int n = dest.Length / Vector256<byte>.Count;
ref Vector256<float> destBase = ref Vector256<float> destBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(dest)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(dest));
var scale = Vector256.Create(1 / (float)byte.MaxValue); var scale = Vector256.Create(1 / (float)byte.MaxValue);
for (int i = 0; i < n; i++) for (int i = 0; i < n; i++)
{ {
int si = Vector256<byte>.Count * i; int si = Vector256<byte>.Count * i;
Vector256<int> i0 = Avx2.ConvertToVector256Int32(sourceBase + si); Vector256<int> i0 = Avx2.ConvertToVector256Int32(sourceBase + si);
Vector256<int> i1 = Avx2.ConvertToVector256Int32(sourceBase + si + Vector256<int>.Count); Vector256<int> i1 = Avx2.ConvertToVector256Int32(sourceBase + si + Vector256<int>.Count);
Vector256<int> i2 = Avx2.ConvertToVector256Int32(sourceBase + si + (Vector256<int>.Count * 2)); Vector256<int> i2 = Avx2.ConvertToVector256Int32(sourceBase + si + (Vector256<int>.Count * 2));
Vector256<int> i3 = Avx2.ConvertToVector256Int32(sourceBase + si + (Vector256<int>.Count * 3)); Vector256<int> i3 = Avx2.ConvertToVector256Int32(sourceBase + si + (Vector256<int>.Count * 3));
Vector256<float> f0 = Avx.Multiply(scale, Avx.ConvertToVector256Single(i0)); Vector256<float> f0 = Avx.Multiply(scale, Avx.ConvertToVector256Single(i0));
Vector256<float> f1 = Avx.Multiply(scale, Avx.ConvertToVector256Single(i1)); Vector256<float> f1 = Avx.Multiply(scale, Avx.ConvertToVector256Single(i1));
Vector256<float> f2 = Avx.Multiply(scale, Avx.ConvertToVector256Single(i2)); Vector256<float> f2 = Avx.Multiply(scale, Avx.ConvertToVector256Single(i2));
Vector256<float> f3 = Avx.Multiply(scale, Avx.ConvertToVector256Single(i3)); Vector256<float> f3 = Avx.Multiply(scale, Avx.ConvertToVector256Single(i3));
ref Vector256<float> d = ref Unsafe.Add(ref destBase, i * 4); ref Vector256<float> d = ref Unsafe.Add(ref destBase, i * 4);
d = f0; d = f0;
Unsafe.Add(ref d, 1) = f1; Unsafe.Add(ref d, 1) = f1;
Unsafe.Add(ref d, 2) = f2; Unsafe.Add(ref d, 2) = f2;
Unsafe.Add(ref d, 3) = f3; Unsafe.Add(ref d, 3) = f3;
}
} }
} else
else {
{ // Sse
// Sse VerifySpanInput(source, dest, Vector128<byte>.Count);
VerifySpanInput(source, dest, Vector128<byte>.Count);
int n = dest.Length / Vector128<byte>.Count;
byte* sourceBase = (byte*)Unsafe.AsPointer(ref MemoryMarshal.GetReference(source));
ref Vector128<float> destBase = int n = dest.Length / Vector128<byte>.Count;
ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(dest));
var scale = Vector128.Create(1 / (float)byte.MaxValue); ref Vector128<float> destBase =
Vector128<byte> zero = Vector128<byte>.Zero; ref Unsafe.As<float, Vector128<float>>(ref MemoryMarshal.GetReference(dest));
for (int i = 0; i < n; i++) var scale = Vector128.Create(1 / (float)byte.MaxValue);
{ Vector128<byte> zero = Vector128<byte>.Zero;
int si = Vector128<byte>.Count * i;
Vector128<int> i0, i1, i2, i3; for (int i = 0; i < n; i++)
if (Sse41.IsSupported)
{
i0 = Sse41.ConvertToVector128Int32(sourceBase + si);
i1 = Sse41.ConvertToVector128Int32(sourceBase + si + Vector128<int>.Count);
i2 = Sse41.ConvertToVector128Int32(sourceBase + si + (Vector128<int>.Count * 2));
i3 = Sse41.ConvertToVector128Int32(sourceBase + si + (Vector128<int>.Count * 3));
}
else
{ {
Vector128<byte> b = Sse2.LoadVector128(sourceBase + si); int si = Vector128<byte>.Count * i;
Vector128<short> s0 = Sse2.UnpackLow(b, zero).AsInt16();
Vector128<short> s1 = Sse2.UnpackHigh(b, zero).AsInt16(); Vector128<int> i0, i1, i2, i3;
if (Sse41.IsSupported)
i0 = Sse2.UnpackLow(s0, zero.AsInt16()).AsInt32(); {
i1 = Sse2.UnpackHigh(s0, zero.AsInt16()).AsInt32(); i0 = Sse41.ConvertToVector128Int32(sourceBase + si);
i2 = Sse2.UnpackLow(s1, zero.AsInt16()).AsInt32(); i1 = Sse41.ConvertToVector128Int32(sourceBase + si + Vector128<int>.Count);
i3 = Sse2.UnpackHigh(s1, zero.AsInt16()).AsInt32(); i2 = Sse41.ConvertToVector128Int32(sourceBase + si + (Vector128<int>.Count * 2));
i3 = Sse41.ConvertToVector128Int32(sourceBase + si + (Vector128<int>.Count * 3));
}
else
{
Vector128<byte> b = Sse2.LoadVector128(sourceBase + si);
Vector128<short> s0 = Sse2.UnpackLow(b, zero).AsInt16();
Vector128<short> s1 = Sse2.UnpackHigh(b, zero).AsInt16();
i0 = Sse2.UnpackLow(s0, zero.AsInt16()).AsInt32();
i1 = Sse2.UnpackHigh(s0, zero.AsInt16()).AsInt32();
i2 = Sse2.UnpackLow(s1, zero.AsInt16()).AsInt32();
i3 = Sse2.UnpackHigh(s1, zero.AsInt16()).AsInt32();
}
Vector128<float> f0 = Sse.Multiply(scale, Sse2.ConvertToVector128Single(i0));
Vector128<float> f1 = Sse.Multiply(scale, Sse2.ConvertToVector128Single(i1));
Vector128<float> f2 = Sse.Multiply(scale, Sse2.ConvertToVector128Single(i2));
Vector128<float> f3 = Sse.Multiply(scale, Sse2.ConvertToVector128Single(i3));
ref Vector128<float> d = ref Unsafe.Add(ref destBase, i * 4);
d = f0;
Unsafe.Add(ref d, 1) = f1;
Unsafe.Add(ref d, 2) = f2;
Unsafe.Add(ref d, 3) = f3;
} }
Vector128<float> f0 = Sse.Multiply(scale, Sse2.ConvertToVector128Single(i0));
Vector128<float> f1 = Sse.Multiply(scale, Sse2.ConvertToVector128Single(i1));
Vector128<float> f2 = Sse.Multiply(scale, Sse2.ConvertToVector128Single(i2));
Vector128<float> f3 = Sse.Multiply(scale, Sse2.ConvertToVector128Single(i3));
ref Vector128<float> d = ref Unsafe.Add(ref destBase, i * 4);
d = f0;
Unsafe.Add(ref d, 1) = f1;
Unsafe.Add(ref d, 2) = f2;
Unsafe.Add(ref d, 3) = f3;
} }
} }
} }

2
src/ImageSharp/Common/Helpers/SimdUtils.Pack.cs

@ -5,9 +5,7 @@ using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86; using System.Runtime.Intrinsics.X86;
#endif #endif

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

@ -98,14 +98,14 @@ namespace SixLabors.ImageSharp.Common.Helpers
} }
/// <summary> /// <summary>
/// Sets the exif profile resolution values. /// Gets the exif profile resolution values.
/// </summary> /// </summary>
/// <param name="exifProfile">The exif profile.</param>
/// <param name="unit">The resolution unit.</param> /// <param name="unit">The resolution unit.</param>
/// <param name="horizontal">The horizontal resolution value.</param> /// <param name="horizontal">The horizontal resolution value.</param>
/// <param name="vertical">The vertical resolution value.</param> /// <param name="vertical">The vertical resolution value.</param>
/// <returns><see cref="ExifResolutionValues"/></returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public static void SetResolutionValues(ExifProfile exifProfile, PixelResolutionUnit unit, double horizontal, double vertical) public static ExifResolutionValues GetExifResolutionValues(PixelResolutionUnit unit, double horizontal, double vertical)
{ {
switch (unit) switch (unit)
{ {
@ -115,9 +115,9 @@ namespace SixLabors.ImageSharp.Common.Helpers
break; break;
case PixelResolutionUnit.PixelsPerMeter: case PixelResolutionUnit.PixelsPerMeter:
{ {
unit = PixelResolutionUnit.PixelsPerCentimeter; unit = PixelResolutionUnit.PixelsPerCentimeter;
horizontal = UnitConverter.MeterToCm(horizontal); horizontal = MeterToCm(horizontal);
vertical = UnitConverter.MeterToCm(vertical); vertical = MeterToCm(vertical);
} }
break; break;
@ -126,18 +126,13 @@ namespace SixLabors.ImageSharp.Common.Helpers
break; break;
} }
exifProfile.SetValue(ExifTag.ResolutionUnit, (ushort)(unit + 1)); ushort exifUnit = (ushort)(unit + 1);
if (unit == PixelResolutionUnit.AspectRatio) if (unit == PixelResolutionUnit.AspectRatio)
{ {
exifProfile.RemoveValue(ExifTag.XResolution); return new ExifResolutionValues(exifUnit, null, null);
exifProfile.RemoveValue(ExifTag.YResolution);
}
else
{
exifProfile.SetValue(ExifTag.XResolution, new Rational(horizontal));
exifProfile.SetValue(ExifTag.YResolution, new Rational(vertical));
} }
return new ExifResolutionValues(exifUnit, horizontal, vertical);
} }
} }
} }

170
src/ImageSharp/Compression/Zlib/Adler32.cs

@ -91,115 +91,117 @@ namespace SixLabors.ImageSharp.Compression.Zlib
int index = 0; int index = 0;
fixed (byte* bufferPtr = buffer) fixed (byte* bufferPtr = buffer)
fixed (byte* tapPtr = Tap1Tap2)
{ {
index += (int)blocks * BLOCK_SIZE; fixed (byte* tapPtr = Tap1Tap2)
var localBufferPtr = bufferPtr;
// _mm_setr_epi8 on x86
Vector128<sbyte> tap1 = Sse2.LoadVector128((sbyte*)tapPtr);
Vector128<sbyte> tap2 = Sse2.LoadVector128((sbyte*)(tapPtr + 0x10));
Vector128<byte> zero = Vector128<byte>.Zero;
var ones = Vector128.Create((short)1);
while (blocks > 0)
{ {
uint n = NMAX / BLOCK_SIZE; /* The NMAX constraint. */ index += (int)blocks * BLOCK_SIZE;
if (n > blocks) var localBufferPtr = bufferPtr;
{
n = blocks;
}
blocks -= n; // _mm_setr_epi8 on x86
Vector128<sbyte> tap1 = Sse2.LoadVector128((sbyte*)tapPtr);
Vector128<sbyte> tap2 = Sse2.LoadVector128((sbyte*)(tapPtr + 0x10));
Vector128<byte> zero = Vector128<byte>.Zero;
var ones = Vector128.Create((short)1);
// Process n blocks of data. At most NMAX data bytes can be while (blocks > 0)
// processed before s2 must be reduced modulo BASE.
Vector128<uint> v_ps = Vector128.CreateScalar(s1 * n);
Vector128<uint> v_s2 = Vector128.CreateScalar(s2);
Vector128<uint> v_s1 = Vector128<uint>.Zero;
do
{ {
// Load 32 input bytes. uint n = NMAX / BLOCK_SIZE; /* The NMAX constraint. */
Vector128<byte> bytes1 = Sse3.LoadDquVector128(localBufferPtr); if (n > blocks)
Vector128<byte> bytes2 = Sse3.LoadDquVector128(localBufferPtr + 0x10); {
n = blocks;
}
// Add previous block byte sum to v_ps. blocks -= n;
v_ps = Sse2.Add(v_ps, v_s1);
// Horizontally add the bytes for s1, multiply-adds the // Process n blocks of data. At most NMAX data bytes can be
// bytes by [ 32, 31, 30, ... ] for s2. // processed before s2 must be reduced modulo BASE.
v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes1, zero).AsUInt32()); Vector128<uint> v_ps = Vector128.CreateScalar(s1 * n);
Vector128<short> mad1 = Ssse3.MultiplyAddAdjacent(bytes1, tap1); Vector128<uint> v_s2 = Vector128.CreateScalar(s2);
v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad1, ones).AsUInt32()); Vector128<uint> v_s1 = Vector128<uint>.Zero;
v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes2, zero).AsUInt32()); do
Vector128<short> mad2 = Ssse3.MultiplyAddAdjacent(bytes2, tap2); {
v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad2, ones).AsUInt32()); // Load 32 input bytes.
Vector128<byte> bytes1 = Sse3.LoadDquVector128(localBufferPtr);
Vector128<byte> bytes2 = Sse3.LoadDquVector128(localBufferPtr + 0x10);
localBufferPtr += BLOCK_SIZE; // Add previous block byte sum to v_ps.
} v_ps = Sse2.Add(v_ps, v_s1);
while (--n > 0);
v_s2 = Sse2.Add(v_s2, Sse2.ShiftLeftLogical(v_ps, 5)); // Horizontally add the bytes for s1, multiply-adds the
// bytes by [ 32, 31, 30, ... ] for s2.
v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes1, zero).AsUInt32());
Vector128<short> mad1 = Ssse3.MultiplyAddAdjacent(bytes1, tap1);
v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad1, ones).AsUInt32());
// Sum epi32 ints v_s1(s2) and accumulate in s1(s2). v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes2, zero).AsUInt32());
const byte S2301 = 0b1011_0001; // A B C D -> B A D C Vector128<short> mad2 = Ssse3.MultiplyAddAdjacent(bytes2, tap2);
const byte S1032 = 0b0100_1110; // A B C D -> C D A B v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad2, ones).AsUInt32());
v_s1 = Sse2.Add(v_s1, Sse2.Shuffle(v_s1, S1032)); localBufferPtr += BLOCK_SIZE;
}
while (--n > 0);
s1 += v_s1.ToScalar(); v_s2 = Sse2.Add(v_s2, Sse2.ShiftLeftLogical(v_ps, 5));
v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, S2301)); // Sum epi32 ints v_s1(s2) and accumulate in s1(s2).
v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, S1032)); const byte S2301 = 0b1011_0001; // A B C D -> B A D C
const byte S1032 = 0b0100_1110; // A B C D -> C D A B
s2 = v_s2.ToScalar(); v_s1 = Sse2.Add(v_s1, Sse2.Shuffle(v_s1, S1032));
// Reduce. s1 += v_s1.ToScalar();
s1 %= BASE;
s2 %= BASE;
}
if (length > 0) v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, S2301));
{ v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, S1032));
if (length >= 16)
{
s2 += s1 += localBufferPtr[0];
s2 += s1 += localBufferPtr[1];
s2 += s1 += localBufferPtr[2];
s2 += s1 += localBufferPtr[3];
s2 += s1 += localBufferPtr[4];
s2 += s1 += localBufferPtr[5];
s2 += s1 += localBufferPtr[6];
s2 += s1 += localBufferPtr[7];
s2 += s1 += localBufferPtr[8];
s2 += s1 += localBufferPtr[9];
s2 += s1 += localBufferPtr[10];
s2 += s1 += localBufferPtr[11];
s2 += s1 += localBufferPtr[12];
s2 += s1 += localBufferPtr[13];
s2 += s1 += localBufferPtr[14];
s2 += s1 += localBufferPtr[15];
localBufferPtr += 16; s2 = v_s2.ToScalar();
length -= 16;
}
while (length-- > 0) // Reduce.
{ s1 %= BASE;
s2 += s1 += *localBufferPtr++; s2 %= BASE;
} }
if (s1 >= BASE) if (length > 0)
{ {
s1 -= BASE; if (length >= 16)
{
s2 += s1 += localBufferPtr[0];
s2 += s1 += localBufferPtr[1];
s2 += s1 += localBufferPtr[2];
s2 += s1 += localBufferPtr[3];
s2 += s1 += localBufferPtr[4];
s2 += s1 += localBufferPtr[5];
s2 += s1 += localBufferPtr[6];
s2 += s1 += localBufferPtr[7];
s2 += s1 += localBufferPtr[8];
s2 += s1 += localBufferPtr[9];
s2 += s1 += localBufferPtr[10];
s2 += s1 += localBufferPtr[11];
s2 += s1 += localBufferPtr[12];
s2 += s1 += localBufferPtr[13];
s2 += s1 += localBufferPtr[14];
s2 += s1 += localBufferPtr[15];
localBufferPtr += 16;
length -= 16;
}
while (length-- > 0)
{
s2 += s1 += *localBufferPtr++;
}
if (s1 >= BASE)
{
s1 -= BASE;
}
s2 %= BASE;
} }
s2 %= BASE; return s1 | (s2 << 16);
} }
return s1 | (s2 << 16);
} }
} }
#endif #endif

180
src/ImageSharp/Compression/Zlib/Crc32.cs

@ -83,117 +83,119 @@ namespace SixLabors.ImageSharp.Compression.Zlib
int length = chunksize; int length = chunksize;
fixed (byte* bufferPtr = buffer) fixed (byte* bufferPtr = buffer)
fixed (ulong* k05PolyPtr = K05Poly)
{ {
byte* localBufferPtr = bufferPtr; fixed (ulong* k05PolyPtr = K05Poly)
ulong* localK05PolyPtr = k05PolyPtr;
// There's at least one block of 64.
Vector128<ulong> x1 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x00));
Vector128<ulong> x2 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x10));
Vector128<ulong> x3 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x20));
Vector128<ulong> x4 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x30));
Vector128<ulong> x5;
x1 = Sse2.Xor(x1, Sse2.ConvertScalarToVector128UInt32(crc).AsUInt64());
// k1, k2
Vector128<ulong> x0 = Sse2.LoadVector128(localK05PolyPtr + 0x0);
localBufferPtr += 64;
length -= 64;
// Parallel fold blocks of 64, if any.
while (length >= 64)
{ {
x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00); byte* localBufferPtr = bufferPtr;
Vector128<ulong> x6 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x00); ulong* localK05PolyPtr = k05PolyPtr;
Vector128<ulong> x7 = Pclmulqdq.CarrylessMultiply(x3, x0, 0x00);
Vector128<ulong> x8 = Pclmulqdq.CarrylessMultiply(x4, x0, 0x00);
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11);
x2 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x11);
x3 = Pclmulqdq.CarrylessMultiply(x3, x0, 0x11);
x4 = Pclmulqdq.CarrylessMultiply(x4, x0, 0x11);
Vector128<ulong> y5 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x00)); // There's at least one block of 64.
Vector128<ulong> y6 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x10)); Vector128<ulong> x1 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x00));
Vector128<ulong> y7 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x20)); Vector128<ulong> x2 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x10));
Vector128<ulong> y8 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x30)); Vector128<ulong> x3 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x20));
Vector128<ulong> x4 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x30));
Vector128<ulong> x5;
x1 = Sse2.Xor(x1, x5); x1 = Sse2.Xor(x1, Sse2.ConvertScalarToVector128UInt32(crc).AsUInt64());
x2 = Sse2.Xor(x2, x6);
x3 = Sse2.Xor(x3, x7);
x4 = Sse2.Xor(x4, x8);
x1 = Sse2.Xor(x1, y5); // k1, k2
x2 = Sse2.Xor(x2, y6); Vector128<ulong> x0 = Sse2.LoadVector128(localK05PolyPtr + 0x0);
x3 = Sse2.Xor(x3, y7);
x4 = Sse2.Xor(x4, y8);
localBufferPtr += 64; localBufferPtr += 64;
length -= 64; length -= 64;
}
// Fold into 128-bits.
// k3, k4
x0 = Sse2.LoadVector128(k05PolyPtr + 0x2);
x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00); // Parallel fold blocks of 64, if any.
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11); while (length >= 64)
x1 = Sse2.Xor(x1, x2); {
x1 = Sse2.Xor(x1, x5); x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
Vector128<ulong> x6 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x00);
Vector128<ulong> x7 = Pclmulqdq.CarrylessMultiply(x3, x0, 0x00);
Vector128<ulong> x8 = Pclmulqdq.CarrylessMultiply(x4, x0, 0x00);
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11);
x2 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x11);
x3 = Pclmulqdq.CarrylessMultiply(x3, x0, 0x11);
x4 = Pclmulqdq.CarrylessMultiply(x4, x0, 0x11);
Vector128<ulong> y5 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x00));
Vector128<ulong> y6 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x10));
Vector128<ulong> y7 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x20));
Vector128<ulong> y8 = Sse2.LoadVector128((ulong*)(localBufferPtr + 0x30));
x1 = Sse2.Xor(x1, x5);
x2 = Sse2.Xor(x2, x6);
x3 = Sse2.Xor(x3, x7);
x4 = Sse2.Xor(x4, x8);
x1 = Sse2.Xor(x1, y5);
x2 = Sse2.Xor(x2, y6);
x3 = Sse2.Xor(x3, y7);
x4 = Sse2.Xor(x4, y8);
localBufferPtr += 64;
length -= 64;
}
// Fold into 128-bits.
// k3, k4
x0 = Sse2.LoadVector128(k05PolyPtr + 0x2);
x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00); x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11); x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11);
x1 = Sse2.Xor(x1, x3); x1 = Sse2.Xor(x1, x2);
x1 = Sse2.Xor(x1, x5); x1 = Sse2.Xor(x1, x5);
x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11);
x1 = Sse2.Xor(x1, x4);
x1 = Sse2.Xor(x1, x5);
// Single fold blocks of 16, if any. x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
while (length >= 16) x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11);
{ x1 = Sse2.Xor(x1, x3);
x2 = Sse2.LoadVector128((ulong*)localBufferPtr); x1 = Sse2.Xor(x1, x5);
x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00); x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11); x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11);
x1 = Sse2.Xor(x1, x2); x1 = Sse2.Xor(x1, x4);
x1 = Sse2.Xor(x1, x5); x1 = Sse2.Xor(x1, x5);
localBufferPtr += 16; // Single fold blocks of 16, if any.
length -= 16; while (length >= 16)
} {
x2 = Sse2.LoadVector128((ulong*)localBufferPtr);
x5 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x11);
x1 = Sse2.Xor(x1, x2);
x1 = Sse2.Xor(x1, x5);
// Fold 128 - bits to 64 - bits. localBufferPtr += 16;
x2 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x10); length -= 16;
x3 = Vector128.Create(~0, 0, ~0, 0).AsUInt64(); // _mm_setr_epi32 on x86 }
x1 = Sse2.ShiftRightLogical128BitLane(x1, 8);
x1 = Sse2.Xor(x1, x2);
// k5, k0 // Fold 128 - bits to 64 - bits.
x0 = Sse2.LoadScalarVector128(localK05PolyPtr + 0x4); x2 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x10);
x3 = Vector128.Create(~0, 0, ~0, 0).AsUInt64(); // _mm_setr_epi32 on x86
x1 = Sse2.ShiftRightLogical128BitLane(x1, 8);
x1 = Sse2.Xor(x1, x2);
x2 = Sse2.ShiftRightLogical128BitLane(x1, 4); // k5, k0
x1 = Sse2.And(x1, x3); x0 = Sse2.LoadScalarVector128(localK05PolyPtr + 0x4);
x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
x1 = Sse2.Xor(x1, x2);
// Barret reduce to 32-bits. x2 = Sse2.ShiftRightLogical128BitLane(x1, 4);
// polynomial x1 = Sse2.And(x1, x3);
x0 = Sse2.LoadVector128(localK05PolyPtr + 0x6); x1 = Pclmulqdq.CarrylessMultiply(x1, x0, 0x00);
x1 = Sse2.Xor(x1, x2);
x2 = Sse2.And(x1, x3); // Barret reduce to 32-bits.
x2 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x10); // polynomial
x2 = Sse2.And(x2, x3); x0 = Sse2.LoadVector128(localK05PolyPtr + 0x6);
x2 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x00);
x1 = Sse2.Xor(x1, x2);
crc = (uint)Sse41.Extract(x1.AsInt32(), 1); x2 = Sse2.And(x1, x3);
return buffer.Length - chunksize == 0 ? crc : CalculateScalar(crc, buffer.Slice(chunksize)); x2 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x10);
x2 = Sse2.And(x2, x3);
x2 = Pclmulqdq.CarrylessMultiply(x2, x0, 0x00);
x1 = Sse2.Xor(x1, x2);
crc = (uint)Sse41.Extract(x1.AsInt32(), 1);
return buffer.Length - chunksize == 0 ? crc : CalculateScalar(crc, buffer.Slice(chunksize));
}
} }
} }
#endif #endif

33
src/ImageSharp/Configuration.cs

@ -11,6 +11,7 @@ using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
@ -159,20 +160,17 @@ namespace SixLabors.ImageSharp
/// Creates a shallow copy of the <see cref="Configuration"/>. /// Creates a shallow copy of the <see cref="Configuration"/>.
/// </summary> /// </summary>
/// <returns>A new configuration instance.</returns> /// <returns>A new configuration instance.</returns>
public Configuration Clone() public Configuration Clone() => new Configuration
{ {
return new Configuration MaxDegreeOfParallelism = this.MaxDegreeOfParallelism,
{ StreamProcessingBufferSize = this.StreamProcessingBufferSize,
MaxDegreeOfParallelism = this.MaxDegreeOfParallelism, ImageFormatsManager = this.ImageFormatsManager,
StreamProcessingBufferSize = this.StreamProcessingBufferSize, MemoryAllocator = this.MemoryAllocator,
ImageFormatsManager = this.ImageFormatsManager, ImageOperationsProvider = this.ImageOperationsProvider,
MemoryAllocator = this.MemoryAllocator, ReadOrigin = this.ReadOrigin,
ImageOperationsProvider = this.ImageOperationsProvider, FileSystem = this.FileSystem,
ReadOrigin = this.ReadOrigin, WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInBytes,
FileSystem = this.FileSystem, };
WorkingBufferSizeHintInBytes = this.WorkingBufferSizeHintInBytes,
};
}
/// <summary> /// <summary>
/// Creates the default instance with the following <see cref="IConfigurationModule"/>s preregistered: /// Creates the default instance with the following <see cref="IConfigurationModule"/>s preregistered:
@ -182,17 +180,16 @@ namespace SixLabors.ImageSharp
/// <see cref="BmpConfigurationModule"/>. /// <see cref="BmpConfigurationModule"/>.
/// <see cref="TgaConfigurationModule"/>. /// <see cref="TgaConfigurationModule"/>.
/// <see cref="TiffConfigurationModule"/>. /// <see cref="TiffConfigurationModule"/>.
/// <see cref="WebpConfigurationModule"/>.
/// </summary> /// </summary>
/// <returns>The default configuration of <see cref="Configuration"/>.</returns> /// <returns>The default configuration of <see cref="Configuration"/>.</returns>
internal static Configuration CreateDefaultInstance() internal static Configuration CreateDefaultInstance() => new Configuration(
{
return new Configuration(
new PngConfigurationModule(), new PngConfigurationModule(),
new JpegConfigurationModule(), new JpegConfigurationModule(),
new GifConfigurationModule(), new GifConfigurationModule(),
new BmpConfigurationModule(), new BmpConfigurationModule(),
new TgaConfigurationModule(), new TgaConfigurationModule(),
new TiffConfigurationModule()); new TiffConfigurationModule(),
} new WebpConfigurationModule());
} }
} }

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

@ -8,7 +8,6 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata; using SixLabors.ImageSharp.Metadata;

1
src/ImageSharp/Formats/Gif/LzwEncoder.cs

@ -6,7 +6,6 @@ using System.Buffers;
using System.IO; using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Gif namespace SixLabors.ImageSharp.Formats.Gif

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

@ -12,6 +12,7 @@ using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.Formats.Tga; using SixLabors.ImageSharp.Formats.Tga;
using SixLabors.ImageSharp.Formats.Webp;
using SixLabors.ImageSharp.Formats.Tiff; using SixLabors.ImageSharp.Formats.Tiff;
namespace SixLabors.ImageSharp namespace SixLabors.ImageSharp
@ -536,6 +537,109 @@ namespace SixLabors.ImageSharp
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance), encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(TgaFormat.Instance),
cancellationToken); cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsWebp(this Image source, string path) => SaveAsWebp(source, path, null);
/// <summary>
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsWebpAsync(this Image source, string path) => SaveAsWebpAsync(source, path, null);
/// <summary>
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsWebpAsync(this Image source, string path, CancellationToken cancellationToken)
=> SaveAsWebpAsync(source, path, null, cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
public static void SaveAsWebp(this Image source, string path, WebpEncoder encoder) =>
source.Save(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="path">The file path to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsWebpAsync(this Image source, string path, WebpEncoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync(
path,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance),
cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
public static void SaveAsWebp(this Image source, Stream stream)
=> SaveAsWebp(source, stream, null);
/// <summary>
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsWebpAsync(this Image source, Stream stream, CancellationToken cancellationToken = default)
=> SaveAsWebpAsync(source, stream, null, cancellationToken);
/// <summary>
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static void SaveAsWebp(this Image source, Stream stream, WebpEncoder encoder)
=> source.Save(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance));
/// <summary>
/// Saves the image to the given stream with the Webp format.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="System.ArgumentNullException">Thrown if the stream is null.</exception>
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
public static Task SaveAsWebpAsync(this Image source, Stream stream, WebpEncoder encoder, CancellationToken cancellationToken = default) =>
source.SaveAsync(
stream,
encoder ?? source.GetConfiguration().ImageFormatsManager.FindEncoder(WebpFormat.Instance),
cancellationToken);
/// <summary> /// <summary>
/// Saves the image to the given stream with the Tiff format. /// Saves the image to the given stream with the Tiff format.
/// </summary> /// </summary>

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

@ -17,6 +17,7 @@ using SixLabors.ImageSharp.Advanced;
"Jpeg", "Jpeg",
"Png", "Png",
"Tga", "Tga",
"Webp",
"Tiff", "Tiff",
}; };

193
src/ImageSharp/Formats/Jpeg/Components/Block8x8.cs

@ -2,17 +2,22 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Diagnostics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
using System.Text; using System.Text;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
/// <summary> /// <summary>
/// Represents a Jpeg block with <see cref="short"/> coefficients. /// 8x8 matrix of <see cref="short"/> coefficients.
/// </summary> /// </summary>
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
[StructLayout(LayoutKind.Explicit)]
internal unsafe struct Block8x8 : IEquatable<Block8x8> internal unsafe struct Block8x8 : IEquatable<Block8x8>
{ {
/// <summary> /// <summary>
@ -20,24 +25,44 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// </summary> /// </summary>
public const int Size = 64; public const int Size = 64;
#pragma warning disable IDE0051 // Remove unused private member
/// <summary> /// <summary>
/// A fixed size buffer holding the values. /// A placeholder buffer so the actual struct occupies exactly 64 * 2 bytes.
/// See: <see>
/// <cref>https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/unsafe-code-pointers/fixed-size-buffers</cref>
/// </see>
/// </summary> /// </summary>
/// <remarks>
/// This is not used directly in the code.
/// </remarks>
[FieldOffset(0)]
private fixed short data[Size]; private fixed short data[Size];
#pragma warning restore IDE0051
/// <summary>
/// Initializes a new instance of the <see cref="Block8x8"/> struct. #if SUPPORTS_RUNTIME_INTRINSICS
/// </summary> [FieldOffset(0)]
/// <param name="coefficients">A <see cref="Span{T}"/> of coefficients</param> public Vector128<short> V0;
public Block8x8(Span<short> coefficients) [FieldOffset(16)]
{ public Vector128<short> V1;
ref byte selfRef = ref Unsafe.As<Block8x8, byte>(ref this); [FieldOffset(32)]
ref byte sourceRef = ref Unsafe.As<short, byte>(ref MemoryMarshal.GetReference(coefficients)); public Vector128<short> V2;
Unsafe.CopyBlock(ref selfRef, ref sourceRef, Size * sizeof(short)); [FieldOffset(48)]
} public Vector128<short> V3;
[FieldOffset(64)]
public Vector128<short> V4;
[FieldOffset(80)]
public Vector128<short> V5;
[FieldOffset(96)]
public Vector128<short> V6;
[FieldOffset(112)]
public Vector128<short> V7;
[FieldOffset(0)]
public Vector256<short> V01;
[FieldOffset(32)]
public Vector256<short> V23;
[FieldOffset(64)]
public Vector256<short> V45;
[FieldOffset(96)]
public Vector256<short> V67;
#endif
/// <summary> /// <summary>
/// Gets or sets a <see cref="short"/> value at the given index /// Gets or sets a <see cref="short"/> value at the given index
@ -49,7 +74,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
GuardBlockIndex(idx); DebugGuard.MustBeBetweenOrEqualTo(idx, 0, Size - 1, nameof(idx));
ref short selfRef = ref Unsafe.As<Block8x8, short>(ref this); ref short selfRef = ref Unsafe.As<Block8x8, short>(ref this);
return Unsafe.Add(ref selfRef, idx); return Unsafe.Add(ref selfRef, idx);
} }
@ -57,7 +83,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
set set
{ {
GuardBlockIndex(idx); DebugGuard.MustBeBetweenOrEqualTo(idx, 0, Size - 1, nameof(idx));
ref short selfRef = ref Unsafe.As<Block8x8, short>(ref this); ref short selfRef = ref Unsafe.As<Block8x8, short>(ref this);
Unsafe.Add(ref selfRef, idx) = value; Unsafe.Add(ref selfRef, idx) = value;
} }
@ -75,15 +102,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
set => this[(y * 8) + x] = value; set => this[(y * 8) + x] = value;
} }
public static bool operator ==(Block8x8 left, Block8x8 right) public static bool operator ==(Block8x8 left, Block8x8 right) => left.Equals(right);
{
return left.Equals(right);
}
public static bool operator !=(Block8x8 left, Block8x8 right) public static bool operator !=(Block8x8 left, Block8x8 right) => !left.Equals(right);
{
return !left.Equals(right);
}
/// <summary> /// <summary>
/// Multiply all elements by a given <see cref="int"/> /// Multiply all elements by a given <see cref="int"/>
@ -149,34 +170,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
return result; return result;
} }
/// <summary> public static Block8x8 Load(Span<short> data)
/// Pointer-based "Indexer" (getter part)
/// </summary>
/// <param name="blockPtr">Block pointer</param>
/// <param name="idx">Index</param>
/// <returns>The scaleVec value at the specified index</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static short GetScalarAt(Block8x8* blockPtr, int idx)
{
GuardBlockIndex(idx);
short* fp = blockPtr->data;
return fp[idx];
}
/// <summary>
/// Pointer-based "Indexer" (setter part)
/// </summary>
/// <param name="blockPtr">Block pointer</param>
/// <param name="idx">Index</param>
/// <param name="value">Value</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetScalarAt(Block8x8* blockPtr, int idx, short value)
{ {
GuardBlockIndex(idx); Unsafe.SkipInit(out Block8x8 result);
result.LoadFrom(data);
short* fp = blockPtr->data; return result;
fp[idx] = value;
} }
/// <summary> /// <summary>
@ -194,7 +192,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// </summary> /// </summary>
public short[] ToArray() public short[] ToArray()
{ {
var result = new short[Size]; short[] result = new short[Size];
this.CopyTo(result); this.CopyTo(result);
return result; return result;
} }
@ -206,7 +204,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
ref byte selfRef = ref Unsafe.As<Block8x8, byte>(ref this); ref byte selfRef = ref Unsafe.As<Block8x8, byte>(ref this);
ref byte destRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<short, byte>(destination)); ref byte destRef = ref MemoryMarshal.GetReference(MemoryMarshal.Cast<short, byte>(destination));
Unsafe.CopyBlock(ref destRef, ref selfRef, Size * sizeof(short)); Unsafe.CopyBlockUnaligned(ref destRef, ref selfRef, Size * sizeof(short));
} }
/// <summary> /// <summary>
@ -220,6 +218,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
} }
} }
/// <summary>
/// Load raw 16bit integers from source.
/// </summary>
/// <param name="source">Source</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void LoadFrom(Span<short> source)
{
ref byte sourceRef = ref Unsafe.As<short, byte>(ref MemoryMarshal.GetReference(source));
ref byte destRef = ref Unsafe.As<Block8x8, byte>(ref this);
Unsafe.CopyBlockUnaligned(ref destRef, ref sourceRef, Size * sizeof(short));
}
/// <summary> /// <summary>
/// Cast and copy <see cref="Size"/> <see cref="int"/>-s from the beginning of 'source' span. /// Cast and copy <see cref="Size"/> <see cref="int"/>-s from the beginning of 'source' span.
/// </summary> /// </summary>
@ -231,13 +242,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
} }
} }
[Conditional("DEBUG")]
private static void GuardBlockIndex(int idx)
{
DebugGuard.MustBeLessThan(idx, Size, nameof(idx));
DebugGuard.MustBeGreaterThanOrEqualTo(idx, 0, nameof(idx));
}
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() public override string ToString()
{ {
@ -271,15 +275,66 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
} }
/// <inheritdoc /> /// <inheritdoc />
public override bool Equals(object obj) public override bool Equals(object obj) => obj is Block8x8 other && this.Equals(other);
{
return obj is Block8x8 other && this.Equals(other);
}
/// <inheritdoc /> /// <inheritdoc />
public override int GetHashCode() public override int GetHashCode() => (this[0] * 31) + this[1];
/// <summary>
/// Returns index of the last non-zero element in given matrix.
/// </summary>
/// <returns>
/// Index of the last non-zero element. Returns -1 if all elements are equal to zero.
/// </returns>
[MethodImpl(InliningOptions.ShortMethod)]
public nint GetLastNonZeroIndex()
{ {
return (this[0] * 31) + this[1]; #if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported)
{
const int equalityMask = unchecked((int)0b1111_1111_1111_1111_1111_1111_1111_1111);
Vector256<short> zero16 = Vector256<short>.Zero;
ref Vector256<short> mcuStride = ref Unsafe.As<Block8x8, Vector256<short>>(ref this);
for (nint i = 3; i >= 0; i--)
{
int areEqual = Avx2.MoveMask(Avx2.CompareEqual(Unsafe.Add(ref mcuStride, i), zero16).AsByte());
if (areEqual != equalityMask)
{
// Each 2 bits represents comparison operation for each 2-byte element in input vectors
// LSB represents first element in the stride
// MSB represents last element in the stride
// lzcnt operation would calculate number of zero numbers at the end
// Given mask is not actually suitable for lzcnt as 1's represent zero elements and 0's represent non-zero elements
// So we need to invert it
int lzcnt = BitOperations.LeadingZeroCount(~(uint)areEqual);
// As input number is represented by 2 bits in the mask, we need to divide lzcnt result by 2
// to get the exact number of zero elements in the stride
int strideRelativeIndex = 15 - (lzcnt / 2);
return (i * 16) + strideRelativeIndex;
}
}
return -1;
}
else
#endif
{
nint index = Size - 1;
ref short elemRef = ref Unsafe.As<Block8x8, short>(ref this);
while (index >= 0 && Unsafe.Add(ref elemRef, index) == 0)
{
index--;
}
return index;
}
} }
/// <summary> /// <summary>

149
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.Intrinsic.cs

@ -0,0 +1,149 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
#if SUPPORTS_RUNTIME_INTRINSICS
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal partial struct Block8x8F
{
/// <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;
private static readonly Vector256<int> MultiplyIntoInt16ShuffleMask = Vector256.Create(0, 1, 4, 5, 2, 3, 6, 7);
private static unsafe void MultiplyIntoInt16_Avx2(ref Block8x8F a, ref Block8x8F b, ref Block8x8 dest)
{
DebugGuard.IsTrue(Avx2.IsSupported, "Avx2 support is required to run this operation!");
ref Vector256<float> aBase = ref a.V0;
ref Vector256<float> bBase = ref b.V0;
ref Vector256<short> destRef = ref dest.V01;
for (nint i = 0; i < 8; i += 2)
{
Vector256<int> row0 = Avx.ConvertToVector256Int32(Avx.Multiply(Unsafe.Add(ref aBase, i + 0), Unsafe.Add(ref bBase, i + 0)));
Vector256<int> row1 = Avx.ConvertToVector256Int32(Avx.Multiply(Unsafe.Add(ref aBase, i + 1), Unsafe.Add(ref bBase, i + 1)));
Vector256<short> row = Avx2.PackSignedSaturate(row0, row1);
row = Avx2.PermuteVar8x32(row.AsInt32(), MultiplyIntoInt16ShuffleMask).AsInt16();
Unsafe.Add(ref destRef, (IntPtr)((uint)i / 2)) = row;
}
}
private static void MultiplyIntoInt16_Sse2(ref Block8x8F a, ref Block8x8F b, ref Block8x8 dest)
{
DebugGuard.IsTrue(Sse2.IsSupported, "Sse2 support is required to run this operation!");
ref Vector128<float> aBase = ref Unsafe.As<Block8x8F, Vector128<float>>(ref a);
ref Vector128<float> bBase = ref Unsafe.As<Block8x8F, Vector128<float>>(ref b);
ref Vector128<short> destBase = ref Unsafe.As<Block8x8, Vector128<short>>(ref dest);
for (int i = 0; i < 16; i += 2)
{
Vector128<int> left = Sse2.ConvertToVector128Int32(Sse.Multiply(Unsafe.Add(ref aBase, i + 0), Unsafe.Add(ref bBase, i + 0)));
Vector128<int> right = Sse2.ConvertToVector128Int32(Sse.Multiply(Unsafe.Add(ref aBase, i + 1), Unsafe.Add(ref bBase, i + 1)));
Vector128<short> row = Sse2.PackSignedSaturate(left, right);
Unsafe.Add(ref destBase, (IntPtr)((uint)i / 2)) = row;
}
}
private void TransposeInplace_Avx()
{
// https://stackoverflow.com/questions/25622745/transpose-an-8x8-float-using-avx-avx2/25627536#25627536
Vector256<float> r0 = Avx.InsertVector128(
this.V0,
Unsafe.As<Vector4, Vector128<float>>(ref this.V4L),
1);
Vector256<float> r1 = Avx.InsertVector128(
this.V1,
Unsafe.As<Vector4, Vector128<float>>(ref this.V5L),
1);
Vector256<float> r2 = Avx.InsertVector128(
this.V2,
Unsafe.As<Vector4, Vector128<float>>(ref this.V6L),
1);
Vector256<float> r3 = Avx.InsertVector128(
this.V3,
Unsafe.As<Vector4, Vector128<float>>(ref this.V7L),
1);
Vector256<float> r4 = Avx.InsertVector128(
Unsafe.As<Vector4, Vector128<float>>(ref this.V0R).ToVector256(),
Unsafe.As<Vector4, Vector128<float>>(ref this.V4R),
1);
Vector256<float> r5 = Avx.InsertVector128(
Unsafe.As<Vector4, Vector128<float>>(ref this.V1R).ToVector256(),
Unsafe.As<Vector4, Vector128<float>>(ref this.V5R),
1);
Vector256<float> r6 = Avx.InsertVector128(
Unsafe.As<Vector4, Vector128<float>>(ref this.V2R).ToVector256(),
Unsafe.As<Vector4, Vector128<float>>(ref this.V6R),
1);
Vector256<float> r7 = Avx.InsertVector128(
Unsafe.As<Vector4, Vector128<float>>(ref this.V3R).ToVector256(),
Unsafe.As<Vector4, Vector128<float>>(ref this.V7R),
1);
Vector256<float> t0 = Avx.UnpackLow(r0, r1);
Vector256<float> t2 = Avx.UnpackLow(r2, r3);
Vector256<float> v = Avx.Shuffle(t0, t2, 0x4E);
this.V0 = Avx.Blend(t0, v, 0xCC);
this.V1 = Avx.Blend(t2, v, 0x33);
Vector256<float> t4 = Avx.UnpackLow(r4, r5);
Vector256<float> t6 = Avx.UnpackLow(r6, r7);
v = Avx.Shuffle(t4, t6, 0x4E);
this.V4 = Avx.Blend(t4, v, 0xCC);
this.V5 = Avx.Blend(t6, v, 0x33);
Vector256<float> t1 = Avx.UnpackHigh(r0, r1);
Vector256<float> t3 = Avx.UnpackHigh(r2, r3);
v = Avx.Shuffle(t1, t3, 0x4E);
this.V2 = Avx.Blend(t1, v, 0xCC);
this.V3 = Avx.Blend(t3, v, 0x33);
Vector256<float> t5 = Avx.UnpackHigh(r4, r5);
Vector256<float> t7 = Avx.UnpackHigh(r6, r7);
v = Avx.Shuffle(t5, t7, 0x4E);
this.V6 = Avx.Blend(t5, v, 0xCC);
this.V7 = Avx.Blend(t7, v, 0x33);
}
}
}
#endif

2
src/ImageSharp/Formats/Jpeg/Components/Block8x8F.ScaledCopyTo.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.Numerics; using System.Numerics;

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

@ -16,7 +16,7 @@ using System.Text;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
/// <summary> /// <summary>
/// Represents a Jpeg block with <see cref="float"/> coefficients. /// 8x8 matrix of <see cref="float"/> coefficients.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Explicit)] [StructLayout(LayoutKind.Explicit)]
internal partial struct Block8x8F : IEquatable<Block8x8F> internal partial struct Block8x8F : IEquatable<Block8x8F>
@ -66,30 +66,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
public Vector4 V7L; public Vector4 V7L;
[FieldOffset(240)] [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>
@ -102,17 +78,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
GuardBlockIndex(idx); DebugGuard.MustBeBetweenOrEqualTo(idx, 0, Size - 1, nameof(idx));
ref float selfRef = ref Unsafe.As<Block8x8F, float>(ref this); ref float selfRef = ref Unsafe.As<Block8x8F, float>(ref this);
return Unsafe.Add(ref selfRef, idx); return Unsafe.Add(ref selfRef, (nint)(uint)idx);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
set set
{ {
GuardBlockIndex(idx); DebugGuard.MustBeBetweenOrEqualTo(idx, 0, Size - 1, nameof(idx));
ref float selfRef = ref Unsafe.As<Block8x8F, float>(ref this); ref float selfRef = ref Unsafe.As<Block8x8F, float>(ref this);
Unsafe.Add(ref selfRef, idx) = value; Unsafe.Add(ref selfRef, (nint)(uint)idx) = value;
} }
} }
@ -188,13 +164,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
return result; return result;
} }
/// <summary>
/// Fill the block with defaults (zeroes).
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public void Clear()
=> this = default; // The cheapest way to do this in C#:
/// <summary> /// <summary>
/// Load raw 32bit floating point data from source. /// Load raw 32bit floating point data from source.
/// </summary> /// </summary>
@ -302,7 +271,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
public float[] ToArray() public float[] ToArray()
{ {
var result = new float[Size]; float[] result = new float[Size];
this.ScaledCopyTo(result); this.ScaledCopyTo(result);
return result; return result;
} }
@ -434,102 +403,37 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
} }
/// <summary> /// <summary>
/// Quantize the block. /// Quantize input block, apply zig-zag ordering and store result as 16bit integers.
/// </summary> /// </summary>
/// <param name="blockPtr">The block pointer.</param> /// <param name="block">Source block.</param>
/// <param name="qtPtr">The qt pointer.</param> /// <param name="dest">Destination block.</param>
/// <param name="unzigPtr">Unzig pointer</param> /// <param name="qt">The quantization table.</param>
public static unsafe void DequantizeBlock(Block8x8F* blockPtr, Block8x8F* qtPtr, byte* unzigPtr) public static void Quantize(ref Block8x8F block, ref Block8x8 dest, ref Block8x8F qt)
{ {
float* b = (float*)blockPtr; #if SUPPORTS_RUNTIME_INTRINSICS
float* qtp = (float*)qtPtr; if (Avx2.IsSupported)
for (int qtIndex = 0; qtIndex < Size; qtIndex++)
{
byte blockIndex = unzigPtr[qtIndex];
float* unzigPos = b + blockIndex;
float val = *unzigPos;
val *= qtp[qtIndex];
*unzigPos = val;
}
}
/// <summary>
/// Quantize 'block' into 'dest' using the 'qt' quantization table:
/// Unzig the elements of block into dest, while dividing them by elements of qt and "pre-rounding" the values.
/// To finish the rounding it's enough to (int)-cast these values.
/// </summary>
/// <param name="block">Source block</param>
/// <param name="dest">Destination block</param>
/// <param name="qt">The quantization table</param>
/// <param name="unZig">The 8x8 Unzig block.</param>
public static unsafe void Quantize(
ref Block8x8F block,
ref Block8x8F dest,
ref Block8x8F qt,
ref ZigZag unZig)
{
for (int zig = 0; zig < Size; zig++)
{ {
dest[zig] = block[unZig[zig]]; MultiplyIntoInt16_Avx2(ref block, ref qt, ref dest);
ZigZag.ApplyZigZagOrderingAvx2(ref dest);
} }
else if (Ssse3.IsSupported)
DivideRoundAll(ref dest, ref qt);
}
[MethodImpl(InliningOptions.ShortMethod)]
private static void DivideRoundAll(ref Block8x8F a, ref Block8x8F b)
{
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx.IsSupported)
{ {
var vnegOne = Vector256.Create(-1f); MultiplyIntoInt16_Sse2(ref block, ref qt, ref dest);
var vadd = Vector256.Create(.5F); ZigZag.ApplyZigZagOrderingSsse3(ref dest);
var vone = Vector256.Create(1f);
for (int i = 0; i < RowCount; i++)
{
ref Vector256<float> aRow = ref Unsafe.Add(ref a.V0, i);
ref Vector256<float> bRow = ref Unsafe.Add(ref b.V0, i);
Vector256<float> voff = Avx.Multiply(Avx.Min(Avx.Max(vnegOne, aRow), vone), vadd);
aRow = Avx.Add(Avx.Divide(aRow, bRow), voff);
}
} }
else else
#endif #endif
{ {
a.V0L = DivideRound(a.V0L, b.V0L); for (int i = 0; i < Size; i++)
a.V0R = DivideRound(a.V0R, b.V0R); {
a.V1L = DivideRound(a.V1L, b.V1L); int idx = ZigZag.ZigZagOrder[i];
a.V1R = DivideRound(a.V1R, b.V1R); float quantizedVal = block[idx] * qt[idx];
a.V2L = DivideRound(a.V2L, b.V2L); quantizedVal += quantizedVal < 0 ? -0.5f : 0.5f;
a.V2R = DivideRound(a.V2R, b.V2R); dest[i] = (short)quantizedVal;
a.V3L = DivideRound(a.V3L, b.V3L); }
a.V3R = DivideRound(a.V3R, b.V3R);
a.V4L = DivideRound(a.V4L, b.V4L);
a.V4R = DivideRound(a.V4R, b.V4R);
a.V5L = DivideRound(a.V5L, b.V5L);
a.V5R = DivideRound(a.V5R, b.V5R);
a.V6L = DivideRound(a.V6L, b.V6L);
a.V6R = DivideRound(a.V6R, b.V6R);
a.V7L = DivideRound(a.V7L, b.V7L);
a.V7R = DivideRound(a.V7R, b.V7R);
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 DivideRound(Vector4 dividend, Vector4 divisor)
{
var neg = new Vector4(-1);
var add = new Vector4(.5F);
// sign(dividend) = max(min(dividend, 1), -1)
Vector4 sign = Numerics.Clamp(dividend, neg, Vector4.One);
// AlmostRound(dividend/divisor) = dividend/divisor + 0.5*sign(dividend)
return (dividend / divisor) + (sign * add);
}
public void RoundInto(ref Block8x8 dest) public void RoundInto(ref Block8x8 dest)
{ {
for (int i = 0; i < Size; i++) for (int i = 0; i < Size; i++)
@ -627,6 +531,47 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
Unsafe.Add(ref dRef, 7) = bottom; Unsafe.Add(ref dRef, 7) = bottom;
} }
/// <summary>
/// Compares entire 8x8 block to a single scalar value.
/// </summary>
/// <param name="value">Value to compare to.</param>
public bool EqualsToScalar(int value)
{
#if SUPPORTS_RUNTIME_INTRINSICS
if (Avx2.IsSupported)
{
const int equalityMask = unchecked((int)0b1111_1111_1111_1111_1111_1111_1111_1111);
var targetVector = Vector256.Create(value);
ref Vector256<float> blockStride = ref this.V0;
for (int i = 0; i < RowCount; i++)
{
Vector256<int> areEqual = Avx2.CompareEqual(Avx.ConvertToVector256Int32WithTruncation(Unsafe.Add(ref this.V0, i)), targetVector);
if (Avx2.MoveMask(areEqual.AsByte()) != equalityMask)
{
return false;
}
}
return true;
}
#endif
{
ref float scalars = ref Unsafe.As<Block8x8F, float>(ref this);
for (int i = 0; i < Size; i++)
{
if ((int)Unsafe.Add(ref scalars, i) != value)
{
return false;
}
}
return true;
}
}
/// <inheritdoc /> /// <inheritdoc />
public bool Equals(Block8x8F other) public bool Equals(Block8x8F other)
=> this.V0L == other.V0L => this.V0L == other.V0L
@ -663,172 +608,89 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
return sb.ToString(); return sb.ToString();
} }
[MethodImpl(InliningOptions.ShortMethod)]
private static Vector<float> NormalizeAndRound(Vector<float> row, Vector<float> off, Vector<float> max)
{
row += off;
row = Vector.Max(row, Vector<float>.Zero);
row = Vector.Min(row, max);
return row.FastRound();
}
[Conditional("DEBUG")]
private static void GuardBlockIndex(int idx)
{
DebugGuard.MustBeLessThan(idx, Size, nameof(idx));
DebugGuard.MustBeGreaterThanOrEqualTo(idx, 0, nameof(idx));
}
/// <summary> /// <summary>
/// Transpose the block into the destination block. /// Transpose the block inplace.
/// </summary> /// </summary>
/// <param name="d">The destination block</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void TransposeInto(ref Block8x8F d) public void TransposeInplace()
{ {
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
if (Avx.IsSupported) if (Avx.IsSupported)
{ {
// https://stackoverflow.com/questions/25622745/transpose-an-8x8-float-using-avx-avx2/25627536#25627536 this.TransposeInplace_Avx();
Vector256<float> r0 = Avx.InsertVector128(
Unsafe.As<Vector4, Vector128<float>>(ref this.V0L).ToVector256(),
Unsafe.As<Vector4, Vector128<float>>(ref this.V4L),
1);
Vector256<float> r1 = Avx.InsertVector128(
Unsafe.As<Vector4, Vector128<float>>(ref this.V1L).ToVector256(),
Unsafe.As<Vector4, Vector128<float>>(ref this.V5L),
1);
Vector256<float> r2 = Avx.InsertVector128(
Unsafe.As<Vector4, Vector128<float>>(ref this.V2L).ToVector256(),
Unsafe.As<Vector4, Vector128<float>>(ref this.V6L),
1);
Vector256<float> r3 = Avx.InsertVector128(
Unsafe.As<Vector4, Vector128<float>>(ref this.V3L).ToVector256(),
Unsafe.As<Vector4, Vector128<float>>(ref this.V7L),
1);
Vector256<float> r4 = Avx.InsertVector128(
Unsafe.As<Vector4, Vector128<float>>(ref this.V0R).ToVector256(),
Unsafe.As<Vector4, Vector128<float>>(ref this.V4R),
1);
Vector256<float> r5 = Avx.InsertVector128(
Unsafe.As<Vector4, Vector128<float>>(ref this.V1R).ToVector256(),
Unsafe.As<Vector4, Vector128<float>>(ref this.V5R),
1);
Vector256<float> r6 = Avx.InsertVector128(
Unsafe.As<Vector4, Vector128<float>>(ref this.V2R).ToVector256(),
Unsafe.As<Vector4, Vector128<float>>(ref this.V6R),
1);
Vector256<float> r7 = Avx.InsertVector128(
Unsafe.As<Vector4, Vector128<float>>(ref this.V3R).ToVector256(),
Unsafe.As<Vector4, Vector128<float>>(ref this.V7R),
1);
Vector256<float> t0 = Avx.UnpackLow(r0, r1);
Vector256<float> t2 = Avx.UnpackLow(r2, r3);
Vector256<float> v = Avx.Shuffle(t0, t2, 0x4E);
d.V0 = Avx.Blend(t0, v, 0xCC);
d.V1 = Avx.Blend(t2, v, 0x33);
Vector256<float> t4 = Avx.UnpackLow(r4, r5);
Vector256<float> t6 = Avx.UnpackLow(r6, r7);
v = Avx.Shuffle(t4, t6, 0x4E);
d.V4 = Avx.Blend(t4, v, 0xCC);
d.V5 = Avx.Blend(t6, v, 0x33);
Vector256<float> t1 = Avx.UnpackHigh(r0, r1);
Vector256<float> t3 = Avx.UnpackHigh(r2, r3);
v = Avx.Shuffle(t1, t3, 0x4E);
d.V2 = Avx.Blend(t1, v, 0xCC);
d.V3 = Avx.Blend(t3, v, 0x33);
Vector256<float> t5 = Avx.UnpackHigh(r4, r5);
Vector256<float> t7 = Avx.UnpackHigh(r6, r7);
v = Avx.Shuffle(t5, t7, 0x4E);
d.V6 = Avx.Blend(t5, v, 0xCC);
d.V7 = Avx.Blend(t7, v, 0x33);
} }
else else
#endif #endif
{ {
d.V0L.X = this.V0L.X; this.TransposeInplace_Scalar();
d.V1L.X = this.V0L.Y; }
d.V2L.X = this.V0L.Z; }
d.V3L.X = this.V0L.W;
d.V4L.X = this.V0R.X; /// <summary>
d.V5L.X = this.V0R.Y; /// Scalar inplace transpose implementation for <see cref="TransposeInplace"/>
d.V6L.X = this.V0R.Z; /// </summary>
d.V7L.X = this.V0R.W; [MethodImpl(InliningOptions.ShortMethod)]
private void TransposeInplace_Scalar()
d.V0L.Y = this.V1L.X; {
d.V1L.Y = this.V1L.Y; ref float elemRef = ref Unsafe.As<Block8x8F, float>(ref this);
d.V2L.Y = this.V1L.Z;
d.V3L.Y = this.V1L.W; // row #0
d.V4L.Y = this.V1R.X; Swap(ref Unsafe.Add(ref elemRef, 1), ref Unsafe.Add(ref elemRef, 8));
d.V5L.Y = this.V1R.Y; Swap(ref Unsafe.Add(ref elemRef, 2), ref Unsafe.Add(ref elemRef, 16));
d.V6L.Y = this.V1R.Z; Swap(ref Unsafe.Add(ref elemRef, 3), ref Unsafe.Add(ref elemRef, 24));
d.V7L.Y = this.V1R.W; Swap(ref Unsafe.Add(ref elemRef, 4), ref Unsafe.Add(ref elemRef, 32));
Swap(ref Unsafe.Add(ref elemRef, 5), ref Unsafe.Add(ref elemRef, 40));
d.V0L.Z = this.V2L.X; Swap(ref Unsafe.Add(ref elemRef, 6), ref Unsafe.Add(ref elemRef, 48));
d.V1L.Z = this.V2L.Y; Swap(ref Unsafe.Add(ref elemRef, 7), ref Unsafe.Add(ref elemRef, 56));
d.V2L.Z = this.V2L.Z;
d.V3L.Z = this.V2L.W; // row #1
d.V4L.Z = this.V2R.X; Swap(ref Unsafe.Add(ref elemRef, 10), ref Unsafe.Add(ref elemRef, 17));
d.V5L.Z = this.V2R.Y; Swap(ref Unsafe.Add(ref elemRef, 11), ref Unsafe.Add(ref elemRef, 25));
d.V6L.Z = this.V2R.Z; Swap(ref Unsafe.Add(ref elemRef, 12), ref Unsafe.Add(ref elemRef, 33));
d.V7L.Z = this.V2R.W; Swap(ref Unsafe.Add(ref elemRef, 13), ref Unsafe.Add(ref elemRef, 41));
Swap(ref Unsafe.Add(ref elemRef, 14), ref Unsafe.Add(ref elemRef, 49));
d.V0L.W = this.V3L.X; Swap(ref Unsafe.Add(ref elemRef, 15), ref Unsafe.Add(ref elemRef, 57));
d.V1L.W = this.V3L.Y;
d.V2L.W = this.V3L.Z; // row #2
d.V3L.W = this.V3L.W; Swap(ref Unsafe.Add(ref elemRef, 19), ref Unsafe.Add(ref elemRef, 26));
d.V4L.W = this.V3R.X; Swap(ref Unsafe.Add(ref elemRef, 20), ref Unsafe.Add(ref elemRef, 34));
d.V5L.W = this.V3R.Y; Swap(ref Unsafe.Add(ref elemRef, 21), ref Unsafe.Add(ref elemRef, 42));
d.V6L.W = this.V3R.Z; Swap(ref Unsafe.Add(ref elemRef, 22), ref Unsafe.Add(ref elemRef, 50));
d.V7L.W = this.V3R.W; Swap(ref Unsafe.Add(ref elemRef, 23), ref Unsafe.Add(ref elemRef, 58));
d.V0R.X = this.V4L.X; // row #3
d.V1R.X = this.V4L.Y; Swap(ref Unsafe.Add(ref elemRef, 28), ref Unsafe.Add(ref elemRef, 35));
d.V2R.X = this.V4L.Z; Swap(ref Unsafe.Add(ref elemRef, 29), ref Unsafe.Add(ref elemRef, 43));
d.V3R.X = this.V4L.W; Swap(ref Unsafe.Add(ref elemRef, 30), ref Unsafe.Add(ref elemRef, 51));
d.V4R.X = this.V4R.X; Swap(ref Unsafe.Add(ref elemRef, 31), ref Unsafe.Add(ref elemRef, 59));
d.V5R.X = this.V4R.Y;
d.V6R.X = this.V4R.Z; // row #4
d.V7R.X = this.V4R.W; Swap(ref Unsafe.Add(ref elemRef, 37), ref Unsafe.Add(ref elemRef, 44));
Swap(ref Unsafe.Add(ref elemRef, 38), ref Unsafe.Add(ref elemRef, 52));
d.V0R.Y = this.V5L.X; Swap(ref Unsafe.Add(ref elemRef, 39), ref Unsafe.Add(ref elemRef, 60));
d.V1R.Y = this.V5L.Y;
d.V2R.Y = this.V5L.Z; // row #5
d.V3R.Y = this.V5L.W; Swap(ref Unsafe.Add(ref elemRef, 46), ref Unsafe.Add(ref elemRef, 53));
d.V4R.Y = this.V5R.X; Swap(ref Unsafe.Add(ref elemRef, 47), ref Unsafe.Add(ref elemRef, 61));
d.V5R.Y = this.V5R.Y;
d.V6R.Y = this.V5R.Z; // row #6
d.V7R.Y = this.V5R.W; Swap(ref Unsafe.Add(ref elemRef, 55), ref Unsafe.Add(ref elemRef, 62));
d.V0R.Z = this.V6L.X; static void Swap(ref float a, ref float b)
d.V1R.Z = this.V6L.Y; {
d.V2R.Z = this.V6L.Z; float tmp = a;
d.V3R.Z = this.V6L.W; a = b;
d.V4R.Z = this.V6R.X; b = tmp;
d.V5R.Z = this.V6R.Y;
d.V6R.Z = this.V6R.Z;
d.V7R.Z = this.V6R.W;
d.V0R.W = this.V7L.X;
d.V1R.W = this.V7L.Y;
d.V2R.W = this.V7L.Z;
d.V3R.W = this.V7L.W;
d.V4R.W = this.V7R.X;
d.V5R.W = this.V7R.Y;
d.V6R.W = this.V7R.Z;
d.V7R.W = this.V7R.W;
} }
} }
[MethodImpl(InliningOptions.ShortMethod)]
private static Vector<float> NormalizeAndRound(Vector<float> row, Vector<float> off, Vector<float> max)
{
row += off;
row = Vector.Max(row, Vector<float>.Zero);
row = Vector.Min(row, max);
return row.FastRound();
}
} }
} }

47
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykAvx2.cs

@ -22,60 +22,39 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result) protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{ {
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
ref Vector256<float> cBase = ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> mBase = ref Vector256<float> c1Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> yBase = ref Vector256<float> c2Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> kBase = ref Vector256<float> c3Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3));
ref Vector256<float> resultBase =
ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(result));
// Used for the color conversion // Used for the color conversion
var scale = Vector256.Create(1 / this.MaximumValue); var scale = Vector256.Create(1 / this.MaximumValue);
var one = Vector256.Create(1F);
// Used for packing
ref byte control = ref MemoryMarshal.GetReference(HwIntrinsics.PermuteMaskEvenOdd8x32);
Vector256<int> vcontrol = Unsafe.As<byte, Vector256<int>>(ref control);
int n = result.Length / 8; nint n = values.Component0.Length / 8;
for (int i = 0; i < n; i++) for (nint i = 0; i < n; i++)
{ {
Vector256<float> k = Avx2.PermuteVar8x32(Unsafe.Add(ref kBase, i), vcontrol); ref Vector256<float> c = ref Unsafe.Add(ref c0Base, i);
Vector256<float> c = Avx2.PermuteVar8x32(Unsafe.Add(ref cBase, i), vcontrol); ref Vector256<float> m = ref Unsafe.Add(ref c1Base, i);
Vector256<float> m = Avx2.PermuteVar8x32(Unsafe.Add(ref mBase, i), vcontrol); ref Vector256<float> y = ref Unsafe.Add(ref c2Base, i);
Vector256<float> y = Avx2.PermuteVar8x32(Unsafe.Add(ref yBase, i), vcontrol); Vector256<float> k = Unsafe.Add(ref c3Base, i);
k = Avx.Multiply(k, scale); k = Avx.Multiply(k, scale);
c = Avx.Multiply(Avx.Multiply(c, k), scale); c = Avx.Multiply(Avx.Multiply(c, k), scale);
m = Avx.Multiply(Avx.Multiply(m, k), scale); m = Avx.Multiply(Avx.Multiply(m, k), scale);
y = Avx.Multiply(Avx.Multiply(y, k), scale); y = Avx.Multiply(Avx.Multiply(y, k), scale);
Vector256<float> cmLo = Avx.UnpackLow(c, m);
Vector256<float> yoLo = Avx.UnpackLow(y, one);
Vector256<float> cmHi = Avx.UnpackHigh(c, m);
Vector256<float> yoHi = Avx.UnpackHigh(y, one);
ref Vector256<float> destination = ref Unsafe.Add(ref resultBase, i * 4);
destination = Avx.Shuffle(cmLo, yoLo, 0b01_00_01_00);
Unsafe.Add(ref destination, 1) = Avx.Shuffle(cmLo, yoLo, 0b11_10_11_10);
Unsafe.Add(ref destination, 2) = Avx.Shuffle(cmHi, yoHi, 0b01_00_01_00);
Unsafe.Add(ref destination, 3) = Avx.Shuffle(cmHi, yoHi, 0b11_10_11_10);
} }
#endif #endif
} }
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) => protected override void ConvertCoreInplace(in ComponentValues values) =>
FromCmykBasic.ConvertCore(values, result, this.MaximumValue); FromCmykBasic.ConvertCoreInplace(values, this.MaximumValue);
} }
} }
} }

45
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykBasic.cs

@ -15,38 +15,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result) public override void ConvertToRgbInplace(in ComponentValues values) =>
{ ConvertCoreInplace(values, this.MaximumValue);
ConvertCore(values, result, this.MaximumValue);
}
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result, float maxValue) internal static void ConvertCoreInplace(in ComponentValues values, float maxValue)
{ {
ReadOnlySpan<float> cVals = values.Component0; Span<float> c0 = values.Component0;
ReadOnlySpan<float> mVals = values.Component1; Span<float> c1 = values.Component1;
ReadOnlySpan<float> yVals = values.Component2; Span<float> c2 = values.Component2;
ReadOnlySpan<float> kVals = values.Component3; Span<float> c3 = values.Component3;
var v = new Vector4(0, 0, 0, 1F);
var maximum = 1 / maxValue;
var scale = new Vector4(maximum, maximum, maximum, 1F);
for (int i = 0; i < result.Length; i++) float scale = 1 / maxValue;
for (int i = 0; i < c0.Length; i++)
{ {
float c = cVals[i]; float c = c0[i];
float m = mVals[i]; float m = c1[i];
float y = yVals[i]; float y = c2[i];
float k = kVals[i] / maxValue; float k = c3[i] / maxValue;
v.X = c * k; c0[i] = c * k * scale;
v.Y = m * k; c1[i] = m * k * scale;
v.Z = y * k; c2[i] = y * k * scale;
v.W = 1F;
v *= scale;
result[i] = v;
} }
} }
} }

34
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromCmykVector8.cs

@ -18,7 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result) protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{ {
ref Vector<float> cBase = ref Vector<float> cBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0)); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
@ -29,43 +29,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
ref Vector<float> kBase = ref Vector<float> kBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component3)); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component3));
ref Vector4Octet resultBase =
ref Unsafe.As<Vector4, Vector4Octet>(ref MemoryMarshal.GetReference(result));
Vector4Pair cc = default;
Vector4Pair mm = default;
Vector4Pair yy = default;
ref Vector<float> ccRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref cc);
ref Vector<float> mmRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref mm);
ref Vector<float> yyRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref yy);
var scale = new Vector<float>(1 / this.MaximumValue); var scale = new Vector<float>(1 / this.MaximumValue);
// Walking 8 elements at one step: // Walking 8 elements at one step:
int n = result.Length / 8; nint n = values.Component0.Length / 8;
for (int i = 0; i < n; i++) for (nint i = 0; i < n; i++)
{ {
Vector<float> c = Unsafe.Add(ref cBase, i); ref Vector<float> c = ref Unsafe.Add(ref cBase, i);
Vector<float> m = Unsafe.Add(ref mBase, i); ref Vector<float> m = ref Unsafe.Add(ref mBase, i);
Vector<float> y = Unsafe.Add(ref yBase, i); ref Vector<float> y = ref Unsafe.Add(ref yBase, i);
Vector<float> k = Unsafe.Add(ref kBase, i) * scale; Vector<float> k = Unsafe.Add(ref kBase, i) * scale;
c = (c * k) * scale; c = (c * k) * scale;
m = (m * k) * scale; m = (m * k) * scale;
y = (y * k) * scale; y = (y * k) * scale;
ccRefAsVector = c;
mmRefAsVector = m;
yyRefAsVector = y;
// Collect (c0,c1...c8) (m0,m1...m8) (y0,y1...y8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order:
ref Vector4Octet destination = ref Unsafe.Add(ref resultBase, i);
destination.Pack(ref cc, ref mm, ref yy);
} }
} }
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) => protected override void ConvertCoreInplace(in ComponentValues values) =>
FromCmykBasic.ConvertCore(values, result, this.MaximumValue); FromCmykBasic.ConvertCoreInplace(values, this.MaximumValue);
} }
} }
} }

32
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleAvx2.cs

@ -22,42 +22,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result) protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{ {
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
ref Vector256<float> gBase = ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> resultBase =
ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(result));
// Used for the color conversion // Used for the color conversion
var scale = Vector256.Create(1 / this.MaximumValue); var scale = Vector256.Create(1 / this.MaximumValue);
var one = Vector256.Create(1F);
// Used for packing
ref byte control = ref MemoryMarshal.GetReference(HwIntrinsics.PermuteMaskEvenOdd8x32);
Vector256<int> vcontrol = Unsafe.As<byte, Vector256<int>>(ref control);
int n = result.Length / 8; nint n = values.Component0.Length / 8;
for (int i = 0; i < n; i++) for (nint i = 0; i < n; i++)
{ {
Vector256<float> g = Avx.Multiply(Unsafe.Add(ref gBase, i), scale); ref Vector256<float> c0 = ref Unsafe.Add(ref c0Base, i);
c0 = Avx.Multiply(c0, scale);
g = Avx2.PermuteVar8x32(g, vcontrol);
ref Vector256<float> destination = ref Unsafe.Add(ref resultBase, i * 4);
destination = Avx.Blend(Avx.Permute(g, 0b00_00_00_00), one, 0b1000_1000);
Unsafe.Add(ref destination, 1) = Avx.Blend(Avx.Shuffle(g, g, 0b01_01_01_01), one, 0b1000_1000);
Unsafe.Add(ref destination, 2) = Avx.Blend(Avx.Shuffle(g, g, 0b10_10_10_10), one, 0b1000_1000);
Unsafe.Add(ref destination, 3) = Avx.Blend(Avx.Shuffle(g, g, 0b11_11_11_11), one, 0b1000_1000);
} }
#endif #endif
} }
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) => protected override void ConvertCoreInplace(in ComponentValues values) =>
FromGrayscaleBasic.ConvertCore(values, result, this.MaximumValue); FromGrayscaleBasic.ScaleValues(values.Component0, this.MaximumValue);
} }
} }
} }

38
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromGrayScaleBasic.cs

@ -17,25 +17,35 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result) public override void ConvertToRgbInplace(in ComponentValues values) =>
{ ScaleValues(values.Component0, this.MaximumValue);
ConvertCore(values, result, this.MaximumValue);
}
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result, float maxValue) internal static void ScaleValues(Span<float> values, float maxValue)
{ {
var maximum = 1 / maxValue; Span<Vector4> vecValues = MemoryMarshal.Cast<float, Vector4>(values);
var scale = new Vector4(maximum, maximum, maximum, 1F);
ref float sBase = ref MemoryMarshal.GetReference(values.Component0); var scaleVector = new Vector4(1 / maxValue);
ref Vector4 dBase = ref MemoryMarshal.GetReference(result);
for (int i = 0; i < result.Length; i++) for (int i = 0; i < vecValues.Length; i++)
{ {
var v = new Vector4(Unsafe.Add(ref sBase, i)); vecValues[i] *= scaleVector;
v.W = 1f; }
v *= scale;
Unsafe.Add(ref dBase, i) = v; values = values.Slice(vecValues.Length * 4);
if (!values.IsEmpty)
{
float scaleValue = 1f / maxValue;
values[0] *= scaleValue;
if ((uint)values.Length > 1)
{
values[1] *= scaleValue;
if ((uint)values.Length > 2)
{
values[2] *= scaleValue;
}
}
} }
} }
} }

40
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbAvx2.cs

@ -22,7 +22,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result) protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{ {
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
ref Vector256<float> rBase = ref Vector256<float> rBase =
@ -32,41 +32,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
ref Vector256<float> bBase = ref Vector256<float> bBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> resultBase =
ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(result));
// Used for the color conversion // Used for the color conversion
var scale = Vector256.Create(1 / this.MaximumValue); var scale = Vector256.Create(1 / this.MaximumValue);
var one = Vector256.Create(1F); nint n = values.Component0.Length / 8;
for (nint i = 0; i < n; i++)
// Used for packing
ref byte control = ref MemoryMarshal.GetReference(HwIntrinsics.PermuteMaskEvenOdd8x32);
Vector256<int> vcontrol = Unsafe.As<byte, Vector256<int>>(ref control);
int n = result.Length / 8;
for (int i = 0; i < n; i++)
{ {
Vector256<float> r = Avx.Multiply(Avx2.PermuteVar8x32(Unsafe.Add(ref rBase, i), vcontrol), scale); ref Vector256<float> r = ref Unsafe.Add(ref rBase, i);
Vector256<float> g = Avx.Multiply(Avx2.PermuteVar8x32(Unsafe.Add(ref gBase, i), vcontrol), scale); ref Vector256<float> g = ref Unsafe.Add(ref gBase, i);
Vector256<float> b = Avx.Multiply(Avx2.PermuteVar8x32(Unsafe.Add(ref bBase, i), vcontrol), scale); ref Vector256<float> b = ref Unsafe.Add(ref bBase, i);
r = Avx.Multiply(r, scale);
Vector256<float> rgLo = Avx.UnpackLow(r, g); g = Avx.Multiply(g, scale);
Vector256<float> boLo = Avx.UnpackLow(b, one); b = Avx.Multiply(b, scale);
Vector256<float> rgHi = Avx.UnpackHigh(r, g);
Vector256<float> boHi = Avx.UnpackHigh(b, one);
ref Vector256<float> destination = ref Unsafe.Add(ref resultBase, i * 4);
destination = Avx.Shuffle(rgLo, boLo, 0b01_00_01_00);
Unsafe.Add(ref destination, 1) = Avx.Shuffle(rgLo, boLo, 0b11_10_11_10);
Unsafe.Add(ref destination, 2) = Avx.Shuffle(rgHi, boHi, 0b01_00_01_00);
Unsafe.Add(ref destination, 3) = Avx.Shuffle(rgHi, boHi, 0b11_10_11_10);
} }
#endif #endif
} }
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) => protected override void ConvertCoreInplace(in ComponentValues values) =>
FromRgbBasic.ConvertCore(values, result, this.MaximumValue); FromRgbBasic.ConvertCoreInplace(values, this.MaximumValue);
} }
} }
} }

33
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbBasic.cs

@ -3,6 +3,7 @@
using System; using System;
using System.Numerics; using System.Numerics;
using System.Runtime.InteropServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
@ -15,36 +16,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result) public override void ConvertToRgbInplace(in ComponentValues values)
{ {
ConvertCore(values, result, this.MaximumValue); ConvertCoreInplace(values, this.MaximumValue);
} }
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result, float maxValue) internal static void ConvertCoreInplace(ComponentValues values, float maxValue)
{ {
ReadOnlySpan<float> rVals = values.Component0; FromGrayscaleBasic.ScaleValues(values.Component0, maxValue);
ReadOnlySpan<float> gVals = values.Component1; FromGrayscaleBasic.ScaleValues(values.Component1, maxValue);
ReadOnlySpan<float> bVals = values.Component2; FromGrayscaleBasic.ScaleValues(values.Component2, maxValue);
var v = new Vector4(0, 0, 0, 1);
var maximum = 1 / maxValue;
var scale = new Vector4(maximum, maximum, maximum, 1F);
for (int i = 0; i < result.Length; i++)
{
float r = rVals[i];
float g = gVals[i];
float b = bVals[i];
v.X = r;
v.Y = g;
v.Z = b;
v *= scale;
result[i] = v;
}
} }
} }
} }

40
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromRgbVector8.cs

@ -18,50 +18,32 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result) protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{ {
ref Vector<float> rBase = ref Vector<float> rBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0)); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector<float> gBase = ref Vector<float> gBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1)); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector<float> bBase = ref Vector<float> bBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2)); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector4Octet resultBase =
ref Unsafe.As<Vector4, Vector4Octet>(ref MemoryMarshal.GetReference(result));
Vector4Pair rr = default;
Vector4Pair gg = default;
Vector4Pair bb = default;
ref Vector<float> rrRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref rr);
ref Vector<float> ggRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref gg);
ref Vector<float> bbRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref bb);
var scale = new Vector<float>(1 / this.MaximumValue); var scale = new Vector<float>(1 / this.MaximumValue);
// Walking 8 elements at one step: // Walking 8 elements at one step:
int n = result.Length / 8; nint n = values.Component0.Length / 8;
for (int i = 0; i < n; i++) for (nint i = 0; i < n; i++)
{ {
Vector<float> r = Unsafe.Add(ref rBase, i); ref Vector<float> r = ref Unsafe.Add(ref rBase, i);
Vector<float> g = Unsafe.Add(ref gBase, i); ref Vector<float> g = ref Unsafe.Add(ref gBase, i);
Vector<float> b = Unsafe.Add(ref bBase, i); ref Vector<float> b = ref Unsafe.Add(ref bBase, i);
r *= scale; r *= scale;
g *= scale; g *= scale;
b *= scale; b *= scale;
rrRefAsVector = r;
ggRefAsVector = g;
bbRefAsVector = b;
// Collect (r0,r1...r8) (g0,g1...g8) (b0,b1...b8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order:
ref Vector4Octet destination = ref Unsafe.Add(ref resultBase, i);
destination.Pack(ref rr, ref gg, ref bb);
} }
} }
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) => protected override void ConvertCoreInplace(in ComponentValues values) =>
FromRgbBasic.ConvertCore(values, result, this.MaximumValue); FromRgbBasic.ConvertCoreInplace(values, this.MaximumValue);
} }
} }
} }

50
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrAvx2.cs

@ -23,19 +23,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result) protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{ {
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
ref Vector256<float> yBase = ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> cbBase = ref Vector256<float> c1Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> crBase = ref Vector256<float> c2Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> resultBase =
ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(result));
// Used for the color conversion // Used for the color conversion
var chromaOffset = Vector256.Create(-this.HalfValue); var chromaOffset = Vector256.Create(-this.HalfValue);
var scale = Vector256.Create(1 / this.MaximumValue); var scale = Vector256.Create(1 / this.MaximumValue);
@ -50,19 +47,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
Vector256<int> vcontrol = Unsafe.As<byte, Vector256<int>>(ref control); Vector256<int> vcontrol = Unsafe.As<byte, Vector256<int>>(ref control);
// Walking 8 elements at one step: // Walking 8 elements at one step:
int n = result.Length / 8; nint n = values.Component0.Length / 8;
for (int i = 0; i < n; i++) for (nint i = 0; i < n; i++)
{ {
// y = yVals[i]; // y = yVals[i];
// cb = cbVals[i] - 128F; // cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F; // cr = crVals[i] - 128F;
Vector256<float> y = Unsafe.Add(ref yBase, i); ref Vector256<float> c0 = ref Unsafe.Add(ref c0Base, i);
Vector256<float> cb = Avx.Add(Unsafe.Add(ref cbBase, i), chromaOffset); ref Vector256<float> c1 = ref Unsafe.Add(ref c1Base, i);
Vector256<float> cr = Avx.Add(Unsafe.Add(ref crBase, i), chromaOffset); ref Vector256<float> c2 = ref Unsafe.Add(ref c2Base, i);
y = Avx2.PermuteVar8x32(y, vcontrol); Vector256<float> y = c0;
cb = Avx2.PermuteVar8x32(cb, vcontrol); Vector256<float> cb = Avx.Add(c1, chromaOffset);
cr = Avx2.PermuteVar8x32(cr, vcontrol); Vector256<float> cr = Avx.Add(c2, chromaOffset);
// r = y + (1.402F * cr); // r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr);
@ -72,30 +69,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
Vector256<float> g = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult); Vector256<float> g = HwIntrinsics.MultiplyAdd(HwIntrinsics.MultiplyAdd(y, cb, gCbMult), cr, gCrMult);
Vector256<float> b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult); Vector256<float> b = HwIntrinsics.MultiplyAdd(y, cb, bCbMult);
// TODO: We should be saving to RGBA not Vector4
r = Avx.Multiply(Avx.RoundToNearestInteger(r), scale); r = Avx.Multiply(Avx.RoundToNearestInteger(r), scale);
g = Avx.Multiply(Avx.RoundToNearestInteger(g), scale); g = Avx.Multiply(Avx.RoundToNearestInteger(g), scale);
b = Avx.Multiply(Avx.RoundToNearestInteger(b), scale); b = Avx.Multiply(Avx.RoundToNearestInteger(b), scale);
Vector256<float> vte = Avx.UnpackLow(r, b); c0 = r;
Vector256<float> vto = Avx.UnpackLow(g, va); c1 = g;
c2 = b;
ref Vector256<float> destination = ref Unsafe.Add(ref resultBase, i * 4);
destination = Avx.UnpackLow(vte, vto);
Unsafe.Add(ref destination, 1) = Avx.UnpackHigh(vte, vto);
vte = Avx.UnpackHigh(r, b);
vto = Avx.UnpackHigh(g, va);
Unsafe.Add(ref destination, 2) = Avx.UnpackLow(vte, vto);
Unsafe.Add(ref destination, 3) = Avx.UnpackHigh(vte, vto);
} }
#endif #endif
} }
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) => protected override void ConvertCoreInplace(in ComponentValues values) =>
FromYCbCrBasic.ConvertCore(values, result, this.MaximumValue, this.HalfValue); FromYCbCrBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
} }
} }
} }

37
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrBasic.cs

@ -15,35 +15,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result) public override void ConvertToRgbInplace(in ComponentValues values)
{ => ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
ConvertCore(values, result, this.MaximumValue, this.HalfValue);
}
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result, float maxValue, float halfValue) internal static void ConvertCoreInplace(in ComponentValues values, float maxValue, float halfValue)
{ {
// TODO: We can optimize a lot here with Vector<float> and SRCS.Unsafe()! Span<float> c0 = values.Component0;
ReadOnlySpan<float> yVals = values.Component0; Span<float> c1 = values.Component1;
ReadOnlySpan<float> cbVals = values.Component1; Span<float> c2 = values.Component2;
ReadOnlySpan<float> crVals = values.Component2;
var v = new Vector4(0, 0, 0, 1);
var scale = new Vector4(1 / maxValue, 1 / maxValue, 1 / maxValue, 1F); var scale = 1 / maxValue;
for (int i = 0; i < result.Length; i++) for (int i = 0; i < c0.Length; i++)
{ {
float y = yVals[i]; float y = c0[i];
float cb = cbVals[i] - halfValue; float cb = c1[i] - halfValue;
float cr = crVals[i] - halfValue; float cr = c2[i] - halfValue;
v.X = MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero);
v.Y = MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero);
v.Z = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero);
v *= scale;
result[i] = v; c0[i] = MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero) * scale;
c1[i] = MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero) * scale;
c2[i] = MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero) * scale;
} }
} }
} }

52
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector4.cs

@ -20,58 +20,54 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
protected override bool IsAvailable => SimdUtils.HasVector4; protected override bool IsAvailable => SimdUtils.HasVector4;
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result) protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{ {
// TODO: Find a way to properly run & test this path on AVX2 PC-s! (Have I already mentioned that Vector<T> is terrible?) DebugGuard.IsTrue(values.Component0.Length % 8 == 0, nameof(values), "Length should be divisible by 8!");
DebugGuard.IsTrue(result.Length % 8 == 0, nameof(result), "result.Length should be divisible by 8!");
ref Vector4Pair yBase = ref Vector4Pair c0Base =
ref Unsafe.As<float, Vector4Pair>(ref MemoryMarshal.GetReference(values.Component0)); ref Unsafe.As<float, Vector4Pair>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector4Pair cbBase = ref Vector4Pair c1Base =
ref Unsafe.As<float, Vector4Pair>(ref MemoryMarshal.GetReference(values.Component1)); ref Unsafe.As<float, Vector4Pair>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector4Pair crBase = ref Vector4Pair c2Base =
ref Unsafe.As<float, Vector4Pair>(ref MemoryMarshal.GetReference(values.Component2)); ref Unsafe.As<float, Vector4Pair>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector4Octet resultBase =
ref Unsafe.As<Vector4, Vector4Octet>(ref MemoryMarshal.GetReference(result));
var chromaOffset = new Vector4(-this.HalfValue); var chromaOffset = new Vector4(-this.HalfValue);
var maxValue = this.MaximumValue; var maxValue = this.MaximumValue;
// Walking 8 elements at one step: // Walking 8 elements at one step:
int n = result.Length / 8; nint n = values.Component0.Length / 8;
for (int i = 0; i < n; i++) for (nint i = 0; i < n; i++)
{ {
// y = yVals[i]; // y = yVals[i];
Vector4Pair y = Unsafe.Add(ref yBase, i); ref Vector4Pair c0 = ref Unsafe.Add(ref c0Base, i);
// cb = cbVals[i] - halfValue); // cb = cbVals[i] - halfValue);
Vector4Pair cb = Unsafe.Add(ref cbBase, i); ref Vector4Pair c1 = ref Unsafe.Add(ref c1Base, i);
cb.AddInplace(chromaOffset); c1.AddInplace(chromaOffset);
// cr = crVals[i] - halfValue; // cr = crVals[i] - halfValue;
Vector4Pair cr = Unsafe.Add(ref crBase, i); ref Vector4Pair c2 = ref Unsafe.Add(ref c2Base, i);
cr.AddInplace(chromaOffset); c2.AddInplace(chromaOffset);
// r = y + (1.402F * cr); // r = y + (1.402F * cr);
Vector4Pair r = y; Vector4Pair r = c0;
Vector4Pair tmp = cr; Vector4Pair tmp = c2;
tmp.MultiplyInplace(1.402F); tmp.MultiplyInplace(1.402F);
r.AddInplace(ref tmp); r.AddInplace(ref tmp);
// g = y - (0.344136F * cb) - (0.714136F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr);
Vector4Pair g = y; Vector4Pair g = c0;
tmp = cb; tmp = c1;
tmp.MultiplyInplace(-0.344136F); tmp.MultiplyInplace(-0.344136F);
g.AddInplace(ref tmp); g.AddInplace(ref tmp);
tmp = cr; tmp = c2;
tmp.MultiplyInplace(-0.714136F); tmp.MultiplyInplace(-0.714136F);
g.AddInplace(ref tmp); g.AddInplace(ref tmp);
// b = y + (1.772F * cb); // b = y + (1.772F * cb);
Vector4Pair b = y; Vector4Pair b = c0;
tmp = cb; tmp = c1;
tmp.MultiplyInplace(1.772F); tmp.MultiplyInplace(1.772F);
b.AddInplace(ref tmp); b.AddInplace(ref tmp);
@ -79,14 +75,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
g.RoundAndDownscalePreVector8(maxValue); g.RoundAndDownscalePreVector8(maxValue);
b.RoundAndDownscalePreVector8(maxValue); b.RoundAndDownscalePreVector8(maxValue);
// Collect (r0,r1...r8) (g0,g1...g8) (b0,b1...b8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order: c0 = r;
ref Vector4Octet destination = ref Unsafe.Add(ref resultBase, i); c1 = g;
destination.Pack(ref r, ref g, ref b); c2 = b;
} }
} }
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) => protected override void ConvertCoreInplace(in ComponentValues values)
FromYCbCrBasic.ConvertCore(values, result, this.MaximumValue, this.HalfValue); => FromYCbCrBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
} }
} }
} }

47
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYCbCrVector8.cs

@ -19,41 +19,32 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result) protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{ {
ref Vector<float> yBase = ref Vector<float> c0Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0)); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector<float> cbBase = ref Vector<float> c1Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1)); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector<float> crBase = ref Vector<float> c2Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2)); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector4Octet resultBase =
ref Unsafe.As<Vector4, Vector4Octet>(ref MemoryMarshal.GetReference(result));
var chromaOffset = new Vector<float>(-this.HalfValue); var chromaOffset = new Vector<float>(-this.HalfValue);
// Walking 8 elements at one step: // Walking 8 elements at one step:
int n = result.Length / 8; nint n = values.Component0.Length / 8;
Vector4Pair rr = default;
Vector4Pair gg = default;
Vector4Pair bb = default;
ref Vector<float> rrRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref rr);
ref Vector<float> ggRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref gg);
ref Vector<float> bbRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref bb);
var scale = new Vector<float>(1 / this.MaximumValue); var scale = new Vector<float>(1 / this.MaximumValue);
for (int i = 0; i < n; i++) for (nint i = 0; i < n; i++)
{ {
// y = yVals[i]; // y = yVals[i];
// cb = cbVals[i] - 128F; // cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F; // cr = crVals[i] - 128F;
Vector<float> y = Unsafe.Add(ref yBase, i); ref Vector<float> c0 = ref Unsafe.Add(ref c0Base, i);
Vector<float> cb = Unsafe.Add(ref cbBase, i) + chromaOffset; ref Vector<float> c1 = ref Unsafe.Add(ref c1Base, i);
Vector<float> cr = Unsafe.Add(ref crBase, i) + chromaOffset; ref Vector<float> c2 = ref Unsafe.Add(ref c2Base, i);
Vector<float> y = Unsafe.Add(ref c0Base, i);
Vector<float> cb = Unsafe.Add(ref c1Base, i) + chromaOffset;
Vector<float> cr = Unsafe.Add(ref c2Base, i) + chromaOffset;
// r = y + (1.402F * cr); // r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr);
@ -70,18 +61,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
g *= scale; g *= scale;
b *= scale; b *= scale;
rrRefAsVector = r; c0 = r;
ggRefAsVector = g; c1 = g;
bbRefAsVector = b; c2 = b;
// Collect (r0,r1...r8) (g0,g1...g8) (b0,b1...b8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order:
ref Vector4Octet destination = ref Unsafe.Add(ref resultBase, i);
destination.Pack(ref rr, ref gg, ref bb);
} }
} }
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) => protected override void ConvertCoreInplace(in ComponentValues values) =>
FromYCbCrBasic.ConvertCore(values, result, this.MaximumValue, this.HalfValue); FromYCbCrBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
} }
} }
} }

64
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKAvx2.cs

@ -22,52 +22,42 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result) protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{ {
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
ref Vector256<float> yBase = ref Vector256<float> c0Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector256<float> cbBase = ref Vector256<float> c1Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector256<float> crBase = ref Vector256<float> c2Base =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector256<float> kBase = ref Vector256<float> kBase =
ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3)); ref Unsafe.As<float, Vector256<float>>(ref MemoryMarshal.GetReference(values.Component3));
ref Vector256<float> resultBase =
ref Unsafe.As<Vector4, Vector256<float>>(ref MemoryMarshal.GetReference(result));
// Used for the color conversion // Used for the color conversion
var chromaOffset = Vector256.Create(-this.HalfValue); var chromaOffset = Vector256.Create(-this.HalfValue);
var scale = Vector256.Create(1 / this.MaximumValue); var scale = Vector256.Create(1 / (this.MaximumValue * this.MaximumValue));
var max = Vector256.Create(this.MaximumValue); var max = Vector256.Create(this.MaximumValue);
var rCrMult = Vector256.Create(1.402F); var rCrMult = Vector256.Create(1.402F);
var gCbMult = Vector256.Create(-0.344136F); var gCbMult = Vector256.Create(-0.344136F);
var gCrMult = Vector256.Create(-0.714136F); var gCrMult = Vector256.Create(-0.714136F);
var bCbMult = Vector256.Create(1.772F); var bCbMult = Vector256.Create(1.772F);
// Used for packing.
var va = Vector256.Create(1F);
ref byte control = ref MemoryMarshal.GetReference(HwIntrinsics.PermuteMaskEvenOdd8x32);
Vector256<int> vcontrol = Unsafe.As<byte, Vector256<int>>(ref control);
// Walking 8 elements at one step: // Walking 8 elements at one step:
int n = result.Length / 8; nint n = values.Component0.Length / 8;
for (int i = 0; i < n; i++) for (nint i = 0; i < n; i++)
{ {
// y = yVals[i]; // y = yVals[i];
// cb = cbVals[i] - 128F; // cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F; // cr = crVals[i] - 128F;
// k = kVals[i] / 256F; // k = kVals[i] / 256F;
Vector256<float> y = Unsafe.Add(ref yBase, i); ref Vector256<float> c0 = ref Unsafe.Add(ref c0Base, i);
Vector256<float> cb = Avx.Add(Unsafe.Add(ref cbBase, i), chromaOffset); ref Vector256<float> c1 = ref Unsafe.Add(ref c1Base, i);
Vector256<float> cr = Avx.Add(Unsafe.Add(ref crBase, i), chromaOffset); ref Vector256<float> c2 = ref Unsafe.Add(ref c2Base, i);
Vector256<float> k = Avx.Divide(Unsafe.Add(ref kBase, i), max); Vector256<float> y = c0;
Vector256<float> cb = Avx.Add(c1, chromaOffset);
y = Avx2.PermuteVar8x32(y, vcontrol); Vector256<float> cr = Avx.Add(c2, chromaOffset);
cb = Avx2.PermuteVar8x32(cb, vcontrol); Vector256<float> scaledK = Avx.Multiply(Unsafe.Add(ref kBase, i), scale);
cr = Avx2.PermuteVar8x32(cr, vcontrol);
k = Avx2.PermuteVar8x32(k, vcontrol);
// r = y + (1.402F * cr); // r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr);
@ -82,29 +72,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
g = Avx.Subtract(max, Avx.RoundToNearestInteger(g)); g = Avx.Subtract(max, Avx.RoundToNearestInteger(g));
b = Avx.Subtract(max, Avx.RoundToNearestInteger(b)); b = Avx.Subtract(max, Avx.RoundToNearestInteger(b));
r = Avx.Multiply(Avx.Multiply(r, k), scale); r = Avx.Multiply(r, scaledK);
g = Avx.Multiply(Avx.Multiply(g, k), scale); g = Avx.Multiply(g, scaledK);
b = Avx.Multiply(Avx.Multiply(b, k), scale); b = Avx.Multiply(b, scaledK);
Vector256<float> vte = Avx.UnpackLow(r, b);
Vector256<float> vto = Avx.UnpackLow(g, va);
ref Vector256<float> destination = ref Unsafe.Add(ref resultBase, i * 4);
destination = Avx.UnpackLow(vte, vto);
Unsafe.Add(ref destination, 1) = Avx.UnpackHigh(vte, vto);
vte = Avx.UnpackHigh(r, b);
vto = Avx.UnpackHigh(g, va);
Unsafe.Add(ref destination, 2) = Avx.UnpackLow(vte, vto); c0 = r;
Unsafe.Add(ref destination, 3) = Avx.UnpackHigh(vte, vto); c1 = g;
c2 = b;
} }
#endif #endif
} }
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) => protected override void ConvertCoreInplace(in ComponentValues values) =>
FromYccKBasic.ConvertCore(values, result, this.MaximumValue, this.HalfValue); FromYccKBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
} }
} }
} }

43
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKBasic.cs

@ -15,39 +15,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
public override void ConvertToRgba(in ComponentValues values, Span<Vector4> result) public override void ConvertToRgbInplace(in ComponentValues values) =>
{ ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
ConvertCore(values, result, this.MaximumValue, this.HalfValue);
}
internal static void ConvertCore(in ComponentValues values, Span<Vector4> result, float maxValue, float halfValue) internal static void ConvertCoreInplace(in ComponentValues values, float maxValue, float halfValue)
{ {
// TODO: We can optimize a lot here with Vector<float> and SRCS.Unsafe()! Span<float> c0 = values.Component0;
ReadOnlySpan<float> yVals = values.Component0; Span<float> c1 = values.Component1;
ReadOnlySpan<float> cbVals = values.Component1; Span<float> c2 = values.Component2;
ReadOnlySpan<float> crVals = values.Component2; Span<float> c3 = values.Component3;
ReadOnlySpan<float> kVals = values.Component3;
var v = new Vector4(0, 0, 0, 1F); var v = new Vector4(0, 0, 0, 1F);
var maximum = 1 / maxValue; var scale = 1 / (maxValue * maxValue);
var scale = new Vector4(maximum, maximum, maximum, 1F);
for (int i = 0; i < result.Length; i++) for (int i = 0; i < values.Component0.Length; i++)
{ {
float y = yVals[i]; float y = c0[i];
float cb = cbVals[i] - halfValue; float cb = c1[i] - halfValue;
float cr = crVals[i] - halfValue; float cr = c2[i] - halfValue;
float k = kVals[i] / maxValue; float scaledK = c3[i] * scale;
v.X = (maxValue - MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * k; c0[i] = (maxValue - MathF.Round(y + (1.402F * cr), MidpointRounding.AwayFromZero)) * scaledK;
v.Y = (maxValue - MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * k; c1[i] = (maxValue - MathF.Round(y - (0.344136F * cb) - (0.714136F * cr), MidpointRounding.AwayFromZero)) * scaledK;
v.Z = (maxValue - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * k; c2[i] = (maxValue - MathF.Round(y + (1.772F * cb), MidpointRounding.AwayFromZero)) * scaledK;
v.W = 1F;
v *= scale;
result[i] = v;
} }
} }
} }

60
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.FromYccKVector8.cs

@ -18,46 +18,39 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
{ {
} }
protected override void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result) protected override void ConvertCoreVectorizedInplace(in ComponentValues values)
{ {
ref Vector<float> yBase = ref Vector<float> c0Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0)); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component0));
ref Vector<float> cbBase = ref Vector<float> c1Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1)); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component1));
ref Vector<float> crBase = ref Vector<float> c2Base =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2)); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component2));
ref Vector<float> kBase = ref Vector<float> kBase =
ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component3)); ref Unsafe.As<float, Vector<float>>(ref MemoryMarshal.GetReference(values.Component3));
ref Vector4Octet resultBase =
ref Unsafe.As<Vector4, Vector4Octet>(ref MemoryMarshal.GetReference(result));
var chromaOffset = new Vector<float>(-this.HalfValue); var chromaOffset = new Vector<float>(-this.HalfValue);
// Walking 8 elements at one step: // Walking 8 elements at one step:
int n = result.Length / 8; nint n = values.Component0.Length / 8;
Vector4Pair rr = default;
Vector4Pair gg = default;
Vector4Pair bb = default;
ref Vector<float> rrRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref rr);
ref Vector<float> ggRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref gg);
ref Vector<float> bbRefAsVector = ref Unsafe.As<Vector4Pair, Vector<float>>(ref bb);
var scale = new Vector<float>(1 / this.MaximumValue);
var max = new Vector<float>(this.MaximumValue); var max = new Vector<float>(this.MaximumValue);
var scale = new Vector<float>(1f) / (max * max);
for (int i = 0; i < n; i++) for (nint i = 0; i < n; i++)
{ {
// y = yVals[i]; // y = yVals[i];
// cb = cbVals[i] - 128F; // cb = cbVals[i] - 128F;
// cr = crVals[i] - 128F; // cr = crVals[i] - 128F;
// k = kVals[i] / 256F; // k = kVals[i] / 256F;
Vector<float> y = Unsafe.Add(ref yBase, i); ref Vector<float> c0 = ref Unsafe.Add(ref c0Base, i);
Vector<float> cb = Unsafe.Add(ref cbBase, i) + chromaOffset; ref Vector<float> c1 = ref Unsafe.Add(ref c1Base, i);
Vector<float> cr = Unsafe.Add(ref crBase, i) + chromaOffset; ref Vector<float> c2 = ref Unsafe.Add(ref c2Base, i);
Vector<float> k = Unsafe.Add(ref kBase, i) / max;
Vector<float> y = c0;
Vector<float> cb = c1 + chromaOffset;
Vector<float> cr = c2 + chromaOffset;
Vector<float> scaledK = Unsafe.Add(ref kBase, i) * scale;
// r = y + (1.402F * cr); // r = y + (1.402F * cr);
// g = y - (0.344136F * cb) - (0.714136F * cr); // g = y - (0.344136F * cb) - (0.714136F * cr);
@ -67,25 +60,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
Vector<float> g = y - (cb * new Vector<float>(0.344136F)) - (cr * new Vector<float>(0.714136F)); Vector<float> g = y - (cb * new Vector<float>(0.344136F)) - (cr * new Vector<float>(0.714136F));
Vector<float> b = y + (cb * new Vector<float>(1.772F)); Vector<float> b = y + (cb * new Vector<float>(1.772F));
r = (max - r.FastRound()) * k; r = (max - r.FastRound()) * scaledK;
g = (max - g.FastRound()) * k; g = (max - g.FastRound()) * scaledK;
b = (max - b.FastRound()) * k; b = (max - b.FastRound()) * scaledK;
r *= scale;
g *= scale;
b *= scale;
rrRefAsVector = r;
ggRefAsVector = g;
bbRefAsVector = b;
// Collect (r0,r1...r8) (g0,g1...g8) (b0,b1...b8) vector values in the expected (r0,g0,g1,1), (r1,g1,g2,1) ... order: c0 = r;
ref Vector4Octet destination = ref Unsafe.Add(ref resultBase, i); c1 = g;
destination.Pack(ref rr, ref gg, ref bb); c2 = b;
} }
} }
protected override void ConvertCore(in ComponentValues values, Span<Vector4> result) => protected override void ConvertCoreInplace(in ComponentValues values) =>
FromYccKBasic.ConvertCore(values, result, this.MaximumValue, this.HalfValue); FromYccKBasic.ConvertCoreInplace(values, this.MaximumValue, this.HalfValue);
} }
} }
} }

15
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.VectorizedJpegColorConverter.cs

@ -18,10 +18,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
this.vectorSize = vectorSize; this.vectorSize = vectorSize;
} }
public sealed override void ConvertToRgba(in ComponentValues values, Span<Vector4> result) public override void ConvertToRgbInplace(in ComponentValues values)
{ {
int remainder = result.Length % this.vectorSize; int length = values.Component0.Length;
int simdCount = result.Length - remainder; int remainder = values.Component0.Length % this.vectorSize;
int simdCount = length - remainder;
if (simdCount > 0) if (simdCount > 0)
{ {
// This implementation is actually AVX specific. // This implementation is actually AVX specific.
@ -32,15 +33,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
"This converter can be used only on architecture having 256 byte floating point SIMD registers!"); "This converter can be used only on architecture having 256 byte floating point SIMD registers!");
} }
this.ConvertCoreVectorized(values.Slice(0, simdCount), result.Slice(0, simdCount)); this.ConvertCoreVectorizedInplace(values.Slice(0, simdCount));
} }
this.ConvertCore(values.Slice(simdCount, remainder), result.Slice(simdCount, remainder)); this.ConvertCoreInplace(values.Slice(simdCount, remainder));
} }
protected abstract void ConvertCoreVectorized(in ComponentValues values, Span<Vector4> result); protected virtual void ConvertCoreVectorizedInplace(in ComponentValues values) => throw new NotImplementedException();
protected abstract void ConvertCore(in ComponentValues values, Span<Vector4> result); protected virtual void ConvertCoreInplace(in ComponentValues values) => throw new NotImplementedException();
} }
} }
} }

156
src/ImageSharp/Formats/Jpeg/Components/Decoder/ColorConverters/JpegColorConverter.cs

@ -76,11 +76,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
} }
/// <summary> /// <summary>
/// He implementation of the conversion. /// Converts planar jpeg component values in <paramref name="values"/> to RGB color space inplace.
/// </summary> /// </summary>
/// <param name="values">The input as a stack-only <see cref="ComponentValues"/> struct</param> /// <param name="values">The input/ouptut as a stack-only <see cref="ComponentValues"/> struct</param>
/// <param name="result">The destination buffer of <see cref="Vector4"/> values</param> public abstract void ConvertToRgbInplace(in ComponentValues values);
public abstract void ConvertToRgba(in ComponentValues values, Span<Vector4> result);
/// <summary> /// <summary>
/// Returns the <see cref="JpegColorConverter"/>s for all supported colorspaces and precisions. /// Returns the <see cref="JpegColorConverter"/>s for all supported colorspaces and precisions.
@ -181,22 +180,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
/// <summary> /// <summary>
/// The component 0 (eg. Y) /// The component 0 (eg. Y)
/// </summary> /// </summary>
public readonly ReadOnlySpan<float> Component0; public readonly Span<float> Component0;
/// <summary> /// <summary>
/// The component 1 (eg. Cb) /// The component 1 (eg. Cb). In case of grayscale, it points to <see cref="Component0"/>.
/// </summary> /// </summary>
public readonly ReadOnlySpan<float> Component1; public readonly Span<float> Component1;
/// <summary> /// <summary>
/// The component 2 (eg. Cr) /// The component 2 (eg. Cr). In case of grayscale, it points to <see cref="Component0"/>.
/// </summary> /// </summary>
public readonly ReadOnlySpan<float> Component2; public readonly Span<float> Component2;
/// <summary> /// <summary>
/// The component 4 /// The component 4
/// </summary> /// </summary>
public readonly ReadOnlySpan<float> Component3; public readonly Span<float> Component3;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ComponentValues"/> struct. /// Initializes a new instance of the <see cref="ComponentValues"/> struct.
@ -208,30 +207,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
this.ComponentCount = componentBuffers.Count; this.ComponentCount = componentBuffers.Count;
this.Component0 = componentBuffers[0].GetRowSpan(row); this.Component0 = componentBuffers[0].GetRowSpan(row);
this.Component1 = Span<float>.Empty;
this.Component2 = Span<float>.Empty; // In case of grayscale, Component1 and Component2 point to Component0 memory area
this.Component3 = Span<float>.Empty; this.Component1 = this.ComponentCount > 1 ? componentBuffers[1].GetRowSpan(row) : this.Component0;
this.Component2 = this.ComponentCount > 2 ? componentBuffers[2].GetRowSpan(row) : this.Component0;
if (this.ComponentCount > 1) this.Component3 = this.ComponentCount > 3 ? componentBuffers[3].GetRowSpan(row) : Span<float>.Empty;
{
this.Component1 = componentBuffers[1].GetRowSpan(row);
if (this.ComponentCount > 2)
{
this.Component2 = componentBuffers[2].GetRowSpan(row);
if (this.ComponentCount > 3)
{
this.Component3 = componentBuffers[3].GetRowSpan(row);
}
}
}
} }
private ComponentValues( internal ComponentValues(
int componentCount, int componentCount,
ReadOnlySpan<float> c0, Span<float> c0,
ReadOnlySpan<float> c1, Span<float> c1,
ReadOnlySpan<float> c2, Span<float> c2,
ReadOnlySpan<float> c3) Span<float> c3)
{ {
this.ComponentCount = componentCount; this.ComponentCount = componentCount;
this.Component0 = c0; this.Component0 = c0;
@ -242,111 +230,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters
public ComponentValues Slice(int start, int length) public ComponentValues Slice(int start, int length)
{ {
ReadOnlySpan<float> c0 = this.Component0.Slice(start, length); Span<float> c0 = this.Component0.Slice(start, length);
ReadOnlySpan<float> c1 = this.ComponentCount > 1 ? this.Component1.Slice(start, length) : ReadOnlySpan<float>.Empty; Span<float> c1 = this.Component1.Length > 0 ? this.Component1.Slice(start, length) : Span<float>.Empty;
ReadOnlySpan<float> c2 = this.ComponentCount > 2 ? this.Component2.Slice(start, length) : ReadOnlySpan<float>.Empty; Span<float> c2 = this.Component2.Length > 0 ? this.Component2.Slice(start, length) : Span<float>.Empty;
ReadOnlySpan<float> c3 = this.ComponentCount > 3 ? this.Component3.Slice(start, length) : ReadOnlySpan<float>.Empty; Span<float> c3 = this.Component3.Length > 0 ? this.Component3.Slice(start, length) : Span<float>.Empty;
return new ComponentValues(this.ComponentCount, c0, c1, c2, c3); return new ComponentValues(this.ComponentCount, c0, c1, c2, c3);
} }
} }
internal struct Vector4Octet
{
#pragma warning disable SA1132 // Do not combine fields
public Vector4 V0, V1, V2, V3, V4, V5, V6, V7;
/// <summary>
/// Pack (r0,r1...r7) (g0,g1...g7) (b0,b1...b7) vector values as (r0,g0,b0,1), (r1,g1,b1,1) ...
/// </summary>
public void Pack(ref Vector4Pair r, ref Vector4Pair g, ref Vector4Pair b)
{
this.V0.X = r.A.X;
this.V0.Y = g.A.X;
this.V0.Z = b.A.X;
this.V0.W = 1f;
this.V1.X = r.A.Y;
this.V1.Y = g.A.Y;
this.V1.Z = b.A.Y;
this.V1.W = 1f;
this.V2.X = r.A.Z;
this.V2.Y = g.A.Z;
this.V2.Z = b.A.Z;
this.V2.W = 1f;
this.V3.X = r.A.W;
this.V3.Y = g.A.W;
this.V3.Z = b.A.W;
this.V3.W = 1f;
this.V4.X = r.B.X;
this.V4.Y = g.B.X;
this.V4.Z = b.B.X;
this.V4.W = 1f;
this.V5.X = r.B.Y;
this.V5.Y = g.B.Y;
this.V5.Z = b.B.Y;
this.V5.W = 1f;
this.V6.X = r.B.Z;
this.V6.Y = g.B.Z;
this.V6.Z = b.B.Z;
this.V6.W = 1f;
this.V7.X = r.B.W;
this.V7.Y = g.B.W;
this.V7.Z = b.B.W;
this.V7.W = 1f;
}
/// <summary>
/// Pack (g0,g1...g7) vector values as (g0,g0,g0,1), (g1,g1,g1,1) ...
/// </summary>
public void Pack(ref Vector4Pair g)
{
this.V0.X = g.A.X;
this.V0.Y = g.A.X;
this.V0.Z = g.A.X;
this.V0.W = 1f;
this.V1.X = g.A.Y;
this.V1.Y = g.A.Y;
this.V1.Z = g.A.Y;
this.V1.W = 1f;
this.V2.X = g.A.Z;
this.V2.Y = g.A.Z;
this.V2.Z = g.A.Z;
this.V2.W = 1f;
this.V3.X = g.A.W;
this.V3.Y = g.A.W;
this.V3.Z = g.A.W;
this.V3.W = 1f;
this.V4.X = g.B.X;
this.V4.Y = g.B.X;
this.V4.Z = g.B.X;
this.V4.W = 1f;
this.V5.X = g.B.Y;
this.V5.Y = g.B.Y;
this.V5.Z = g.B.Y;
this.V5.W = 1f;
this.V6.X = g.B.Z;
this.V6.Y = g.B.Z;
this.V6.Z = g.B.Z;
this.V6.W = 1f;
this.V7.X = g.B.W;
this.V7.Y = g.B.W;
this.V7.Z = g.B.W;
this.V7.W = 1f;
}
}
} }
} }

6
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs

@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public bool HasBadMarker() => this.Marker != JpegConstants.Markers.XFF && !this.HasRestartMarker(); public bool HasBadMarker() => this.Marker != JpegConstants.Markers.XFF && !this.HasRestartMarker();
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.AlwaysInline)]
public void FillBuffer() public void FillBuffer()
{ {
// Attempt to load at least the minimum number of required bits into the buffer. // Attempt to load at least the minimum number of required bits into the buffer.
@ -130,7 +130,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public int PeekBits(int nbits) => (int)ExtractBits(this.data, this.remainingBits - nbits, nbits); public int PeekBits(int nbits) => (int)ExtractBits(this.data, this.remainingBits - nbits, nbits);
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.AlwaysInline)]
private static ulong ExtractBits(ulong value, int offset, int size) => (value >> offset) & (ulong)((1 << size) - 1); private static ulong ExtractBits(ulong value, int offset, int size) => (value >> offset) & (ulong)((1 << size) - 1);
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
@ -207,7 +207,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
} }
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.AlwaysInline)]
private int ReadStream() private int ReadStream()
{ {
int value = this.badData ? 0 : this.stream.ReadByte(); int value = this.badData ? 0 : this.stream.ReadByte();

231
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs

@ -16,94 +16,113 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary> /// </summary>
internal class HuffmanScanDecoder internal class HuffmanScanDecoder
{ {
private readonly JpegFrame frame;
private readonly HuffmanTable[] dcHuffmanTables;
private readonly HuffmanTable[] acHuffmanTables;
private readonly BufferedReadStream stream; private readonly BufferedReadStream stream;
private readonly JpegComponent[] components;
// The restart interval.
private readonly int restartInterval;
// The number of interleaved components. /// <summary>
private readonly int componentsLength; /// <see cref="JpegFrame"/> instance containing decoding-related information.
/// </summary>
// The spectral selection start. private JpegFrame frame;
private readonly int spectralStart;
// The spectral selection end. /// <summary>
private readonly int spectralEnd; /// Shortcut for <see cref="frame"/>.Components.
/// </summary>
private JpegComponent[] components;
// The successive approximation high bit end. /// <summary>
private readonly int successiveHigh; /// Number of component in the current scan.
/// </summary>
private int componentsCount;
// The successive approximation low bit end. /// <summary>
private readonly int successiveLow; /// The reset interval determined by RST markers.
/// </summary>
private int restartInterval;
// How many mcu's are left to do. /// <summary>
/// How many mcu's are left to do.
/// </summary>
private int todo; private int todo;
// The End-Of-Block countdown for ending the sequence prematurely when the remaining coefficients are zero. /// <summary>
/// The End-Of-Block countdown for ending the sequence prematurely when the remaining coefficients are zero.
/// </summary>
private int eobrun; private int eobrun;
// The unzig data. /// <summary>
private ZigZag dctZigZag; /// The DC Huffman tables.
/// </summary>
private readonly HuffmanTable[] dcHuffmanTables;
/// <summary>
/// The AC Huffman tables
/// </summary>
private readonly HuffmanTable[] acHuffmanTables;
private HuffmanScanBuffer scanBuffer; private HuffmanScanBuffer scanBuffer;
private CancellationToken cancellationToken; private readonly SpectralConverter spectralConverter;
private readonly CancellationToken cancellationToken;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="HuffmanScanDecoder"/> class. /// Initializes a new instance of the <see cref="HuffmanScanDecoder"/> class.
/// </summary> /// </summary>
/// <param name="stream">The input stream.</param> /// <param name="stream">The input stream.</param>
/// <param name="frame">The image frame.</param> /// <param name="converter">Spectral to pixel converter.</param>
/// <param name="dcHuffmanTables">The DC Huffman tables.</param>
/// <param name="acHuffmanTables">The AC Huffman tables.</param>
/// <param name="componentsLength">The length of the components. Different to the array length.</param>
/// <param name="restartInterval">The reset interval.</param>
/// <param name="spectralStart">The spectral selection start.</param>
/// <param name="spectralEnd">The spectral selection end.</param>
/// <param name="successiveHigh">The successive approximation bit high end.</param>
/// <param name="successiveLow">The successive approximation bit low end.</param>
/// <param name="cancellationToken">The token to monitor cancellation.</param> /// <param name="cancellationToken">The token to monitor cancellation.</param>
public HuffmanScanDecoder( public HuffmanScanDecoder(
BufferedReadStream stream, BufferedReadStream stream,
JpegFrame frame, SpectralConverter converter,
HuffmanTable[] dcHuffmanTables,
HuffmanTable[] acHuffmanTables,
int componentsLength,
int restartInterval,
int spectralStart,
int spectralEnd,
int successiveHigh,
int successiveLow,
CancellationToken cancellationToken) CancellationToken cancellationToken)
{ {
this.dctZigZag = ZigZag.CreateUnzigTable();
this.stream = stream; this.stream = stream;
this.scanBuffer = new HuffmanScanBuffer(stream); this.spectralConverter = converter;
this.frame = frame;
this.dcHuffmanTables = dcHuffmanTables;
this.acHuffmanTables = acHuffmanTables;
this.components = frame.Components;
this.componentsLength = componentsLength;
this.restartInterval = restartInterval;
this.todo = restartInterval;
this.spectralStart = spectralStart;
this.spectralEnd = spectralEnd;
this.successiveHigh = successiveHigh;
this.successiveLow = successiveLow;
this.cancellationToken = cancellationToken; this.cancellationToken = cancellationToken;
// TODO: this is actually a variable value depending on component count
const int maxTables = 4;
this.dcHuffmanTables = new HuffmanTable[maxTables];
this.acHuffmanTables = new HuffmanTable[maxTables];
} }
/// <summary>
/// Sets reset interval determined by RST markers.
/// </summary>
public int ResetInterval
{
set
{
this.restartInterval = value;
this.todo = value;
}
}
// The spectral selection start.
public int SpectralStart { get; set; }
// The spectral selection end.
public int SpectralEnd { get; set; }
// The successive approximation high bit end.
public int SuccessiveHigh { get; set; }
// The successive approximation low bit end.
public int SuccessiveLow { get; set; }
/// <summary> /// <summary>
/// Decodes the entropy coded data. /// Decodes the entropy coded data.
/// </summary> /// </summary>
public void ParseEntropyCodedData() public void ParseEntropyCodedData(int componentCount)
{ {
this.cancellationToken.ThrowIfCancellationRequested(); this.cancellationToken.ThrowIfCancellationRequested();
this.componentsCount = componentCount;
this.scanBuffer = new HuffmanScanBuffer(this.stream);
bool fullScan = this.frame.Progressive || this.frame.MultiScan;
this.frame.AllocateComponents(fullScan);
if (!this.frame.Progressive) if (!this.frame.Progressive)
{ {
this.ParseBaselineData(); this.ParseBaselineData();
@ -119,15 +138,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
} }
} }
public void InjectFrameData(JpegFrame frame, IRawJpegData jpegData)
{
this.frame = frame;
this.components = frame.Components;
this.spectralConverter.InjectFrameData(frame, jpegData);
}
private void ParseBaselineData() private void ParseBaselineData()
{ {
if (this.componentsLength == 1) if (this.componentsCount == this.frame.ComponentCount)
{ {
this.ParseBaselineDataNonInterleaved(); this.ParseBaselineDataInterleaved();
} }
else else
{ {
this.ParseBaselineDataInterleaved(); this.ParseBaselineDataNonInterleaved();
} }
} }
@ -140,7 +167,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
ref HuffmanScanBuffer buffer = ref this.scanBuffer; ref HuffmanScanBuffer buffer = ref this.scanBuffer;
// Pre-derive the huffman table to avoid in-loop checks. // Pre-derive the huffman table to avoid in-loop checks.
for (int i = 0; i < this.componentsLength; i++) for (int i = 0; i < this.componentsCount; i++)
{ {
int order = this.frame.ComponentOrder[i]; int order = this.frame.ComponentOrder[i];
JpegComponent component = this.components[order]; JpegComponent component = this.components[order];
@ -155,12 +182,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
this.cancellationToken.ThrowIfCancellationRequested(); this.cancellationToken.ThrowIfCancellationRequested();
// decode from binary to spectral
for (int i = 0; i < mcusPerLine; i++) for (int i = 0; i < mcusPerLine; i++)
{ {
// Scan an interleaved mcu... process components in order // Scan an interleaved mcu... process components in order
int mcuRow = mcu / mcusPerLine;
int mcuCol = mcu % mcusPerLine; int mcuCol = mcu % mcusPerLine;
for (int k = 0; k < this.componentsLength; k++) for (int k = 0; k < this.componentsCount; k++)
{ {
int order = this.frame.ComponentOrder[k]; int order = this.frame.ComponentOrder[k];
JpegComponent component = this.components[order]; JpegComponent component = this.components[order];
@ -175,14 +202,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// by the basic H and V specified for the component // by the basic H and V specified for the component
for (int y = 0; y < v; y++) for (int y = 0; y < v; y++)
{ {
int blockRow = (mcuRow * v) + y; Span<Block8x8> blockSpan = component.SpectralBlocks.GetRowSpan(y);
Span<Block8x8> blockSpan = component.SpectralBlocks.GetRowSpan(blockRow);
ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan); ref Block8x8 blockRef = ref MemoryMarshal.GetReference(blockSpan);
for (int x = 0; x < h; x++) for (int x = 0; x < h; x++)
{ {
if (buffer.NoData) if (buffer.NoData)
{ {
// It is very likely that some spectral data was decoded before we encountered EOI marker
// so we need to decode what's left and return (or maybe throw?)
this.spectralConverter.ConvertStrideBaseline();
return; return;
} }
@ -202,6 +231,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
mcu++; mcu++;
this.HandleRestart(); this.HandleRestart();
} }
// convert from spectral to actual pixels via given converter
this.spectralConverter.ConvertStrideBaseline();
} }
} }
@ -248,9 +280,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// Logic has been adapted from libjpeg. // Logic has been adapted from libjpeg.
// See Table B.3 – Scan header parameter size and values. itu-t81.pdf // See Table B.3 – Scan header parameter size and values. itu-t81.pdf
bool invalid = false; bool invalid = false;
if (this.spectralStart == 0) if (this.SpectralStart == 0)
{ {
if (this.spectralEnd != 0) if (this.SpectralEnd != 0)
{ {
invalid = true; invalid = true;
} }
@ -258,22 +290,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
else else
{ {
// Need not check Ss/Se < 0 since they came from unsigned bytes. // Need not check Ss/Se < 0 since they came from unsigned bytes.
if (this.spectralEnd < this.spectralStart || this.spectralEnd > 63) if (this.SpectralEnd < this.SpectralStart || this.SpectralEnd > 63)
{ {
invalid = true; invalid = true;
} }
// AC scans may have only one component. // AC scans may have only one component.
if (this.componentsLength != 1) if (this.componentsCount != 1)
{ {
invalid = true; invalid = true;
} }
} }
if (this.successiveHigh != 0) if (this.SuccessiveHigh != 0)
{ {
// Successive approximation refinement scan: must have Al = Ah-1. // Successive approximation refinement scan: must have Al = Ah-1.
if (this.successiveHigh - 1 != this.successiveLow) if (this.SuccessiveHigh - 1 != this.SuccessiveLow)
{ {
invalid = true; invalid = true;
} }
@ -281,14 +313,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// TODO: How does this affect 12bit jpegs. // TODO: How does this affect 12bit jpegs.
// According to libjpeg the range covers 8bit only? // According to libjpeg the range covers 8bit only?
if (this.successiveLow > 13) if (this.SuccessiveLow > 13)
{ {
invalid = true; invalid = true;
} }
if (invalid) if (invalid)
{ {
JpegThrowHelper.ThrowBadProgressiveScan(this.spectralStart, this.spectralEnd, this.successiveHigh, this.successiveLow); JpegThrowHelper.ThrowBadProgressiveScan(this.SpectralStart, this.SpectralEnd, this.SuccessiveHigh, this.SuccessiveLow);
} }
} }
@ -296,7 +328,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
this.CheckProgressiveData(); this.CheckProgressiveData();
if (this.componentsLength == 1) if (this.componentsCount == 1)
{ {
this.ParseProgressiveDataNonInterleaved(); this.ParseProgressiveDataNonInterleaved();
} }
@ -315,7 +347,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
ref HuffmanScanBuffer buffer = ref this.scanBuffer; ref HuffmanScanBuffer buffer = ref this.scanBuffer;
// Pre-derive the huffman table to avoid in-loop checks. // Pre-derive the huffman table to avoid in-loop checks.
for (int k = 0; k < this.componentsLength; k++) for (int k = 0; k < this.componentsCount; k++)
{ {
int order = this.frame.ComponentOrder[k]; int order = this.frame.ComponentOrder[k];
JpegComponent component = this.components[order]; JpegComponent component = this.components[order];
@ -330,7 +362,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
// Scan an interleaved mcu... process components in order // Scan an interleaved mcu... process components in order
int mcuRow = mcu / mcusPerLine; int mcuRow = mcu / mcusPerLine;
int mcuCol = mcu % mcusPerLine; int mcuCol = mcu % mcusPerLine;
for (int k = 0; k < this.componentsLength; k++) for (int k = 0; k < this.componentsCount; k++)
{ {
int order = this.frame.ComponentOrder[k]; int order = this.frame.ComponentOrder[k];
JpegComponent component = this.components[order]; JpegComponent component = this.components[order];
@ -380,7 +412,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
int w = component.WidthInBlocks; int w = component.WidthInBlocks;
int h = component.HeightInBlocks; int h = component.HeightInBlocks;
if (this.spectralStart == 0) if (this.SpectralStart == 0)
{ {
ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId]; ref HuffmanTable dcHuffmanTable = ref this.dcHuffmanTables[component.DCHuffmanTableId];
dcHuffmanTable.Configure(); dcHuffmanTable.Configure();
@ -445,7 +477,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
ref short blockDataRef = ref Unsafe.As<Block8x8, short>(ref block); ref short blockDataRef = ref Unsafe.As<Block8x8, short>(ref block);
ref HuffmanScanBuffer buffer = ref this.scanBuffer; ref HuffmanScanBuffer buffer = ref this.scanBuffer;
ref ZigZag zigzag = ref this.dctZigZag;
// DC // DC
int t = buffer.DecodeHuffman(ref dcTable); int t = buffer.DecodeHuffman(ref dcTable);
@ -470,7 +501,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
i += r; i += r;
s = buffer.Receive(s); s = buffer.Receive(s);
Unsafe.Add(ref blockDataRef, zigzag[i++]) = (short)s; Unsafe.Add(ref blockDataRef, ZigZag.ZigZagOrder[i++]) = (short)s;
} }
else else
{ {
@ -489,7 +520,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
ref short blockDataRef = ref Unsafe.As<Block8x8, short>(ref block); ref short blockDataRef = ref Unsafe.As<Block8x8, short>(ref block);
ref HuffmanScanBuffer buffer = ref this.scanBuffer; ref HuffmanScanBuffer buffer = ref this.scanBuffer;
if (this.successiveHigh == 0) if (this.SuccessiveHigh == 0)
{ {
// First scan for DC coefficient, must be first // First scan for DC coefficient, must be first
int s = buffer.DecodeHuffman(ref dcTable); int s = buffer.DecodeHuffman(ref dcTable);
@ -500,20 +531,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
s += component.DcPredictor; s += component.DcPredictor;
component.DcPredictor = s; component.DcPredictor = s;
blockDataRef = (short)(s << this.successiveLow); blockDataRef = (short)(s << this.SuccessiveLow);
} }
else else
{ {
// Refinement scan for DC coefficient // Refinement scan for DC coefficient
buffer.CheckBits(); buffer.CheckBits();
blockDataRef |= (short)(buffer.GetBits(1) << this.successiveLow); blockDataRef |= (short)(buffer.GetBits(1) << this.SuccessiveLow);
} }
} }
private void DecodeBlockProgressiveAC(ref Block8x8 block, ref HuffmanTable acTable) private void DecodeBlockProgressiveAC(ref Block8x8 block, ref HuffmanTable acTable)
{ {
ref short blockDataRef = ref Unsafe.As<Block8x8, short>(ref block); ref short blockDataRef = ref Unsafe.As<Block8x8, short>(ref block);
if (this.successiveHigh == 0) if (this.SuccessiveHigh == 0)
{ {
// MCU decoding for AC initial scan (either spectral selection, // MCU decoding for AC initial scan (either spectral selection,
// or first pass of successive approximation). // or first pass of successive approximation).
@ -524,10 +555,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
} }
ref HuffmanScanBuffer buffer = ref this.scanBuffer; ref HuffmanScanBuffer buffer = ref this.scanBuffer;
ref ZigZag zigzag = ref this.dctZigZag; int start = this.SpectralStart;
int start = this.spectralStart; int end = this.SpectralEnd;
int end = this.spectralEnd; int low = this.SuccessiveLow;
int low = this.successiveLow;
for (int i = start; i <= end; ++i) for (int i = start; i <= end; ++i)
{ {
@ -540,7 +570,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
if (s != 0) if (s != 0)
{ {
s = buffer.Receive(s); s = buffer.Receive(s);
Unsafe.Add(ref blockDataRef, zigzag[i]) = (short)(s << low); Unsafe.Add(ref blockDataRef, ZigZag.ZigZagOrder[i]) = (short)(s << low);
} }
else else
{ {
@ -570,12 +600,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
// Refinement scan for these AC coefficients // Refinement scan for these AC coefficients
ref HuffmanScanBuffer buffer = ref this.scanBuffer; ref HuffmanScanBuffer buffer = ref this.scanBuffer;
ref ZigZag zigzag = ref this.dctZigZag; int start = this.SpectralStart;
int start = this.spectralStart; int end = this.SpectralEnd;
int end = this.spectralEnd;
int p1 = 1 << this.successiveLow; int p1 = 1 << this.SuccessiveLow;
int m1 = (-1) << this.successiveLow; int m1 = (-1) << this.SuccessiveLow;
int k = start; int k = start;
@ -617,7 +646,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
do do
{ {
ref short coef = ref Unsafe.Add(ref blockDataRef, zigzag[k]); ref short coef = ref Unsafe.Add(ref blockDataRef, ZigZag.ZigZagOrder[k]);
if (coef != 0) if (coef != 0)
{ {
buffer.CheckBits(); buffer.CheckBits();
@ -643,7 +672,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
if ((s != 0) && (k < 64)) if ((s != 0) && (k < 64))
{ {
Unsafe.Add(ref blockDataRef, zigzag[k]) = (short)s; Unsafe.Add(ref blockDataRef, ZigZag.ZigZagOrder[k]) = (short)s;
} }
} }
} }
@ -652,7 +681,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
for (; k <= end; k++) for (; k <= end; k++)
{ {
ref short coef = ref Unsafe.Add(ref blockDataRef, zigzag[k]); ref short coef = ref Unsafe.Add(ref blockDataRef, ZigZag.ZigZagOrder[k]);
if (coef != 0) if (coef != 0)
{ {
@ -714,5 +743,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
return false; return false;
} }
/// <summary>
/// Build the huffman table using code lengths and code values.
/// </summary>
/// <param name="type">Table type.</param>
/// <param name="index">Table index.</param>
/// <param name="codeLengths">Code lengths.</param>
/// <param name="values">Code values.</param>
[MethodImpl(InliningOptions.ShortMethod)]
public void BuildHuffmanTable(int type, int index, ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values)
{
HuffmanTable[] tables = type == 0 ? this.dcHuffmanTables : this.acHuffmanTables;
tables[index] = new HuffmanTable(codeLengths, values);
}
} }
} }

17
src/ImageSharp/Formats/Jpeg/Components/Decoder/IRawJpegData.cs

@ -11,33 +11,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary> /// </summary>
internal interface IRawJpegData : IDisposable internal interface IRawJpegData : IDisposable
{ {
/// <summary>
/// Gets the image size in pixels.
/// </summary>
Size ImageSizeInPixels { get; }
/// <summary>
/// Gets the number of components.
/// </summary>
int ComponentCount { get; }
/// <summary> /// <summary>
/// Gets the color space /// Gets the color space
/// </summary> /// </summary>
JpegColorSpace ColorSpace { get; } JpegColorSpace ColorSpace { get; }
/// <summary>
/// Gets the number of bits used for precision.
/// </summary>
int Precision { get; }
/// <summary> /// <summary>
/// Gets the components. /// Gets the components.
/// </summary> /// </summary>
IJpegComponent[] Components { get; } IJpegComponent[] Components { get; }
/// <summary> /// <summary>
/// Gets the quantization tables, in zigzag order. /// Gets the quantization tables, in natural order.
/// </summary> /// </summary>
Block8x8F[] QuantizationTables { get; } Block8x8F[] QuantizationTables { get; }
} }

32
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegBlockPostProcessor.cs

@ -19,14 +19,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
public Block8x8F SourceBlock; public Block8x8F SourceBlock;
/// <summary> /// <summary>
/// Temporal block 1 to store intermediate and/or final computation results. /// Temporal block to store intermediate computation results.
/// </summary> /// </summary>
public Block8x8F WorkspaceBlock1; public Block8x8F WorkspaceBlock;
/// <summary>
/// Temporal block 2 to store intermediate and/or final computation results.
/// </summary>
public Block8x8F WorkspaceBlock2;
/// <summary> /// <summary>
/// The quantization table as <see cref="Block8x8F"/>. /// The quantization table as <see cref="Block8x8F"/>.
@ -38,11 +33,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary> /// </summary>
private Size subSamplingDivisors; private Size subSamplingDivisors;
/// <summary>
/// Defines the maximum value derived from the bitdepth.
/// </summary>
private readonly int maximumValue;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JpegBlockPostProcessor"/> struct. /// Initializes a new instance of the <see cref="JpegBlockPostProcessor"/> struct.
/// </summary> /// </summary>
@ -51,13 +41,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
public JpegBlockPostProcessor(IRawJpegData decoder, IJpegComponent component) public JpegBlockPostProcessor(IRawJpegData decoder, IJpegComponent component)
{ {
int qtIndex = component.QuantizationTableIndex; int qtIndex = component.QuantizationTableIndex;
this.DequantiazationTable = ZigZag.CreateDequantizationTable(ref decoder.QuantizationTables[qtIndex]); this.DequantiazationTable = decoder.QuantizationTables[qtIndex];
this.subSamplingDivisors = component.SubSamplingDivisors; this.subSamplingDivisors = component.SubSamplingDivisors;
this.maximumValue = (int)MathF.Pow(2, decoder.Precision) - 1;
this.SourceBlock = default; this.SourceBlock = default;
this.WorkspaceBlock1 = default; this.WorkspaceBlock = default;
this.WorkspaceBlock2 = default;
} }
/// <summary> /// <summary>
@ -77,20 +65,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
int destAreaStride, int destAreaStride,
float maximumValue) float maximumValue)
{ {
ref Block8x8F b = ref this.SourceBlock; ref Block8x8F block = ref this.SourceBlock;
b.LoadFrom(ref sourceBlock); block.LoadFrom(ref sourceBlock);
// Dequantize: // Dequantize:
b.MultiplyInPlace(ref this.DequantiazationTable); block.MultiplyInPlace(ref this.DequantiazationTable);
FastFloatingPointDCT.TransformIDCT(ref b, ref this.WorkspaceBlock1, ref this.WorkspaceBlock2); FastFloatingPointDCT.TransformIDCT(ref block, ref this.WorkspaceBlock);
// To conform better to libjpeg we actually NEED TO loose precision here. // To conform better to libjpeg we actually NEED TO loose precision here.
// This is because they store blocks as Int16 between all the operations. // This is because they store blocks as Int16 between all the operations.
// To be "more accurate", we need to emulate this by rounding! // To be "more accurate", we need to emulate this by rounding!
this.WorkspaceBlock1.NormalizeColorsAndRoundInPlace(maximumValue); block.NormalizeColorsAndRoundInPlace(maximumValue);
this.WorkspaceBlock1.ScaledCopyTo( block.ScaledCopyTo(
ref destAreaOrigin, ref destAreaOrigin,
destAreaStride, destAreaStride,
this.subSamplingDivisors.Width, this.subSamplingDivisors.Width,

33
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
@ -32,7 +31,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
if (quantizationTableIndex > 3) if (quantizationTableIndex > 3)
{ {
JpegThrowHelper.ThrowBadQuantizationTable(); JpegThrowHelper.ThrowBadQuantizationTableIndex(quantizationTableIndex);
} }
this.QuantizationTableIndex = quantizationTableIndex; this.QuantizationTableIndex = quantizationTableIndex;
@ -106,31 +105,43 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.SpectralBlocks = null; this.SpectralBlocks = null;
} }
public void Init() /// <summary>
/// Initializes component for future buffers initialization.
/// </summary>
/// <param name="maxSubFactorH">Maximal horizontal subsampling factor among all the components.</param>
/// <param name="maxSubFactorV">Maximal vertical subsampling factor among all the components.</param>
public void Init(int maxSubFactorH, int maxSubFactorV)
{ {
this.WidthInBlocks = (int)MathF.Ceiling( this.WidthInBlocks = (int)MathF.Ceiling(
MathF.Ceiling(this.Frame.SamplesPerLine / 8F) * this.HorizontalSamplingFactor / this.Frame.MaxHorizontalFactor); MathF.Ceiling(this.Frame.PixelWidth / 8F) * this.HorizontalSamplingFactor / maxSubFactorH);
this.HeightInBlocks = (int)MathF.Ceiling( this.HeightInBlocks = (int)MathF.Ceiling(
MathF.Ceiling(this.Frame.Scanlines / 8F) * this.VerticalSamplingFactor / this.Frame.MaxVerticalFactor); MathF.Ceiling(this.Frame.PixelHeight / 8F) * this.VerticalSamplingFactor / maxSubFactorV);
int blocksPerLineForMcu = this.Frame.McusPerLine * this.HorizontalSamplingFactor; int blocksPerLineForMcu = this.Frame.McusPerLine * this.HorizontalSamplingFactor;
int blocksPerColumnForMcu = this.Frame.McusPerColumn * this.VerticalSamplingFactor; int blocksPerColumnForMcu = this.Frame.McusPerColumn * this.VerticalSamplingFactor;
this.SizeInBlocks = new Size(blocksPerLineForMcu, blocksPerColumnForMcu); this.SizeInBlocks = new Size(blocksPerLineForMcu, blocksPerColumnForMcu);
JpegComponent c0 = this.Frame.Components[0]; this.SubSamplingDivisors = new Size(maxSubFactorH, maxSubFactorV).DivideBy(this.SamplingFactors);
this.SubSamplingDivisors = c0.SamplingFactors.DivideBy(this.SamplingFactors);
if (this.SubSamplingDivisors.Width == 0 || this.SubSamplingDivisors.Height == 0) if (this.SubSamplingDivisors.Width == 0 || this.SubSamplingDivisors.Height == 0)
{ {
JpegThrowHelper.ThrowBadSampling(); JpegThrowHelper.ThrowBadSampling();
} }
}
public void AllocateSpectral(bool fullScan)
{
if (this.SpectralBlocks != null)
{
// this method will be called each scan marker so we need to allocate only once
return;
}
int totalNumberOfBlocks = blocksPerColumnForMcu * (blocksPerLineForMcu + 1); int spectralAllocWidth = this.SizeInBlocks.Width;
int width = this.WidthInBlocks + 1; int spectralAllocHeight = fullScan ? this.SizeInBlocks.Height : this.VerticalSamplingFactor;
int height = totalNumberOfBlocks / width;
this.SpectralBlocks = this.memoryAllocator.Allocate2D<Block8x8>(width, height, AllocationOptions.Clean); this.SpectralBlocks = this.memoryAllocator.Allocate2D<Block8x8>(spectralAllocWidth, spectralAllocHeight, AllocationOptions.Clean);
} }
} }
} }

64
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponentPostProcessor.cs

@ -2,15 +2,12 @@
// 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;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
/// <summary> /// <summary>
/// Encapsulates postprocessing data for one component for <see cref="JpegImagePostProcessor"/>. /// Encapsulates spectral data to rgba32 processing for one component.
/// </summary> /// </summary>
internal class JpegComponentPostProcessor : IDisposable internal class JpegComponentPostProcessor : IDisposable
{ {
@ -24,26 +21,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary> /// </summary>
private readonly Size blockAreaSize; private readonly Size blockAreaSize;
/// <summary>
/// Jpeg frame instance containing required decoding metadata.
/// </summary>
private readonly JpegFrame frame;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JpegComponentPostProcessor"/> class. /// Initializes a new instance of the <see cref="JpegComponentPostProcessor"/> class.
/// </summary> /// </summary>
public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, JpegImagePostProcessor imagePostProcessor, IJpegComponent component) public JpegComponentPostProcessor(MemoryAllocator memoryAllocator, JpegFrame frame, IRawJpegData rawJpeg, Size postProcessorBufferSize, IJpegComponent component)
{ {
this.frame = frame;
this.Component = component; this.Component = component;
this.ImagePostProcessor = imagePostProcessor; this.RawJpeg = rawJpeg;
this.blockAreaSize = this.Component.SubSamplingDivisors * 8; this.blockAreaSize = this.Component.SubSamplingDivisors * 8;
this.ColorBuffer = memoryAllocator.Allocate2DOveraligned<float>( this.ColorBuffer = memoryAllocator.Allocate2DOveraligned<float>(
imagePostProcessor.PostProcessorBufferSize.Width, postProcessorBufferSize.Width,
imagePostProcessor.PostProcessorBufferSize.Height, postProcessorBufferSize.Height,
this.blockAreaSize.Height); this.blockAreaSize.Height);
this.BlockRowsPerStep = JpegImagePostProcessor.BlockRowsPerStep / this.Component.SubSamplingDivisors.Height; this.BlockRowsPerStep = postProcessorBufferSize.Height / 8 / this.Component.SubSamplingDivisors.Height;
} }
/// <summary> public IRawJpegData RawJpeg { get; }
/// Gets the <see cref="JpegImagePostProcessor"/>
/// </summary>
public JpegImagePostProcessor ImagePostProcessor { get; }
/// <summary> /// <summary>
/// Gets the <see cref="Component"/> /// Gets the <see cref="Component"/>
@ -66,26 +67,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
public int BlockRowsPerStep { get; } public int BlockRowsPerStep { get; }
/// <inheritdoc /> /// <inheritdoc />
public void Dispose() public void Dispose() => this.ColorBuffer.Dispose();
{
this.ColorBuffer.Dispose();
}
/// <summary> /// <summary>
/// Invoke <see cref="JpegBlockPostProcessor"/> for <see cref="BlockRowsPerStep"/> block rows, copy the result into <see cref="ColorBuffer"/>. /// Invoke <see cref="JpegBlockPostProcessor"/> for <see cref="BlockRowsPerStep"/> block rows, copy the result into <see cref="ColorBuffer"/>.
/// </summary> /// </summary>
public void CopyBlocksToColorBuffer() public void CopyBlocksToColorBuffer(int step)
{ {
var blockPp = new JpegBlockPostProcessor(this.ImagePostProcessor.RawJpeg, this.Component); Buffer2D<Block8x8> spectralBuffer = this.Component.SpectralBlocks;
float maximumValue = MathF.Pow(2, this.ImagePostProcessor.RawJpeg.Precision) - 1;
var blockPp = new JpegBlockPostProcessor(this.RawJpeg, this.Component);
float maximumValue = this.frame.MaxColorChannelValue;
int destAreaStride = this.ColorBuffer.Width; int destAreaStride = this.ColorBuffer.Width;
int yBlockStart = step * this.BlockRowsPerStep;
for (int y = 0; y < this.BlockRowsPerStep; y++) for (int y = 0; y < this.BlockRowsPerStep; y++)
{ {
int yBlock = this.currentComponentRowInBlocks + y; int yBlock = yBlockStart + y;
if (yBlock >= this.SizeInBlocks.Height) if (yBlock >= spectralBuffer.Height)
{ {
break; break;
} }
@ -93,10 +96,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
int yBuffer = y * this.blockAreaSize.Height; int yBuffer = y * this.blockAreaSize.Height;
Span<float> colorBufferRow = this.ColorBuffer.GetRowSpan(yBuffer); Span<float> colorBufferRow = this.ColorBuffer.GetRowSpan(yBuffer);
Span<Block8x8> blockRow = this.Component.SpectralBlocks.GetRowSpan(yBlock); Span<Block8x8> blockRow = spectralBuffer.GetRowSpan(yBlock);
// see: https://github.com/SixLabors/ImageSharp/issues/824 // see: https://github.com/SixLabors/ImageSharp/issues/824
int widthInBlocks = Math.Min(this.Component.SpectralBlocks.Width, this.SizeInBlocks.Width); int widthInBlocks = Math.Min(spectralBuffer.Width, this.SizeInBlocks.Width);
for (int xBlock = 0; xBlock < widthInBlocks; xBlock++) for (int xBlock = 0; xBlock < widthInBlocks; xBlock++)
{ {
@ -107,7 +110,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
blockPp.ProcessBlockColorsInto(ref block, ref destAreaOrigin, destAreaStride, maximumValue); blockPp.ProcessBlockColorsInto(ref block, ref destAreaOrigin, destAreaStride, maximumValue);
} }
} }
}
public void ClearSpectralBuffers()
{
Buffer2D<Block8x8> spectralBlocks = this.Component.SpectralBlocks;
for (int i = 0; i < spectralBlocks.Height; i++)
{
spectralBlocks.GetRowSpan(i).Clear();
}
}
public void CopyBlocksToColorBuffer()
{
this.CopyBlocksToColorBuffer(this.currentComponentRowInBlocks);
this.currentComponentRowInBlocks += this.BlockRowsPerStep; this.currentComponentRowInBlocks += this.BlockRowsPerStep;
} }
} }

91
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegFrame.cs

@ -10,35 +10,67 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary> /// </summary>
internal sealed class JpegFrame : IDisposable internal sealed class JpegFrame : IDisposable
{ {
public JpegFrame(JpegFileMarker sofMarker, byte precision, int width, int height, byte componentCount)
{
this.Extended = sofMarker.Marker == JpegConstants.Markers.SOF1;
this.Progressive = sofMarker.Marker == JpegConstants.Markers.SOF2;
this.Precision = precision;
this.MaxColorChannelValue = MathF.Pow(2, precision) - 1;
this.PixelWidth = width;
this.PixelHeight = height;
this.ComponentCount = componentCount;
}
/// <summary>
/// Gets a value indicating whether the frame uses the extended specification.
/// </summary>
public bool Extended { get; private set; }
/// <summary>
/// Gets a value indicating whether the frame uses the progressive specification.
/// </summary>
public bool Progressive { get; private set; }
/// <summary>
/// Gets or sets a value indicating whether the frame is encoded using multiple scans (SOS markers).
/// </summary>
/// <remarks>
/// This is true for progressive and baseline non-interleaved images.
/// </remarks>
public bool MultiScan { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the frame uses the extended specification. /// Gets the precision.
/// </summary> /// </summary>
public bool Extended { get; set; } public byte Precision { get; private set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the frame uses the progressive specification. /// Gets the maximum color value derived from <see cref="Precision"/>.
/// </summary> /// </summary>
public bool Progressive { get; set; } public float MaxColorChannelValue { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the precision. /// Gets the number of pixel per row.
/// </summary> /// </summary>
public byte Precision { get; set; } public int PixelHeight { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the number of scanlines within the frame. /// Gets the number of pixels per line.
/// </summary> /// </summary>
public int Scanlines { get; set; } public int PixelWidth { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the number of samples per scanline. /// Gets the pixel size of the image.
/// </summary> /// </summary>
public int SamplesPerLine { get; set; } public Size PixelSize => new Size(this.PixelWidth, this.PixelHeight);
/// <summary> /// <summary>
/// Gets or sets the number of components within a frame. In progressive frames this value can range from only 1 to 4. /// Gets the number of components within a frame.
/// </summary> /// </summary>
public byte ComponentCount { get; set; } public byte ComponentCount { get; private set; }
/// <summary> /// <summary>
/// Gets or sets the component id collection. /// Gets or sets the component id collection.
@ -57,24 +89,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
public JpegComponent[] Components { get; set; } public JpegComponent[] Components { get; set; }
/// <summary> /// <summary>
/// Gets or sets the maximum horizontal sampling factor. /// Gets or sets the number of MCU's per line.
/// </summary> /// </summary>
public int MaxHorizontalFactor { get; set; } public int McusPerLine { get; set; }
/// <summary> /// <summary>
/// Gets or sets the maximum vertical sampling factor. /// Gets or sets the number of MCU's per column.
/// </summary> /// </summary>
public int MaxVerticalFactor { get; set; } public int McusPerColumn { get; set; }
/// <summary> /// <summary>
/// Gets or sets the number of MCU's per line. /// Gets the mcu size of the image.
/// </summary> /// </summary>
public int McusPerLine { get; set; } public Size McuSize => new Size(this.McusPerLine, this.McusPerColumn);
/// <summary> /// <summary>
/// Gets or sets the number of MCU's per column. /// Gets the color depth, in number of bits per pixel.
/// </summary> /// </summary>
public int McusPerColumn { get; set; } public int BitsPerPixel => this.ComponentCount * this.Precision;
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
@ -93,15 +125,26 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <summary> /// <summary>
/// Allocates the frame component blocks. /// Allocates the frame component blocks.
/// </summary> /// </summary>
public void InitComponents() /// <param name="maxSubFactorH">Maximal horizontal subsampling factor among all the components.</param>
/// <param name="maxSubFactorV">Maximal vertical subsampling factor among all the components.</param>
public void Init(int maxSubFactorH, int maxSubFactorV)
{ {
this.McusPerLine = (int)MathF.Ceiling(this.SamplesPerLine / 8F / this.MaxHorizontalFactor); this.McusPerLine = (int)Numerics.DivideCeil((uint)this.PixelWidth, (uint)maxSubFactorH * 8);
this.McusPerColumn = (int)MathF.Ceiling(this.Scanlines / 8F / this.MaxVerticalFactor); this.McusPerColumn = (int)Numerics.DivideCeil((uint)this.PixelHeight, (uint)maxSubFactorV * 8);
for (int i = 0; i < this.ComponentCount; i++) for (int i = 0; i < this.ComponentCount; i++)
{ {
JpegComponent component = this.Components[i]; JpegComponent component = this.Components[i];
component.Init(); component.Init(maxSubFactorH, maxSubFactorV);
}
}
public void AllocateComponents(bool fullScan)
{
for (int i = 0; i < this.ComponentCount; i++)
{
JpegComponent component = this.Components[i];
component.AllocateSpectral(fullScan);
} }
} }
} }

181
src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegImagePostProcessor.cs

@ -1,181 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Numerics;
using System.Threading;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
using JpegColorConverter = SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters.JpegColorConverter;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Encapsulates the execution od post-processing algorithms to be applied on a <see cref="IRawJpegData"/> to produce a valid <see cref="Image{TPixel}"/>: <br/>
/// (1) Dequantization <br/>
/// (2) IDCT <br/>
/// (3) Color conversion form one of the <see cref="JpegColorSpace"/>-s into a <see cref="Vector4"/> buffer of RGBA values <br/>
/// (4) Packing <see cref="Image{TPixel}"/> pixels from the <see cref="Vector4"/> buffer. <br/>
/// These operations are executed in <see cref="NumberOfPostProcessorSteps"/> steps.
/// <see cref="PixelRowsPerStep"/> image rows are converted in one step,
/// which means that size of the allocated memory is limited (does not depend on <see cref="ImageFrame.Height"/>).
/// </summary>
internal class JpegImagePostProcessor : IDisposable
{
private readonly Configuration configuration;
/// <summary>
/// The number of block rows to be processed in one Step.
/// </summary>
public const int BlockRowsPerStep = 4;
/// <summary>
/// The number of image pixel rows to be processed in one step.
/// </summary>
public const int PixelRowsPerStep = 4 * 8;
/// <summary>
/// Temporal buffer to store a row of colors.
/// </summary>
private readonly IMemoryOwner<Vector4> rgbaBuffer;
/// <summary>
/// The <see cref="JpegColorConverter"/> corresponding to the current <see cref="JpegColorSpace"/> determined by <see cref="IRawJpegData.ColorSpace"/>.
/// </summary>
private readonly JpegColorConverter colorConverter;
/// <summary>
/// Initializes a new instance of the <see cref="JpegImagePostProcessor"/> class.
/// </summary>
/// <param name="configuration">The <see cref="Configuration"/> to configure internal operations.</param>
/// <param name="rawJpeg">The <see cref="IRawJpegData"/> representing the uncompressed spectral Jpeg data</param>
public JpegImagePostProcessor(Configuration configuration, IRawJpegData rawJpeg)
{
this.configuration = configuration;
this.RawJpeg = rawJpeg;
IJpegComponent c0 = rawJpeg.Components[0];
this.NumberOfPostProcessorSteps = c0.SizeInBlocks.Height / BlockRowsPerStep;
this.PostProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, PixelRowsPerStep);
MemoryAllocator memoryAllocator = configuration.MemoryAllocator;
this.ComponentProcessors = new JpegComponentPostProcessor[rawJpeg.Components.Length];
for (int i = 0; i < rawJpeg.Components.Length; i++)
{
this.ComponentProcessors[i] = new JpegComponentPostProcessor(memoryAllocator, this, rawJpeg.Components[i]);
}
this.rgbaBuffer = memoryAllocator.Allocate<Vector4>(rawJpeg.ImageSizeInPixels.Width);
this.colorConverter = JpegColorConverter.GetConverter(rawJpeg.ColorSpace, rawJpeg.Precision);
}
/// <summary>
/// Gets the <see cref="JpegComponentPostProcessor"/> instances.
/// </summary>
public JpegComponentPostProcessor[] ComponentProcessors { get; }
/// <summary>
/// Gets the <see cref="IRawJpegData"/> to be processed.
/// </summary>
public IRawJpegData RawJpeg { get; }
/// <summary>
/// Gets the total number of post processor steps deduced from the height of the image and <see cref="PixelRowsPerStep"/>.
/// </summary>
public int NumberOfPostProcessorSteps { get; }
/// <summary>
/// Gets the size of the temporary buffers we need to allocate into <see cref="JpegComponentPostProcessor.ColorBuffer"/>.
/// </summary>
public Size PostProcessorBufferSize { get; }
/// <summary>
/// Gets the value of the counter that grows by each step by <see cref="PixelRowsPerStep"/>.
/// </summary>
public int PixelRowCounter { get; private set; }
/// <inheritdoc />
public void Dispose()
{
foreach (JpegComponentPostProcessor cpp in this.ComponentProcessors)
{
cpp.Dispose();
}
this.rgbaBuffer.Dispose();
}
/// <summary>
/// Process all pixels into 'destination'. The image dimensions should match <see cref="RawJpeg"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="destination">The destination image</param>
/// <param name="cancellationToken">The token to request cancellation.</param>
public void PostProcess<TPixel>(ImageFrame<TPixel> destination, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
this.PixelRowCounter = 0;
if (this.RawJpeg.ImageSizeInPixels != destination.Size())
{
throw new ArgumentException("Input image is not of the size of the processed one!");
}
while (this.PixelRowCounter < this.RawJpeg.ImageSizeInPixels.Height)
{
cancellationToken.ThrowIfCancellationRequested();
this.DoPostProcessorStep(destination);
}
}
/// <summary>
/// Execute one step processing <see cref="PixelRowsPerStep"/> pixel rows into 'destination'.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="destination">The destination image.</param>
public void DoPostProcessorStep<TPixel>(ImageFrame<TPixel> destination)
where TPixel : unmanaged, IPixel<TPixel>
{
foreach (JpegComponentPostProcessor cpp in this.ComponentProcessors)
{
cpp.CopyBlocksToColorBuffer();
}
this.ConvertColorsInto(destination);
this.PixelRowCounter += PixelRowsPerStep;
}
/// <summary>
/// Convert and copy <see cref="PixelRowsPerStep"/> row of colors into 'destination' starting at row <see cref="PixelRowCounter"/>.
/// </summary>
/// <typeparam name="TPixel">The pixel type</typeparam>
/// <param name="destination">The destination image</param>
private void ConvertColorsInto<TPixel>(ImageFrame<TPixel> destination)
where TPixel : unmanaged, IPixel<TPixel>
{
int maxY = Math.Min(destination.Height, this.PixelRowCounter + PixelRowsPerStep);
var buffers = new Buffer2D<float>[this.ComponentProcessors.Length];
for (int i = 0; i < this.ComponentProcessors.Length; i++)
{
buffers[i] = this.ComponentProcessors[i].ColorBuffer;
}
for (int yy = this.PixelRowCounter; yy < maxY; yy++)
{
int y = yy - this.PixelRowCounter;
var values = new JpegColorConverter.ComponentValues(buffers, y);
this.colorConverter.ConvertToRgba(values, this.rgbaBuffer.GetSpan());
Span<TPixel> destRow = destination.GetPixelRowSpan(yy);
// TODO: Investigate if slicing is actually necessary
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, this.rgbaBuffer.GetSpan().Slice(0, destRow.Length), destRow);
}
}
}
}

144
src/ImageSharp/Formats/Jpeg/Components/Decoder/QualityEvaluator.cs

@ -1,144 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Provides methods to evaluate the quality of an image.
/// Ported from <see href="https://github.com/ImageMagick/ImageMagick/blob/f362c02083d27211b913c6e44794f0ac6edaf2bd/coders/jpeg.c#L855"/>
/// </summary>
internal static class QualityEvaluator
{
private static readonly int[] Hash = new int[101]
{
1020, 1015, 932, 848, 780, 735, 702, 679, 660, 645,
632, 623, 613, 607, 600, 594, 589, 585, 581, 571,
555, 542, 529, 514, 494, 474, 457, 439, 424, 410,
397, 386, 373, 364, 351, 341, 334, 324, 317, 309,
299, 294, 287, 279, 274, 267, 262, 257, 251, 247,
243, 237, 232, 227, 222, 217, 213, 207, 202, 198,
192, 188, 183, 177, 173, 168, 163, 157, 153, 148,
143, 139, 132, 128, 125, 119, 115, 108, 104, 99,
94, 90, 84, 79, 74, 70, 64, 59, 55, 49,
45, 40, 34, 30, 25, 20, 15, 11, 6, 4,
0
};
private static readonly int[] Sums = new int[101]
{
32640, 32635, 32266, 31495, 30665, 29804, 29146, 28599, 28104,
27670, 27225, 26725, 26210, 25716, 25240, 24789, 24373, 23946,
23572, 22846, 21801, 20842, 19949, 19121, 18386, 17651, 16998,
16349, 15800, 15247, 14783, 14321, 13859, 13535, 13081, 12702,
12423, 12056, 11779, 11513, 11135, 10955, 10676, 10392, 10208,
9928, 9747, 9564, 9369, 9193, 9017, 8822, 8639, 8458,
8270, 8084, 7896, 7710, 7527, 7347, 7156, 6977, 6788,
6607, 6422, 6236, 6054, 5867, 5684, 5495, 5305, 5128,
4945, 4751, 4638, 4442, 4248, 4065, 3888, 3698, 3509,
3326, 3139, 2957, 2775, 2586, 2405, 2216, 2037, 1846,
1666, 1483, 1297, 1109, 927, 735, 554, 375, 201,
128, 0
};
private static readonly int[] Hash1 = new int[101]
{
510, 505, 422, 380, 355, 338, 326, 318, 311, 305,
300, 297, 293, 291, 288, 286, 284, 283, 281, 280,
279, 278, 277, 273, 262, 251, 243, 233, 225, 218,
211, 205, 198, 193, 186, 181, 177, 172, 168, 164,
158, 156, 152, 148, 145, 142, 139, 136, 133, 131,
129, 126, 123, 120, 118, 115, 113, 110, 107, 105,
102, 100, 97, 94, 92, 89, 87, 83, 81, 79,
76, 74, 70, 68, 66, 63, 61, 57, 55, 52,
50, 48, 44, 42, 39, 37, 34, 31, 29, 26,
24, 21, 18, 16, 13, 11, 8, 6, 3, 2,
0
};
private static readonly int[] Sums1 = new int[101]
{
16320, 16315, 15946, 15277, 14655, 14073, 13623, 13230, 12859,
12560, 12240, 11861, 11456, 11081, 10714, 10360, 10027, 9679,
9368, 9056, 8680, 8331, 7995, 7668, 7376, 7084, 6823,
6562, 6345, 6125, 5939, 5756, 5571, 5421, 5240, 5086,
4976, 4829, 4719, 4616, 4463, 4393, 4280, 4166, 4092,
3980, 3909, 3835, 3755, 3688, 3621, 3541, 3467, 3396,
3323, 3247, 3170, 3096, 3021, 2952, 2874, 2804, 2727,
2657, 2583, 2509, 2437, 2362, 2290, 2211, 2136, 2068,
1996, 1915, 1858, 1773, 1692, 1620, 1552, 1477, 1398,
1326, 1251, 1179, 1109, 1031, 961, 884, 814, 736,
667, 592, 518, 441, 369, 292, 221, 151, 86,
64, 0
};
/// <summary>
/// Returns an estimated quality of the image based on the quantization tables.
/// </summary>
/// <param name="quantizationTables">The quantization tables.</param>
/// <returns>The <see cref="int"/>.</returns>
public static int EstimateQuality(Block8x8F[] quantizationTables)
{
int quality = 75;
float sum = 0;
for (int i = 0; i < quantizationTables.Length; i++)
{
ref Block8x8F qTable = ref quantizationTables[i];
if (!qTable.Equals(default))
{
for (int j = 0; j < Block8x8F.Size; j++)
{
sum += qTable[j];
}
}
}
ref Block8x8F qTable0 = ref quantizationTables[0];
ref Block8x8F qTable1 = ref quantizationTables[1];
if (!qTable0.Equals(default))
{
if (!qTable1.Equals(default))
{
quality = (int)(qTable0[2]
+ qTable0[53]
+ qTable1[0]
+ qTable1[Block8x8F.Size - 1]);
for (int i = 0; i < 100; i++)
{
if (quality < Hash[i] && sum < Sums[i])
{
continue;
}
if (((quality <= Hash[i]) && (sum <= Sums[i])) || (i >= 50))
{
return i + 1;
}
}
}
else
{
quality = (int)(qTable0[2] + qTable0[53]);
for (int i = 0; i < 100; i++)
{
if (quality < Hash1[i] && sum < Sums1[i])
{
continue;
}
if (((quality <= Hash1[i]) && (sum <= Sums1[i])) || (i >= 50))
{
return i + 1;
}
}
}
}
return quality;
}
}
}

44
src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs

@ -0,0 +1,44 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
/// Converter used to convert jpeg spectral data.
/// </summary>
/// <remarks>
/// This is tightly coupled with <see cref="HuffmanScanDecoder"/> and <see cref="JpegDecoderCore"/>.
/// </remarks>
internal abstract class SpectralConverter
{
/// <summary>
/// Injects jpeg image decoding metadata.
/// </summary>
/// <remarks>
/// This is guaranteed to be called only once at SOF marker by <see cref="HuffmanScanDecoder"/>.
/// </remarks>
/// <param name="frame"><see cref="JpegFrame"/> instance containing decoder-specific parameters.</param>
/// <param name="jpegData"><see cref="IRawJpegData"/> instance containing decoder-specific parameters.</param>
public abstract void InjectFrameData(JpegFrame frame, IRawJpegData jpegData);
/// <summary>
/// Called once per spectral stride for each component in <see cref="HuffmanScanDecoder"/>.
/// This is called only for baseline interleaved jpegs.
/// </summary>
/// <remarks>
/// Spectral 'stride' doesn't particularly mean 'single stride'.
/// Actual stride height depends on the subsampling factor of the given component.
/// </remarks>
public abstract void ConvertStrideBaseline();
/// <summary>
/// Gets the color converter.
/// </summary>
/// <param name="frame">The jpeg frame with the color space to convert to.</param>
/// <param name="jpegData">The raw JPEG data.</param>
/// <returns>The color converter.</returns>
protected virtual JpegColorConverter GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverter.GetConverter(jpegData.ColorSpace, frame.Precision);
}
}

172
src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs

@ -0,0 +1,172 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Numerics;
using System.Threading;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
internal class SpectralConverter<TPixel> : SpectralConverter, IDisposable
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly Configuration configuration;
private readonly CancellationToken cancellationToken;
private JpegComponentPostProcessor[] componentProcessors;
private JpegColorConverter colorConverter;
// private IMemoryOwner<Vector4> rgbaBuffer;
private IMemoryOwner<byte> rgbBuffer;
private IMemoryOwner<TPixel> paddedProxyPixelRow;
private Buffer2D<TPixel> pixelBuffer;
private int blockRowsPerStep;
private int pixelRowsPerStep;
private int pixelRowCounter;
public SpectralConverter(Configuration configuration, CancellationToken cancellationToken)
{
this.configuration = configuration;
this.cancellationToken = cancellationToken;
}
private bool Converted => this.pixelRowCounter >= this.pixelBuffer.Height;
public Buffer2D<TPixel> GetPixelBuffer()
{
if (!this.Converted)
{
int steps = (int)Math.Ceiling(this.pixelBuffer.Height / (float)this.pixelRowsPerStep);
for (int step = 0; step < steps; step++)
{
this.cancellationToken.ThrowIfCancellationRequested();
this.ConvertNextStride(step);
}
}
return this.pixelBuffer;
}
/// <inheritdoc/>
public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData)
{
MemoryAllocator allocator = this.configuration.MemoryAllocator;
// iteration data
IJpegComponent c0 = frame.Components[0];
const int blockPixelHeight = 8;
this.blockRowsPerStep = c0.SamplingFactors.Height;
this.pixelRowsPerStep = this.blockRowsPerStep * blockPixelHeight;
// pixel buffer for resulting image
this.pixelBuffer = allocator.Allocate2D<TPixel>(frame.PixelWidth, frame.PixelHeight);
this.paddedProxyPixelRow = allocator.Allocate<TPixel>(frame.PixelWidth + 3);
// component processors from spectral to Rgba32
var postProcessorBufferSize = new Size(c0.SizeInBlocks.Width * 8, this.pixelRowsPerStep);
this.componentProcessors = new JpegComponentPostProcessor[frame.Components.Length];
for (int i = 0; i < this.componentProcessors.Length; i++)
{
this.componentProcessors[i] = new JpegComponentPostProcessor(allocator, frame, jpegData, postProcessorBufferSize, frame.Components[i]);
}
// single 'stride' rgba32 buffer for conversion between spectral and TPixel
// this.rgbaBuffer = allocator.Allocate<Vector4>(frame.PixelWidth);
this.rgbBuffer = allocator.Allocate<byte>(frame.PixelWidth * 3);
// color converter from Rgba32 to TPixel
this.colorConverter = this.GetColorConverter(frame, jpegData);
}
/// <inheritdoc/>
public override void ConvertStrideBaseline()
{
// Convert next pixel stride using single spectral `stride'
// Note that zero passing eliminates the need of virtual call from JpegComponentPostProcessor
this.ConvertNextStride(spectralStep: 0);
// Clear spectral stride - this is VERY important as jpeg possibly won't fill entire buffer each stride
// Which leads to decoding artifacts
// Note that this code clears all buffers of the post processors, it's their responsibility to allocate only single stride
foreach (JpegComponentPostProcessor cpp in this.componentProcessors)
{
cpp.ClearSpectralBuffers();
}
}
public void Dispose()
{
if (this.componentProcessors != null)
{
foreach (JpegComponentPostProcessor cpp in this.componentProcessors)
{
cpp.Dispose();
}
}
this.rgbBuffer?.Dispose();
this.paddedProxyPixelRow?.Dispose();
}
private void ConvertNextStride(int spectralStep)
{
int maxY = Math.Min(this.pixelBuffer.Height, this.pixelRowCounter + this.pixelRowsPerStep);
var buffers = new Buffer2D<float>[this.componentProcessors.Length];
for (int i = 0; i < this.componentProcessors.Length; i++)
{
this.componentProcessors[i].CopyBlocksToColorBuffer(spectralStep);
buffers[i] = this.componentProcessors[i].ColorBuffer;
}
int width = this.pixelBuffer.Width;
for (int yy = this.pixelRowCounter; yy < maxY; yy++)
{
int y = yy - this.pixelRowCounter;
var values = new JpegColorConverter.ComponentValues(buffers, y);
this.colorConverter.ConvertToRgbInplace(values);
values = values.Slice(0, width); // slice away Jpeg padding
Span<byte> r = this.rgbBuffer.Slice(0, width);
Span<byte> g = this.rgbBuffer.Slice(width, width);
Span<byte> b = this.rgbBuffer.Slice(width * 2, width);
SimdUtils.NormalizedFloatToByteSaturate(values.Component0, r);
SimdUtils.NormalizedFloatToByteSaturate(values.Component1, g);
SimdUtils.NormalizedFloatToByteSaturate(values.Component2, b);
// PackFromRgbPlanes expects the destination to be padded, so try to get padded span containing extra elements from the next row.
// If we can't get such a padded row because we are on a MemoryGroup boundary or at the last row,
// pack pixels to a temporary, padded proxy buffer, then copy the relevant values to the destination row.
if (this.pixelBuffer.TryGetPaddedRowSpan(yy, 3, out Span<TPixel> destRow))
{
PixelOperations<TPixel>.Instance.PackFromRgbPlanes(this.configuration, r, g, b, destRow);
}
else
{
Span<TPixel> proxyRow = this.paddedProxyPixelRow.GetSpan();
PixelOperations<TPixel>.Instance.PackFromRgbPlanes(this.configuration, r, g, b, proxyRow);
proxyRow.Slice(0, width).CopyTo(this.pixelBuffer.GetRowSpan(yy));
}
}
this.pixelRowCounter += this.pixelRowsPerStep;
}
}
}

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

@ -5,10 +5,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{ {
/// <summary> /// <summary>
/// A compiled look-up table representation of a huffmanSpec. /// A compiled look-up table representation of a huffmanSpec.
/// Each value maps to a int32 of which the 24 most significant bits hold the
/// codeword in bits and the 8 least significant bits hold the codeword size.
/// The maximum codeword size is 16 bits. /// The maximum codeword size is 16 bits.
/// </summary> /// </summary>
/// <remarks>
/// <para>
/// Each value maps to a int32 of which the 24 most significant bits hold the
/// codeword in bits and the 8 least significant bits hold the codeword size.
/// </para>
/// <para>
/// Code value occupies 24 most significant bits as integer value.
/// This value is shifted to the MSB position for performance reasons.
/// For example, decimal value 10 is stored like this:
/// <code>
/// MSB LSB
/// 1010 0000 00000000 00000000 | 00000100
/// </code>
/// This was done to eliminate extra binary shifts in the encoder.
/// While code length is represented as 8 bit integer value
/// </para>
/// </remarks>
internal readonly struct HuffmanLut internal readonly struct HuffmanLut
{ {
/// <summary> /// <summary>
@ -54,7 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
int len = i + 1; int len = i + 1;
for (int j = 0; j < spec.Count[i]; j++) for (int j = 0; j < spec.Count[i]; j++)
{ {
this.Values[spec.Values[k]] = len | (code << 8); this.Values[spec.Values[k]] = len | (code << (32 - len));
code++; code++;
k++; k++;
} }

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

@ -1,12 +1,11 @@
// 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.IO; using System.IO;
using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
#if SUPPORTS_RUNTIME_INTRINSICS using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
#endif
using System.Threading; using System.Threading;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -16,67 +15,134 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
internal class HuffmanScanEncoder internal class HuffmanScanEncoder
{ {
/// <summary> /// <summary>
/// Compiled huffman tree to encode given values. /// Maximum number of bytes encoded jpeg 8x8 block can occupy.
/// It's highly unlikely for block to occupy this much space - it's a theoretical limit.
/// </summary> /// </summary>
/// <remarks>Yields codewords by index consisting of [run length | bitsize].</remarks> /// <remarks>
private HuffmanLut[] huffmanTables; /// Where 16 is maximum huffman code binary length according to itu
/// specs. 10 is maximum value binary length, value comes from discrete
/// cosine tranform with value range: [-1024..1023]. Block stores
/// 8x8 = 64 values thus multiplication by 64. Then divided by 8 to get
/// the number of bytes. This value is then multiplied by
/// <see cref="MaxBytesPerBlockMultiplier"/> for performance reasons.
/// </remarks>
private const int MaxBytesPerBlock = (16 + 10) * 64 / 8 * MaxBytesPerBlockMultiplier;
/// <summary>
/// Multiplier used within cache buffers size calculation.
/// </summary>
/// <remarks>
/// <para>
/// Theoretically, <see cref="MaxBytesPerBlock"/> bytes buffer can fit
/// exactly one minimal coding unit. In reality, coding blocks occupy much
/// less space than the theoretical maximum - this can be exploited.
/// If temporal buffer size is multiplied by at least 2, second half of
/// the resulting buffer will be used as an overflow 'guard' if next
/// block would occupy maximum number of bytes. While first half may fit
/// many blocks before needing to flush.
/// </para>
/// <para>
/// This is subject to change. This can be equal to 1 but recomended
/// value is 2 or even greater - futher benchmarking needed.
/// </para>
/// </remarks>
private const int MaxBytesPerBlockMultiplier = 2;
/// <summary> /// <summary>
/// Number of bytes cached before being written to target stream via Stream.Write(byte[], offest, count). /// <see cref="streamWriteBuffer"/> size multiplier.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This is subject to change, 1024 seems to be the best value in terms of performance. /// Jpeg specification requiers to insert 'stuff' bytes after each
/// <see cref="Emit(int, int)"/> expects it to be at least 8 (see comments in method body). /// 0xff byte value. Worst case scenarion is when all bytes are 0xff.
/// While it's highly unlikely (if not impossible) to get such
/// combination, it's theoretically possible so buffer size must be guarded.
/// </remarks> /// </remarks>
private const int EmitBufferSizeInBytes = 1024; private const int OutputBufferLengthMultiplier = 2;
/// <summary> /// <summary>
/// A buffer for reducing the number of stream writes when emitting Huffman tables. /// Compiled huffman tree to encode given values.
/// </summary> /// </summary>
private readonly byte[] emitBuffer = new byte[EmitBufferSizeInBytes]; /// <remarks>Yields codewords by index consisting of [run length | bitsize].</remarks>
private HuffmanLut[] huffmanTables;
/// <summary> /// <summary>
/// Number of filled bytes in <see cref="emitBuffer"/> buffer /// Emitted bits 'micro buffer' before being transferred to the <see cref="emitBuffer"/>.
/// </summary> /// </summary>
private int emitLen = 0; private uint accumulatedBits;
/// <summary> /// <summary>
/// Emmited bits 'micro buffer' before being transfered to the <see cref="emitBuffer"/>. /// Buffer for temporal storage of huffman rle encoding bit data.
/// </summary> /// </summary>
private int accumulatedBits; /// <remarks>
/// Encoding bits are assembled to 4 byte unsigned integers and then copied to this buffer.
/// This process does NOT include inserting stuff bytes.
/// </remarks>
private readonly uint[] emitBuffer;
/// <summary>
/// Buffer for temporal storage which is then written to the output stream.
/// </summary>
/// <remarks>
/// Encoding bits from <see cref="emitBuffer"/> are copied to this byte buffer including stuff bytes.
/// </remarks>
private readonly byte[] streamWriteBuffer;
/// <summary> /// <summary>
/// Number of jagged bits stored in <see cref="accumulatedBits"/> /// Number of jagged bits stored in <see cref="accumulatedBits"/>
/// </summary> /// </summary>
private int bitCount; private int bitCount;
private Block8x8F temporalBlock1; private int emitWriteIndex;
private Block8x8F temporalBlock2;
private Block8x8 tempBlock;
/// <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>
private readonly Stream target; private readonly Stream target;
public HuffmanScanEncoder(Stream outputStream) /// <summary>
/// Initializes a new instance of the <see cref="HuffmanScanEncoder"/> class.
/// </summary>
/// <param name="blocksPerCodingUnit">Amount of encoded 8x8 blocks per single jpeg macroblock.</param>
/// <param name="outputStream">Output stream for saving encoded data.</param>
public HuffmanScanEncoder(int blocksPerCodingUnit, Stream outputStream)
{ {
int emitBufferByteLength = MaxBytesPerBlock * blocksPerCodingUnit;
this.emitBuffer = new uint[emitBufferByteLength / sizeof(uint)];
this.emitWriteIndex = this.emitBuffer.Length;
this.streamWriteBuffer = new byte[emitBufferByteLength * OutputBufferLengthMultiplier];
this.target = outputStream; this.target = outputStream;
} }
/// <summary>
/// Gets a value indicating whether <see cref="emitBuffer"/> is full
/// and must be flushed using <see cref="FlushToStream()"/>
/// before encoding next 8x8 coding block.
/// </summary>
private bool IsStreamFlushNeeded
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.emitWriteIndex < (uint)this.emitBuffer.Length / 2;
}
/// <summary> /// <summary>
/// Encodes the image with no subsampling. /// Encodes the image with no subsampling.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param> /// <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="luminanceQuantTable">Luminance quantization table provided by the callee.</param>
/// <param name="chrominanceQuantTable">Chrominance 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> /// <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) public void Encode444<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
this.huffmanTables = HuffmanLut.TheHuffmanLut; FastFloatingPointDCT.AdjustToFDCT(ref luminanceQuantTable);
FastFloatingPointDCT.AdjustToFDCT(ref chrominanceQuantTable);
var unzig = ZigZag.CreateUnzigTable(); this.huffmanTables = HuffmanLut.TheHuffmanLut;
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
@ -100,26 +166,28 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
QuantIndex.Luminance, QuantIndex.Luminance,
prevDCY, prevDCY,
ref pixelConverter.Y, ref pixelConverter.Y,
ref luminanceQuantTable, ref luminanceQuantTable);
ref unzig);
prevDCCb = this.WriteBlock( prevDCCb = this.WriteBlock(
QuantIndex.Chrominance, QuantIndex.Chrominance,
prevDCCb, prevDCCb,
ref pixelConverter.Cb, ref pixelConverter.Cb,
ref chrominanceQuantTable, ref chrominanceQuantTable);
ref unzig);
prevDCCr = this.WriteBlock( prevDCCr = this.WriteBlock(
QuantIndex.Chrominance, QuantIndex.Chrominance,
prevDCCr, prevDCCr,
ref pixelConverter.Cr, ref pixelConverter.Cr,
ref chrominanceQuantTable, ref chrominanceQuantTable);
ref unzig);
if (this.IsStreamFlushNeeded)
{
this.FlushToStream();
}
} }
} }
this.FlushInternalBuffer(); this.FlushRemainingBytes();
} }
/// <summary> /// <summary>
@ -128,15 +196,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param> /// <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="luminanceQuantTable">Luminance quantization table provided by the callee.</param>
/// <param name="chrominanceQuantTable">Chrominance 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> /// <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) public void Encode420<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
this.huffmanTables = HuffmanLut.TheHuffmanLut; FastFloatingPointDCT.AdjustToFDCT(ref luminanceQuantTable);
FastFloatingPointDCT.AdjustToFDCT(ref chrominanceQuantTable);
var unzig = ZigZag.CreateUnzigTable(); this.huffmanTables = HuffmanLut.TheHuffmanLut;
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0; int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
@ -161,34 +230,35 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
QuantIndex.Luminance, QuantIndex.Luminance,
prevDCY, prevDCY,
ref pixelConverter.YLeft, ref pixelConverter.YLeft,
ref luminanceQuantTable, ref luminanceQuantTable);
ref unzig);
prevDCY = this.WriteBlock( prevDCY = this.WriteBlock(
QuantIndex.Luminance, QuantIndex.Luminance,
prevDCY, prevDCY,
ref pixelConverter.YRight, ref pixelConverter.YRight,
ref luminanceQuantTable, ref luminanceQuantTable);
ref unzig);
} }
prevDCCb = this.WriteBlock( prevDCCb = this.WriteBlock(
QuantIndex.Chrominance, QuantIndex.Chrominance,
prevDCCb, prevDCCb,
ref pixelConverter.Cb, ref pixelConverter.Cb,
ref chrominanceQuantTable, ref chrominanceQuantTable);
ref unzig);
prevDCCr = this.WriteBlock( prevDCCr = this.WriteBlock(
QuantIndex.Chrominance, QuantIndex.Chrominance,
prevDCCr, prevDCCr,
ref pixelConverter.Cr, ref pixelConverter.Cr,
ref chrominanceQuantTable, ref chrominanceQuantTable);
ref unzig);
if (this.IsStreamFlushNeeded)
{
this.FlushToStream();
}
} }
} }
this.FlushInternalBuffer(); this.FlushRemainingBytes();
} }
/// <summary> /// <summary>
@ -196,14 +266,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param> /// <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="luminanceQuantTable">Luminance quantization table provided by the callee.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param> /// <param name="cancellationToken">The token to monitor for cancellation.</param>
public void EncodeGrayscale<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, CancellationToken cancellationToken) public void EncodeGrayscale<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
this.huffmanTables = HuffmanLut.TheHuffmanLut; FastFloatingPointDCT.AdjustToFDCT(ref luminanceQuantTable);
var unzig = ZigZag.CreateUnzigTable(); this.huffmanTables = HuffmanLut.TheHuffmanLut;
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
int prevDCY = 0; int prevDCY = 0;
@ -226,12 +296,76 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
QuantIndex.Luminance, QuantIndex.Luminance,
prevDCY, prevDCY,
ref pixelConverter.Y, ref pixelConverter.Y,
ref luminanceQuantTable, ref luminanceQuantTable);
ref unzig);
if (this.IsStreamFlushNeeded)
{
this.FlushToStream();
}
} }
} }
this.FlushInternalBuffer(); this.FlushRemainingBytes();
}
/// <summary>
/// Encodes the image with no subsampling and keeps the pixel data as Rgb24.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="quantTable">Quantization table provided by the callee.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
public void EncodeRgb<TPixel>(Image<TPixel> pixels, ref Block8x8F quantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
FastFloatingPointDCT.AdjustToFDCT(ref quantTable);
this.huffmanTables = HuffmanLut.TheHuffmanLut;
// ReSharper disable once InconsistentNaming
int prevDCR = 0, prevDCG = 0, prevDCB = 0;
ImageFrame<TPixel> frame = pixels.Frames.RootFrame;
Buffer2D<TPixel> pixelBuffer = frame.PixelBuffer;
RowOctet<TPixel> currentRows = default;
var pixelConverter = new RgbForwardConverter<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);
prevDCR = this.WriteBlock(
QuantIndex.Luminance,
prevDCR,
ref pixelConverter.R,
ref quantTable);
prevDCG = this.WriteBlock(
QuantIndex.Luminance,
prevDCG,
ref pixelConverter.G,
ref quantTable);
prevDCB = this.WriteBlock(
QuantIndex.Luminance,
prevDCB,
ref pixelConverter.B,
ref quantTable);
if (this.IsStreamFlushNeeded)
{
this.FlushToStream();
}
}
}
this.FlushRemainingBytes();
} }
/// <summary> /// <summary>
@ -241,47 +375,53 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// </summary> /// </summary>
/// <param name="index">The quantization table index.</param> /// <param name="index">The quantization table index.</param>
/// <param name="prevDC">The previous DC value.</param> /// <param name="prevDC">The previous DC value.</param>
/// <param name="src">Source block</param> /// <param name="block">Source block.</param>
/// <param name="quant">Quantization table</param> /// <param name="quant">Quantization table.</param>
/// <param name="unZig">The 8x8 Unzig block.</param>
/// <returns>The <see cref="int"/>.</returns> /// <returns>The <see cref="int"/>.</returns>
private int WriteBlock( private int WriteBlock(
QuantIndex index, QuantIndex index,
int prevDC, int prevDC,
ref Block8x8F src, ref Block8x8F block,
ref Block8x8F quant, ref Block8x8F quant)
ref ZigZag unZig)
{ {
ref Block8x8F refTemp1 = ref this.temporalBlock1; ref Block8x8 spectralBlock = ref this.tempBlock;
ref Block8x8F refTemp2 = ref this.temporalBlock2;
FastFloatingPointDCT.TransformFDCT(ref src, ref refTemp1, ref refTemp2); // Shifting level from 0..255 to -128..127
block.AddInPlace(-128f);
Block8x8F.Quantize(ref refTemp1, ref refTemp2, ref quant, ref unZig); // Discrete cosine transform
FastFloatingPointDCT.TransformFDCT(ref block);
// Quantization
Block8x8F.Quantize(ref block, ref spectralBlock, ref quant);
// Emit the DC delta. // Emit the DC delta.
int dc = (int)refTemp2[0]; int dc = spectralBlock[0];
this.EmitDirectCurrentTerm(this.huffmanTables[2 * (int)index].Values, dc - prevDC); this.EmitHuffRLE(this.huffmanTables[2 * (int)index].Values, 0, dc - prevDC);
// Emit the AC components. // Emit the AC components.
int[] acHuffTable = this.huffmanTables[(2 * (int)index) + 1].Values; int[] acHuffTable = this.huffmanTables[(2 * (int)index) + 1].Values;
nint lastValuableIndex = spectralBlock.GetLastNonZeroIndex();
int runLength = 0; int runLength = 0;
int lastValuableIndex = GetLastValuableElementIndex(ref refTemp2); ref short blockRef = ref Unsafe.As<Block8x8, short>(ref spectralBlock);
for (int zig = 1; zig <= lastValuableIndex; zig++) for (nint zig = 1; zig <= lastValuableIndex; zig++)
{ {
int ac = (int)refTemp2[zig]; const int zeroRun1 = 1 << 4;
const int zeroRun16 = 16 << 4;
int ac = Unsafe.Add(ref blockRef, zig);
if (ac == 0) if (ac == 0)
{ {
runLength++; runLength += zeroRun1;
} }
else else
{ {
while (runLength > 15) while (runLength >= zeroRun16)
{ {
this.EmitHuff(acHuffTable, 0xf0); this.EmitHuff(acHuffTable, 0xf0);
runLength -= 16; runLength -= zeroRun16;
} }
this.EmitHuffRLE(acHuffTable, runLength, ac); this.EmitHuffRLE(acHuffTable, runLength, ac);
@ -301,100 +441,89 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
} }
/// <summary> /// <summary>
/// Emits the least significant count of bits to the stream write buffer. /// Emits the most significant count of bits to the buffer.
/// The precondition is bits
/// <example>
/// &lt; 1&lt;&lt;nBits &amp;&amp; nBits &lt;= 16
/// </example>
/// .
/// </summary> /// </summary>
/// <param name="bits">The packed bits.</param> /// <remarks>
/// <param name="count">The number of bits</param> /// <para>
/// Supports up to 32 count of bits but, generally speaking, jpeg
/// standard assures that there won't be more than 16 bits per single
/// value.
/// </para>
/// <para>
/// Emitting algorithm uses 3 intermediate buffers for caching before
/// writing to the stream:
/// <list type="number">
/// <item>
/// <term>uint32</term>
/// <description>
/// Bit buffer. Encoded spectral values can occupy up to 16 bits, bits
/// are assembled to whole bytes via this intermediate buffer.
/// </description>
/// </item>
/// <item>
/// <term>uint32[]</term>
/// <description>
/// Assembled bytes from uint32 buffer are saved into this buffer.
/// uint32 buffer values are saved using indices from the last to the first.
/// As bytes are saved to the memory as 4-byte packages endianness matters:
/// Jpeg stream is big-endian, indexing buffer bytes from the last index to the
/// first eliminates all operations to extract separate bytes. This only works for
/// little-endian machines (there are no known examples of big-endian users atm).
/// For big-endians this approach is slower due to the separate byte extraction.
/// </description>
/// </item>
/// <item>
/// <term>byte[]</term>
/// <description>
/// Byte buffer used only during <see cref="FlushToStream(int)"/> method.
/// </description>
/// </item>
/// </list>
/// </para>
/// </remarks>
/// <param name="bits">Bits to emit, must be shifted to the left.</param>
/// <param name="count">Bits count stored in the bits parameter.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private void Emit(int bits, int count) private void Emit(uint bits, int count)
{ {
this.accumulatedBits |= bits >> this.bitCount;
count += this.bitCount; count += this.bitCount;
bits <<= 32 - count;
bits |= this.accumulatedBits;
// Only write if more than 8 bits. if (count >= 32)
if (count >= 8)
{ {
// Track length this.emitBuffer[--this.emitWriteIndex] = this.accumulatedBits;
while (count >= 8) this.accumulatedBits = bits << (32 - this.bitCount);
{
byte b = (byte)(bits >> 24);
this.emitBuffer[this.emitLen++] = b;
// Adding stuff byte
// This is because by JPEG standard scan data can contain JPEG markers (indicated by the 0xFF byte, followed by a non-zero byte)
// Considering this every 0xFF byte must be followed by 0x00 padding byte to signal that this is not a marker
if (b == byte.MaxValue)
{
this.emitBuffer[this.emitLen++] = byte.MinValue;
}
bits <<= 8;
count -= 8;
}
// This can emit 4 times of: count -= 32;
// 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; this.bitCount = count;
} }
/// <summary> /// <summary>
/// Emits the given value with the given Huffman encoder. /// Emits the given value with the given Huffman table.
/// </summary> /// </summary>
/// <param name="table">Compiled Huffman spec values.</param> /// <param name="table">Huffman table.</param>
/// <param name="value">The value to encode.</param> /// <param name="value">Value to encode.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private void EmitHuff(int[] table, int value) private void EmitHuff(int[] table, int value)
{ {
int x = table[value]; int x = table[value];
this.Emit(x >> 8, x & 0xff); this.Emit((uint)x & 0xffff_ff00u, x & 0xff);
}
[MethodImpl(InliningOptions.ShortMethod)]
private void EmitDirectCurrentTerm(int[] table, int value)
{
int a = value;
int b = value;
if (a < 0)
{
a = -value;
b = value - 1;
}
int bt = GetHuffmanEncodingLength((uint)a);
this.EmitHuff(table, bt);
if (bt > 0)
{
this.Emit(b & ((1 << bt) - 1), bt);
}
} }
/// <summary> /// <summary>
/// Emits a run of runLength copies of value encoded with the given Huffman encoder. /// Emits given value via huffman rle encoding.
/// </summary> /// </summary>
/// <param name="table">Compiled Huffman spec values.</param> /// <param name="table">Huffman table.</param>
/// <param name="runLength">The number of copies to encode.</param> /// <param name="runLength">The number of preceding zeroes, preshifted by 4 to the left.</param>
/// <param name="value">The value to encode.</param> /// <param name="value">Value to encode.</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private void EmitHuffRLE(int[] table, int runLength, int value) private void EmitHuffRLE(int[] table, int runLength, int value)
{ {
DebugGuard.IsTrue((runLength & 0xf) == 0, $"{nameof(runLength)} parameter must be shifted to the left by 4 bits");
int a = value; int a = value;
int b = value; int b = value;
if (a < 0) if (a < 0)
@ -403,25 +532,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
b = value - 1; b = value - 1;
} }
int bt = GetHuffmanEncodingLength((uint)a); int valueLen = GetHuffmanEncodingLength((uint)a);
this.EmitHuff(table, (runLength << 4) | bt); // Huffman prefix code
this.Emit(b & ((1 << bt) - 1), bt); int huffPackage = table[runLength | valueLen];
} int prefixLen = huffPackage & 0xff;
uint prefix = (uint)huffPackage & 0xffff_0000u;
/// <summary> // Actual encoded value
/// Writes remaining bytes from internal buffer to the target stream. uint encodedValue = (uint)b << (32 - valueLen);
/// </summary>
/// <remarks>Pads last byte with 1's if necessary</remarks> // Doing two binary shifts to get rid of leading 1's in negative value case
private void FlushInternalBuffer() this.Emit(prefix | (encodedValue >> prefixLen), prefixLen + valueLen);
{
// pad last byte with 1's
int padBitsCount = 8 - (this.bitCount % 8);
if (padBitsCount != 0)
{
this.Emit((1 << padBitsCount) - 1, padBitsCount);
this.target.Write(this.emitBuffer, 0, this.emitLen);
}
} }
/// <summary> /// <summary>
@ -437,19 +559,19 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
DebugGuard.IsTrue(value <= (1 << 16), "Huffman encoder is supposed to encode a value of 16bit size max"); DebugGuard.IsTrue(value <= (1 << 16), "Huffman encoder is supposed to encode a value of 16bit size max");
#if SUPPORTS_BITOPERATIONS #if SUPPORTS_BITOPERATIONS
// This should have been implemented as (BitOperations.Log2(value) + 1) as in non-intrinsic implementation // 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)) // But internal log2 is implemented like this: (31 - (int)Lzcnt.LeadingZeroCount(value))
// BitOperations.Log2 implementation also checks if input value is zero for the convention 0->0 // 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 // 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 // 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 // But most modern CPUs support this instruction so this should not be a problem
return 32 - System.Numerics.BitOperations.LeadingZeroCount(value); return 32 - BitOperations.LeadingZeroCount(value);
#else #else
// Ideally: // Ideally:
// if 0 - return 0 in this case // if 0 - return 0 in this case
// else - return log2(value) + 1 // else - return log2(value) + 1
// //
// Hack based on input value constaint: // Hack based on input value constraint:
// We know that input values are guaranteed to be maximum 16 bit large for huffman encoding // 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) // We can safely shift input value for one bit -> log2(value << 1)
// Because of the 16 bit value constraint it won't overflow // Because of the 16 bit value constraint it won't overflow
@ -460,65 +582,108 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
} }
/// <summary> /// <summary>
/// Returns index of the last non-zero element in given mcu block. /// General method for flushing cached spectral data bytes to
/// If all values of the mcu block are zero, this method might return different results depending on the runtime and hardware support. /// the ouput stream respecting stuff bytes.
/// This is jpeg mcu specific code, mcu[0] stores a dc value which will be encoded outside of the loop.
/// This method is guaranteed to return either -1 or 0 if all elements are zero.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This is an internal operation supposed to be used only in <see cref="HuffmanScanEncoder"/> class for jpeg encoding. /// Bytes cached via <see cref="Emit"/> are stored in 4-bytes blocks
/// which makes this method endianness dependent.
/// </remarks> /// </remarks>
/// <param name="mcu">Mcu block.</param>
/// <returns>Index of the last non-zero element.</returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
internal static int GetLastValuableElementIndex(ref Block8x8F mcu) private void FlushToStream(int endIndex)
{ {
#if SUPPORTS_RUNTIME_INTRINSICS Span<byte> emitBytes = MemoryMarshal.AsBytes(this.emitBuffer.AsSpan());
if (Avx2.IsSupported)
{
const int equalityMask = unchecked((int)0b1111_1111_1111_1111_1111_1111_1111_1111);
Vector256<int> zero8 = Vector256<int>.Zero; int writeIdx = 0;
int startIndex = emitBytes.Length - 1;
ref Vector256<float> mcuStride = ref mcu.V0; // Some platforms may fail to eliminate this if-else branching
// Even if it happens - buffer is flushed in big packs,
for (int i = 7; i >= 0; i--) // branching overhead shouldn't be noticeable
if (BitConverter.IsLittleEndian)
{
// For little endian case bytes are ordered and can be
// safely written to the stream with stuff bytes
// First byte is cached on the most significant index
// so we are going from the end of the array to its beginning:
// ... [ double word #1 ] [ double word #0 ]
// ... [idx3|idx2|idx1|idx0] [idx3|idx2|idx1|idx0]
for (int i = startIndex; i >= endIndex; i--)
{ {
int areEqual = Avx2.MoveMask(Avx2.CompareEqual(Avx.ConvertToVector256Int32(Unsafe.Add(ref mcuStride, i)), zero8).AsByte()); byte value = emitBytes[i];
this.streamWriteBuffer[writeIdx++] = value;
// we do not know for sure if this stride contain all non-zero elements or if it has some trailing zeros // Inserting stuff byte
if (areEqual != equalityMask) if (value == 0xff)
{ {
// last index in the stride, we go from the end to the start of the stride this.streamWriteBuffer[writeIdx++] = 0x00;
int startIndex = i * 8;
int index = startIndex + 7;
ref float elemRef = ref Unsafe.As<Block8x8F, float>(ref mcu);
while (index >= startIndex && (int)Unsafe.Add(ref elemRef, index) == 0)
{
index--;
}
// this implementation will return -1 if all ac components are zero and dc are zero
return index;
} }
} }
return -1;
} }
else else
#endif
{ {
int index = Block8x8F.Size - 1; // For big endian case bytes are ordered in 4-byte packs
ref float elemRef = ref Unsafe.As<Block8x8F, float>(ref mcu); // which are ordered like bytes in the little endian case by in 4-byte packs:
// ... [ double word #1 ] [ double word #0 ]
while (index > 0 && (int)Unsafe.Add(ref elemRef, index) == 0) // ... [idx0|idx1|idx2|idx3] [idx0|idx1|idx2|idx3]
// So we must write each 4-bytes in 'natural order'
for (int i = startIndex; i >= endIndex; i -= 4)
{ {
index--; // This loop is caused by the nature of underlying byte buffer
} // implementation and indeed causes performace by somewhat 5%
// compared to little endian scenario
// Even with this performance drop this cached buffer implementation
// is faster than individually writing bytes using binary shifts and binary and(s)
for (int j = i - 3; j <= i; j++)
{
byte value = emitBytes[j];
this.streamWriteBuffer[writeIdx++] = value;
// this implementation will return 0 if all ac components and dc are zero // Inserting stuff byte
return index; if (value == 0xff)
{
this.streamWriteBuffer[writeIdx++] = 0x00;
}
}
}
} }
this.target.Write(this.streamWriteBuffer, 0, writeIdx);
}
/// <summary>
/// Flushes spectral data bytes after encoding all channel blocks
/// in a single jpeg macroblock using <see cref="WriteBlock"/>.
/// </summary>
/// <remarks>
/// This must be called only if <see cref="IsStreamFlushNeeded"/> is true
/// only during the macroblocks encoding routine.
/// </remarks>
private void FlushToStream()
{
this.FlushToStream(this.emitWriteIndex * 4);
this.emitWriteIndex = this.emitBuffer.Length;
}
/// <summary>
/// Flushes final cached bits to the stream padding 1's to
/// complement full bytes.
/// </summary>
/// <remarks>
/// This must be called only once at the end of the encoding routine.
/// <see cref="IsStreamFlushNeeded"/> check is not needed.
/// </remarks>
[MethodImpl(InliningOptions.ShortMethod)]
private void FlushRemainingBytes()
{
// Padding all 4 bytes with 1's while not corrupting initial bits stored in accumulatedBits
// And writing only valuable count of bytes count we want to write to the output stream
int valuableBytesCount = (int)Numerics.DivideCeil((uint)this.bitCount, 8);
uint packedBytes = this.accumulatedBits | (uint.MaxValue >> this.bitCount);
this.emitBuffer[--this.emitWriteIndex] = packedBytes;
// Flush cached bytes to the output stream with padding bits
this.FlushToStream((this.emitWriteIndex * 4) - 4 + valuableBytesCount);
} }
} }
} }

8
src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs

@ -1,20 +1,20 @@
// 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
{ {
/// <summary> /// <summary>
/// Enumerates the quantization tables /// Enumerates the quantization tables.
/// </summary> /// </summary>
internal enum QuantIndex internal enum QuantIndex
{ {
/// <summary> /// <summary>
/// The luminance quantization table index /// The luminance quantization table index.
/// </summary> /// </summary>
Luminance = 0, Luminance = 0,
/// <summary> /// <summary>
/// The chrominance quantization table index /// The chrominance quantization table index.
/// </summary> /// </summary>
Chrominance = 1, Chrominance = 1,
} }

114
src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs

@ -0,0 +1,114 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// On-stack worker struct to convert TPixel -> Rgb24 of 8x8 pixel blocks.
/// </summary>
/// <typeparam name="TPixel">The pixel type to work on.</typeparam>
internal ref struct RgbForwardConverter<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 Red component.
/// </summary>
public Block8x8F R;
/// <summary>
/// The Green component.
/// </summary>
public Block8x8F G;
/// <summary>
/// The Blue component.
/// </summary>
public Block8x8F B;
/// <summary>
/// Temporal 64-byte span to hold unconverted TPixel data.
/// </summary>
private readonly Span<TPixel> pixelSpan;
/// <summary>
/// Temporal 64-byte span to hold converted Rgb24 data.
/// </summary>
private readonly Span<Rgb24> rgbSpan;
/// <summary>
/// Sampled pixel buffer size.
/// </summary>
private readonly Size samplingAreaSize;
/// <summary>
/// <see cref="Configuration"/> for internal operations.
/// </summary>
private readonly Configuration config;
public RgbForwardConverter(ImageFrame<TPixel> frame)
{
this.R = default;
this.G = default;
this.B = 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();
}
/// <summary>
/// Converts a 8x8 image area inside 'pixels' at position (x, y) to Rgb24.
/// </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 redBlock = ref this.R;
ref Block8x8F greenBlock = ref this.G;
ref Block8x8F blueBlock = ref this.B;
CopyToBlock(this.rgbSpan, ref redBlock, ref greenBlock, ref blueBlock);
}
private static void CopyToBlock(Span<Rgb24> rgbSpan, ref Block8x8F redBlock, ref Block8x8F greenBlock, ref Block8x8F blueBlock)
{
ref Rgb24 rgbStart = ref MemoryMarshal.GetReference(rgbSpan);
for (int i = 0; i < Block8x8F.Size; i++)
{
Rgb24 c = Unsafe.Add(ref rgbStart, (nint)(uint)i);
redBlock[i] = c.R;
greenBlock[i] = c.G;
blueBlock[i] = c.B;
}
}
}
}

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

@ -58,22 +58,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// <summary> /// <summary>
/// Temporal 16x8 block to hold TPixel data /// Temporal 16x8 block to hold TPixel data
/// </summary> /// </summary>
private Span<TPixel> pixelSpan; private readonly Span<TPixel> pixelSpan;
/// <summary> /// <summary>
/// Temporal RGB block /// Temporal RGB block
/// </summary> /// </summary>
private Span<Rgb24> rgbSpan; private readonly Span<Rgb24> rgbSpan;
/// <summary> /// <summary>
/// Sampled pixel buffer size /// Sampled pixel buffer size
/// </summary> /// </summary>
private Size samplingAreaSize; private readonly Size samplingAreaSize;
/// <summary> /// <summary>
/// <see cref="Configuration"/> for internal operations /// <see cref="Configuration"/> for internal operations
/// </summary> /// </summary>
private Configuration config; private readonly Configuration config;
public YCbCrForwardConverter420(ImageFrame<TPixel> frame) public YCbCrForwardConverter420(ImageFrame<TPixel> frame)
{ {

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

@ -53,22 +53,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// <summary> /// <summary>
/// Temporal 64-byte span to hold unconverted TPixel data /// Temporal 64-byte span to hold unconverted TPixel data
/// </summary> /// </summary>
private Span<TPixel> pixelSpan; private readonly Span<TPixel> pixelSpan;
/// <summary> /// <summary>
/// Temporal 64-byte span to hold converted Rgb24 data /// Temporal 64-byte span to hold converted Rgb24 data
/// </summary> /// </summary>
private Span<Rgb24> rgbSpan; private readonly Span<Rgb24> rgbSpan;
/// <summary> /// <summary>
/// Sampled pixel buffer size /// Sampled pixel buffer size
/// </summary> /// </summary>
private Size samplingAreaSize; private readonly Size samplingAreaSize;
/// <summary> /// <summary>
/// <see cref="Configuration"/> for internal operations /// <see cref="Configuration"/> for internal operations
/// </summary> /// </summary>
private Configuration config; private readonly Configuration config;
public YCbCrForwardConverter444(ImageFrame<TPixel> frame) public YCbCrForwardConverter444(ImageFrame<TPixel> frame)
{ {

161
src/ImageSharp/Formats/Jpeg/Components/FastFloatingPointDCT.Intrinsic.cs

@ -0,0 +1,161 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
#if SUPPORTS_RUNTIME_INTRINSICS
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal static partial class FastFloatingPointDCT
{
#pragma warning disable SA1310, SA1311, IDE1006 // naming rules violation warnings
private static readonly Vector256<float> mm256_F_0_7071 = Vector256.Create(0.707106781f);
private static readonly Vector256<float> mm256_F_0_3826 = Vector256.Create(0.382683433f);
private static readonly Vector256<float> mm256_F_0_5411 = Vector256.Create(0.541196100f);
private static readonly Vector256<float> mm256_F_1_3065 = Vector256.Create(1.306562965f);
private static readonly Vector256<float> mm256_F_1_1758 = Vector256.Create(1.175876f);
private static readonly Vector256<float> mm256_F_n1_9615 = Vector256.Create(-1.961570560f);
private static readonly Vector256<float> mm256_F_n0_3901 = Vector256.Create(-0.390180644f);
private static readonly Vector256<float> mm256_F_n0_8999 = Vector256.Create(-0.899976223f);
private static readonly Vector256<float> mm256_F_n2_5629 = Vector256.Create(-2.562915447f);
private static readonly Vector256<float> mm256_F_0_2986 = Vector256.Create(0.298631336f);
private static readonly Vector256<float> mm256_F_2_0531 = Vector256.Create(2.053119869f);
private static readonly Vector256<float> mm256_F_3_0727 = Vector256.Create(3.072711026f);
private static readonly Vector256<float> mm256_F_1_5013 = Vector256.Create(1.501321110f);
private static readonly Vector256<float> mm256_F_n1_8477 = Vector256.Create(-1.847759065f);
private static readonly Vector256<float> mm256_F_0_7653 = Vector256.Create(0.765366865f);
#pragma warning restore SA1310, SA1311, IDE1006
/// <summary>
/// Apply floating point FDCT inplace using simd operations.
/// </summary>
/// <param name="block">Input matrix.</param>
private static void ForwardTransform_Avx(ref Block8x8F block)
{
DebugGuard.IsTrue(Avx.IsSupported, "Avx support is required to execute this operation.");
// First pass - process rows
block.TransposeInplace();
FDCT8x8_Avx(ref block);
// Second pass - process columns
block.TransposeInplace();
FDCT8x8_Avx(ref block);
}
/// <summary>
/// Apply 1D floating point FDCT inplace using AVX operations on 8x8 matrix.
/// </summary>
/// <remarks>
/// Requires Avx support.
/// </remarks>
/// <param name="block">Input matrix.</param>
public static void FDCT8x8_Avx(ref Block8x8F block)
{
DebugGuard.IsTrue(Avx.IsSupported, "Avx support is required to execute this operation.");
Vector256<float> tmp0 = Avx.Add(block.V0, block.V7);
Vector256<float> tmp7 = Avx.Subtract(block.V0, block.V7);
Vector256<float> tmp1 = Avx.Add(block.V1, block.V6);
Vector256<float> tmp6 = Avx.Subtract(block.V1, block.V6);
Vector256<float> tmp2 = Avx.Add(block.V2, block.V5);
Vector256<float> tmp5 = Avx.Subtract(block.V2, block.V5);
Vector256<float> tmp3 = Avx.Add(block.V3, block.V4);
Vector256<float> tmp4 = Avx.Subtract(block.V3, block.V4);
// Even part
Vector256<float> tmp10 = Avx.Add(tmp0, tmp3);
Vector256<float> tmp13 = Avx.Subtract(tmp0, tmp3);
Vector256<float> tmp11 = Avx.Add(tmp1, tmp2);
Vector256<float> tmp12 = Avx.Subtract(tmp1, tmp2);
block.V0 = Avx.Add(tmp10, tmp11);
block.V4 = Avx.Subtract(tmp10, tmp11);
Vector256<float> z1 = Avx.Multiply(Avx.Add(tmp12, tmp13), mm256_F_0_7071);
block.V2 = Avx.Add(tmp13, z1);
block.V6 = Avx.Subtract(tmp13, z1);
// Odd part
tmp10 = Avx.Add(tmp4, tmp5);
tmp11 = Avx.Add(tmp5, tmp6);
tmp12 = Avx.Add(tmp6, tmp7);
Vector256<float> z5 = Avx.Multiply(Avx.Subtract(tmp10, tmp12), mm256_F_0_3826);
Vector256<float> z2 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, mm256_F_0_5411, tmp10);
Vector256<float> z4 = SimdUtils.HwIntrinsics.MultiplyAdd(z5, mm256_F_1_3065, tmp12);
Vector256<float> z3 = Avx.Multiply(tmp11, mm256_F_0_7071);
Vector256<float> z11 = Avx.Add(tmp7, z3);
Vector256<float> z13 = Avx.Subtract(tmp7, z3);
block.V5 = Avx.Add(z13, z2);
block.V3 = Avx.Subtract(z13, z2);
block.V1 = Avx.Add(z11, z4);
block.V7 = Avx.Subtract(z11, z4);
}
/// <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)
{
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), mm256_F_1_1758);
mz2 = SimdUtils.HwIntrinsics.MultiplyAdd(mz4, mz2, mm256_F_n1_9615);
mz3 = SimdUtils.HwIntrinsics.MultiplyAdd(mz4, mz3, mm256_F_n0_3901);
mz0 = Avx.Multiply(mz0, mm256_F_n0_8999);
mz1 = Avx.Multiply(mz1, mm256_F_n2_5629);
Vector256<float> mb3 = Avx.Add(SimdUtils.HwIntrinsics.MultiplyAdd(mz0, my7, mm256_F_0_2986), mz2);
Vector256<float> mb2 = Avx.Add(SimdUtils.HwIntrinsics.MultiplyAdd(mz1, my5, mm256_F_2_0531), mz3);
Vector256<float> mb1 = Avx.Add(SimdUtils.HwIntrinsics.MultiplyAdd(mz1, my3, mm256_F_3_0727), mz2);
Vector256<float> mb0 = Avx.Add(SimdUtils.HwIntrinsics.MultiplyAdd(mz0, my1, mm256_F_1_5013), mz3);
Vector256<float> my2 = s.V2;
Vector256<float> my6 = s.V6;
mz4 = Avx.Multiply(Avx.Add(my2, my6), mm256_F_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, mm256_F_n1_8477);
mz3 = SimdUtils.HwIntrinsics.MultiplyAdd(mz4, my2, mm256_F_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

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

@ -1,11 +1,9 @@
// 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 #if SUPPORTS_RUNTIME_INTRINSICS
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86; using System.Runtime.Intrinsics.X86;
#endif #endif
@ -19,283 +17,304 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
#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;
private const float C_1_961571 = -1.961570560f; private const float C_1_961571 = -1.961570560f;
private const float C_0_390181 = -0.390180644f; private const float C_0_390181 = -0.390180644f;
private const float C_0_899976 = -0.899976223f; private const float C_0_899976 = -0.899976223f;
private const float C_2_562915 = -2.562915447f; private const float C_2_562915 = -2.562915447f;
private const float C_0_298631 = 0.298631336f; private const float C_0_298631 = 0.298631336f;
private const float C_2_053120 = 2.053119869f; private const float C_2_053120 = 2.053119869f;
private const float C_3_072711 = 3.072711026f; private const float C_3_072711 = 3.072711026f;
private const float C_1_501321 = 1.501321110f; private const float C_1_501321 = 1.501321110f;
private const float C_0_541196 = 0.541196100f; private const float C_0_541196 = 0.541196100f;
private const float C_1_847759 = -1.847759065f; private const float C_1_847759 = -1.847759065f;
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 #pragma warning disable SA1311, IDE1006 // naming rules violation warnings
private static readonly Vector256<float> C_V_0_5411 = Vector256.Create(0.541196f); private static readonly Vector4 mm128_F_0_7071 = new Vector4(0.707106781f);
private static readonly Vector256<float> C_V_1_3065 = Vector256.Create(1.306563f); private static readonly Vector4 mm128_F_0_3826 = new Vector4(0.382683433f);
private static readonly Vector256<float> C_V_1_1758 = Vector256.Create(1.175876f); private static readonly Vector4 mm128_F_0_5411 = new Vector4(0.541196100f);
private static readonly Vector256<float> C_V_0_7856 = Vector256.Create(0.785695f); private static readonly Vector4 mm128_F_1_3065 = new Vector4(1.306562965f);
private static readonly Vector256<float> C_V_1_3870 = Vector256.Create(1.387040f); #pragma warning restore SA1311, IDE1006
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);
/// <summary> /// <summary>
/// Original: /// Gets reciprocal coefficients for jpeg quantization tables calculation.
/// <see>
/// <cref>https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L15</cref>
/// </see>
/// </summary> /// </summary>
/// <param name="s">Source</param> /// <remarks>
/// <param name="d">Destination</param> /// <para>
public static void FDCT8x4_LeftPart(ref Block8x8F s, ref Block8x8F d) /// Current FDCT implementation expects its results to be multiplied by
/// a reciprocal quantization table. To get 8x8 reciprocal block values in this
/// table must be divided by quantization table values scaled with quality settings.
/// </para>
/// <para>
/// These values were calculates with this formula:
/// <code>
/// value[row * 8 + col] = scalefactor[row] * scalefactor[col] * 8;
/// </code>
/// Where:
/// <code>
/// scalefactor[0] = 1
/// </code>
/// <code>
/// scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7
/// </code>
/// Values are also scaled by 8 so DCT code won't do extra division/multiplication.
/// </para>
/// </remarks>
internal static readonly float[] DctReciprocalAdjustmentCoefficients = new float[]
{ {
Vector4 c0 = s.V0L; 0.125f, 0.09011998f, 0.09567086f, 0.10630376f, 0.125f, 0.15909483f, 0.23096988f, 0.45306373f,
Vector4 c1 = s.V7L; 0.09011998f, 0.064972885f, 0.068974845f, 0.07664074f, 0.09011998f, 0.11470097f, 0.16652f, 0.32664075f,
Vector4 t0 = c0 + c1; 0.09567086f, 0.068974845f, 0.07322331f, 0.081361376f, 0.09567086f, 0.121765904f, 0.17677669f, 0.34675997f,
Vector4 t7 = c0 - c1; 0.10630376f, 0.07664074f, 0.081361376f, 0.09040392f, 0.10630376f, 0.13529903f, 0.19642374f, 0.38529903f,
0.125f, 0.09011998f, 0.09567086f, 0.10630376f, 0.125f, 0.15909483f, 0.23096988f, 0.45306373f,
c1 = s.V6L; 0.15909483f, 0.11470097f, 0.121765904f, 0.13529903f, 0.15909483f, 0.2024893f, 0.2939689f, 0.5766407f,
c0 = s.V1L; 0.23096988f, 0.16652f, 0.17677669f, 0.19642374f, 0.23096988f, 0.2939689f, 0.4267767f, 0.8371526f,
Vector4 t1 = c0 + c1; 0.45306373f, 0.32664075f, 0.34675997f, 0.38529903f, 0.45306373f, 0.5766407f, 0.8371526f, 1.642134f,
Vector4 t6 = c0 - c1; };
c1 = s.V5L;
c0 = s.V2L;
Vector4 t2 = c0 + c1;
Vector4 t5 = c0 - c1;
c0 = s.V3L;
c1 = s.V4L;
Vector4 t3 = c0 + c1;
Vector4 t4 = c0 - c1;
c0 = t0 + t3;
Vector4 c3 = t0 - t3;
c1 = t1 + t2;
Vector4 c2 = t1 - t2;
d.V0L = c0 + c1;
d.V4L = c0 - c1;
float w0 = 0.541196f;
float w1 = 1.306563f;
d.V2L = (w0 * c2) + (w1 * c3);
d.V6L = (w0 * c3) - (w1 * c2);
w0 = 1.175876f;
w1 = 0.785695f;
c3 = (w0 * t4) + (w1 * t7);
c0 = (w0 * t7) - (w1 * t4);
w0 = 1.387040f;
w1 = 0.275899f;
c2 = (w0 * t5) + (w1 * t6);
c1 = (w0 * t6) - (w1 * t5);
d.V3L = c0 - c2;
d.V5L = c3 - c1;
float invsqrt2 = 0.707107f;
c0 = (c0 + c2) * invsqrt2;
c3 = (c3 + c1) * invsqrt2;
d.V1L = c0 + c3;
d.V7L = c0 - c3;
}
/// <summary> /// <summary>
/// Original: /// Adjusts given quantization table to be complient with FDCT implementation.
/// <see>
/// <cref>https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L15</cref>
/// </see>
/// </summary> /// </summary>
/// <param name="s">Source</param> /// <remarks>
/// <param name="d">Destination</param> /// See <see cref="DctReciprocalAdjustmentCoefficients"/> docs for explanation.
public static void FDCT8x4_RightPart(ref Block8x8F s, ref Block8x8F d) /// </remarks>
/// <param name="quantizationtable">Quantization table to adjust.</param>
public static void AdjustToFDCT(ref Block8x8F quantizationtable)
{ {
Vector4 c0 = s.V0R; for (int i = 0; i < Block8x8F.Size; i++)
Vector4 c1 = s.V7R; {
Vector4 t0 = c0 + c1; quantizationtable[i] = DctReciprocalAdjustmentCoefficients[i] / quantizationtable[i];
Vector4 t7 = c0 - c1; }
c1 = s.V6R;
c0 = s.V1R;
Vector4 t1 = c0 + c1;
Vector4 t6 = c0 - c1;
c1 = s.V5R;
c0 = s.V2R;
Vector4 t2 = c0 + c1;
Vector4 t5 = c0 - c1;
c0 = s.V3R;
c1 = s.V4R;
Vector4 t3 = c0 + c1;
Vector4 t4 = c0 - c1;
c0 = t0 + t3;
Vector4 c3 = t0 - t3;
c1 = t1 + t2;
Vector4 c2 = t1 - t2;
d.V0R = c0 + c1;
d.V4R = c0 - c1;
float w0 = 0.541196f;
float w1 = 1.306563f;
d.V2R = (w0 * c2) + (w1 * c3);
d.V6R = (w0 * c3) - (w1 * c2);
w0 = 1.175876f;
w1 = 0.785695f;
c3 = (w0 * t4) + (w1 * t7);
c0 = (w0 * t7) - (w1 * t4);
w0 = 1.387040f;
w1 = 0.275899f;
c2 = (w0 * t5) + (w1 * t6);
c1 = (w0 * t6) - (w1 * t5);
d.V3R = c0 - c2;
d.V5R = c3 - c1;
c0 = (c0 + c2) * InvSqrt2;
c3 = (c3 + c1) * InvSqrt2;
d.V1R = c0 + c3;
d.V7R = c0 - c3;
} }
/// <summary> /// <summary>
/// Combined operation of <see cref="FDCT8x4_LeftPart(ref Block8x8F, ref Block8x8F)"/> and <see cref="FDCT8x4_RightPart(ref Block8x8F, ref Block8x8F)"/> /// Apply 2D floating point FDCT inplace.
/// using AVX commands.
/// </summary> /// </summary>
/// <param name="s">Source</param> /// <param name="block">Input matrix.</param>
/// <param name="d">Destination</param> public static void TransformFDCT(ref Block8x8F block)
public static void FDCT8x8_Avx(ref Block8x8F s, ref Block8x8F d)
{ {
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
Debug.Assert(Avx.IsSupported, "AVX is required to execute this method"); if (Avx.IsSupported)
{
Vector256<float> t0 = Avx.Add(s.V0, s.V7); ForwardTransform_Avx(ref block);
Vector256<float> t7 = Avx.Subtract(s.V0, s.V7); }
Vector256<float> t1 = Avx.Add(s.V1, s.V6); else
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 #endif
if (Vector.IsHardwareAccelerated)
{
ForwardTransform_Vector4(ref block);
}
else
{
ForwardTransform_Scalar(ref block);
}
} }
/// <summary> /// <summary>
/// Performs 8x8 matrix Forward Discrete Cosine Transform /// Apply 2D floating point FDCT inplace using scalar operations.
/// </summary> /// </summary>
/// <param name="s">Source</param> /// <remarks>
/// <param name="d">Destination</param> /// Ported from libjpeg-turbo https://github.com/libjpeg-turbo/libjpeg-turbo/blob/main/jfdctflt.c.
public static void FDCT8x8(ref Block8x8F s, ref Block8x8F d) /// </remarks>
/// <param name="block">Input matrix.</param>
private static void ForwardTransform_Scalar(ref Block8x8F block)
{ {
#if SUPPORTS_RUNTIME_INTRINSICS const int dctSize = 8;
if (Avx.IsSupported)
float tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7;
float tmp10, tmp11, tmp12, tmp13;
float z1, z2, z3, z4, z5, z11, z13;
// First pass - process rows
ref float dataRef = ref Unsafe.As<Block8x8F, float>(ref block);
for (int ctr = 7; ctr >= 0; ctr--)
{ {
FDCT8x8_Avx(ref s, ref d); tmp0 = Unsafe.Add(ref dataRef, 0) + Unsafe.Add(ref dataRef, 7);
tmp7 = Unsafe.Add(ref dataRef, 0) - Unsafe.Add(ref dataRef, 7);
tmp1 = Unsafe.Add(ref dataRef, 1) + Unsafe.Add(ref dataRef, 6);
tmp6 = Unsafe.Add(ref dataRef, 1) - Unsafe.Add(ref dataRef, 6);
tmp2 = Unsafe.Add(ref dataRef, 2) + Unsafe.Add(ref dataRef, 5);
tmp5 = Unsafe.Add(ref dataRef, 2) - Unsafe.Add(ref dataRef, 5);
tmp3 = Unsafe.Add(ref dataRef, 3) + Unsafe.Add(ref dataRef, 4);
tmp4 = Unsafe.Add(ref dataRef, 3) - Unsafe.Add(ref dataRef, 4);
// Even part
tmp10 = tmp0 + tmp3;
tmp13 = tmp0 - tmp3;
tmp11 = tmp1 + tmp2;
tmp12 = tmp1 - tmp2;
Unsafe.Add(ref dataRef, 0) = tmp10 + tmp11;
Unsafe.Add(ref dataRef, 4) = tmp10 - tmp11;
z1 = (tmp12 + tmp13) * 0.707106781f;
Unsafe.Add(ref dataRef, 2) = tmp13 + z1;
Unsafe.Add(ref dataRef, 6) = tmp13 - z1;
// Odd part
tmp10 = tmp4 + tmp5;
tmp11 = tmp5 + tmp6;
tmp12 = tmp6 + tmp7;
z5 = (tmp10 - tmp12) * 0.382683433f;
z2 = (0.541196100f * tmp10) + z5;
z4 = (1.306562965f * tmp12) + z5;
z3 = tmp11 * 0.707106781f;
z11 = tmp7 + z3;
z13 = tmp7 - z3;
Unsafe.Add(ref dataRef, 5) = z13 + z2;
Unsafe.Add(ref dataRef, 3) = z13 - z2;
Unsafe.Add(ref dataRef, 1) = z11 + z4;
Unsafe.Add(ref dataRef, 7) = z11 - z4;
dataRef = ref Unsafe.Add(ref dataRef, dctSize);
} }
else
#endif // Second pass - process columns
dataRef = ref Unsafe.As<Block8x8F, float>(ref block);
for (int ctr = 7; ctr >= 0; ctr--)
{ {
FDCT8x4_LeftPart(ref s, ref d); tmp0 = Unsafe.Add(ref dataRef, dctSize * 0) + Unsafe.Add(ref dataRef, dctSize * 7);
FDCT8x4_RightPart(ref s, ref d); tmp7 = Unsafe.Add(ref dataRef, dctSize * 0) - Unsafe.Add(ref dataRef, dctSize * 7);
tmp1 = Unsafe.Add(ref dataRef, dctSize * 1) + Unsafe.Add(ref dataRef, dctSize * 6);
tmp6 = Unsafe.Add(ref dataRef, dctSize * 1) - Unsafe.Add(ref dataRef, dctSize * 6);
tmp2 = Unsafe.Add(ref dataRef, dctSize * 2) + Unsafe.Add(ref dataRef, dctSize * 5);
tmp5 = Unsafe.Add(ref dataRef, dctSize * 2) - Unsafe.Add(ref dataRef, dctSize * 5);
tmp3 = Unsafe.Add(ref dataRef, dctSize * 3) + Unsafe.Add(ref dataRef, dctSize * 4);
tmp4 = Unsafe.Add(ref dataRef, dctSize * 3) - Unsafe.Add(ref dataRef, dctSize * 4);
// Even part
tmp10 = tmp0 + tmp3;
tmp13 = tmp0 - tmp3;
tmp11 = tmp1 + tmp2;
tmp12 = tmp1 - tmp2;
Unsafe.Add(ref dataRef, dctSize * 0) = tmp10 + tmp11;
Unsafe.Add(ref dataRef, dctSize * 4) = tmp10 - tmp11;
z1 = (tmp12 + tmp13) * 0.707106781f;
Unsafe.Add(ref dataRef, dctSize * 2) = tmp13 + z1;
Unsafe.Add(ref dataRef, dctSize * 6) = tmp13 - z1;
// Odd part
tmp10 = tmp4 + tmp5;
tmp11 = tmp5 + tmp6;
tmp12 = tmp6 + tmp7;
z5 = (tmp10 - tmp12) * 0.382683433f;
z2 = (0.541196100f * tmp10) + z5;
z4 = (1.306562965f * tmp12) + z5;
z3 = tmp11 * 0.707106781f;
z11 = tmp7 + z3;
z13 = tmp7 - z3;
Unsafe.Add(ref dataRef, dctSize * 5) = z13 + z2;
Unsafe.Add(ref dataRef, dctSize * 3) = z13 - z2;
Unsafe.Add(ref dataRef, dctSize * 1) = z11 + z4;
Unsafe.Add(ref dataRef, dctSize * 7) = z11 - z4;
dataRef = ref Unsafe.Add(ref dataRef, 1);
} }
} }
/// <summary> /// <summary>
/// Apply floating point FDCT from src into dest /// Apply floating point FDCT inplace using <see cref="Vector4"/> API.
/// </summary> /// </summary>
/// <param name="src">Source</param> /// <remarks>
/// <param name="dest">Destination</param> /// This implementation must be called only if hardware supports 4
/// <param name="temp">Temporary block provided by the caller for optimization</param> /// floating point numbers vector. Otherwise explicit scalar
/// <param name="offsetSourceByNeg128">If true, a constant -128.0 offset is applied for all values before FDCT </param> /// implementation <see cref="ForwardTransform_Scalar"/> is faster
public static void TransformFDCT( /// because it does not rely on matrix transposition.
ref Block8x8F src, /// </remarks>
ref Block8x8F dest, /// <param name="block">Input matrix.</param>
ref Block8x8F temp, private static void ForwardTransform_Vector4(ref Block8x8F block)
bool offsetSourceByNeg128 = true)
{ {
src.TransposeInto(ref temp); DebugGuard.IsTrue(Vector.IsHardwareAccelerated, "Scalar implementation should be called for non-accelerated hardware.");
if (offsetSourceByNeg128)
{
temp.AddInPlace(-128F);
}
FDCT8x8(ref temp, ref dest); // First pass - process rows
block.TransposeInplace();
FDCT8x4_Vector4(ref block.V0L);
FDCT8x4_Vector4(ref block.V0R);
dest.TransposeInto(ref temp); // Second pass - process columns
block.TransposeInplace();
FDCT8x4_Vector4(ref block.V0L);
FDCT8x4_Vector4(ref block.V0R);
}
FDCT8x8(ref temp, ref dest); /// <summary>
/// Apply 1D floating point FDCT inplace on 8x4 part of 8x8 matrix.
/// </summary>
/// <remarks>
/// Implemented using Vector4 API operations for either scalar or sse hardware implementation.
/// Must be called on both 8x4 matrix parts for the full FDCT transform.
/// </remarks>
/// <param name="blockRef">Input reference to the first </param>
private static void FDCT8x4_Vector4(ref Vector4 blockRef)
{
Vector4 tmp0 = Unsafe.Add(ref blockRef, 0) + Unsafe.Add(ref blockRef, 14);
Vector4 tmp7 = Unsafe.Add(ref blockRef, 0) - Unsafe.Add(ref blockRef, 14);
Vector4 tmp1 = Unsafe.Add(ref blockRef, 2) + Unsafe.Add(ref blockRef, 12);
Vector4 tmp6 = Unsafe.Add(ref blockRef, 2) - Unsafe.Add(ref blockRef, 12);
Vector4 tmp2 = Unsafe.Add(ref blockRef, 4) + Unsafe.Add(ref blockRef, 10);
Vector4 tmp5 = Unsafe.Add(ref blockRef, 4) - Unsafe.Add(ref blockRef, 10);
Vector4 tmp3 = Unsafe.Add(ref blockRef, 6) + Unsafe.Add(ref blockRef, 8);
Vector4 tmp4 = Unsafe.Add(ref blockRef, 6) - Unsafe.Add(ref blockRef, 8);
// Even part
Vector4 tmp10 = tmp0 + tmp3;
Vector4 tmp13 = tmp0 - tmp3;
Vector4 tmp11 = tmp1 + tmp2;
Vector4 tmp12 = tmp1 - tmp2;
Unsafe.Add(ref blockRef, 0) = tmp10 + tmp11;
Unsafe.Add(ref blockRef, 8) = tmp10 - tmp11;
Vector4 z1 = (tmp12 + tmp13) * mm128_F_0_7071;
Unsafe.Add(ref blockRef, 4) = tmp13 + z1;
Unsafe.Add(ref blockRef, 12) = tmp13 - z1;
// Odd part
tmp10 = tmp4 + tmp5;
tmp11 = tmp5 + tmp6;
tmp12 = tmp6 + tmp7;
Vector4 z5 = (tmp10 - tmp12) * mm128_F_0_3826;
Vector4 z2 = (mm128_F_0_5411 * tmp10) + z5;
Vector4 z4 = (mm128_F_1_3065 * tmp12) + z5;
Vector4 z3 = tmp11 * mm128_F_0_7071;
Vector4 z11 = tmp7 + z3;
Vector4 z13 = tmp7 - z3;
Unsafe.Add(ref blockRef, 10) = z13 + z2;
Unsafe.Add(ref blockRef, 6) = z13 - z2;
Unsafe.Add(ref blockRef, 2) = z11 + z4;
Unsafe.Add(ref blockRef, 14) = z11 - z4;
}
dest.MultiplyInPlace(C_0_125); /// <summary>
/// Apply floating point IDCT inplace.
/// Ported from https://github.com/norishigefukushima/dct_simd/blob/master/dct/dct8x8_simd.cpp#L239.
/// </summary>
/// <param name="block">Input matrix.</param>
/// <param name="temp">Matrix to store temporal results.</param>
public static void TransformIDCT(ref Block8x8F block, ref Block8x8F temp)
{
block.TransposeInplace();
IDCT8x8(ref block, ref temp);
temp.TransposeInplace();
IDCT8x8(ref temp, ref block);
// TODO: This can be fused into quantization table step
block.MultiplyInPlace(C_0_125);
} }
/// <summary> /// <summary>
@ -303,7 +322,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// </summary> /// </summary>
/// <param name="s">Source</param> /// <param name="s">Source</param>
/// <param name="d">Destination</param> /// <param name="d">Destination</param>
public static void IDCT8x8(ref Block8x8F s, ref Block8x8F d) private static void IDCT8x8(ref Block8x8F s, ref Block8x8F d)
{ {
#if SUPPORTS_RUNTIME_INTRINSICS #if SUPPORTS_RUNTIME_INTRINSICS
if (Avx.IsSupported) if (Avx.IsSupported)
@ -432,83 +451,5 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
d.V3R = my3 + mb3; d.V3R = my3 + mb3;
d.V4R = 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);
}
} }
} }

199
src/ImageSharp/Formats/Jpeg/Components/Quantization.cs

@ -0,0 +1,199 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
/// <summary>
/// Provides methods and properties related to jpeg quantization.
/// </summary>
internal static class Quantization
{
/// <summary>
/// Upper bound (inclusive) for jpeg quality setting.
/// </summary>
public const int MaxQualityFactor = 100;
/// <summary>
/// Lower bound (inclusive) for jpeg quality setting.
/// </summary>
public const int MinQualityFactor = 1;
/// <summary>
/// Default JPEG quality for both luminance and chominance tables.
/// </summary>
public const int DefaultQualityFactor = 75;
/// <summary>
/// Represents lowest quality setting which can be estimated with enough confidence.
/// Any quality below it results in a highly compressed jpeg image
/// which shouldn't use standard itu quantization tables for re-encoding.
/// </summary>
public const int QualityEstimationConfidenceLowerThreshold = 25;
/// <summary>
/// Represents highest quality setting which can be estimated with enough confidence.
/// </summary>
public const int QualityEstimationConfidenceUpperThreshold = 98;
/// <summary>
/// Gets unscaled luminance quantization table.
/// </summary>
/// <remarks>
/// The values are derived from ITU section K.1.
/// </remarks>
// 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
public static ReadOnlySpan<byte> LuminanceTable => new byte[]
{
16, 11, 10, 16, 24, 40, 51, 61,
12, 12, 14, 19, 26, 58, 60, 55,
14, 13, 16, 24, 40, 57, 69, 56,
14, 17, 22, 29, 51, 87, 80, 62,
18, 22, 37, 56, 68, 109, 103, 77,
24, 35, 55, 64, 81, 104, 113, 92,
49, 64, 78, 87, 103, 121, 120, 101,
72, 92, 95, 98, 112, 100, 103, 99,
};
/// <summary>
/// Gets unscaled chrominance quantization table.
/// </summary>
/// <remarks>
/// The values are derived from ITU section K.1.
/// </remarks>
// 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
public static ReadOnlySpan<byte> ChrominanceTable => new byte[]
{
17, 18, 24, 47, 99, 99, 99, 99,
18, 21, 26, 66, 99, 99, 99, 99,
24, 26, 56, 99, 99, 99, 99, 99,
47, 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,
};
/// Ported from JPEGsnoop:
/// https://github.com/ImpulseAdventure/JPEGsnoop/blob/9732ee0961f100eb69bbff4a0c47438d5997abee/source/JfifDecode.cpp#L4570-L4694
/// <summary>
/// Estimates jpeg quality based on standard quantization table.
/// </summary>
/// <remarks>
/// Technically, this can be used with any given table but internal decoder code uses ITU spec tables:
/// <see cref="LuminanceTable"/> and <see cref="ChrominanceTable"/>.
/// </remarks>
/// <param name="table">Input quantization table.</param>
/// <param name="target">Natural order quantization table to estimate against.</param>
/// <returns>Estimated quality.</returns>
public static int EstimateQuality(ref Block8x8F table, ReadOnlySpan<byte> target)
{
// This method can be SIMD'ified if standard table is injected as Block8x8F.
// Or when we go to full-int16 spectral code implementation and inject both tables as Block8x8.
double comparePercent;
double sumPercent = 0;
// Corner case - all 1's => 100 quality
// It would fail to deduce using algorithm below without this check
if (table.EqualsToScalar(1))
{
// While this is a 100% to be 100 quality, any given table can be scaled to all 1's.
// According to jpeg creators, top of the line quality is 99, 100 is just a technical 'limit' which will affect result filesize drastically.
// Quality=100 shouldn't be used in usual use case.
return 100;
}
int quality;
for (int i = 0; i < Block8x8F.Size; i++)
{
int coeff = (int)table[i];
// Coefficients are actually int16 casted to float numbers so there's no truncating error.
if (coeff != 0)
{
comparePercent = 100.0 * (table[i] / target[i]);
}
else
{
// No 'valid' quantization table should contain zero at any position
// while this is okay to decode with, it will throw DivideByZeroException at encoding proces stage.
// Not sure what to do here, we can't throw as this technically correct
// but this will screw up the encoder.
comparePercent = 999.99;
}
sumPercent += comparePercent;
}
// Perform some statistical analysis of the quality factor
// to determine the likelihood of the current quantization
// table being a scaled version of the "standard" tables.
// If the variance is high, it is unlikely to be the case.
sumPercent /= 64.0;
// Generate the equivalent IJQ "quality" factor
if (sumPercent <= 100.0)
{
quality = (int)Math.Round((200 - sumPercent) / 2);
}
else
{
quality = (int)Math.Round(5000.0 / sumPercent);
}
return quality;
}
/// <summary>
/// Estimates jpeg quality based on quantization table in zig-zag order.
/// </summary>
/// <param name="luminanceTable">Luminance quantization table.</param>
/// <returns>Estimated quality</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int EstimateLuminanceQuality(ref Block8x8F luminanceTable)
=> EstimateQuality(ref luminanceTable, LuminanceTable);
/// <summary>
/// Estimates jpeg quality based on quantization table in zig-zag order.
/// </summary>
/// <param name="chrominanceTable">Chrominance quantization table.</param>
/// <returns>Estimated quality</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int EstimateChrominanceQuality(ref Block8x8F chrominanceTable)
=> EstimateQuality(ref chrominanceTable, ChrominanceTable);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int QualityToScale(int quality)
{
DebugGuard.MustBeBetweenOrEqualTo(quality, MinQualityFactor, MaxQualityFactor, nameof(quality));
return quality < 50 ? (5000 / quality) : (200 - (quality * 2));
}
private static Block8x8F ScaleQuantizationTable(int scale, ReadOnlySpan<byte> unscaledTable)
{
Block8x8F table = default;
for (int j = 0; j < Block8x8F.Size; j++)
{
int x = ((unscaledTable[j] * scale) + 50) / 100;
table[j] = Numerics.Clamp(x, 1, 255);
}
return table;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Block8x8F ScaleLuminanceTable(int quality)
=> ScaleQuantizationTable(scale: QualityToScale(quality), LuminanceTable);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Block8x8F ScaleChrominanceTable(int quality)
=> ScaleQuantizationTable(scale: QualityToScale(quality), ChrominanceTable);
}
}

300
src/ImageSharp/Formats/Jpeg/Components/ZigZag.Intrinsic.cs

@ -0,0 +1,300 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
#if SUPPORTS_RUNTIME_INTRINSICS
using System;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
internal static partial class ZigZag
{
#pragma warning disable SA1309 // naming rules violation warnings
/// <summary>
/// Special byte value to zero out elements during Sse/Avx shuffle intrinsics.
/// </summary>
private const byte _ = 0xff;
#pragma warning restore SA1309
/// <summary>
/// Gets shuffle vectors for <see cref="ApplyZigZagOrderingSsse3"/>
/// zig zag implementation.
/// </summary>
private static ReadOnlySpan<byte> SseShuffleMasks => new byte[]
{
// row0
0, 1, 2, 3, _, _, _, _, _, _, 4, 5, 6, 7, _, _,
_, _, _, _, 0, 1, _, _, 2, 3, _, _, _, _, 4, 5,
_, _, _, _, _, _, 0, 1, _, _, _, _, _, _, _, _,
// row1
_, _, _, _, _, _, _, _, _, _, _, _, 8, 9, 10, 11,
2, 3, _, _, _, _, _, _, 4, 5, _, _, _, _, _, _,
_, _, 0, 1, _, _, 2, 3, _, _, _, _, _, _, _, _,
// row2
_, _, _, _, _, _, 2, 3, _, _, _, _, _, _, 4, 5,
_, _, _, _, _, _, _, _, 0, 1, _, _, 2, 3, _, _,
// row3
_, _, _, _, _, _, 12, 13, 14, 15, _, _, _, _, _, _,
_, _, _, _, 10, 11, _, _, _, _, 12, 13, _, _, _, _,
_, _, 8, 9, _, _, _, _, _, _, _, _, 10, 11, _, _,
6, 7, _, _, _, _, _, _, _, _, _, _, _, _, 8, 9,
// row4
_, _, 4, 5, _, _, _, _, _, _, _, _, 6, 7, _, _,
_, _, _, _, 2, 3, _, _, _, _, 4, 5, _, _, _, _,
_, _, _, _, _, _, 0, 1, 2, 3, _, _, _, _, _, _,
// row5
_, _, 12, 13, _, _, 14, 15, _, _, _, _, _, _, _, _,
10, 11, _, _, _, _, _, _, 12, 13, _, _, _, _, _, _,
// row6
_, _, _, _, _, _, _, _, 12, 13, _, _, 14, 15, _, _,
_, _, _, _, _, _, 10, 11, _, _, _, _, _, _, 12, 13,
4, 5, 6, 7, _, _, _, _, _, _, _, _, _, _, _, _,
// row7
10, 11, _, _, _, _, 12, 13, _, _, 14, 15, _, _, _, _,
_, _, 8, 9, 10, 11, _, _, _, _, _, _, 12, 13, 14, 15
};
/// <summary>
/// Gets shuffle vectors for <see cref="ApplyZigZagOrderingAvx2"/>
/// zig zag implementation.
/// </summary>
private static ReadOnlySpan<byte> AvxShuffleMasks => new byte[]
{
// 01_AB/01_EF/23_CD - cross-lane
0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0,
// 01_AB - inner-lane
0, 1, 2, 3, 8, 9, _, _, 10, 11, 4, 5, 6, 7, 12, 13, _, _, _, _, _, _, _, _, _, _, 10, 11, 4, 5, 6, 7,
// 01_CD/23_GH - cross-lane
0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, _, _, _, _, 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, _, _, _, _,
// 01_CD - inner-lane
_, _, _, _, _, _, 0, 1, _, _, _, _, _, _, _, _, 2, 3, 8, 9, _, _, 10, 11, 4, 5, _, _, _, _, _, _,
// 01_EF - inner-lane
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 0, 1, _, _, _, _, _, _, _, _, _, _,
// 23_AB/45_CD/67_EF - cross-lane
3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, _, _, _, _, 3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, _, _, _, _,
// 23_AB - inner-lane
4, 5, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 6, 7, 0, 1, 2, 3, 8, 9, _, _, _, _,
// 23_CD - inner-lane
_, _, 6, 7, 12, 13, _, _, _, _, _, _, _, _, _, _, 10, 11, 4, 5, _, _, _, _, _, _, _, _, 6, 7, 12, 13,
// 23_EF - inner-lane
_, _, _, _, _, _, 2, 3, 8, 9, _, _, 10, 11, 4, 5, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
// 23_GH - inner-lane
_, _, _, _, _, _, _, _, _, _, 0, 1, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
// 45_AB - inner-lane
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 10, 11, _, _, _, _, _, _, _, _, _, _,
// 45_CD - inner-lane
_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 6, 7, 0, 1, _, _, 2, 3, 8, 9, _, _, _, _, _, _,
// 45_EF - cross-lane
1, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0, _, _, _, _, 2, 0, 0, 0, 3, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0,
// 45_EF - inner-lane
2, 3, 8, 9, _, _, _, _, _, _, _, _, 10, 11, 4, 5, _, _, _, _, _, _, _, _, _, _, 2, 3, 8, 9, _, _,
// 45_GH - inner-lane
_, _, _, _, 2, 3, 8, 9, 10, 11, 4, 5, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, 6, 7,
// 67_CD - inner-lane
_, _, _, _, _, _, _, _, _, _, 10, 11, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _,
// 67_EF - inner-lane
_, _, _, _, _, _, 6, 7, 0, 1, _, _, 2, 3, 8, 9, _, _, _, _, _, _, _, _, 10, 11, _, _, _, _, _, _,
// 67_GH - inner-lane
8, 9, 10, 11, 4, 5, _, _, _, _, _, _, _, _, _, _, 2, 3, 8, 9, 10, 11, 4, 5, _, _, 6, 7, 12, 13, 14, 15
};
/// <summary>
/// Applies zig zag ordering for given 8x8 matrix using SSE cpu intrinsics.
/// </summary>
/// <param name="block">Input matrix.</param>
public static unsafe void ApplyZigZagOrderingSsse3(ref Block8x8 block)
{
DebugGuard.IsTrue(Ssse3.IsSupported, "Ssse3 support is required to run this operation!");
fixed (byte* maskPtr = SseShuffleMasks)
{
Vector128<byte> rowA = block.V0.AsByte();
Vector128<byte> rowB = block.V1.AsByte();
Vector128<byte> rowC = block.V2.AsByte();
Vector128<byte> rowD = block.V3.AsByte();
Vector128<byte> rowE = block.V4.AsByte();
Vector128<byte> rowF = block.V5.AsByte();
Vector128<byte> rowG = block.V6.AsByte();
Vector128<byte> rowH = block.V7.AsByte();
// row0 - A0 A1 B0 C0 B1 A2 A3 B2
Vector128<short> rowA0 = Ssse3.Shuffle(rowA, Sse2.LoadVector128(maskPtr + (16 * 0))).AsInt16();
Vector128<short> rowB0 = Ssse3.Shuffle(rowB, Sse2.LoadVector128(maskPtr + (16 * 1))).AsInt16();
Vector128<short> row0 = Sse2.Or(rowA0, rowB0);
Vector128<short> rowC0 = Ssse3.Shuffle(rowC, Sse2.LoadVector128(maskPtr + (16 * 2))).AsInt16();
row0 = Sse2.Or(row0, rowC0);
// row1 - C1 D0 E0 D1 C2 B3 A4 A5
Vector128<short> rowA1 = Ssse3.Shuffle(rowA, Sse2.LoadVector128(maskPtr + (16 * 3))).AsInt16();
Vector128<short> rowC1 = Ssse3.Shuffle(rowC, Sse2.LoadVector128(maskPtr + (16 * 4))).AsInt16();
Vector128<short> row1 = Sse2.Or(rowA1, rowC1);
Vector128<short> rowD1 = Ssse3.Shuffle(rowD, Sse2.LoadVector128(maskPtr + (16 * 5))).AsInt16();
row1 = Sse2.Or(row1, rowD1);
row1 = Sse2.Insert(row1.AsUInt16(), Sse2.Extract(rowB.AsUInt16(), 3), 5).AsInt16();
row1 = Sse2.Insert(row1.AsUInt16(), Sse2.Extract(rowE.AsUInt16(), 0), 2).AsInt16();
// row2
Vector128<short> rowE2 = Ssse3.Shuffle(rowE, Sse2.LoadVector128(maskPtr + (16 * 6))).AsInt16();
Vector128<short> rowF2 = Ssse3.Shuffle(rowF, Sse2.LoadVector128(maskPtr + (16 * 7))).AsInt16();
Vector128<short> row2 = Sse2.Or(rowE2, rowF2);
row2 = Sse2.Insert(row2.AsUInt16(), Sse2.Extract(rowB.AsUInt16(), 4), 0).AsInt16();
row2 = Sse2.Insert(row2.AsUInt16(), Sse2.Extract(rowC.AsUInt16(), 3), 1).AsInt16();
row2 = Sse2.Insert(row2.AsUInt16(), Sse2.Extract(rowD.AsUInt16(), 2), 2).AsInt16();
row2 = Sse2.Insert(row2.AsUInt16(), Sse2.Extract(rowG.AsUInt16(), 0), 5).AsInt16();
// row3
Vector128<short> rowA3 = Ssse3.Shuffle(rowA, Sse2.LoadVector128(maskPtr + (16 * 8))).AsInt16().AsInt16();
Vector128<short> rowB3 = Ssse3.Shuffle(rowB, Sse2.LoadVector128(maskPtr + (16 * 9))).AsInt16().AsInt16();
Vector128<short> row3 = Sse2.Or(rowA3, rowB3);
Vector128<short> rowC3 = Ssse3.Shuffle(rowC, Sse2.LoadVector128(maskPtr + (16 * 10))).AsInt16();
row3 = Sse2.Or(row3, rowC3);
Vector128<byte> shuffleRowD3EF = Sse2.LoadVector128(maskPtr + (16 * 11));
Vector128<short> rowD3 = Ssse3.Shuffle(rowD, shuffleRowD3EF).AsInt16();
row3 = Sse2.Or(row3, rowD3);
// row4
Vector128<short> rowE4 = Ssse3.Shuffle(rowE, shuffleRowD3EF).AsInt16();
Vector128<short> rowF4 = Ssse3.Shuffle(rowF, Sse2.LoadVector128(maskPtr + (16 * 12))).AsInt16();
Vector128<short> row4 = Sse2.Or(rowE4, rowF4);
Vector128<short> rowG4 = Ssse3.Shuffle(rowG, Sse2.LoadVector128(maskPtr + (16 * 13))).AsInt16();
row4 = Sse2.Or(row4, rowG4);
Vector128<short> rowH4 = Ssse3.Shuffle(rowH, Sse2.LoadVector128(maskPtr + (16 * 14))).AsInt16();
row4 = Sse2.Or(row4, rowH4);
// row5
Vector128<short> rowC5 = Ssse3.Shuffle(rowC, Sse2.LoadVector128(maskPtr + (16 * 15))).AsInt16();
Vector128<short> rowD5 = Ssse3.Shuffle(rowD, Sse2.LoadVector128(maskPtr + (16 * 16))).AsInt16();
Vector128<short> row5 = Sse2.Or(rowC5, rowD5);
row5 = Sse2.Insert(row5.AsUInt16(), Sse2.Extract(rowB.AsUInt16(), 7), 2).AsInt16();
row5 = Sse2.Insert(row5.AsUInt16(), Sse2.Extract(rowE.AsUInt16(), 5), 5).AsInt16();
row5 = Sse2.Insert(row5.AsUInt16(), Sse2.Extract(rowF.AsUInt16(), 4), 6).AsInt16();
row5 = Sse2.Insert(row5.AsUInt16(), Sse2.Extract(rowG.AsUInt16(), 3), 7).AsInt16();
// row6
Vector128<short> rowE6 = Ssse3.Shuffle(rowE, Sse2.LoadVector128(maskPtr + (16 * 17))).AsInt16();
Vector128<short> rowF6 = Ssse3.Shuffle(rowF, Sse2.LoadVector128(maskPtr + (16 * 18))).AsInt16();
Vector128<short> row6 = Sse2.Or(rowE6, rowF6);
Vector128<short> rowH6 = Ssse3.Shuffle(rowH, Sse2.LoadVector128(maskPtr + (16 * 19))).AsInt16();
row6 = Sse2.Or(row6, rowH6);
row6 = Sse2.Insert(row6.AsUInt16(), Sse2.Extract(rowD.AsUInt16(), 7), 5).AsInt16();
row6 = Sse2.Insert(row6.AsUInt16(), Sse2.Extract(rowG.AsUInt16(), 4), 2).AsInt16();
// row7
Vector128<short> rowG7 = Ssse3.Shuffle(rowG, Sse2.LoadVector128(maskPtr + (16 * 20))).AsInt16();
Vector128<short> rowH7 = Ssse3.Shuffle(rowH, Sse2.LoadVector128(maskPtr + (16 * 21))).AsInt16();
Vector128<short> row7 = Sse2.Or(rowG7, rowH7);
row7 = Sse2.Insert(row7.AsUInt16(), Sse2.Extract(rowF.AsUInt16(), 7), 4).AsInt16();
block.V0 = row0;
block.V1 = row1;
block.V2 = row2;
block.V3 = row3;
block.V4 = row4;
block.V5 = row5;
block.V6 = row6;
block.V7 = row7;
}
}
/// <summary>
/// Applies zig zag ordering for given 8x8 matrix using AVX cpu intrinsics.
/// </summary>
/// <param name="block">Input matrix.</param>
public static unsafe void ApplyZigZagOrderingAvx2(ref Block8x8 block)
{
DebugGuard.IsTrue(Avx2.IsSupported, "Avx2 support is required to run this operation!");
fixed (byte* shuffleVectorsPtr = AvxShuffleMasks)
{
Vector256<byte> rowsAB = block.V01.AsByte();
Vector256<byte> rowsCD = block.V23.AsByte();
Vector256<byte> rowsEF = block.V45.AsByte();
Vector256<byte> rowsGH = block.V67.AsByte();
// rows 0 1
Vector256<int> rows_AB01_EF01_CD23_shuffleMask = Avx.LoadVector256(shuffleVectorsPtr + (0 * 32)).AsInt32();
Vector256<byte> row01_AB = Avx2.PermuteVar8x32(rowsAB.AsInt32(), rows_AB01_EF01_CD23_shuffleMask).AsByte();
row01_AB = Avx2.Shuffle(row01_AB, Avx.LoadVector256(shuffleVectorsPtr + (1 * 32))).AsByte();
Vector256<int> rows_CD01_GH23_shuffleMask = Avx.LoadVector256(shuffleVectorsPtr + (2 * 32)).AsInt32();
Vector256<byte> row01_CD = Avx2.PermuteVar8x32(rowsCD.AsInt32(), rows_CD01_GH23_shuffleMask).AsByte();
row01_CD = Avx2.Shuffle(row01_CD, Avx.LoadVector256(shuffleVectorsPtr + (3 * 32))).AsByte();
Vector256<byte> row0123_EF = Avx2.PermuteVar8x32(rowsEF.AsInt32(), rows_AB01_EF01_CD23_shuffleMask).AsByte();
Vector256<byte> row01_EF = Avx2.Shuffle(row0123_EF, Avx.LoadVector256(shuffleVectorsPtr + (4 * 32))).AsByte();
Vector256<byte> row01 = Avx2.Or(Avx2.Or(row01_AB, row01_CD), row01_EF);
// rows 2 3
Vector256<int> rows_AB23_CD45_EF67_shuffleMask = Avx.LoadVector256(shuffleVectorsPtr + (5 * 32)).AsInt32();
Vector256<byte> row2345_AB = Avx2.PermuteVar8x32(rowsAB.AsInt32(), rows_AB23_CD45_EF67_shuffleMask).AsByte();
Vector256<byte> row23_AB = Avx2.Shuffle(row2345_AB, Avx.LoadVector256(shuffleVectorsPtr + (6 * 32))).AsByte();
Vector256<byte> row23_CD = Avx2.PermuteVar8x32(rowsCD.AsInt32(), rows_AB01_EF01_CD23_shuffleMask).AsByte();
row23_CD = Avx2.Shuffle(row23_CD, Avx.LoadVector256(shuffleVectorsPtr + (7 * 32))).AsByte();
Vector256<byte> row23_EF = Avx2.Shuffle(row0123_EF, Avx.LoadVector256(shuffleVectorsPtr + (8 * 32))).AsByte();
Vector256<byte> row2345_GH = Avx2.PermuteVar8x32(rowsGH.AsInt32(), rows_CD01_GH23_shuffleMask).AsByte();
Vector256<byte> row23_GH = Avx2.Shuffle(row2345_GH, Avx.LoadVector256(shuffleVectorsPtr + (9 * 32)).AsByte());
Vector256<byte> row23 = Avx2.Or(Avx2.Or(row23_AB, row23_CD), Avx2.Or(row23_EF, row23_GH));
// rows 4 5
Vector256<byte> row45_AB = Avx2.Shuffle(row2345_AB, Avx.LoadVector256(shuffleVectorsPtr + (10 * 32)).AsByte());
Vector256<byte> row4567_CD = Avx2.PermuteVar8x32(rowsCD.AsInt32(), rows_AB23_CD45_EF67_shuffleMask).AsByte();
Vector256<byte> row45_CD = Avx2.Shuffle(row4567_CD, Avx.LoadVector256(shuffleVectorsPtr + (11 * 32)).AsByte());
Vector256<int> rows_EF45_GH67_shuffleMask = Avx.LoadVector256(shuffleVectorsPtr + (12 * 32)).AsInt32();
Vector256<byte> row45_EF = Avx2.PermuteVar8x32(rowsEF.AsInt32(), rows_EF45_GH67_shuffleMask).AsByte();
row45_EF = Avx2.Shuffle(row45_EF, Avx.LoadVector256(shuffleVectorsPtr + (13 * 32)).AsByte());
Vector256<byte> row45_GH = Avx2.Shuffle(row2345_GH, Avx.LoadVector256(shuffleVectorsPtr + (14 * 32)).AsByte());
Vector256<byte> row45 = Avx2.Or(Avx2.Or(row45_AB, row45_CD), Avx2.Or(row45_EF, row45_GH));
// rows 6 7
Vector256<byte> row67_CD = Avx2.Shuffle(row4567_CD, Avx.LoadVector256(shuffleVectorsPtr + (15 * 32)).AsByte());
Vector256<byte> row67_EF = Avx2.PermuteVar8x32(rowsEF.AsInt32(), rows_AB23_CD45_EF67_shuffleMask).AsByte();
row67_EF = Avx2.Shuffle(row67_EF, Avx.LoadVector256(shuffleVectorsPtr + (16 * 32)).AsByte());
Vector256<byte> row67_GH = Avx2.PermuteVar8x32(rowsGH.AsInt32(), rows_EF45_GH67_shuffleMask).AsByte();
row67_GH = Avx2.Shuffle(row67_GH, Avx.LoadVector256(shuffleVectorsPtr + (17 * 32)).AsByte());
Vector256<byte> row67 = Avx2.Or(Avx2.Or(row67_CD, row67_EF), row67_GH);
block.V01 = row01.AsInt16();
block.V23 = row23.AsInt16();
block.V45 = row45.AsInt16();
block.V67 = row67.AsInt16();
}
}
}
}
#endif

79
src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs

@ -2,21 +2,15 @@
// 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;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{ {
/// <summary> internal static partial class ZigZag
/// Holds the Jpeg UnZig array in a value/stack type.
/// Unzig maps from the zigzag ordering to the natural ordering. For example,
/// unzig[3] is the column and row of the fourth element in zigzag order. The
/// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2).
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct ZigZag
{ {
/// <summary> /// <summary>
/// Gets span of zig-zag ordering indices.
/// </summary>
/// <remarks>
/// When reading corrupted data, the Huffman decoders could attempt /// When reading corrupted data, the Huffman decoders could attempt
/// to reference an entry beyond the end of this array (if the decoded /// to reference an entry beyond the end of this array (if the decoded
/// zero run length reaches past the end of the block). To prevent /// zero run length reaches past the end of the block). To prevent
@ -25,20 +19,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
/// to be stored in location 63 of the block, not somewhere random. /// to be stored in location 63 of the block, not somewhere random.
/// The worst case would be a run-length of 15, which means we need 16 /// The worst case would be a run-length of 15, which means we need 16
/// fake entries. /// fake entries.
/// </summary> /// </remarks>
private const int Size = 64 + 16; public static ReadOnlySpan<byte> ZigZagOrder => new byte[]
/// <summary>
/// Copy of <see cref="Unzig"/> in a value type
/// </summary>
public fixed byte Data[Size];
/// <summary>
/// Gets the unzigs map, which maps from the zigzag ordering to the natural ordering.
/// For example, unzig[3] is the column and row of the fourth element in zigzag order.
/// The value is 16, which means first column (16%8 == 0) and third row (16/8 == 2).
/// </summary>
private static ReadOnlySpan<byte> Unzig => new byte[]
{ {
0, 1, 8, 16, 9, 2, 3, 10, 0, 1, 8, 16, 9, 2, 3, 10,
17, 24, 32, 25, 18, 11, 4, 5, 17, 24, 32, 25, 18, 11, 4, 5,
@ -48,53 +30,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
29, 22, 15, 23, 30, 37, 44, 51, 29, 22, 15, 23, 30, 37, 44, 51,
58, 59, 52, 45, 38, 31, 39, 46, 58, 59, 52, 45, 38, 31, 39, 46,
53, 60, 61, 54, 47, 55, 62, 63, 53, 60, 61, 54, 47, 55, 62, 63,
63, 63, 63, 63, 63, 63, 63, 63, // Extra entries for safety in decoder
// Extra entries for safety in decoder
63, 63, 63, 63, 63, 63, 63, 63,
63, 63, 63, 63, 63, 63, 63, 63 63, 63, 63, 63, 63, 63, 63, 63
}; };
/// <summary>
/// Returns the value at the given index
/// </summary>
/// <param name="idx">The index</param>
/// <returns>The <see cref="byte"/></returns>
public byte this[int idx]
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
ref byte self = ref Unsafe.As<ZigZag, byte>(ref this);
return Unsafe.Add(ref self, idx);
}
}
/// <summary>
/// Creates and fills an instance of <see cref="ZigZag"/> with Jpeg unzig indices
/// </summary>
/// <returns>The new instance</returns>
public static ZigZag CreateUnzigTable()
{
ZigZag result = default;
ref byte sourceRef = ref MemoryMarshal.GetReference(Unzig);
ref byte destinationRef = ref Unsafe.AsRef<byte>(result.Data);
Unzig.CopyTo(new Span<byte>(result.Data, Size));
return result;
}
/// <summary>
/// Apply Zigging to the given quantization table, so it will be sufficient to multiply blocks for dequantizing them.
/// </summary>
public static Block8x8F CreateDequantizationTable(ref Block8x8F qt)
{
Block8x8F result = default;
for (int i = 0; i < Block8x8F.Size; i++)
{
result[Unzig[i]] = qt[i];
}
return result;
}
} }
} }

14
src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs

@ -9,20 +9,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
internal interface IJpegEncoderOptions internal interface IJpegEncoderOptions
{ {
/// <summary> /// <summary>
/// Gets the quality, that will be used to encode the image. Quality /// Gets or sets the quality, that will be used to encode the image. Quality
/// index must be between 0 and 100 (compression from max to min). /// index must be between 0 and 100 (compression from max to min).
/// Defaults to <value>75</value>.
/// </summary> /// </summary>
/// <value>The quality of the jpg image from 0 to 100.</value> public int? Quality { get; set; }
int? Quality { get; }
/// <summary> /// <summary>
/// Gets the subsample ration, that will be used to encode the image. /// Gets the color type, that will be used to encode the image.
/// </summary>
/// <value>The subsample ratio of the jpg image.</value>
JpegSubsample? Subsample { get; }
/// <summary>
/// Gets the color type.
/// </summary> /// </summary>
JpegColorType? ColorType { get; } JpegColorType? ColorType { get; }
} }

49
src/ImageSharp/Formats/Jpeg/JpegColorType.cs

@ -10,12 +10,57 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{ {
/// <summary> /// <summary>
/// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification. /// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification.
/// Medium Quality - The horizontal sampling is halved and the Cb and Cr channels are only
/// sampled on each alternate line.
/// </summary> /// </summary>
YCbCr = 0, YCbCrRatio420 = 0,
/// <summary>
/// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification.
/// High Quality - Each of the three Y'CbCr components have the same sample rate,
/// thus there is no chroma subsampling.
/// </summary>
YCbCrRatio444 = 1,
/// <summary>
/// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification.
/// The two chroma components are sampled at half the horizontal sample rate of luma while vertically it has full resolution.
///
/// Note: Not supported by the encoder.
/// </summary>
YCbCrRatio422 = 2,
/// <summary>
/// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification.
/// In 4:1:1 chroma subsampling, the horizontal color resolution is quartered.
///
/// Note: Not supported by the encoder.
/// </summary>
YCbCrRatio411 = 3,
/// <summary>
/// YCbCr (luminance, blue chroma, red chroma) color as defined in the ITU-T T.871 specification.
/// This ratio uses half of the vertical and one-fourth the horizontal color resolutions.
///
/// Note: Not supported by the encoder.
/// </summary>
YCbCrRatio410 = 4,
/// <summary> /// <summary>
/// Single channel, luminance. /// Single channel, luminance.
/// </summary> /// </summary>
Luminance = 1 Luminance = 5,
/// <summary>
/// The pixel data will be preserved as RGB without any sub sampling.
/// </summary>
Rgb = 6,
/// <summary>
/// CMYK colorspace (cyan, magenta, yellow, and key black) intended for printing.
///
/// Note: Not supported by the encoder.
/// </summary>
Cmyk = 7,
} }
} }

55
src/ImageSharp/Formats/Jpeg/JpegConstants.cs

@ -133,6 +133,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
public const byte APP15 = 0xEF; public const byte APP15 = 0xEF;
/// <summary>
/// Define arithmetic coding conditioning marker.
/// </summary>
public const byte DAC = 0xCC;
/// <summary> /// <summary>
/// The text comment marker /// The text comment marker
/// </summary> /// </summary>
@ -173,6 +178,56 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
public const byte SOF2 = 0xC2; public const byte SOF2 = 0xC2;
/// <summary>
/// Start of Frame marker, non differential lossless, Huffman coding.
/// </summary>
public const byte SOF3 = 0xC3;
/// <summary>
/// Start of Frame marker, differential, Huffman coding, Differential sequential DCT.
/// </summary>
public const byte SOF5 = 0xC5;
/// <summary>
/// Start of Frame marker, differential, Huffman coding, Differential progressive DCT.
/// </summary>
public const byte SOF6 = 0xC6;
/// <summary>
/// Start of Frame marker, differential lossless, Huffman coding.
/// </summary>
public const byte SOF7 = 0xC7;
/// <summary>
/// Start of Frame marker, non-differential, arithmetic coding, Extended sequential DCT.
/// </summary>
public const byte SOF9 = 0xC9;
/// <summary>
/// Start of Frame marker, non-differential, arithmetic coding, Progressive DCT.
/// </summary>
public const byte SOF10 = 0xCA;
/// <summary>
/// Start of Frame marker, non-differential, arithmetic coding, Lossless (sequential).
/// </summary>
public const byte SOF11 = 0xCB;
/// <summary>
/// Start of Frame marker, differential, arithmetic coding, Differential sequential DCT.
/// </summary>
public const byte SOF13 = 0xCD;
/// <summary>
/// Start of Frame marker, differential, arithmetic coding, Differential progressive DCT.
/// </summary>
public const byte SOF14 = 0xCE;
/// <summary>
/// Start of Frame marker, differential, arithmetic coding, Differential lossless (sequential).
/// </summary>
public const byte SOF15 = 0xCF;
/// <summary> /// <summary>
/// Define Huffman Table(s) /// Define Huffman Table(s)
/// <remarks> /// <remarks>

4
src/ImageSharp/Formats/Jpeg/JpegDecoder.cs

@ -4,8 +4,6 @@
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg namespace SixLabors.ImageSharp.Formats.Jpeg
@ -30,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <inheritdoc /> /// <inheritdoc />
public Image Decode(Configuration configuration, Stream stream) public Image Decode(Configuration configuration, Stream stream)
=> this.Decode<Rgba32>(configuration, stream); => this.Decode<Rgb24>(configuration, stream);
/// <inheritdoc/> /// <inheritdoc/>
public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken) public Task<Image<TPixel>> DecodeAsync<TPixel>(Configuration configuration, Stream stream, CancellationToken cancellationToken)

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

@ -4,6 +4,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
using System.Buffers.Binary; using System.Buffers.Binary;
using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Threading;
@ -30,7 +31,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary> /// <summary>
/// The only supported precision /// The only supported precision
/// </summary> /// </summary>
private readonly int[] supportedPrecisions = { 8, 12 }; private readonly byte[] supportedPrecisions = { 8, 12 };
/// <summary> /// <summary>
/// The buffer used to temporarily store bytes read from the stream. /// The buffer used to temporarily store bytes read from the stream.
@ -42,21 +43,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
private readonly byte[] markerBuffer = new byte[2]; private readonly byte[] markerBuffer = new byte[2];
/// <summary>
/// The DC Huffman tables.
/// </summary>
private HuffmanTable[] dcHuffmanTables;
/// <summary>
/// The AC Huffman tables
/// </summary>
private HuffmanTable[] acHuffmanTables;
/// <summary>
/// The reset interval determined by RST markers.
/// </summary>
private ushort resetInterval;
/// <summary> /// <summary>
/// Whether the image has an EXIF marker. /// Whether the image has an EXIF marker.
/// </summary> /// </summary>
@ -97,6 +83,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
private AdobeMarker adobe; private AdobeMarker adobe;
/// <summary>
/// Scan decoder.
/// </summary>
private HuffmanScanDecoder scanDecoder;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JpegDecoderCore" /> class. /// Initializes a new instance of the <see cref="JpegDecoderCore" /> class.
/// </summary> /// </summary>
@ -117,30 +108,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public JpegFrame Frame { get; private set; } public JpegFrame Frame { get; private set; }
/// <inheritdoc/> /// <inheritdoc/>
public Size ImageSizeInPixels { get; private set; } Size IImageDecoderInternals.Dimensions => this.Frame.PixelSize;
/// <inheritdoc/>
Size IImageDecoderInternals.Dimensions => this.ImageSizeInPixels;
/// <summary>
/// Gets the number of MCU blocks in the image as <see cref="Size"/>.
/// </summary>
public Size ImageSizeInMCU { get; private set; }
/// <summary>
/// Gets the image width
/// </summary>
public int ImageWidth => this.ImageSizeInPixels.Width;
/// <summary>
/// Gets the image height
/// </summary>
public int ImageHeight => this.ImageSizeInPixels.Height;
/// <summary>
/// Gets the color depth, in number of bits per pixel.
/// </summary>
public int BitsPerPixel => this.ComponentCount * this.Frame.Precision;
/// <summary> /// <summary>
/// Gets a value indicating whether the metadata should be ignored when the image is being decoded. /// Gets a value indicating whether the metadata should be ignored when the image is being decoded.
@ -152,15 +120,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
public ImageMetadata Metadata { get; private set; } public ImageMetadata Metadata { get; private set; }
/// <inheritdoc/>
public int ComponentCount { get; private set; }
/// <inheritdoc/> /// <inheritdoc/>
public JpegColorSpace ColorSpace { get; private set; } public JpegColorSpace ColorSpace { get; private set; }
/// <inheritdoc/>
public int Precision { get; private set; }
/// <summary> /// <summary>
/// Gets the components. /// Gets the components.
/// </summary> /// </summary>
@ -175,8 +137,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary> /// <summary>
/// Finds the next file marker within the byte stream. /// Finds the next file marker within the byte stream.
/// </summary> /// </summary>
/// <param name="marker">The buffer to read file markers to</param> /// <param name="marker">The buffer to read file markers to.</param>
/// <param name="stream">The input stream</param> /// <param name="stream">The input stream.</param>
/// <returns>The <see cref="JpegFileMarker"/></returns> /// <returns>The <see cref="JpegFileMarker"/></returns>
public static JpegFileMarker FindNextFileMarker(byte[] marker, BufferedReadStream stream) public static JpegFileMarker FindNextFileMarker(byte[] marker, BufferedReadStream stream)
{ {
@ -213,35 +175,45 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken) public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
this.ParseStream(stream, cancellationToken: cancellationToken); using var spectralConverter = new SpectralConverter<TPixel>(this.Configuration, cancellationToken);
var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, cancellationToken);
this.ParseStream(stream, scanDecoder, cancellationToken);
this.InitExifProfile(); this.InitExifProfile();
this.InitIccProfile(); this.InitIccProfile();
this.InitIptcProfile(); this.InitIptcProfile();
this.InitDerivedMetadataProperties(); this.InitDerivedMetadataProperties();
return this.PostProcessIntoImage<TPixel>(cancellationToken);
return new Image<TPixel>(this.Configuration, spectralConverter.GetPixelBuffer(), this.Metadata);
} }
/// <inheritdoc/> /// <inheritdoc/>
public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken) public IImageInfo Identify(BufferedReadStream stream, CancellationToken cancellationToken)
{ {
this.ParseStream(stream, true, cancellationToken); this.ParseStream(stream, scanDecoder: null, cancellationToken);
this.InitExifProfile(); this.InitExifProfile();
this.InitIccProfile(); this.InitIccProfile();
this.InitIptcProfile(); this.InitIptcProfile();
this.InitDerivedMetadataProperties(); this.InitDerivedMetadataProperties();
return new ImageInfo(new PixelTypeInfo(this.BitsPerPixel), this.ImageWidth, this.ImageHeight, this.Metadata); Size pixelSize = this.Frame.PixelSize;
return new ImageInfo(new PixelTypeInfo(this.Frame.BitsPerPixel), pixelSize.Width, pixelSize.Height, this.Metadata);
} }
/// <summary> /// <summary>
/// Parses the input stream for file markers /// Load quantization and/or Huffman tables for subsequent use for jpeg's embedded in tiff's,
/// so those tables do not need to be duplicated with segmented tiff's (tiff's with multiple strips).
/// </summary> /// </summary>
/// <param name="stream">The input stream</param> /// <param name="tableBytes">The table bytes.</param>
/// <param name="metadataOnly">Whether to decode metadata only.</param> /// <param name="huffmanScanDecoder">The scan decoder.</param>
/// <param name="cancellationToken">The token to monitor cancellation.</param> public void LoadTables(byte[] tableBytes, HuffmanScanDecoder huffmanScanDecoder)
public void ParseStream(BufferedReadStream stream, bool metadataOnly = false, CancellationToken cancellationToken = default)
{ {
this.Metadata = new ImageMetadata(); this.Metadata = new ImageMetadata();
this.QuantizationTables = new Block8x8F[4];
this.scanDecoder = huffmanScanDecoder;
using var ms = new MemoryStream(tableBytes);
using var stream = new BufferedReadStream(this.Configuration, ms);
// Check for the Start Of Image marker. // Check for the Start Of Image marker.
stream.Read(this.markerBuffer, 0, 2); stream.Read(this.markerBuffer, 0, 2);
@ -251,18 +223,71 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker."); JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker.");
} }
// Read next marker.
stream.Read(this.markerBuffer, 0, 2); stream.Read(this.markerBuffer, 0, 2);
byte marker = this.markerBuffer[1]; byte marker = this.markerBuffer[1];
fileMarker = new JpegFileMarker(marker, (int)stream.Position - 2); fileMarker = new JpegFileMarker(marker, (int)stream.Position - 2);
this.QuantizationTables = new Block8x8F[4];
// Only assign what we need while (fileMarker.Marker != JpegConstants.Markers.EOI || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid))
if (!metadataOnly)
{ {
const int maxTables = 4; if (!fileMarker.Invalid)
this.dcHuffmanTables = new HuffmanTable[maxTables]; {
this.acHuffmanTables = new HuffmanTable[maxTables]; // Get the marker length.
int remaining = this.ReadUint16(stream) - 2;
switch (fileMarker.Marker)
{
case JpegConstants.Markers.SOI:
break;
case JpegConstants.Markers.RST0:
case JpegConstants.Markers.RST7:
break;
case JpegConstants.Markers.DHT:
this.ProcessDefineHuffmanTablesMarker(stream, remaining);
break;
case JpegConstants.Markers.DQT:
this.ProcessDefineQuantizationTablesMarker(stream, remaining);
break;
case JpegConstants.Markers.DRI:
this.ProcessDefineRestartIntervalMarker(stream, remaining);
break;
case JpegConstants.Markers.EOI:
return;
}
}
// Read next marker.
stream.Read(this.markerBuffer, 0, 2);
fileMarker = new JpegFileMarker(this.markerBuffer[1], 0);
} }
}
/// <summary>
/// Parses the input stream for file markers.
/// </summary>
/// <param name="stream">The input stream.</param>
/// <param name="scanDecoder">Scan decoder used exclusively to decode SOS marker.</param>
/// <param name="cancellationToken">The token to monitor cancellation.</param>
internal void ParseStream(BufferedReadStream stream, HuffmanScanDecoder scanDecoder, CancellationToken cancellationToken)
{
bool metadataOnly = scanDecoder == null;
this.scanDecoder = scanDecoder;
this.Metadata = new ImageMetadata();
// Check for the Start Of Image marker.
stream.Read(this.markerBuffer, 0, 2);
var fileMarker = new JpegFileMarker(this.markerBuffer[1], 0);
if (fileMarker.Marker != JpegConstants.Markers.SOI)
{
JpegThrowHelper.ThrowInvalidImageContentException("Missing SOI marker.");
}
stream.Read(this.markerBuffer, 0, 2);
byte marker = this.markerBuffer[1];
fileMarker = new JpegFileMarker(marker, (int)stream.Position - 2);
this.QuantizationTables ??= new Block8x8F[4];
// Break only when we discover a valid EOI marker. // Break only when we discover a valid EOI marker.
// https://github.com/SixLabors/ImageSharp/issues/695 // https://github.com/SixLabors/ImageSharp/issues/695
@ -273,7 +298,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (!fileMarker.Invalid) if (!fileMarker.Invalid)
{ {
// Get the marker length // Get the marker length.
int remaining = this.ReadUint16(stream) - 2; int remaining = this.ReadUint16(stream) - 2;
switch (fileMarker.Marker) switch (fileMarker.Marker)
@ -284,10 +309,32 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.ProcessStartOfFrameMarker(stream, remaining, fileMarker, metadataOnly); this.ProcessStartOfFrameMarker(stream, remaining, fileMarker, metadataOnly);
break; break;
case JpegConstants.Markers.SOF5:
JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with differential sequential DCT is not supported.");
break;
case JpegConstants.Markers.SOF6:
JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with differential progressive DCT is not supported.");
break;
case JpegConstants.Markers.SOF3:
case JpegConstants.Markers.SOF7:
JpegThrowHelper.ThrowNotSupportedException("Decoding lossless jpeg files is not supported.");
break;
case JpegConstants.Markers.SOF9:
case JpegConstants.Markers.SOF10:
case JpegConstants.Markers.SOF11:
case JpegConstants.Markers.SOF13:
case JpegConstants.Markers.SOF14:
case JpegConstants.Markers.SOF15:
JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with arithmetic coding is not supported.");
break;
case JpegConstants.Markers.SOS: case JpegConstants.Markers.SOS:
if (!metadataOnly) if (!metadataOnly)
{ {
this.ProcessStartOfScanMarker(stream, cancellationToken); this.ProcessStartOfScanMarker(stream, remaining);
break; break;
} }
else else
@ -363,6 +410,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegConstants.Markers.COM: case JpegConstants.Markers.COM:
stream.Skip(remaining); stream.Skip(remaining);
break; break;
case JpegConstants.Markers.DAC:
JpegThrowHelper.ThrowNotSupportedException("Decoding jpeg files with arithmetic coding is not supported.");
break;
} }
} }
@ -378,44 +429,108 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Set large fields to null. // Set large fields to null.
this.Frame = null; this.Frame = null;
this.dcHuffmanTables = null; this.scanDecoder = null;
this.acHuffmanTables = null;
} }
/// <summary> /// <summary>
/// Returns the correct colorspace based on the image component count /// Returns the correct colorspace based on the image component count and the jpeg frame component id's.
/// </summary> /// </summary>
/// <param name="componentCount">The number of components.</param>
/// <returns>The <see cref="JpegColorSpace"/></returns> /// <returns>The <see cref="JpegColorSpace"/></returns>
private JpegColorSpace DeduceJpegColorSpace() private JpegColorSpace DeduceJpegColorSpace(byte componentCount)
{ {
if (this.ComponentCount == 1) if (componentCount == 1)
{ {
return JpegColorSpace.Grayscale; return JpegColorSpace.Grayscale;
} }
if (this.ComponentCount == 3) if (componentCount == 3)
{ {
if (!this.adobe.Equals(default) && this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown) if (!this.adobe.Equals(default) && this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformUnknown)
{ {
return JpegColorSpace.RGB; return JpegColorSpace.RGB;
} }
// If the component Id's are R, G, B in ASCII the colorspace is RGB and not YCbCr.
if (this.Components[2].Id == 66 && this.Components[1].Id == 71 && this.Components[0].Id == 82)
{
return JpegColorSpace.RGB;
}
// Some images are poorly encoded and contain incorrect colorspace transform metadata. // Some images are poorly encoded and contain incorrect colorspace transform metadata.
// We ignore that and always fall back to the default colorspace. // We ignore that and always fall back to the default colorspace.
return JpegColorSpace.YCbCr; return JpegColorSpace.YCbCr;
} }
if (this.ComponentCount == 4) if (componentCount == 4)
{ {
return this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYcck return this.adobe.ColorTransform == JpegConstants.Adobe.ColorTransformYcck
? JpegColorSpace.Ycck ? JpegColorSpace.Ycck
: JpegColorSpace.Cmyk; : JpegColorSpace.Cmyk;
} }
JpegThrowHelper.ThrowInvalidImageContentException($"Unsupported color mode. Supported component counts 1, 3, and 4; found {this.ComponentCount}"); JpegThrowHelper.ThrowInvalidImageContentException($"Unsupported color mode. Supported component counts 1, 3, and 4; found {componentCount}");
return default; return default;
} }
/// <summary>
/// Returns the jpeg color type based on the colorspace and subsampling used.
/// </summary>
/// <returns>Jpeg color type.</returns>
private JpegColorType DeduceJpegColorType()
{
switch (this.ColorSpace)
{
case JpegColorSpace.Grayscale:
return JpegColorType.Luminance;
case JpegColorSpace.RGB:
return JpegColorType.Rgb;
case JpegColorSpace.YCbCr:
if (this.Frame.Components[0].HorizontalSamplingFactor == 1 && this.Frame.Components[0].VerticalSamplingFactor == 1 &&
this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1)
{
return JpegColorType.YCbCrRatio444;
}
else if (this.Frame.Components[0].HorizontalSamplingFactor == 2 && this.Frame.Components[0].VerticalSamplingFactor == 2 &&
this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1)
{
return JpegColorType.YCbCrRatio420;
}
else if (this.Frame.Components[0].HorizontalSamplingFactor == 1 && this.Frame.Components[0].VerticalSamplingFactor == 1 &&
this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 2 &&
this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 2)
{
return JpegColorType.YCbCrRatio422;
}
else if (this.Frame.Components[0].HorizontalSamplingFactor == 4 && this.Frame.Components[0].VerticalSamplingFactor == 1 &&
this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1)
{
return JpegColorType.YCbCrRatio411;
}
else if (this.Frame.Components[0].HorizontalSamplingFactor == 4 && this.Frame.Components[0].VerticalSamplingFactor == 2 &&
this.Frame.Components[1].HorizontalSamplingFactor == 1 && this.Frame.Components[1].VerticalSamplingFactor == 1 &&
this.Frame.Components[2].HorizontalSamplingFactor == 1 && this.Frame.Components[2].VerticalSamplingFactor == 1)
{
return JpegColorType.YCbCrRatio410;
}
else
{
return JpegColorType.YCbCrRatio420;
}
case JpegColorSpace.Cmyk:
return JpegColorType.Cmyk;
default:
return JpegColorType.YCbCrRatio420;
}
}
/// <summary> /// <summary>
/// Initializes the EXIF profile. /// Initializes the EXIF profile.
/// </summary> /// </summary>
@ -551,7 +666,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowInvalidImageContentException("Bad App1 Marker length."); JpegThrowHelper.ThrowInvalidImageContentException("Bad App1 Marker length.");
} }
var profile = new byte[remaining]; byte[] profile = new byte[remaining];
stream.Read(profile, 0, remaining); stream.Read(profile, 0, remaining);
if (ProfileResolver.IsProfile(profile, ProfileResolver.ExifMarker)) if (ProfileResolver.IsProfile(profile, ProfileResolver.ExifMarker))
@ -585,14 +700,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
return; return;
} }
var identifier = new byte[Icclength]; byte[] identifier = new byte[Icclength];
stream.Read(identifier, 0, Icclength); stream.Read(identifier, 0, Icclength);
remaining -= Icclength; // We have read it by this point remaining -= Icclength; // We have read it by this point
if (ProfileResolver.IsProfile(identifier, ProfileResolver.IccMarker)) if (ProfileResolver.IsProfile(identifier, ProfileResolver.IccMarker))
{ {
this.isIcc = true; this.isIcc = true;
var profile = new byte[remaining]; byte[] profile = new byte[remaining];
stream.Read(profile, 0, remaining); stream.Read(profile, 0, remaining);
if (this.iccData is null) if (this.iccData is null)
@ -614,7 +729,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary> /// <summary>
/// Processes a App13 marker, which contains IPTC data stored with Adobe Photoshop. /// Processes a App13 marker, which contains IPTC data stored with Adobe Photoshop.
/// The content of an APP13 segment is formed by an identifier string followed by a sequence of resource data blocks. /// The tableBytes of an APP13 segment is formed by an identifier string followed by a sequence of resource data blocks.
/// </summary> /// </summary>
/// <param name="stream">The input stream.</param> /// <param name="stream">The input stream.</param>
/// <param name="remaining">The remaining bytes in the segment block.</param> /// <param name="remaining">The remaining bytes in the segment block.</param>
@ -630,7 +745,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
remaining -= ProfileResolver.AdobePhotoshopApp13Marker.Length; remaining -= ProfileResolver.AdobePhotoshopApp13Marker.Length;
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.AdobePhotoshopApp13Marker)) if (ProfileResolver.IsProfile(this.temp, ProfileResolver.AdobePhotoshopApp13Marker))
{ {
var resourceBlockData = new byte[remaining]; byte[] resourceBlockData = new byte[remaining];
stream.Read(resourceBlockData, 0, remaining); stream.Read(resourceBlockData, 0, remaining);
Span<byte> blockDataSpan = resourceBlockData.AsSpan(); Span<byte> blockDataSpan = resourceBlockData.AsSpan();
@ -645,8 +760,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
Span<byte> imageResourceBlockId = blockDataSpan.Slice(0, 2); Span<byte> imageResourceBlockId = blockDataSpan.Slice(0, 2);
if (ProfileResolver.IsProfile(imageResourceBlockId, ProfileResolver.AdobeIptcMarker)) if (ProfileResolver.IsProfile(imageResourceBlockId, ProfileResolver.AdobeIptcMarker))
{ {
var resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan); int resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan);
var resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength); int resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength);
int dataStartIdx = 2 + resourceBlockNameLength + 4; int dataStartIdx = 2 + resourceBlockNameLength + 4;
if (resourceDataSize > 0 && blockDataSpan.Length >= dataStartIdx + resourceDataSize) if (resourceDataSize > 0 && blockDataSpan.Length >= dataStartIdx + resourceDataSize)
{ {
@ -657,8 +772,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
else else
{ {
var resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan); int resourceBlockNameLength = ReadImageResourceNameLength(blockDataSpan);
var resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength); int resourceDataSize = ReadResourceDataLength(blockDataSpan, resourceBlockNameLength);
int dataStartIdx = 2 + resourceBlockNameLength + 4; int dataStartIdx = 2 + resourceBlockNameLength + 4;
if (blockDataSpan.Length < dataStartIdx + resourceDataSize) if (blockDataSpan.Length < dataStartIdx + resourceDataSize)
{ {
@ -681,7 +796,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
private static int ReadImageResourceNameLength(Span<byte> blockDataSpan) private static int ReadImageResourceNameLength(Span<byte> blockDataSpan)
{ {
byte nameLength = blockDataSpan[2]; byte nameLength = blockDataSpan[2];
var nameDataSize = nameLength == 0 ? 2 : nameLength; int nameDataSize = nameLength == 0 ? 2 : nameLength;
if (nameDataSize % 2 != 0) if (nameDataSize % 2 != 0)
{ {
nameDataSize++; nameDataSize++;
@ -698,9 +813,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <returns>The block length.</returns> /// <returns>The block length.</returns>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private static int ReadResourceDataLength(Span<byte> blockDataSpan, int resourceBlockNameLength) private static int ReadResourceDataLength(Span<byte> blockDataSpan, int resourceBlockNameLength)
{ => BinaryPrimitives.ReadInt32BigEndian(blockDataSpan.Slice(2 + resourceBlockNameLength, 4));
return BinaryPrimitives.ReadInt32BigEndian(blockDataSpan.Slice(2 + resourceBlockNameLength, 4));
}
/// <summary> /// <summary>
/// Processes the application header containing the Adobe identifier /// Processes the application header containing the Adobe identifier
@ -739,85 +852,101 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </exception> /// </exception>
private void ProcessDefineQuantizationTablesMarker(BufferedReadStream stream, int remaining) private void ProcessDefineQuantizationTablesMarker(BufferedReadStream stream, int remaining)
{ {
JpegMetadata jpegMetadata = this.Metadata.GetFormatMetadata(JpegFormat.Instance);
while (remaining > 0) while (remaining > 0)
{ {
bool done = false; // 1 byte: quantization table spec
remaining--; // bit 0..3: table index (0..3)
// bit 4..7: table precision (0 = 8 bit, 1 = 16 bit)
int quantizationTableSpec = stream.ReadByte(); int quantizationTableSpec = stream.ReadByte();
int tableIndex = quantizationTableSpec & 15; int tableIndex = quantizationTableSpec & 15;
int tablePrecision = quantizationTableSpec >> 4;
// Max index. 4 Tables max. // Validate:
if (tableIndex > 3) if (tableIndex > 3)
{ {
JpegThrowHelper.ThrowBadQuantizationTable(); JpegThrowHelper.ThrowBadQuantizationTableIndex(tableIndex);
} }
switch (quantizationTableSpec >> 4) remaining--;
// Decoding single 8x8 table
ref Block8x8F table = ref this.QuantizationTables[tableIndex];
switch (tablePrecision)
{ {
// 8 bit values
case 0: case 0:
{ {
// 8 bit values // Validate: 8 bit table needs exactly 64 bytes
if (remaining < 64) if (remaining < 64)
{ {
done = true; JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DQT), remaining);
break;
} }
stream.Read(this.temp, 0, 64); stream.Read(this.temp, 0, 64);
remaining -= 64; remaining -= 64;
ref Block8x8F table = ref this.QuantizationTables[tableIndex]; // Parsing quantization table & saving it in natural order
for (int j = 0; j < 64; j++) for (int j = 0; j < 64; j++)
{ {
table[j] = this.temp[j]; table[ZigZag.ZigZagOrder[j]] = this.temp[j];
} }
break;
} }
break; // 16 bit values
case 1: case 1:
{ {
// 16 bit values // Validate: 16 bit table needs exactly 128 bytes
if (remaining < 128) if (remaining < 128)
{ {
done = true; JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DQT), remaining);
break;
} }
stream.Read(this.temp, 0, 128); stream.Read(this.temp, 0, 128);
remaining -= 128; remaining -= 128;
ref Block8x8F table = ref this.QuantizationTables[tableIndex]; // Parsing quantization table & saving it in natural order
for (int j = 0; j < 64; j++) for (int j = 0; j < 64; j++)
{ {
table[j] = (this.temp[2 * j] << 8) | this.temp[(2 * j) + 1]; table[ZigZag.ZigZagOrder[j]] = (this.temp[2 * j] << 8) | this.temp[(2 * j) + 1];
} }
}
break; break;
}
// Unknown precision - error
default: default:
{ {
JpegThrowHelper.ThrowBadQuantizationTable(); JpegThrowHelper.ThrowBadQuantizationTablePrecision(tablePrecision);
break; break;
} }
} }
if (done) // Estimating quality
switch (tableIndex)
{ {
break; // luminance table
} case 0:
} {
jpegMetadata.LuminanceQuality = Quantization.EstimateLuminanceQuality(ref table);
break;
}
if (remaining != 0) // chrominance table
{ case 1:
JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DQT), remaining); {
jpegMetadata.ChrominanceQuality = Quantization.EstimateChrominanceQuality(ref table);
break;
}
}
} }
this.Metadata.GetFormatMetadata(JpegFormat.Instance).Quality = QualityEvaluator.EstimateQuality(this.QuantizationTables);
} }
/// <summary> /// <summary>
/// Processes the Start of Frame marker. Specified in section B.2.2. /// Processes the Start of Frame marker. Specified in section B.2.2.
/// </summary> /// </summary>
/// <param name="stream">The input stream.</param> /// <param name="stream">The input stream.</param>
/// <param name="remaining">The remaining bytes in the segment block.</param> /// <param name="remaining">The remaining bytes in the segment block.</param>
@ -835,87 +964,87 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowInvalidImageContentException("Multiple SOF markers. Only single frame jpegs supported."); JpegThrowHelper.ThrowInvalidImageContentException("Multiple SOF markers. Only single frame jpegs supported.");
} }
// Read initial marker definitions. // Read initial marker definitions
const int length = 6; const int length = 6;
stream.Read(this.temp, 0, length); stream.Read(this.temp, 0, length);
// We only support 8-bit and 12-bit precision. // 1 byte: Bits/sample precision
if (Array.IndexOf(this.supportedPrecisions, this.temp[0]) == -1) byte precision = this.temp[0];
// Validate: only 8-bit and 12-bit precisions are supported
if (Array.IndexOf(this.supportedPrecisions, precision) == -1)
{ {
JpegThrowHelper.ThrowInvalidImageContentException("Only 8-Bit and 12-Bit precision supported."); JpegThrowHelper.ThrowInvalidImageContentException("Only 8-Bit and 12-Bit precision supported.");
} }
this.Precision = this.temp[0]; // 2 byte: Height
int frameHeight = (this.temp[1] << 8) | this.temp[2];
this.Frame = new JpegFrame // 2 byte: Width
{ int frameWidth = (this.temp[3] << 8) | this.temp[4];
Extended = frameMarker.Marker == JpegConstants.Markers.SOF1,
Progressive = frameMarker.Marker == JpegConstants.Markers.SOF2, // Validate: width/height > 0 (they are upper-bounded by 2 byte max value so no need to check that)
Precision = this.temp[0], if (frameHeight == 0 || frameWidth == 0)
Scanlines = (this.temp[1] << 8) | this.temp[2],
SamplesPerLine = (this.temp[3] << 8) | this.temp[4],
ComponentCount = this.temp[5]
};
if (this.Frame.SamplesPerLine == 0 || this.Frame.Scanlines == 0)
{ {
JpegThrowHelper.ThrowInvalidImageDimensions(this.Frame.SamplesPerLine, this.Frame.Scanlines); JpegThrowHelper.ThrowInvalidImageDimensions(frameWidth, frameHeight);
} }
this.ImageSizeInPixels = new Size(this.Frame.SamplesPerLine, this.Frame.Scanlines); // 1 byte: Number of components
this.ComponentCount = this.Frame.ComponentCount; byte componentCount = this.temp[5];
if (!metadataOnly) this.Frame = new JpegFrame(frameMarker, precision, frameWidth, frameHeight, componentCount);
remaining -= length;
// Validate: remaining part must be equal to components * 3
const int componentBytes = 3;
if (remaining != componentCount * componentBytes)
{ {
remaining -= length; JpegThrowHelper.ThrowBadMarker("SOFn", remaining);
}
const int componentBytes = 3; // components*3 bytes: component data
if (remaining > this.ComponentCount * componentBytes) stream.Read(this.temp, 0, remaining);
{
JpegThrowHelper.ThrowBadMarker("SOFn", remaining);
}
stream.Read(this.temp, 0, remaining); // No need to pool this. They max out at 4
this.Frame.ComponentIds = new byte[componentCount];
this.Frame.ComponentOrder = new byte[componentCount];
this.Frame.Components = new JpegComponent[componentCount];
// No need to pool this. They max out at 4 int maxH = 0;
this.Frame.ComponentIds = new byte[this.ComponentCount]; int maxV = 0;
this.Frame.ComponentOrder = new byte[this.ComponentCount]; int index = 0;
this.Frame.Components = new JpegComponent[this.ComponentCount]; for (int i = 0; i < componentCount; i++)
this.ColorSpace = this.DeduceJpegColorSpace(); {
byte hv = this.temp[index + 1];
int h = (hv >> 4) & 15;
int v = hv & 15;
int maxH = 0; if (maxH < h)
int maxV = 0;
int index = 0;
for (int i = 0; i < this.ComponentCount; i++)
{ {
byte hv = this.temp[index + 1]; maxH = h;
int h = (hv >> 4) & 15; }
int v = hv & 15;
if (maxH < h) if (maxV < v)
{ {
maxH = h; maxV = v;
} }
if (maxV < v) var component = new JpegComponent(this.Configuration.MemoryAllocator, this.Frame, this.temp[index], h, v, this.temp[index + 2], i);
{
maxV = v;
}
var component = new JpegComponent(this.Configuration.MemoryAllocator, this.Frame, this.temp[index], h, v, this.temp[index + 2], i); this.Frame.Components[i] = component;
this.Frame.ComponentIds[i] = component.Id;
this.Frame.Components[i] = component; index += componentBytes;
this.Frame.ComponentIds[i] = component.Id; }
index += componentBytes; this.ColorSpace = this.DeduceJpegColorSpace(componentCount);
} this.Metadata.GetJpegMetadata().ColorType = this.DeduceJpegColorType();
this.Frame.MaxHorizontalFactor = maxH; if (!metadataOnly)
this.Frame.MaxVerticalFactor = maxV; {
this.ColorSpace = this.DeduceJpegColorSpace(); this.Frame.Init(maxH, maxV);
this.Metadata.GetJpegMetadata().ColorType = this.ColorSpace == JpegColorSpace.Grayscale ? JpegColorType.Luminance : JpegColorType.YCbCr; this.scanDecoder.InjectFrameData(this.Frame, this);
this.Frame.InitComponents();
this.ImageSizeInMCU = new Size(this.Frame.McusPerLine, this.Frame.McusPerColumn);
} }
} }
@ -942,13 +1071,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
// Types 0..1 DC..AC // Types 0..1 DC..AC
if (tableType > 1) if (tableType > 1)
{ {
JpegThrowHelper.ThrowInvalidImageContentException("Bad Huffman Table type."); JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table type: {tableType}");
} }
// Max tables of each type // Max tables of each type
if (tableIndex > 3) if (tableIndex > 3)
{ {
JpegThrowHelper.ThrowInvalidImageContentException("Bad Huffman Table index."); JpegThrowHelper.ThrowInvalidImageContentException($"Bad huffman table index: {tableIndex}");
} }
stream.Read(huffmanDataSpan, 0, 16); stream.Read(huffmanDataSpan, 0, 16);
@ -978,8 +1107,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
i += 17 + codeLengthSum; i += 17 + codeLengthSum;
this.BuildHuffmanTable( this.scanDecoder.BuildHuffmanTable(
tableType == 0 ? this.dcHuffmanTables : this.acHuffmanTables, tableType,
tableIndex, tableIndex,
codeLengthsSpan, codeLengthsSpan,
huffmanValuesSpan); huffmanValuesSpan);
@ -1002,80 +1131,101 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DRI), remaining); JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.DRI), remaining);
} }
this.resetInterval = this.ReadUint16(stream); this.scanDecoder.ResetInterval = this.ReadUint16(stream);
} }
/// <summary> /// <summary>
/// Processes the SOS (Start of scan marker). /// Processes the SOS (Start of scan marker).
/// </summary> /// </summary>
private void ProcessStartOfScanMarker(BufferedReadStream stream, CancellationToken cancellationToken) private void ProcessStartOfScanMarker(BufferedReadStream stream, int remaining)
{ {
if (this.Frame is null) if (this.Frame is null)
{ {
JpegThrowHelper.ThrowInvalidImageContentException("No readable SOFn (Start Of Frame) marker found."); JpegThrowHelper.ThrowInvalidImageContentException("No readable SOFn (Start Of Frame) marker found.");
} }
// 1 byte: Number of components in scan
int selectorsCount = stream.ReadByte(); int selectorsCount = stream.ReadByte();
for (int i = 0; i < selectorsCount; i++)
// Validate: 0 < count <= totalComponents
if (selectorsCount == 0 || selectorsCount > this.Frame.ComponentCount)
{ {
int componentIndex = -1; // TODO: extract as separate method?
int selector = stream.ReadByte(); JpegThrowHelper.ThrowInvalidImageContentException($"Invalid number of components in scan: {selectorsCount}.");
}
// Validate: marker must contain exactly (4 + selectorsCount*2) bytes
int selectorsBytes = selectorsCount * 2;
if (remaining != 4 + selectorsBytes)
{
JpegThrowHelper.ThrowBadMarker("SOS", remaining);
}
// selectorsCount*2 bytes: component index + huffman tables indices
stream.Read(this.temp, 0, selectorsBytes);
this.Frame.MultiScan = this.Frame.ComponentCount != selectorsCount;
for (int i = 0; i < selectorsBytes; i += 2)
{
// 1 byte: Component id
int componentSelectorId = this.temp[i];
int componentIndex = -1;
for (int j = 0; j < this.Frame.ComponentIds.Length; j++) for (int j = 0; j < this.Frame.ComponentIds.Length; j++)
{ {
byte id = this.Frame.ComponentIds[j]; byte id = this.Frame.ComponentIds[j];
if (selector == id) if (componentSelectorId == id)
{ {
componentIndex = j; componentIndex = j;
break; break;
} }
} }
if (componentIndex < 0) // Validate: must be found among registered components
if (componentIndex == -1)
{ {
JpegThrowHelper.ThrowInvalidImageContentException($"Unknown component selector {componentIndex}."); // TODO: extract as separate method?
JpegThrowHelper.ThrowInvalidImageContentException($"Unknown component id in scan: {componentSelectorId}.");
} }
ref JpegComponent component = ref this.Frame.Components[componentIndex]; this.Frame.ComponentOrder[i / 2] = (byte)componentIndex;
int tableSpec = stream.ReadByte();
component.DCHuffmanTableId = tableSpec >> 4; JpegComponent component = this.Frame.Components[componentIndex];
component.ACHuffmanTableId = tableSpec & 15;
this.Frame.ComponentOrder[i] = (byte)componentIndex; // 1 byte: Huffman table selectors.
// 4 bits - dc
// 4 bits - ac
int tableSpec = this.temp[i + 1];
int dcTableIndex = tableSpec >> 4;
int acTableIndex = tableSpec & 15;
// Validate: both must be < 4
if (dcTableIndex >= 4 || acTableIndex >= 4)
{
// TODO: extract as separate method?
JpegThrowHelper.ThrowInvalidImageContentException($"Invalid huffman table for component:{componentSelectorId}: dc={dcTableIndex}, ac={acTableIndex}");
}
component.DCHuffmanTableId = dcTableIndex;
component.ACHuffmanTableId = acTableIndex;
} }
// 3 bytes: Progressive scan decoding data
stream.Read(this.temp, 0, 3); stream.Read(this.temp, 0, 3);
int spectralStart = this.temp[0]; int spectralStart = this.temp[0];
this.scanDecoder.SpectralStart = spectralStart;
int spectralEnd = this.temp[1]; int spectralEnd = this.temp[1];
this.scanDecoder.SpectralEnd = spectralEnd;
int successiveApproximation = this.temp[2]; int successiveApproximation = this.temp[2];
this.scanDecoder.SuccessiveHigh = successiveApproximation >> 4;
this.scanDecoder.SuccessiveLow = successiveApproximation & 15;
var sd = new HuffmanScanDecoder( this.scanDecoder.ParseEntropyCodedData(selectorsCount);
stream,
this.Frame,
this.dcHuffmanTables,
this.acHuffmanTables,
selectorsCount,
this.resetInterval,
spectralStart,
spectralEnd,
successiveApproximation >> 4,
successiveApproximation & 15,
cancellationToken);
sd.ParseEntropyCodedData();
} }
/// <summary>
/// Builds the huffman tables
/// </summary>
/// <param name="tables">The tables</param>
/// <param name="index">The table index</param>
/// <param name="codeLengths">The codelengths</param>
/// <param name="values">The values</param>
[MethodImpl(InliningOptions.ShortMethod)]
private void BuildHuffmanTable(HuffmanTable[] tables, int index, ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values)
=> tables[index] = new HuffmanTable(codeLengths, values);
/// <summary> /// <summary>
/// Reads a <see cref="ushort"/> from the stream advancing it by two bytes /// Reads a <see cref="ushort"/> from the stream advancing it by two bytes
/// </summary> /// </summary>
@ -1087,32 +1237,5 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
stream.Read(this.markerBuffer, 0, 2); stream.Read(this.markerBuffer, 0, 2);
return BinaryPrimitives.ReadUInt16BigEndian(this.markerBuffer); return BinaryPrimitives.ReadUInt16BigEndian(this.markerBuffer);
} }
/// <summary>
/// Post processes the pixels into the destination image.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
private Image<TPixel> PostProcessIntoImage<TPixel>(CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
if (this.ImageWidth == 0 || this.ImageHeight == 0)
{
JpegThrowHelper.ThrowInvalidImageDimensions(this.ImageWidth, this.ImageHeight);
}
var image = Image.CreateUninitialized<TPixel>(
this.Configuration,
this.ImageWidth,
this.ImageHeight,
this.Metadata);
using (var postProcessor = new JpegImagePostProcessor(this.Configuration, this))
{
postProcessor.PostProcess(image.Frames.RootFrame, cancellationToken);
}
return image;
}
} }
} }

40
src/ImageSharp/Formats/Jpeg/JpegEncoder.cs

@ -13,21 +13,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions public sealed class JpegEncoder : IImageEncoder, IJpegEncoderOptions
{ {
/// <summary> /// <inheritdoc/>
/// Gets or sets the quality, that will be used to encode the image. Quality
/// index must be between 0 and 100 (compression from max to min).
/// Defaults to <value>75</value>.
/// </summary>
public int? Quality { get; set; } public int? Quality { get; set; }
/// <summary> /// <inheritdoc/>
/// Gets or sets the subsample ration, that will be used to encode the image.
/// </summary>
public JpegSubsample? Subsample { get; set; }
/// <summary>
/// Gets or sets the color type, that will be used to encode the image.
/// </summary>
public JpegColorType? ColorType { get; set; } public JpegColorType? ColorType { get; set; }
/// <summary> /// <summary>
@ -40,7 +29,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var encoder = new JpegEncoderCore(this); var encoder = new JpegEncoderCore(this);
this.InitializeColorType<TPixel>(image);
encoder.Encode(image, stream); encoder.Encode(image, stream);
} }
@ -56,31 +44,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var encoder = new JpegEncoderCore(this); var encoder = new JpegEncoderCore(this);
this.InitializeColorType<TPixel>(image);
return encoder.EncodeAsync(image, stream, cancellationToken); return encoder.EncodeAsync(image, stream, cancellationToken);
} }
/// <summary>
/// If ColorType was not set, set it based on the given image.
/// </summary>
private void InitializeColorType<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
// First inspect the image metadata.
if (this.ColorType == null)
{
JpegMetadata metadata = image.Metadata.GetJpegMetadata();
this.ColorType = metadata.ColorType;
}
// Secondly, inspect the pixel type.
if (this.ColorType == null)
{
bool isGrayscale =
typeof(TPixel) == typeof(L8) || typeof(TPixel) == typeof(L16) ||
typeof(TPixel) == typeof(La16) || typeof(TPixel) == typeof(La32);
this.ColorType = isGrayscale ? JpegColorType.Luminance : JpegColorType.YCbCr;
}
}
} }
} }

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

@ -33,20 +33,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
private readonly byte[] buffer = new byte[20]; private readonly byte[] buffer = new byte[20];
/// <summary>
/// Gets or sets the subsampling method to use.
/// </summary>
private JpegSubsample? subsample;
/// <summary> /// <summary>
/// The quality, that will be used to encode the image. /// The quality, that will be used to encode the image.
/// </summary> /// </summary>
private readonly int? quality; private readonly int? quality;
/// <summary> /// <summary>
/// Gets or sets the subsampling method to use. /// Gets or sets the colorspace to use.
/// </summary> /// </summary>
private readonly JpegColorType? colorType; private JpegColorType? colorType;
/// <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.
@ -56,51 +51,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JpegEncoderCore"/> class. /// Initializes a new instance of the <see cref="JpegEncoderCore"/> class.
/// </summary> /// </summary>
/// <param name="options">The options</param> /// <param name="options">The options.</param>
public JpegEncoderCore(IJpegEncoderOptions options) public JpegEncoderCore(IJpegEncoderOptions options)
{ {
this.quality = options.Quality; this.quality = options.Quality;
this.subsample = options.Subsample;
this.colorType = options.ColorType;
}
/// <summary>
/// Gets the unscaled quantization tables in zig-zag order. Each
/// encoder copies and scales the tables according to its quality parameter.
/// The values are derived from section K.1 after converting from natural to
/// zig-zag order.
/// </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> UnscaledQuant_Luminance => new byte[]
{
// Luminance.
16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24,
40, 26, 24, 22, 22, 24, 49, 35, 37, 29, 40, 58, 51, 61, 60,
57, 51, 56, 55, 64, 72, 92, 78, 64, 68, 87, 69, 55, 56, 80,
109, 81, 87, 95, 98, 103, 104, 103, 62, 77, 113, 121, 112,
100, 120, 92, 101, 103, 99,
};
/// <summary> if (IsSupportedColorType(options.ColorType))
/// Gets the unscaled quantization tables in zig-zag order. Each {
/// encoder copies and scales the tables according to its quality parameter. this.colorType = options.ColorType;
/// The values are derived from section K.1 after converting from natural to }
/// zig-zag order. }
/// </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> UnscaledQuant_Chrominance => new byte[]
{
// Chrominance.
17, 18, 18, 24, 21, 24, 47, 26, 26, 47, 99, 66, 56, 66,
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
};
/// <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.
@ -124,71 +84,70 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.outputStream = stream; this.outputStream = stream;
ImageMetadata metadata = image.Metadata; ImageMetadata metadata = image.Metadata;
JpegMetadata jpegMetadata = metadata.GetJpegMetadata();
// If the color type was not specified by the user, preserve the color type of the input image.
if (!this.colorType.HasValue)
{
this.colorType = GetFallbackColorType(image);
}
// Compute number of components based on color type in options. // Compute number of components based on color type in options.
int componentCount = (this.colorType == JpegColorType.Luminance) ? 1 : 3; int componentCount = (this.colorType == JpegColorType.Luminance) ? 1 : 3;
ReadOnlySpan<byte> componentIds = this.GetComponentIds();
// System.Drawing produces identical output for jpegs with a quality parameter of 0 and 1. // TODO: Right now encoder writes both quantization tables for grayscale images - we shouldn't do that
int qlty = Numerics.Clamp(this.quality ?? metadata.GetJpegMetadata().Quality, 1, 100); // Initialize the quantization tables.
this.subsample ??= qlty >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420; this.InitQuantizationTables(componentCount, jpegMetadata, out Block8x8F luminanceQuantTable, out Block8x8F chrominanceQuantTable);
// Convert from a quality rating to a scaling factor. // Write the Start Of Image marker.
int scale; this.WriteStartOfImage();
if (qlty < 50)
{
scale = 5000 / qlty;
}
else
{
scale = 200 - (qlty * 2);
}
// Initialize the quantization tables. // Do not write APP0 marker for RGB colorspace.
// TODO: This looks ugly, should we write chrominance table for luminance-only images? if (this.colorType != JpegColorType.Rgb)
// If not - this can code can be simplified
Block8x8F luminanceQuantTable = default;
Block8x8F chrominanceQuantTable = default;
InitQuantizationTable(0, scale, ref luminanceQuantTable);
if (componentCount > 1)
{ {
InitQuantizationTable(1, scale, ref chrominanceQuantTable); this.WriteJfifApplicationHeader(metadata);
} }
// Write the Start Of Image marker.
this.WriteApplicationHeader(metadata);
// Write Exif, ICC and IPTC profiles // Write Exif, ICC and IPTC profiles
this.WriteProfiles(metadata); this.WriteProfiles(metadata);
if (this.colorType == JpegColorType.Rgb)
{
// Write App14 marker to indicate RGB color space.
this.WriteApp14Marker();
}
// Write the quantization tables. // Write the quantization tables.
this.WriteDefineQuantizationTables(ref luminanceQuantTable, ref chrominanceQuantTable); 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, componentIds);
// Write the Huffman tables. // Write the Huffman tables.
this.WriteDefineHuffmanTables(componentCount); this.WriteDefineHuffmanTables(componentCount);
// Write the scan header. // Write the scan header.
this.WriteStartOfScan(image, componentCount, cancellationToken); this.WriteStartOfScan(componentCount, componentIds);
// Write the scan compressed data. // Write the scan compressed data.
var scanEncoder = new HuffmanScanEncoder(stream); switch (this.colorType)
if (this.colorType == JpegColorType.Luminance) {
{ case JpegColorType.YCbCrRatio444:
scanEncoder.EncodeGrayscale(image, ref luminanceQuantTable, cancellationToken); new HuffmanScanEncoder(3, stream).Encode444(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken);
} break;
else case JpegColorType.YCbCrRatio420:
{ new HuffmanScanEncoder(6, stream).Encode420(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken);
switch (this.subsample) break;
{ case JpegColorType.Luminance:
case JpegSubsample.Ratio444: new HuffmanScanEncoder(1, stream).EncodeGrayscale(image, ref luminanceQuantTable, cancellationToken);
scanEncoder.Encode444(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken); break;
break; case JpegColorType.Rgb:
case JpegSubsample.Ratio420: new HuffmanScanEncoder(3, stream).EncodeRgb(image, ref luminanceQuantTable, cancellationToken);
scanEncoder.Encode420(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken); break;
break; default:
} // all other non-supported color types are checked at the start of this method
break;
} }
// Write the End Of Image marker. // Write the End Of Image marker.
@ -198,68 +157,129 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
/// <summary> /// <summary>
/// Writes data to "Define Quantization Tables" block for QuantIndex /// If color type was not set, set it based on the given image.
/// Note, if there is no metadata and the image has multiple components this method
/// returns <see langword="null"/> defering the field assignment
/// to <see cref="InitQuantizationTables(int, JpegMetadata, out Block8x8F, out Block8x8F)"/>.
/// </summary>
private static JpegColorType? GetFallbackColorType<TPixel>(Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
// First inspect the image metadata.
JpegColorType? colorType = null;
JpegMetadata metadata = image.Metadata.GetJpegMetadata();
if (IsSupportedColorType(metadata.ColorType))
{
return metadata.ColorType;
}
// Secondly, inspect the pixel type.
// TODO: PixelTypeInfo should contain a component count!
bool isGrayscale =
typeof(TPixel) == typeof(L8) || typeof(TPixel) == typeof(L16) ||
typeof(TPixel) == typeof(La16) || typeof(TPixel) == typeof(La32);
// We don't set multi-component color types here since we can set it based upon
// the quality in InitQuantizationTables.
if (isGrayscale)
{
colorType = JpegColorType.Luminance;
}
return colorType;
}
/// <summary>
/// Returns true, if the color type is supported by the encoder.
/// </summary> /// </summary>
/// <param name="dqt">The "Define Quantization Tables" block</param> /// <param name="colorType">The color type.</param>
/// <param name="offset">Offset in "Define Quantization Tables" block</param> /// <returns>true, if color type is supported.</returns>
/// <param name="i">The quantization index</param> private static bool IsSupportedColorType(JpegColorType? colorType)
/// <param name="quant">The quantization table to copy data from</param> => colorType == JpegColorType.YCbCrRatio444
|| colorType == JpegColorType.YCbCrRatio420
|| colorType == JpegColorType.Luminance
|| colorType == JpegColorType.Rgb;
/// <summary>
/// Gets the component ids.
/// For color space RGB this will be RGB as ASCII, otherwise 1, 2, 3.
/// </summary>
/// <returns>The component Ids.</returns>
private ReadOnlySpan<byte> GetComponentIds() => this.colorType == JpegColorType.Rgb
? new ReadOnlySpan<byte>(new byte[] { 82, 71, 66 })
: new ReadOnlySpan<byte>(new byte[] { 1, 2, 3 });
/// <summary>
/// Writes data to "Define Quantization Tables" block for QuantIndex.
/// </summary>
/// <param name="dqt">The "Define Quantization Tables" block.</param>
/// <param name="offset">Offset in "Define Quantization Tables" block.</param>
/// <param name="i">The quantization index.</param>
/// <param name="quant">The quantization table to copy data from.</param>
private static void WriteDataToDqt(byte[] dqt, ref int offset, QuantIndex i, ref Block8x8F quant) private static void WriteDataToDqt(byte[] dqt, ref int offset, QuantIndex i, ref Block8x8F quant)
{ {
dqt[offset++] = (byte)i; dqt[offset++] = (byte)i;
for (int j = 0; j < Block8x8F.Size; j++) for (int j = 0; j < Block8x8F.Size; j++)
{ {
dqt[offset++] = (byte)quant[j]; dqt[offset++] = (byte)quant[ZigZag.ZigZagOrder[j]];
} }
} }
/// <summary> /// <summary>
/// Writes the application header containing the JFIF identifier plus extra data. /// Write the start of image marker.
/// </summary> /// </summary>
/// <param name="meta">The image metadata.</param> private void WriteStartOfImage()
private void WriteApplicationHeader(ImageMetadata meta)
{ {
// Write the start of image marker. Markers are always prefixed with 0xff. // Markers are always prefixed with 0xff.
this.buffer[0] = JpegConstants.Markers.XFF; this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = JpegConstants.Markers.SOI; this.buffer[1] = JpegConstants.Markers.SOI;
this.outputStream.Write(this.buffer, 0, 2);
}
/// <summary>
/// Writes the application header containing the JFIF identifier plus extra data.
/// </summary>
/// <param name="meta">The image metadata.</param>
private void WriteJfifApplicationHeader(ImageMetadata meta)
{
// Write the JFIF headers // Write the JFIF headers
this.buffer[2] = JpegConstants.Markers.XFF; this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[3] = JpegConstants.Markers.APP0; // Application Marker this.buffer[1] = JpegConstants.Markers.APP0; // Application Marker
this.buffer[4] = 0x00; this.buffer[2] = 0x00;
this.buffer[5] = 0x10; this.buffer[3] = 0x10;
this.buffer[6] = 0x4a; // J this.buffer[4] = 0x4a; // J
this.buffer[5] = 0x46; // F
this.buffer[6] = 0x49; // I
this.buffer[7] = 0x46; // F this.buffer[7] = 0x46; // F
this.buffer[8] = 0x49; // I this.buffer[8] = 0x00; // = "JFIF",'\0'
this.buffer[9] = 0x46; // F this.buffer[9] = 0x01; // versionhi
this.buffer[10] = 0x00; // = "JFIF",'\0' this.buffer[10] = 0x01; // versionlo
this.buffer[11] = 0x01; // versionhi
this.buffer[12] = 0x01; // versionlo
// Resolution. Big Endian // Resolution. Big Endian
Span<byte> hResolution = this.buffer.AsSpan(14, 2); Span<byte> hResolution = this.buffer.AsSpan(12, 2);
Span<byte> vResolution = this.buffer.AsSpan(16, 2); Span<byte> vResolution = this.buffer.AsSpan(14, 2);
if (meta.ResolutionUnits == PixelResolutionUnit.PixelsPerMeter) if (meta.ResolutionUnits == PixelResolutionUnit.PixelsPerMeter)
{ {
// Scale down to PPI // Scale down to PPI
this.buffer[13] = (byte)PixelResolutionUnit.PixelsPerInch; // xyunits this.buffer[11] = (byte)PixelResolutionUnit.PixelsPerInch; // xyunits
BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.HorizontalResolution))); BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.HorizontalResolution)));
BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.VerticalResolution))); BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.VerticalResolution)));
} }
else else
{ {
// We can simply pass the value. // We can simply pass the value.
this.buffer[13] = (byte)meta.ResolutionUnits; // xyunits this.buffer[11] = (byte)meta.ResolutionUnits; // xyunits
BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(meta.HorizontalResolution)); BinaryPrimitives.WriteInt16BigEndian(hResolution, (short)Math.Round(meta.HorizontalResolution));
BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(meta.VerticalResolution)); BinaryPrimitives.WriteInt16BigEndian(vResolution, (short)Math.Round(meta.VerticalResolution));
} }
// No thumbnail // No thumbnail
this.buffer[18] = 0x00; // Thumbnail width this.buffer[16] = 0x00; // Thumbnail width
this.buffer[19] = 0x00; // Thumbnail height this.buffer[17] = 0x00; // Thumbnail height
this.outputStream.Write(this.buffer, 0, 20); this.outputStream.Write(this.buffer, 0, 18);
} }
/// <summary> /// <summary>
@ -269,7 +289,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
private void WriteDefineHuffmanTables(int componentCount) private void WriteDefineHuffmanTables(int componentCount)
{ {
// Table identifiers. // Table identifiers.
Span<byte> headers = stackalloc byte[] ReadOnlySpan<byte> headers = stackalloc byte[]
{ {
0x00, 0x00,
0x10, 0x10,
@ -306,7 +326,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
private void WriteDefineQuantizationTables(ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable) 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));
this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen); this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen);
@ -322,6 +342,35 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.outputStream.Write(dqt, 0, dqtCount); this.outputStream.Write(dqt, 0, dqtCount);
} }
/// <summary>
/// Writes the APP14 marker to indicate the image is in RGB color space.
/// </summary>
private void WriteApp14Marker()
{
this.WriteMarkerHeader(JpegConstants.Markers.APP14, 2 + AdobeMarker.Length);
// Identifier: ASCII "Adobe".
this.buffer[0] = 0x41;
this.buffer[1] = 0x64;
this.buffer[2] = 0x6F;
this.buffer[3] = 0x62;
this.buffer[4] = 0x65;
// Version, currently 100.
BinaryPrimitives.WriteInt16BigEndian(this.buffer.AsSpan(5, 2), 100);
// Flags0
BinaryPrimitives.WriteInt16BigEndian(this.buffer.AsSpan(7, 2), 0);
// Flags1
BinaryPrimitives.WriteInt16BigEndian(this.buffer.AsSpan(9, 2), 0);
// Transform byte, 0 in combination with three components means the image is in RGB colorspace.
this.buffer[11] = 0;
this.outputStream.Write(this.buffer.AsSpan(0, 12));
}
/// <summary> /// <summary>
/// Writes the EXIF profile. /// Writes the EXIF profile.
/// </summary> /// </summary>
@ -400,7 +449,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
throw new ImageFormatException($"Iptc profile size exceeds limit of {Max} bytes"); throw new ImageFormatException($"Iptc profile size exceeds limit of {Max} bytes");
} }
var app13Length = 2 + ProfileResolver.AdobePhotoshopApp13Marker.Length + int app13Length = 2 + ProfileResolver.AdobePhotoshopApp13Marker.Length +
ProfileResolver.AdobeImageResourceBlockMarker.Length + ProfileResolver.AdobeImageResourceBlockMarker.Length +
ProfileResolver.AdobeIptcMarker.Length + ProfileResolver.AdobeIptcMarker.Length +
2 + 4 + data.Length; 2 + 4 + data.Length;
@ -442,7 +491,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
/// <param name="iccProfile">The ICC profile to write.</param> /// <param name="iccProfile">The ICC profile to write.</param>
/// <exception cref="ImageFormatException"> /// <exception cref="ImageFormatException">
/// Thrown if any of the ICC profiles size exceeds the limit /// Thrown if any of the ICC profiles size exceeds the limit.
/// </exception> /// </exception>
private void WriteIccProfile(IccProfile iccProfile) private void WriteIccProfile(IccProfile iccProfile)
{ {
@ -462,7 +511,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
return; return;
} }
// Calculate the number of markers we'll need, rounding up of course // Calculate the number of markers we'll need, rounding up of course.
int dataLength = data.Length; int dataLength = data.Length;
int count = dataLength / MaxData; int count = dataLength / MaxData;
@ -535,22 +584,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
/// <summary> /// <summary>
/// Writes the Start Of Frame (Baseline) marker /// Writes the Start Of Frame (Baseline) marker.
/// </summary> /// </summary>
/// <param name="width">The width of the image</param> /// <param name="width">The width of the image.</param>
/// <param name="height">The height of the image</param> /// <param name="height">The height of the image.</param>
/// <param name="componentCount">The number of components in a pixel</param> /// <param name="componentCount">The number of components in a pixel.</param>
private void WriteStartOfFrame(int width, int height, int componentCount) /// <param name="componentIds">The component Id's.</param>
private void WriteStartOfFrame(int width, int height, int componentCount, ReadOnlySpan<byte> componentIds)
{ {
// This uses a C#'s compiler optimization that refers to the static data segment of the assembly,
// and doesn't incur any allocation at all.
// "default" to 4:2:0 // "default" to 4:2:0
Span<byte> subsamples = stackalloc byte[] ReadOnlySpan<byte> subsamples = new byte[]
{ {
0x22, 0x22,
0x11, 0x11,
0x11 0x11
}; };
Span<byte> chroma = stackalloc byte[] ReadOnlySpan<byte> chroma = new byte[]
{ {
0x00, 0x00,
0x01, 0x01,
@ -559,7 +611,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (this.colorType == JpegColorType.Luminance) if (this.colorType == JpegColorType.Luminance)
{ {
subsamples = stackalloc byte[] subsamples = new byte[]
{ {
0x11, 0x11,
0x00, 0x00,
@ -568,18 +620,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
else else
{ {
switch (this.subsample) switch (this.colorType)
{ {
case JpegSubsample.Ratio444: case JpegColorType.YCbCrRatio444:
subsamples = stackalloc byte[] case JpegColorType.Rgb:
subsamples = new byte[]
{ {
0x11, 0x11,
0x11, 0x11,
0x11 0x11
}; };
if (this.colorType == JpegColorType.Rgb)
{
chroma = new byte[]
{
0x00,
0x00,
0x00
};
}
break; break;
case JpegSubsample.Ratio420: case JpegColorType.YCbCrRatio420:
subsamples = stackalloc byte[] subsamples = new byte[]
{ {
0x22, 0x22,
0x11, 0x11,
@ -602,10 +666,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
for (int i = 0; i < componentCount; i++) for (int i = 0; i < componentCount; i++)
{ {
int i3 = 3 * i; int i3 = 3 * i;
this.buffer[i3 + 6] = (byte)(i + 1);
this.buffer[i3 + 7] = subsamples[i]; // Component ID.
this.buffer[i3 + 8] = chroma[i]; Span<byte> bufferSpan = this.buffer.AsSpan(i3 + 6, 3);
bufferSpan[2] = chroma[i];
bufferSpan[1] = subsamples[i];
bufferSpan[0] = componentIds[i];
} }
this.outputStream.Write(this.buffer, 0, (3 * (componentCount - 1)) + 9); this.outputStream.Write(this.buffer, 0, (3 * (componentCount - 1)) + 9);
@ -614,26 +680,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary> /// <summary>
/// Writes the StartOfScan marker. /// Writes the StartOfScan marker.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="image">The pixel accessor providing access to the image pixels.</param>
/// <param name="componentCount">The number of components in a pixel.</param> /// <param name="componentCount">The number of components in a pixel.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param> /// <param name="componentIds">The componentId's.</param>
private void WriteStartOfScan<TPixel>(Image<TPixel> image, int componentCount, CancellationToken cancellationToken) private void WriteStartOfScan(int componentCount, ReadOnlySpan<byte> componentIds)
where TPixel : unmanaged, IPixel<TPixel>
{ {
Span<byte> componentId = stackalloc byte[] // This uses a C#'s compiler optimization that refers to the static data segment of the assembly,
{ // and doesn't incur any allocation at all.
0x01, ReadOnlySpan<byte> huffmanId = new byte[]
0x02,
0x03
};
Span<byte> huffmanId = stackalloc byte[]
{ {
0x00, 0x00,
0x11, 0x11,
0x11 0x11
}; };
// Use the same DC/AC tables for all channels for RGB.
if (this.colorType == JpegColorType.Rgb)
{
huffmanId = new byte[]
{
0x00,
0x00,
0x00
};
}
// Write the SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes: // Write the SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes:
// - the marker length "\x00\x0c", // - the marker length "\x00\x0c",
// - the number of components "\x03", // - the number of components "\x03",
@ -654,7 +724,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
for (int i = 0; i < componentCount; i++) for (int i = 0; i < componentCount; i++)
{ {
int i2 = 2 * i; int i2 = 2 * i;
this.buffer[i2 + 5] = componentId[i]; // Component Id this.buffer[i2 + 5] = componentIds[i]; // Component Id
this.buffer[i2 + 6] = huffmanId[i]; // DC/AC Huffman table this.buffer[i2 + 6] = huffmanId[i]; // DC/AC Huffman table
} }
@ -690,31 +760,53 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
/// <summary> /// <summary>
/// Initializes quantization table. /// Initializes quantization tables.
/// </summary> /// </summary>
/// <param name="i">The quantization index.</param> /// <remarks>
/// <param name="scale">The scaling factor.</param> /// <para>
/// <param name="quant">The quantization table.</param> /// Zig-zag ordering is NOT applied to the resulting tables.
private static void InitQuantizationTable(int i, int scale, ref Block8x8F quant) /// </para>
/// <para>
/// We take quality values in a hierarchical order:
/// 1. Check if encoder has set quality
/// 2. Check if metadata has set quality
/// 3. Take default quality value - 75
/// </para>
/// </remarks>
/// <param name="componentCount">Color components count.</param>
/// <param name="metadata">Jpeg metadata instance.</param>
/// <param name="luminanceQuantTable">Output luminance quantization table.</param>
/// <param name="chrominanceQuantTable">Output chrominance quantization table.</param>
private void InitQuantizationTables(int componentCount, JpegMetadata metadata, out Block8x8F luminanceQuantTable, out Block8x8F chrominanceQuantTable)
{ {
DebugGuard.MustBeBetweenOrEqualTo(i, 0, 1, nameof(i)); int lumaQuality;
ReadOnlySpan<byte> unscaledQuant = (i == 0) ? UnscaledQuant_Luminance : UnscaledQuant_Chrominance; int chromaQuality;
if (this.quality.HasValue)
{
lumaQuality = this.quality.Value;
chromaQuality = this.quality.Value;
}
else
{
lumaQuality = metadata.LuminanceQuality;
chromaQuality = metadata.ChrominanceQuality;
}
for (int j = 0; j < Block8x8F.Size; j++) // Luminance
lumaQuality = Numerics.Clamp(lumaQuality, 1, 100);
luminanceQuantTable = Quantization.ScaleLuminanceTable(lumaQuality);
// Chrominance
chrominanceQuantTable = default;
if (componentCount > 1)
{ {
int x = unscaledQuant[j]; chromaQuality = Numerics.Clamp(chromaQuality, 1, 100);
x = ((x * scale) + 50) / 100; chrominanceQuantTable = Quantization.ScaleChrominanceTable(chromaQuality);
if (x < 1)
{
x = 1;
}
if (x > 255) if (!this.colorType.HasValue)
{ {
x = 255; this.colorType = chromaQuality >= 91 ? JpegColorType.YCbCrRatio444 : JpegColorType.YCbCrRatio420;
} }
quant[j] = x;
} }
} }
} }

81
src/ImageSharp/Formats/Jpeg/JpegMetadata.cs

@ -1,6 +1,9 @@
// 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 SixLabors.ImageSharp.Formats.Jpeg.Components;
namespace SixLabors.ImageSharp.Formats.Jpeg namespace SixLabors.ImageSharp.Formats.Jpeg
{ {
/// <summary> /// <summary>
@ -8,6 +11,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary> /// </summary>
public class JpegMetadata : IDeepCloneable public class JpegMetadata : IDeepCloneable
{ {
/// <summary>
/// Backing field for <see cref="LuminanceQuality"/>
/// </summary>
private int? luminanceQuality;
/// <summary>
/// Backing field for <see cref="ChrominanceQuality"/>
/// </summary>
private int? chrominanceQuality;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="JpegMetadata"/> class. /// Initializes a new instance of the <see cref="JpegMetadata"/> class.
/// </summary> /// </summary>
@ -21,18 +34,80 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="other">The metadata to create an instance from.</param> /// <param name="other">The metadata to create an instance from.</param>
private JpegMetadata(JpegMetadata other) private JpegMetadata(JpegMetadata other)
{ {
this.Quality = other.Quality;
this.ColorType = other.ColorType; this.ColorType = other.ColorType;
this.luminanceQuality = other.luminanceQuality;
this.chrominanceQuality = other.chrominanceQuality;
} }
/// <summary> /// <summary>
/// Gets or sets the encoded quality. /// Gets or sets the jpeg luminance quality.
/// </summary>
/// <remarks>
/// This value might not be accurate if it was calculated during jpeg decoding
/// with non-complient ITU quantization tables.
/// </remarks>
internal int LuminanceQuality
{
get => this.luminanceQuality ?? Quantization.DefaultQualityFactor;
set => this.luminanceQuality = value;
}
/// <summary>
/// Gets or sets the jpeg chrominance quality.
/// </summary> /// </summary>
public int Quality { get; set; } = 75; /// <remarks>
/// This value might not be accurate if it was calculated during jpeg decoding
/// with non-complient ITU quantization tables.
/// </remarks>
internal int ChrominanceQuality
{
get => this.chrominanceQuality ?? Quantization.DefaultQualityFactor;
set => this.chrominanceQuality = value;
}
/// <summary> /// <summary>
/// Gets or sets the encoded quality. /// Gets or sets the encoded quality.
/// </summary> /// </summary>
/// <remarks>
/// Note that jpeg image can have different quality for luminance and chrominance components.
/// This property returns maximum value of luma/chroma qualities.
/// </remarks>
public int Quality
{
get
{
// Jpeg always has a luminance table thus it must have a luminance quality derived from it
if (!this.luminanceQuality.HasValue)
{
return Quantization.DefaultQualityFactor;
}
int lumaQuality = this.luminanceQuality.Value;
// Jpeg might not have a chrominance table - return luminance quality (grayscale images)
if (!this.chrominanceQuality.HasValue)
{
return lumaQuality;
}
int chromaQuality = this.chrominanceQuality.Value;
// Theoretically, luma quality would always be greater or equal to chroma quality
// But we've already encountered images which can have higher quality of chroma components
return Math.Max(lumaQuality, chromaQuality);
}
set
{
this.LuminanceQuality = value;
this.ChrominanceQuality = value;
}
}
/// <summary>
/// Gets or sets the color type.
/// </summary>
public JpegColorType? ColorType { get; set; } public JpegColorType? ColorType { get; set; }
/// <inheritdoc/> /// <inheritdoc/>

23
src/ImageSharp/Formats/Jpeg/JpegSubsample.cs

@ -1,23 +0,0 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Jpeg
{
/// <summary>
/// Enumerates the chroma subsampling method applied to the image.
/// </summary>
public enum JpegSubsample
{
/// <summary>
/// High Quality - Each of the three Y'CbCr components have the same sample rate,
/// thus there is no chroma subsampling.
/// </summary>
Ratio444,
/// <summary>
/// Medium Quality - The horizontal sampling is halved and the Cb and Cr channels are only
/// sampled on each alternate line.
/// </summary>
Ratio420
}
}

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

@ -8,6 +8,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{ {
internal static class JpegThrowHelper internal static class JpegThrowHelper
{ {
/// <summary>
/// Cold path optimization for throwing <see cref="NotSupportedException"/>'s.
/// </summary>
/// <param name="errorMessage">The error message for the exception.</param>
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNotSupportedException(string errorMessage) => throw new NotSupportedException(errorMessage);
/// <summary> /// <summary>
/// Cold path optimization for throwing <see cref="InvalidImageContentException"/>'s. /// Cold path optimization for throwing <see cref="InvalidImageContentException"/>'s.
/// </summary> /// </summary>
@ -36,7 +43,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public static void ThrowBadMarker(string marker, int length) => throw new InvalidImageContentException($"Marker {marker} has bad length {length}."); public static void ThrowBadMarker(string marker, int length) => throw new InvalidImageContentException($"Marker {marker} has bad length {length}.");
[MethodImpl(InliningOptions.ColdPath)] [MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadQuantizationTable() => throw new InvalidImageContentException("Bad Quantization Table index."); public static void ThrowBadQuantizationTableIndex(int index) => throw new InvalidImageContentException($"Bad Quantization Table index {index}.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadQuantizationTablePrecision(int precision) => throw new InvalidImageContentException($"Unknown Quantization Table precision {precision}.");
[MethodImpl(InliningOptions.ColdPath)] [MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadSampling() => throw new InvalidImageContentException("Bad sampling factor."); public static void ThrowBadSampling() => throw new InvalidImageContentException("Bad sampling factor.");

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

@ -506,11 +506,15 @@ namespace SixLabors.ImageSharp.Formats.Png
while (this.currentRow < this.header.Height) while (this.currentRow < this.header.Height)
{ {
Span<byte> scanlineSpan = this.scanline.GetSpan(); Span<byte> scanlineSpan = this.scanline.GetSpan();
int bytesRead = compressedStream.Read(scanlineSpan, this.currentRowBytesRead, this.bytesPerScanline - this.currentRowBytesRead); while (this.currentRowBytesRead < this.bytesPerScanline)
this.currentRowBytesRead += bytesRead;
if (this.currentRowBytesRead < this.bytesPerScanline)
{ {
return; int bytesRead = compressedStream.Read(scanlineSpan, this.currentRowBytesRead, this.bytesPerScanline - this.currentRowBytesRead);
if (bytesRead <= 0)
{
return;
}
this.currentRowBytesRead += bytesRead;
} }
this.currentRowBytesRead = 0; this.currentRowBytesRead = 0;
@ -577,11 +581,15 @@ namespace SixLabors.ImageSharp.Formats.Png
while (this.currentRow < this.header.Height) while (this.currentRow < this.header.Height)
{ {
int bytesRead = compressedStream.Read(this.scanline.GetSpan(), this.currentRowBytesRead, bytesPerInterlaceScanline - this.currentRowBytesRead); while (this.currentRowBytesRead < bytesPerInterlaceScanline)
this.currentRowBytesRead += bytesRead;
if (this.currentRowBytesRead < bytesPerInterlaceScanline)
{ {
return; int bytesRead = compressedStream.Read(this.scanline.GetSpan(), this.currentRowBytesRead, bytesPerInterlaceScanline - this.currentRowBytesRead);
if (bytesRead <= 0)
{
return;
}
this.currentRowBytesRead += bytesRead;
} }
this.currentRowBytesRead = 0; this.currentRowBytesRead = 0;
@ -1063,7 +1071,7 @@ namespace SixLabors.ImageSharp.Formats.Png
int bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length); int bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length);
while (bytesRead != 0) while (bytesRead != 0)
{ {
uncompressedBytes.AddRange(this.buffer.AsSpan().Slice(0, bytesRead).ToArray()); uncompressedBytes.AddRange(this.buffer.AsSpan(0, bytesRead).ToArray());
bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length); bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length);
} }

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

@ -268,35 +268,27 @@ namespace SixLabors.ImageSharp.Formats.Png
if (this.use16Bit) if (this.use16Bit)
{ {
// 16 bit grayscale + alpha // 16 bit grayscale + alpha
// TODO: Should we consider in the future a GrayAlpha32 type. using IMemoryOwner<La32> laBuffer = this.memoryAllocator.Allocate<La32>(rowSpan.Length);
using (IMemoryOwner<Rgba64> rgbaBuffer = this.memoryAllocator.Allocate<Rgba64>(rowSpan.Length)) Span<La32> laSpan = laBuffer.GetSpan();
{ ref La32 laRef = ref MemoryMarshal.GetReference(laSpan);
Span<Rgba64> rgbaSpan = rgbaBuffer.GetSpan(); PixelOperations<TPixel>.Instance.ToLa32(this.configuration, rowSpan, laSpan);
ref Rgba64 rgbaRef = ref MemoryMarshal.GetReference(rgbaSpan);
PixelOperations<TPixel>.Instance.ToRgba64(this.configuration, rowSpan, rgbaSpan);
// Can't map directly to byte array as it's big endian. // Can't map directly to byte array as it's big endian.
for (int x = 0, o = 0; x < rgbaSpan.Length; x++, o += 4) for (int x = 0, o = 0; x < laSpan.Length; x++, o += 4)
{ {
Rgba64 rgba = Unsafe.Add(ref rgbaRef, x); La32 la = Unsafe.Add(ref laRef, x);
ushort luminance = ColorNumerics.Get16BitBT709Luminance(rgba.R, rgba.G, rgba.B); BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), la.L);
BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o, 2), luminance); BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), la.A);
BinaryPrimitives.WriteUInt16BigEndian(rawScanlineSpan.Slice(o + 2, 2), rgba.A);
}
} }
} }
else else
{ {
// 8 bit grayscale + alpha // 8 bit grayscale + alpha
// TODO: Should we consider in the future a GrayAlpha16 type. PixelOperations<TPixel>.Instance.ToLa16Bytes(
Rgba32 rgba = default; this.configuration,
for (int x = 0, o = 0; x < rowSpan.Length; x++, o += 2) rowSpan,
{ rawScanlineSpan,
Unsafe.Add(ref rowSpanRef, x).ToRgba32(ref rgba); rowSpan.Length);
Unsafe.Add(ref rawScanlineSpanRef, o) =
ColorNumerics.Get8BitBT709Luminance(rgba.R, rgba.G, rgba.B);
Unsafe.Add(ref rawScanlineSpanRef, o + 1) = rgba.A;
}
} }
} }
} }

8
src/ImageSharp/Formats/Png/PngEncoderOptions.cs

@ -18,11 +18,7 @@ namespace SixLabors.ImageSharp.Formats.Png
{ {
this.BitDepth = source.BitDepth; this.BitDepth = source.BitDepth;
this.ColorType = source.ColorType; this.ColorType = source.ColorType;
this.FilterMethod = source.FilterMethod;
// Specification recommends default filter method None for paletted images and Paeth for others.
this.FilterMethod = source.FilterMethod ?? (source.ColorType == PngColorType.Palette
? PngFilterMethod.None
: PngFilterMethod.Paeth);
this.CompressionLevel = source.CompressionLevel; this.CompressionLevel = source.CompressionLevel;
this.TextCompressionThreshold = source.TextCompressionThreshold; this.TextCompressionThreshold = source.TextCompressionThreshold;
this.Gamma = source.Gamma; this.Gamma = source.Gamma;
@ -41,7 +37,7 @@ namespace SixLabors.ImageSharp.Formats.Png
public PngColorType? ColorType { get; set; } public PngColorType? ColorType { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public PngFilterMethod? FilterMethod { get; } public PngFilterMethod? FilterMethod { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public PngCompressionLevel CompressionLevel { get; } = PngCompressionLevel.DefaultCompression; public PngCompressionLevel CompressionLevel { get; } = PngCompressionLevel.DefaultCompression;

12
src/ImageSharp/Formats/Png/PngEncoderOptionsHelpers.cs

@ -34,6 +34,18 @@ namespace SixLabors.ImageSharp.Formats.Png
// a sensible default based upon the pixel format. // a sensible default based upon the pixel format.
options.ColorType ??= pngMetadata.ColorType ?? SuggestColorType<TPixel>(); options.ColorType ??= pngMetadata.ColorType ?? SuggestColorType<TPixel>();
options.BitDepth ??= pngMetadata.BitDepth ?? SuggestBitDepth<TPixel>(); options.BitDepth ??= pngMetadata.BitDepth ?? SuggestBitDepth<TPixel>();
if (!options.FilterMethod.HasValue)
{
// Specification recommends default filter method None for paletted images and Paeth for others.
if (options.ColorType == PngColorType.Palette)
{
options.FilterMethod = PngFilterMethod.None;
}
else
{
options.FilterMethod = PngFilterMethod.Paeth;
}
}
// Ensure bit depth and color type are a supported combination. // Ensure bit depth and color type are a supported combination.
// Bit8 is the only bit depth supported by all color types. // Bit8 is the only bit depth supported by all color types.

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

@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
private static bool IsReplicateRun(ReadOnlySpan<byte> rowSpan, int startPos) private static bool IsReplicateRun(ReadOnlySpan<byte> rowSpan, int startPos)
{ {
// We consider run which has at least 3 same consecutive bytes a candidate for a run. // We consider run which has at least 3 same consecutive bytes a candidate for a run.
var startByte = rowSpan[startPos]; byte startByte = rowSpan[startPos];
int count = 0; int count = 0;
for (int i = startPos + 1; i < rowSpan.Length; i++) for (int i = startPos + 1; i < rowSpan.Length; i++)
{ {

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

@ -0,0 +1,49 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Compressors
{
internal class TiffJpegCompressor : TiffBaseCompressor
{
public TiffJpegCompressor(Stream output, MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None)
: base(output, memoryAllocator, width, bitsPerPixel, predictor)
{
}
/// <inheritdoc/>
public override TiffCompression Method => TiffCompression.Jpeg;
/// <inheritdoc/>
public override void Initialize(int rowsPerStrip)
{
}
/// <inheritdoc/>
public override void CompressStrip(Span<byte> rows, int height)
{
int pixelCount = rows.Length / 3;
int width = pixelCount / height;
using var memoryStream = new MemoryStream();
var image = Image.LoadPixelData<Rgb24>(rows, width, height);
image.Save(memoryStream, new JpegEncoder()
{
ColorType = JpegColorType.Rgb
});
memoryStream.Position = 0;
memoryStream.WriteTo(this.Output);
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
}
}

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

@ -0,0 +1,154 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
/// <summary>
/// Represents a reference scan line for CCITT 2D decoding.
/// </summary>
internal readonly ref struct CcittReferenceScanline
{
private readonly ReadOnlySpan<byte> scanLine;
private readonly int width;
private readonly byte whiteByte;
/// <summary>
/// Initializes a new instance of the <see cref="CcittReferenceScanline"/> struct.
/// </summary>
/// <param name="whiteIsZero">Indicates, if white is zero, otherwise black is zero.</param>
/// <param name="scanLine">The scan line.</param>
public CcittReferenceScanline(bool whiteIsZero, ReadOnlySpan<byte> scanLine)
{
this.scanLine = scanLine;
this.width = scanLine.Length;
this.whiteByte = whiteIsZero ? (byte)0 : (byte)255;
}
/// <summary>
/// Initializes a new instance of the <see cref="CcittReferenceScanline"/> struct.
/// </summary>
/// <param name="whiteIsZero">Indicates, if white is zero, otherwise black is zero.</param>
/// <param name="width">The width of the scanline.</param>
public CcittReferenceScanline(bool whiteIsZero, int width)
{
this.scanLine = default;
this.width = width;
this.whiteByte = whiteIsZero ? (byte)0 : (byte)255;
}
public bool IsEmpty => this.scanLine.IsEmpty;
/// <summary>
/// Finds b1: The first changing element on the reference line to the right of a0 and of opposite color to a0.
/// </summary>
/// <param name="a0">The reference or starting element om the coding line.</param>
/// <param name="a0Byte">Fill byte.</param>
/// <returns>Position of b1.</returns>
public int FindB1(int a0, byte a0Byte)
{
if (this.IsEmpty)
{
return this.FindB1ForImaginaryWhiteLine(a0, a0Byte);
}
return this.FindB1ForNormalLine(a0, a0Byte);
}
/// <summary>
/// Finds b2: The next changing element to the right of b1 on the reference line.
/// </summary>
/// <param name="b1">The first changing element on the reference line to the right of a0 and opposite of color to a0.</param>
/// <returns>Position of b1.</returns>
public int FindB2(int b1)
{
if (this.IsEmpty)
{
return this.FindB2ForImaginaryWhiteLine();
}
return this.FindB2ForNormalLine(b1);
}
private int FindB1ForImaginaryWhiteLine(int a0, byte a0Byte)
{
if (a0 < 0)
{
if (a0Byte != this.whiteByte)
{
return 0;
}
}
return this.width;
}
private int FindB1ForNormalLine(int a0, byte a0Byte)
{
int offset = 0;
if (a0 < 0)
{
if (a0Byte != this.scanLine[0])
{
return 0;
}
}
else
{
offset = a0;
}
ReadOnlySpan<byte> searchSpace = this.scanLine.Slice(offset);
byte searchByte = (byte)~a0Byte;
int index = searchSpace.IndexOf(searchByte);
if (index < 0)
{
return this.scanLine.Length;
}
if (index != 0)
{
return offset + index;
}
searchByte = (byte)~searchSpace[0];
index = searchSpace.IndexOf(searchByte);
if (index < 0)
{
return this.scanLine.Length;
}
searchSpace = searchSpace.Slice(index);
offset += index;
index = searchSpace.IndexOf((byte)~searchByte);
if (index < 0)
{
return this.scanLine.Length;
}
return index + offset;
}
private int FindB2ForImaginaryWhiteLine() => this.width;
private int FindB2ForNormalLine(int b1)
{
if (b1 >= this.scanLine.Length)
{
return this.scanLine.Length;
}
byte searchByte = (byte)~this.scanLine[b1];
int offset = b1 + 1;
ReadOnlySpan<byte> searchSpace = this.scanLine.Slice(offset);
int index = searchSpace.IndexOf(searchByte);
if (index == -1)
{
return this.scanLine.Length;
}
return offset + index;
}
}
}

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

@ -0,0 +1,27 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Diagnostics;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
[DebuggerDisplay("Type = {Type}")]
internal readonly struct CcittTwoDimensionalCode
{
private readonly ushort value;
/// <summary>
/// Initializes a new instance of the <see cref="CcittTwoDimensionalCode"/> struct.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="bitsRequired">The bits required.</param>
/// <param name="extensionBits">The extension bits.</param>
public CcittTwoDimensionalCode(CcittTwoDimensionalCodeType type, int bitsRequired, int extensionBits = 0)
=> this.value = (ushort)((byte)type | ((bitsRequired & 0b1111) << 8) | ((extensionBits & 0b111) << 11));
/// <summary>
/// Gets the code type.
/// </summary>
public CcittTwoDimensionalCodeType Type => (CcittTwoDimensionalCodeType)(this.value & 0b11111111);
}
}

73
src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCodeType.cs

@ -0,0 +1,73 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
/// <summary>
/// Enum for the different two dimensional code words for the ccitt fax compression.
/// </summary>
internal enum CcittTwoDimensionalCodeType
{
/// <summary>
/// No valid code word was read.
/// </summary>
None = 0,
/// <summary>
/// Pass mode: This mode is identified when the position of b2 lies to the left of a1.
/// </summary>
Pass = 1,
/// <summary>
/// Indicates horizontal mode.
/// </summary>
Horizontal = 2,
/// <summary>
/// Vertical 0 code word: relative distance between a1 and b1 is 0.
/// </summary>
Vertical0 = 3,
/// <summary>
/// Vertical r1 code word: relative distance between a1 and b1 is 1, a1 is to the right of b1.
/// </summary>
VerticalR1 = 4,
/// <summary>
/// Vertical r2 code word: relative distance between a1 and b1 is 2, a1 is to the right of b1.
/// </summary>
VerticalR2 = 5,
/// <summary>
/// Vertical r3 code word: relative distance between a1 and b1 is 3, a1 is to the right of b1.
/// </summary>
VerticalR3 = 6,
/// <summary>
/// Vertical l1 code word: relative distance between a1 and b1 is 1, a1 is to the left of b1.
/// </summary>
VerticalL1 = 7,
/// <summary>
/// Vertical l2 code word: relative distance between a1 and b1 is 2, a1 is to the left of b1.
/// </summary>
VerticalL2 = 8,
/// <summary>
/// Vertical l3 code word: relative distance between a1 and b1 is 3, a1 is to the left of b1.
/// </summary>
VerticalL3 = 9,
/// <summary>
/// 1d extensions code word, extension code is used to indicate the change from the current mode to another mode, e.g., another coding scheme.
/// Not supported.
/// </summary>
Extensions1D = 10,
/// <summary>
/// 2d extensions code word, extension code is used to indicate the change from the current mode to another mode, e.g., another coding scheme.
/// Not supported.
/// </summary>
Extensions2D = 11,
}
}

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

@ -3,10 +3,9 @@
using System; using System;
using System.IO.Compression; using System.IO.Compression;
using SixLabors.ImageSharp.Compression.Zlib; using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Tiff.Compression;
using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -18,22 +17,30 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <remarks> /// <remarks>
/// Note that the 'OldDeflate' compression type is identical to the 'Deflate' compression type. /// Note that the 'OldDeflate' compression type is identical to the 'Deflate' compression type.
/// </remarks> /// </remarks>
internal class DeflateTiffCompression : TiffBaseDecompressor internal sealed class DeflateTiffCompression : TiffBaseDecompressor
{ {
private readonly bool isBigEndian;
private readonly TiffColorType colorType;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="DeflateTiffCompression" /> class. /// Initializes a new instance of the <see cref="DeflateTiffCompression" /> class.
/// </summary> /// </summary>
/// <param name="memoryAllocator">The memoryAllocator to use for buffer allocations.</param> /// <param name="memoryAllocator">The memoryAllocator to use for buffer allocations.</param>
/// <param name="width">The image width.</param> /// <param name="width">The image width.</param>
/// <param name="bitsPerPixel">The bits used per pixel.</param> /// <param name="bitsPerPixel">The bits used per pixel.</param>
/// <param name="colorType">The color type of the pixel data.</param>
/// <param name="predictor">The tiff predictor used.</param> /// <param name="predictor">The tiff predictor used.</param>
public DeflateTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor) /// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public DeflateTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffColorType colorType, TiffPredictor predictor, bool isBigEndian)
: base(memoryAllocator, width, bitsPerPixel, predictor) : base(memoryAllocator, width, bitsPerPixel, predictor)
{ {
this.colorType = colorType;
this.isBigEndian = isBigEndian;
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer) protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
{ {
long pos = stream.Position; long pos = stream.Position;
using (var deframeStream = new ZlibInflateStream( using (var deframeStream = new ZlibInflateStream(
@ -46,12 +53,23 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{ {
deframeStream.AllocateNewBytes(byteCount, true); deframeStream.AllocateNewBytes(byteCount, true);
DeflateStream dataStream = deframeStream.CompressedStream; DeflateStream dataStream = deframeStream.CompressedStream;
dataStream.Read(buffer, 0, buffer.Length);
int totalRead = 0;
while (totalRead < buffer.Length)
{
int bytesRead = dataStream.Read(buffer, totalRead, buffer.Length - totalRead);
if (bytesRead <= 0)
{
break;
}
totalRead += bytesRead;
}
} }
if (this.Predictor == TiffPredictor.Horizontal) if (this.Predictor == TiffPredictor.Horizontal)
{ {
HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel); HorizontalPredictor.Undo(buffer, this.Width, this.colorType, this.isBigEndian);
} }
} }

94
src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs

@ -0,0 +1,94 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.InteropServices;
using System.Threading;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Metadata;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
/// <summary>
/// Class to handle cases where TIFF image data is compressed as a jpeg stream.
/// </summary>
internal sealed class JpegTiffCompression : TiffBaseDecompressor
{
private readonly Configuration configuration;
private readonly byte[] jpegTables;
private readonly TiffPhotometricInterpretation photometricInterpretation;
/// <summary>
/// Initializes a new instance of the <see cref="JpegTiffCompression"/> class.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="memoryAllocator">The memoryAllocator to use for buffer allocations.</param>
/// <param name="width">The image width.</param>
/// <param name="bitsPerPixel">The bits per pixel.</param>
/// <param name="jpegTables">The JPEG tables containing the quantization and/or Huffman tables.</param>
/// <param name="photometricInterpretation">The photometric interpretation.</param>
public JpegTiffCompression(
Configuration configuration,
MemoryAllocator memoryAllocator,
int width,
int bitsPerPixel,
byte[] jpegTables,
TiffPhotometricInterpretation photometricInterpretation)
: base(memoryAllocator, width, bitsPerPixel)
{
this.configuration = configuration;
this.jpegTables = jpegTables;
this.photometricInterpretation = photometricInterpretation;
}
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
{
if (this.jpegTables != null)
{
using var jpegDecoder = new JpegDecoderCore(this.configuration, new JpegDecoder());
// TODO: Should we pass through the CancellationToken from the tiff decoder?
// If the PhotometricInterpretation is YCbCr we explicitly assume the JPEG data is in RGB color space.
// There seems no other way to determine that the JPEG data is RGB colorspace (no APP14 marker, componentId's are not RGB).
using SpectralConverter<Rgb24> spectralConverter = this.photometricInterpretation == TiffPhotometricInterpretation.YCbCr ?
new RgbJpegSpectralConverter<Rgb24>(this.configuration, CancellationToken.None) : new SpectralConverter<Rgb24>(this.configuration, CancellationToken.None);
var scanDecoder = new HuffmanScanDecoder(stream, spectralConverter, CancellationToken.None);
jpegDecoder.LoadTables(this.jpegTables, scanDecoder);
scanDecoder.ResetInterval = 0;
jpegDecoder.ParseStream(stream, scanDecoder, CancellationToken.None);
CopyImageBytesToBuffer(buffer, spectralConverter.GetPixelBuffer());
}
else
{
using var image = Image.Load<Rgb24>(stream);
CopyImageBytesToBuffer(buffer, image.Frames.RootFrame.PixelBuffer);
}
}
private static void CopyImageBytesToBuffer(Span<byte> buffer, Buffer2D<Rgb24> pixelBuffer)
{
int offset = 0;
for (int y = 0; y < pixelBuffer.Height; y++)
{
Span<Rgb24> pixelRowSpan = pixelBuffer.GetRowSpan(y);
Span<byte> rgbBytes = MemoryMarshal.AsBytes(pixelRowSpan);
rgbBytes.CopyTo(buffer.Slice(offset));
offset += rgbBytes.Length;
}
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
}
}

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

@ -3,6 +3,7 @@
using System; using System;
using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -11,29 +12,37 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <summary> /// <summary>
/// Class to handle cases where TIFF image data is compressed using LZW compression. /// Class to handle cases where TIFF image data is compressed using LZW compression.
/// </summary> /// </summary>
internal class LzwTiffCompression : TiffBaseDecompressor internal sealed class LzwTiffCompression : TiffBaseDecompressor
{ {
private readonly bool isBigEndian;
private readonly TiffColorType colorType;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="LzwTiffCompression" /> class. /// Initializes a new instance of the <see cref="LzwTiffCompression" /> class.
/// </summary> /// </summary>
/// <param name="memoryAllocator">The memoryAllocator to use for buffer allocations.</param> /// <param name="memoryAllocator">The memoryAllocator to use for buffer allocations.</param>
/// <param name="width">The image width.</param> /// <param name="width">The image width.</param>
/// <param name="bitsPerPixel">The bits used per pixel.</param> /// <param name="bitsPerPixel">The bits used per pixel.</param>
/// <param name="colorType">The color type of the pixel data.</param>
/// <param name="predictor">The tiff predictor used.</param> /// <param name="predictor">The tiff predictor used.</param>
public LzwTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor) /// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public LzwTiffCompression(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffColorType colorType, TiffPredictor predictor, bool isBigEndian)
: base(memoryAllocator, width, bitsPerPixel, predictor) : base(memoryAllocator, width, bitsPerPixel, predictor)
{ {
this.colorType = colorType;
this.isBigEndian = isBigEndian;
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer) protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
{ {
var decoder = new TiffLzwDecoder(stream); var decoder = new TiffLzwDecoder(stream);
decoder.DecodePixels(buffer); decoder.DecodePixels(buffer);
if (this.Predictor == TiffPredictor.Horizontal) if (this.Predictor == TiffPredictor.Horizontal)
{ {
HorizontalPredictor.Undo(buffer, this.Width, this.BitsPerPixel); HorizontalPredictor.Undo(buffer, this.Width, this.colorType, this.isBigEndian);
} }
} }

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

@ -0,0 +1,73 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
/// <summary>
/// Bit reader for data encoded with the modified huffman rle method.
/// See TIFF 6.0 specification, section 10.
/// </summary>
internal sealed class ModifiedHuffmanBitReader : T4BitReader
{
/// <summary>
/// Initializes a new instance of the <see cref="ModifiedHuffmanBitReader"/> class.
/// </summary>
/// <param name="input">The compressed input stream.</param>
/// <param name="fillOrder">The logical order of bits within a byte.</param>
/// <param name="bytesToRead">The number of bytes to read from the stream.</param>
/// <param name="allocator">The memory allocator.</param>
public ModifiedHuffmanBitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator)
: base(input, fillOrder, bytesToRead, allocator)
{
}
/// <inheritdoc/>
public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || ((uint)(this.BitsRead - 1) < (7 - 1));
/// <inheritdoc/>
public override bool IsEndOfScanLine
{
get
{
if (this.IsWhiteRun && this.CurValueBitsRead == 12 && this.Value == 1)
{
return true;
}
if (this.CurValueBitsRead == 11 && this.Value == 0)
{
// black run.
return true;
}
return false;
}
}
/// <inheritdoc/>
public override void StartNewRow()
{
base.StartNewRow();
int remainder = this.BitsRead & 7; // bit-hack for % 8
if (remainder != 0)
{
// Skip padding bits, move to next byte.
this.Position++;
this.ResetBitsRead();
}
}
/// <summary>
/// No EOL is expected at the start of a run for the modified huffman encoding.
/// </summary>
protected override void ReadEolBeforeFirstData()
{
// Nothing to do here.
}
}
}

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

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -12,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <summary> /// <summary>
/// Class to handle cases where TIFF image data is compressed using Modified Huffman Compression. /// Class to handle cases where TIFF image data is compressed using Modified Huffman Compression.
/// </summary> /// </summary>
internal class ModifiedHuffmanTiffCompression : T4TiffCompression internal sealed class ModifiedHuffmanTiffCompression : TiffBaseDecompressor
{ {
private readonly byte whiteValue; private readonly byte whiteValue;
@ -22,21 +21,28 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// Initializes a new instance of the <see cref="ModifiedHuffmanTiffCompression" /> class. /// Initializes a new instance of the <see cref="ModifiedHuffmanTiffCompression" /> class.
/// </summary> /// </summary>
/// <param name="allocator">The memory allocator.</param> /// <param name="allocator">The memory allocator.</param>
/// <param name="fillOrder">The logical order of bits within a byte.</param>
/// <param name="width">The image width.</param> /// <param name="width">The image width.</param>
/// <param name="bitsPerPixel">The number of bits per pixel.</param> /// <param name="bitsPerPixel">The number of bits per pixel.</param>
/// <param name="photometricInterpretation">The photometric interpretation.</param> /// <param name="photometricInterpretation">The photometric interpretation.</param>
public ModifiedHuffmanTiffCompression(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPhotometricInterpretation photometricInterpretation) public ModifiedHuffmanTiffCompression(MemoryAllocator allocator, TiffFillOrder fillOrder, int width, int bitsPerPixel, TiffPhotometricInterpretation photometricInterpretation)
: base(allocator, width, bitsPerPixel, FaxCompressionOptions.None, photometricInterpretation) : base(allocator, width, bitsPerPixel)
{ {
this.FillOrder = fillOrder;
bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero;
this.whiteValue = (byte)(isWhiteZero ? 0 : 1); this.whiteValue = (byte)(isWhiteZero ? 0 : 1);
this.blackValue = (byte)(isWhiteZero ? 1 : 0); this.blackValue = (byte)(isWhiteZero ? 1 : 0);
} }
/// <summary>
/// Gets the logical order of bits within a byte.
/// </summary>
private TiffFillOrder FillOrder { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer) protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
{ {
using var bitReader = new T4BitReader(stream, byteCount, this.Allocator, eolPadding: false, isModifiedHuffman: true); using var bitReader = new ModifiedHuffmanBitReader(stream, this.FillOrder, byteCount, this.Allocator);
buffer.Clear(); buffer.Clear();
uint bitsWritten = 0; uint bitsWritten = 0;
@ -50,20 +56,20 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
if (bitReader.IsWhiteRun) if (bitReader.IsWhiteRun)
{ {
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue); BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue);
bitsWritten += bitReader.RunLength;
pixelsWritten += bitReader.RunLength;
} }
else else
{ {
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue); BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue);
bitsWritten += bitReader.RunLength;
pixelsWritten += bitReader.RunLength;
} }
bitsWritten += bitReader.RunLength;
pixelsWritten += bitReader.RunLength;
} }
if (pixelsWritten % this.Width == 0) if (pixelsWritten == this.Width)
{ {
bitReader.StartNewRow(); bitReader.StartNewRow();
pixelsWritten = 0;
// Write padding bits, if necessary. // Write padding bits, if necessary.
uint pad = 8 - (bitsWritten % 8); uint pad = 8 - (bitsWritten % 8);
@ -73,7 +79,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
bitsWritten += pad; bitsWritten += pad;
} }
} }
if (pixelsWritten > this.Width)
{
TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error, decoded more pixels then image width");
}
} }
} }
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
} }
} }

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

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -11,7 +10,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <summary> /// <summary>
/// Class to handle cases where TIFF image data is not compressed. /// Class to handle cases where TIFF image data is not compressed.
/// </summary> /// </summary>
internal class NoneTiffCompression : TiffBaseDecompressor internal sealed class NoneTiffCompression : TiffBaseDecompressor
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="NoneTiffCompression" /> class. /// Initializes a new instance of the <see cref="NoneTiffCompression" /> class.
@ -25,7 +24,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer) => _ = stream.Read(buffer, 0, Math.Min(buffer.Length, byteCount)); protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
=> _ = stream.Read(buffer, 0, Math.Min(buffer.Length, byteCount));
/// <inheritdoc/> /// <inheritdoc/>
protected override void Dispose(bool disposing) protected override void Dispose(bool disposing)

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

@ -3,7 +3,6 @@
using System; using System;
using System.Buffers; using System.Buffers;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -12,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <summary> /// <summary>
/// Class to handle cases where TIFF image data is compressed using PackBits compression. /// Class to handle cases where TIFF image data is compressed using PackBits compression.
/// </summary> /// </summary>
internal class PackBitsTiffCompression : TiffBaseDecompressor internal sealed class PackBitsTiffCompression : TiffBaseDecompressor
{ {
private IMemoryOwner<byte> compressedDataMemory; private IMemoryOwner<byte> compressedDataMemory;
@ -28,7 +27,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer) protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
{ {
if (this.compressedDataMemory == null) if (this.compressedDataMemory == null)
{ {

33
src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs

@ -0,0 +1,33 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Threading;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder;
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
/// <summary>
/// Spectral converter for YCbCr TIFF's which use the JPEG compression.
/// The jpeg data should be always treated as RGB color space.
/// </summary>
/// <typeparam name="TPixel">The type of the pixel.</typeparam>
internal sealed class RgbJpegSpectralConverter<TPixel> : SpectralConverter<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="RgbJpegSpectralConverter{TPixel}"/> class.
/// This Spectral converter will always convert the pixel data to RGB color.
/// </summary>
/// <param name="configuration">The configuration.</param>
/// <param name="cancellationToken">The cancellation token.</param>
public RgbJpegSpectralConverter(Configuration configuration, CancellationToken cancellationToken)
: base(configuration, cancellationToken)
{
}
/// <inheritdoc/>
protected override JpegColorConverter GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverter.GetConverter(JpegColorSpace.RGB, frame.Precision);
}
}

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

@ -5,7 +5,8 @@ using System;
using System.Buffers; using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
@ -16,24 +17,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
internal class T4BitReader : IDisposable internal class T4BitReader : IDisposable
{ {
/// <summary> /// <summary>
/// Number of bits read. /// The logical order of bits within a byte.
/// </summary>
private int bitsRead;
/// <summary>
/// Current value.
/// </summary>
private uint value;
/// <summary>
/// Number of bits read for the current run value.
/// </summary>
private int curValueBitsRead;
/// <summary>
/// Byte position in the buffer.
/// </summary> /// </summary>
private ulong position; private readonly TiffFillOrder fillOrder;
/// <summary> /// <summary>
/// Indicates whether its the first line of data which is read from the image. /// Indicates whether its the first line of data which is read from the image.
@ -51,20 +37,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// </summary> /// </summary>
private bool isStartOfRow; private bool isStartOfRow;
/// <summary>
/// Indicates whether the modified huffman compression, as specified in the TIFF spec in section 10, is used.
/// </summary>
private readonly bool isModifiedHuffmanRle;
/// <summary> /// <summary>
/// Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false. /// Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false.
/// </summary> /// </summary>
private readonly bool eolPadding; private readonly bool eolPadding;
private readonly int dataLength; /// <summary>
/// The minimum code length in bits.
/// </summary>
private const int MinCodeLength = 2; private const int MinCodeLength = 2;
/// <summary>
/// The maximum code length in bits.
/// </summary>
private readonly int maxCodeLength = 13; private readonly int maxCodeLength = 13;
private static readonly Dictionary<uint, uint> WhiteLen4TermCodes = new Dictionary<uint, uint>() private static readonly Dictionary<uint, uint> WhiteLen4TermCodes = new Dictionary<uint, uint>()
@ -221,21 +206,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// Initializes a new instance of the <see cref="T4BitReader" /> class. /// Initializes a new instance of the <see cref="T4BitReader" /> class.
/// </summary> /// </summary>
/// <param name="input">The compressed input stream.</param> /// <param name="input">The compressed input stream.</param>
/// <param name="fillOrder">The logical order of bits within a byte.</param>
/// <param name="bytesToRead">The number of bytes to read from the stream.</param> /// <param name="bytesToRead">The number of bytes to read from the stream.</param>
/// <param name="allocator">The memory allocator.</param> /// <param name="allocator">The memory allocator.</param>
/// <param name="eolPadding">Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false.</param> /// <param name="eolPadding">Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false.</param>
/// <param name="isModifiedHuffman">Indicates, if its the modified huffman code variation. Defaults to false.</param> public T4BitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator, bool eolPadding = false)
public T4BitReader(Stream input, int bytesToRead, MemoryAllocator allocator, bool eolPadding = false, bool isModifiedHuffman = false)
{ {
this.fillOrder = fillOrder;
this.Data = allocator.Allocate<byte>(bytesToRead); this.Data = allocator.Allocate<byte>(bytesToRead);
this.ReadImageDataFromStream(input, bytesToRead); this.ReadImageDataFromStream(input, bytesToRead);
this.isModifiedHuffmanRle = isModifiedHuffman; this.DataLength = bytesToRead;
this.dataLength = bytesToRead; this.BitsRead = 0;
this.bitsRead = 0; this.Value = 0;
this.value = 0; this.CurValueBitsRead = 0;
this.curValueBitsRead = 0; this.Position = 0;
this.position = 0;
this.IsWhiteRun = true; this.IsWhiteRun = true;
this.isFirstScanLine = true; this.isFirstScanLine = true;
this.isStartOfRow = true; this.isStartOfRow = true;
@ -249,6 +234,31 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
} }
} }
/// <summary>
/// Gets the current value.
/// </summary>
protected uint Value { get; private set; }
/// <summary>
/// Gets the number of bits read for the current run value.
/// </summary>
protected int CurValueBitsRead { get; private set; }
/// <summary>
/// Gets the number of bits read.
/// </summary>
protected int BitsRead { get; private set; }
/// <summary>
/// Gets the available data in bytes.
/// </summary>
protected int DataLength { get; }
/// <summary>
/// Gets or sets the byte position in the buffer.
/// </summary>
protected ulong Position { get; set; }
/// <summary> /// <summary>
/// Gets the compressed image data. /// Gets the compressed image data.
/// </summary> /// </summary>
@ -257,23 +267,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <summary> /// <summary>
/// Gets a value indicating whether there is more data to read left. /// Gets a value indicating whether there is more data to read left.
/// </summary> /// </summary>
public bool HasMoreData public virtual bool HasMoreData => this.Position < (ulong)this.DataLength - 1;
{
get
{
if (this.isModifiedHuffmanRle)
{
return this.position < (ulong)this.dataLength - 1 || (this.bitsRead > 0 && this.bitsRead < 7);
}
return this.position < (ulong)this.dataLength - 1;
}
}
/// <summary> /// <summary>
/// Gets a value indicating whether the current run is a white pixel run, otherwise its a black pixel run. /// Gets or sets a value indicating whether the current run is a white pixel run, otherwise its a black pixel run.
/// </summary> /// </summary>
public bool IsWhiteRun { get; private set; } public bool IsWhiteRun { get; protected set; }
/// <summary> /// <summary>
/// Gets the number of pixels in the current run. /// Gets the number of pixels in the current run.
@ -283,16 +282,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <summary> /// <summary>
/// Gets a value indicating whether the end of a pixel row has been reached. /// Gets a value indicating whether the end of a pixel row has been reached.
/// </summary> /// </summary>
public bool IsEndOfScanLine public virtual bool IsEndOfScanLine
{ {
get get
{ {
if (this.eolPadding) if (this.eolPadding)
{ {
return this.curValueBitsRead >= 12 && this.value == 1; return this.CurValueBitsRead >= 12 && this.Value == 1;
} }
return this.curValueBitsRead == 12 && this.value == 1; return this.CurValueBitsRead == 12 && this.Value == 1;
} }
} }
@ -307,29 +306,20 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
this.terminationCodeFound = false; this.terminationCodeFound = false;
} }
// Initialize for next run.
this.Reset(); this.Reset();
if (this.isFirstScanLine && !this.isModifiedHuffmanRle) // We expect an EOL before the first data.
{ this.ReadEolBeforeFirstData();
// We expect an EOL before the first data.
this.value = this.ReadValue(this.eolPadding ? 16 : 12);
if (!this.IsEndOfScanLine)
{
TiffThrowHelper.ThrowImageFormatException("t4 parsing error: expected start of data marker not found");
}
this.Reset();
}
// A code word must have at least 2 bits. // A code word must have at least 2 bits.
this.value = this.ReadValue(MinCodeLength); this.Value = this.ReadValue(MinCodeLength);
do do
{ {
if (this.curValueBitsRead > this.maxCodeLength) if (this.CurValueBitsRead > this.maxCodeLength)
{ {
TiffThrowHelper.ThrowImageFormatException("t4 parsing error: invalid code length read"); TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error: invalid code length read");
} }
bool isMakeupCode = this.IsMakeupCode(); bool isMakeupCode = this.IsMakeupCode();
@ -355,10 +345,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
// Each line starts with a white run. If the image starts with black, a white run with length zero is written. // Each line starts with a white run. If the image starts with black, a white run with length zero is written.
if (this.isStartOfRow && this.IsWhiteRun && this.WhiteTerminatingCodeRunLength() == 0) if (this.isStartOfRow && this.IsWhiteRun && this.WhiteTerminatingCodeRunLength() == 0)
{ {
this.IsWhiteRun = !this.IsWhiteRun;
this.Reset(); this.Reset();
this.isStartOfRow = false; this.isStartOfRow = false;
continue; this.terminationCodeFound = true;
this.RunLength = 0;
break;
} }
if (this.IsWhiteRun) if (this.IsWhiteRun)
@ -375,8 +366,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
break; break;
} }
var currBit = this.ReadValue(1); uint currBit = this.ReadValue(1);
this.value = (this.value << 1) | currBit; this.Value = (this.Value << 1) | currBit;
if (this.IsEndOfScanLine) if (this.IsEndOfScanLine)
{ {
@ -388,55 +379,106 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
this.isFirstScanLine = false; this.isFirstScanLine = false;
} }
public void StartNewRow() /// <summary>
/// Initialization for a new row.
/// </summary>
public virtual void StartNewRow()
{ {
// Each new row starts with a white run. // Each new row starts with a white run.
this.IsWhiteRun = true; this.IsWhiteRun = true;
this.isStartOfRow = true; this.isStartOfRow = true;
this.terminationCodeFound = false; this.terminationCodeFound = false;
}
/// <inheritdoc/>
public void Dispose() => this.Data.Dispose();
if (this.isModifiedHuffmanRle) /// <summary>
/// An EOL is expected before the first data.
/// </summary>
protected virtual void ReadEolBeforeFirstData()
{
if (this.isFirstScanLine)
{ {
int pad = 8 - (this.bitsRead % 8); this.Value = this.ReadValue(this.eolPadding ? 16 : 12);
if (pad != 8)
if (!this.IsEndOfScanLine)
{ {
// Skip padding bits, move to next byte. TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error: expected start of data marker not found");
this.position++;
this.bitsRead = 0;
} }
this.Reset();
} }
} }
/// <inheritdoc/> /// <summary>
public void Dispose() => this.Data.Dispose(); /// Resets the current value read and the number of bits read.
/// </summary>
/// <param name="resetRunLength">if set to true resets also the run length.</param>
protected void Reset(bool resetRunLength = true)
{
this.Value = 0;
this.CurValueBitsRead = 0;
if (resetRunLength)
{
this.RunLength = 0;
}
}
/// <summary>
/// Resets the bits read to 0.
/// </summary>
protected void ResetBitsRead() => this.BitsRead = 0;
/// <summary>
/// Reads the next value.
/// </summary>
/// <param name="nBits">The number of bits to read.</param>
/// <returns>The value read.</returns>
protected uint ReadValue(int nBits)
{
DebugGuard.MustBeGreaterThan(nBits, 0, nameof(nBits));
uint v = 0;
int shift = nBits;
while (shift-- > 0)
{
uint bit = this.GetBit();
v |= bit << shift;
this.CurValueBitsRead++;
}
return v;
}
private uint WhiteTerminatingCodeRunLength() private uint WhiteTerminatingCodeRunLength()
{ {
switch (this.curValueBitsRead) switch (this.CurValueBitsRead)
{ {
case 4: case 4:
{ {
return WhiteLen4TermCodes[this.value]; return WhiteLen4TermCodes[this.Value];
} }
case 5: case 5:
{ {
return WhiteLen5TermCodes[this.value]; return WhiteLen5TermCodes[this.Value];
} }
case 6: case 6:
{ {
return WhiteLen6TermCodes[this.value]; return WhiteLen6TermCodes[this.Value];
} }
case 7: case 7:
{ {
return WhiteLen7TermCodes[this.value]; return WhiteLen7TermCodes[this.Value];
} }
case 8: case 8:
{ {
return WhiteLen8TermCodes[this.value]; return WhiteLen8TermCodes[this.Value];
} }
} }
@ -445,61 +487,61 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private uint BlackTerminatingCodeRunLength() private uint BlackTerminatingCodeRunLength()
{ {
switch (this.curValueBitsRead) switch (this.CurValueBitsRead)
{ {
case 2: case 2:
{ {
return BlackLen2TermCodes[this.value]; return BlackLen2TermCodes[this.Value];
} }
case 3: case 3:
{ {
return BlackLen3TermCodes[this.value]; return BlackLen3TermCodes[this.Value];
} }
case 4: case 4:
{ {
return BlackLen4TermCodes[this.value]; return BlackLen4TermCodes[this.Value];
} }
case 5: case 5:
{ {
return BlackLen5TermCodes[this.value]; return BlackLen5TermCodes[this.Value];
} }
case 6: case 6:
{ {
return BlackLen6TermCodes[this.value]; return BlackLen6TermCodes[this.Value];
} }
case 7: case 7:
{ {
return BlackLen7TermCodes[this.value]; return BlackLen7TermCodes[this.Value];
} }
case 8: case 8:
{ {
return BlackLen8TermCodes[this.value]; return BlackLen8TermCodes[this.Value];
} }
case 9: case 9:
{ {
return BlackLen9TermCodes[this.value]; return BlackLen9TermCodes[this.Value];
} }
case 10: case 10:
{ {
return BlackLen10TermCodes[this.value]; return BlackLen10TermCodes[this.Value];
} }
case 11: case 11:
{ {
return BlackLen11TermCodes[this.value]; return BlackLen11TermCodes[this.Value];
} }
case 12: case 12:
{ {
return BlackLen12TermCodes[this.value]; return BlackLen12TermCodes[this.Value];
} }
} }
@ -508,41 +550,41 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private uint WhiteMakeupCodeRunLength() private uint WhiteMakeupCodeRunLength()
{ {
switch (this.curValueBitsRead) switch (this.CurValueBitsRead)
{ {
case 5: case 5:
{ {
return WhiteLen5MakeupCodes[this.value]; return WhiteLen5MakeupCodes[this.Value];
} }
case 6: case 6:
{ {
return WhiteLen6MakeupCodes[this.value]; return WhiteLen6MakeupCodes[this.Value];
} }
case 7: case 7:
{ {
return WhiteLen7MakeupCodes[this.value]; return WhiteLen7MakeupCodes[this.Value];
} }
case 8: case 8:
{ {
return WhiteLen8MakeupCodes[this.value]; return WhiteLen8MakeupCodes[this.Value];
} }
case 9: case 9:
{ {
return WhiteLen9MakeupCodes[this.value]; return WhiteLen9MakeupCodes[this.Value];
} }
case 11: case 11:
{ {
return WhiteLen11MakeupCodes[this.value]; return WhiteLen11MakeupCodes[this.Value];
} }
case 12: case 12:
{ {
return WhiteLen12MakeupCodes[this.value]; return WhiteLen12MakeupCodes[this.Value];
} }
} }
@ -551,26 +593,26 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private uint BlackMakeupCodeRunLength() private uint BlackMakeupCodeRunLength()
{ {
switch (this.curValueBitsRead) switch (this.CurValueBitsRead)
{ {
case 10: case 10:
{ {
return BlackLen10MakeupCodes[this.value]; return BlackLen10MakeupCodes[this.Value];
} }
case 11: case 11:
{ {
return BlackLen11MakeupCodes[this.value]; return BlackLen11MakeupCodes[this.Value];
} }
case 12: case 12:
{ {
return BlackLen12MakeupCodes[this.value]; return BlackLen12MakeupCodes[this.Value];
} }
case 13: case 13:
{ {
return BlackLen13MakeupCodes[this.value]; return BlackLen13MakeupCodes[this.Value];
} }
} }
@ -589,49 +631,41 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private bool IsWhiteMakeupCode() private bool IsWhiteMakeupCode()
{ {
switch (this.curValueBitsRead) switch (this.CurValueBitsRead)
{ {
case 5: case 5:
{ {
return WhiteLen5MakeupCodes.ContainsKey(this.value); return WhiteLen5MakeupCodes.ContainsKey(this.Value);
} }
case 6: case 6:
{ {
return WhiteLen6MakeupCodes.ContainsKey(this.value); return WhiteLen6MakeupCodes.ContainsKey(this.Value);
} }
case 7: case 7:
{ {
return WhiteLen7MakeupCodes.ContainsKey(this.value); return WhiteLen7MakeupCodes.ContainsKey(this.Value);
} }
case 8: case 8:
{ {
return WhiteLen8MakeupCodes.ContainsKey(this.value); return WhiteLen8MakeupCodes.ContainsKey(this.Value);
} }
case 9: case 9:
{ {
return WhiteLen9MakeupCodes.ContainsKey(this.value); return WhiteLen9MakeupCodes.ContainsKey(this.Value);
} }
case 11: case 11:
{ {
return WhiteLen11MakeupCodes.ContainsKey(this.value); return WhiteLen11MakeupCodes.ContainsKey(this.Value);
} }
case 12: case 12:
{ {
if (this.isModifiedHuffmanRle) return WhiteLen12MakeupCodes.ContainsKey(this.Value);
{
if (this.value == 1)
{
return true;
}
}
return WhiteLen12MakeupCodes.ContainsKey(this.value);
} }
} }
@ -640,34 +674,26 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private bool IsBlackMakeupCode() private bool IsBlackMakeupCode()
{ {
switch (this.curValueBitsRead) switch (this.CurValueBitsRead)
{ {
case 10: case 10:
{ {
return BlackLen10MakeupCodes.ContainsKey(this.value); return BlackLen10MakeupCodes.ContainsKey(this.Value);
} }
case 11: case 11:
{ {
if (this.isModifiedHuffmanRle) return BlackLen11MakeupCodes.ContainsKey(this.Value);
{
if (this.value == 0)
{
return true;
}
}
return BlackLen11MakeupCodes.ContainsKey(this.value);
} }
case 12: case 12:
{ {
return BlackLen12MakeupCodes.ContainsKey(this.value); return BlackLen12MakeupCodes.ContainsKey(this.Value);
} }
case 13: case 13:
{ {
return BlackLen13MakeupCodes.ContainsKey(this.value); return BlackLen13MakeupCodes.ContainsKey(this.Value);
} }
} }
@ -686,31 +712,31 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private bool IsWhiteTerminatingCode() private bool IsWhiteTerminatingCode()
{ {
switch (this.curValueBitsRead) switch (this.CurValueBitsRead)
{ {
case 4: case 4:
{ {
return WhiteLen4TermCodes.ContainsKey(this.value); return WhiteLen4TermCodes.ContainsKey(this.Value);
} }
case 5: case 5:
{ {
return WhiteLen5TermCodes.ContainsKey(this.value); return WhiteLen5TermCodes.ContainsKey(this.Value);
} }
case 6: case 6:
{ {
return WhiteLen6TermCodes.ContainsKey(this.value); return WhiteLen6TermCodes.ContainsKey(this.Value);
} }
case 7: case 7:
{ {
return WhiteLen7TermCodes.ContainsKey(this.value); return WhiteLen7TermCodes.ContainsKey(this.Value);
} }
case 8: case 8:
{ {
return WhiteLen8TermCodes.ContainsKey(this.value); return WhiteLen8TermCodes.ContainsKey(this.Value);
} }
} }
@ -719,117 +745,90 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private bool IsBlackTerminatingCode() private bool IsBlackTerminatingCode()
{ {
switch (this.curValueBitsRead) switch (this.CurValueBitsRead)
{ {
case 2: case 2:
{ {
return BlackLen2TermCodes.ContainsKey(this.value); return BlackLen2TermCodes.ContainsKey(this.Value);
} }
case 3: case 3:
{ {
return BlackLen3TermCodes.ContainsKey(this.value); return BlackLen3TermCodes.ContainsKey(this.Value);
} }
case 4: case 4:
{ {
return BlackLen4TermCodes.ContainsKey(this.value); return BlackLen4TermCodes.ContainsKey(this.Value);
} }
case 5: case 5:
{ {
return BlackLen5TermCodes.ContainsKey(this.value); return BlackLen5TermCodes.ContainsKey(this.Value);
} }
case 6: case 6:
{ {
return BlackLen6TermCodes.ContainsKey(this.value); return BlackLen6TermCodes.ContainsKey(this.Value);
} }
case 7: case 7:
{ {
return BlackLen7TermCodes.ContainsKey(this.value); return BlackLen7TermCodes.ContainsKey(this.Value);
} }
case 8: case 8:
{ {
return BlackLen8TermCodes.ContainsKey(this.value); return BlackLen8TermCodes.ContainsKey(this.Value);
} }
case 9: case 9:
{ {
return BlackLen9TermCodes.ContainsKey(this.value); return BlackLen9TermCodes.ContainsKey(this.Value);
} }
case 10: case 10:
{ {
return BlackLen10TermCodes.ContainsKey(this.value); return BlackLen10TermCodes.ContainsKey(this.Value);
} }
case 11: case 11:
{ {
return BlackLen11TermCodes.ContainsKey(this.value); return BlackLen11TermCodes.ContainsKey(this.Value);
} }
case 12: case 12:
{ {
return BlackLen12TermCodes.ContainsKey(this.value); return BlackLen12TermCodes.ContainsKey(this.Value);
} }
} }
return false; return false;
} }
private void Reset(bool resetRunLength = true)
{
this.value = 0;
this.curValueBitsRead = 0;
if (resetRunLength)
{
this.RunLength = 0;
}
}
private uint ReadValue(int nBits)
{
Guard.MustBeGreaterThan(nBits, 0, nameof(nBits));
uint v = 0;
int shift = nBits;
while (shift-- > 0)
{
uint bit = this.GetBit();
v |= bit << shift;
this.curValueBitsRead++;
}
return v;
}
private uint GetBit() private uint GetBit()
{ {
if (this.bitsRead >= 8) if (this.BitsRead >= 8)
{ {
this.LoadNewByte(); this.LoadNewByte();
} }
Span<byte> dataSpan = this.Data.GetSpan(); Span<byte> dataSpan = this.Data.GetSpan();
int shift = 8 - this.bitsRead - 1; int shift = 8 - this.BitsRead - 1;
var bit = (uint)((dataSpan[(int)this.position] & (1 << shift)) != 0 ? 1 : 0); uint bit = (uint)((dataSpan[(int)this.Position] & (1 << shift)) != 0 ? 1 : 0);
this.bitsRead++; this.BitsRead++;
return bit; return bit;
} }
private void LoadNewByte() private void LoadNewByte()
{ {
this.position++; this.Position++;
this.bitsRead = 0; this.ResetBitsRead();
if (this.position >= (ulong)this.dataLength) if (this.Position >= (ulong)this.DataLength)
{ {
TiffThrowHelper.ThrowImageFormatException("tiff image has invalid t4 compressed data"); TiffThrowHelper.ThrowImageFormatException("tiff image has invalid ccitt compressed data");
} }
} }
@ -837,6 +836,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{ {
Span<byte> dataSpan = this.Data.GetSpan(); Span<byte> dataSpan = this.Data.GetSpan();
input.Read(dataSpan, 0, bytesToRead); input.Read(dataSpan, 0, bytesToRead);
if (this.fillOrder == TiffFillOrder.LeastSignificantBitFirst)
{
for (int i = 0; i < dataSpan.Length; i++)
{
dataSpan[i] = ReverseBits(dataSpan[i]);
}
}
} }
// http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64Bits
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte ReverseBits(byte b) =>
(byte)((((b * 0x80200802UL) & 0x0884422110UL) * 0x0101010101UL) >> 32);
} }
} }

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

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -12,7 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <summary> /// <summary>
/// Class to handle cases where TIFF image data is compressed using CCITT T4 compression. /// Class to handle cases where TIFF image data is compressed using CCITT T4 compression.
/// </summary> /// </summary>
internal class T4TiffCompression : TiffBaseDecompressor internal sealed class T4TiffCompression : TiffBaseDecompressor
{ {
private readonly FaxCompressionOptions faxCompressionOptions; private readonly FaxCompressionOptions faxCompressionOptions;
@ -20,53 +19,63 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private readonly byte blackValue; private readonly byte blackValue;
private readonly int width;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="T4TiffCompression" /> class. /// Initializes a new instance of the <see cref="T4TiffCompression" /> class.
/// </summary> /// </summary>
/// <param name="allocator">The memory allocator.</param> /// <param name="allocator">The memory allocator.</param>
/// <param name="fillOrder">The logical order of bits within a byte.</param>
/// <param name="width">The image width.</param> /// <param name="width">The image width.</param>
/// <param name="bitsPerPixel">The number of bits per pixel.</param> /// <param name="bitsPerPixel">The number of bits per pixel.</param>
/// <param name="faxOptions">Fax compression options.</param> /// <param name="faxOptions">Fax compression options.</param>
/// <param name="photometricInterpretation">The photometric interpretation.</param> /// <param name="photometricInterpretation">The photometric interpretation.</param>
public T4TiffCompression(MemoryAllocator allocator, int width, int bitsPerPixel, FaxCompressionOptions faxOptions, TiffPhotometricInterpretation photometricInterpretation) public T4TiffCompression(
MemoryAllocator allocator,
TiffFillOrder fillOrder,
int width,
int bitsPerPixel,
FaxCompressionOptions faxOptions,
TiffPhotometricInterpretation photometricInterpretation)
: base(allocator, width, bitsPerPixel) : base(allocator, width, bitsPerPixel)
{ {
this.faxCompressionOptions = faxOptions; this.faxCompressionOptions = faxOptions;
this.FillOrder = fillOrder;
this.width = width;
bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero; bool isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero;
this.whiteValue = (byte)(isWhiteZero ? 0 : 1); this.whiteValue = (byte)(isWhiteZero ? 0 : 1);
this.blackValue = (byte)(isWhiteZero ? 1 : 0); this.blackValue = (byte)(isWhiteZero ? 1 : 0);
} }
/// <summary>
/// Gets the logical order of bits within a byte.
/// </summary>
private TiffFillOrder FillOrder { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer) protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
{ {
if (this.faxCompressionOptions.HasFlag(FaxCompressionOptions.TwoDimensionalCoding)) if (this.faxCompressionOptions.HasFlag(FaxCompressionOptions.TwoDimensionalCoding))
{ {
TiffThrowHelper.ThrowNotSupported("TIFF CCITT 2D compression is not yet supported"); TiffThrowHelper.ThrowNotSupported("TIFF CCITT 2D compression is not yet supported");
} }
var eolPadding = this.faxCompressionOptions.HasFlag(FaxCompressionOptions.EolPadding); bool eolPadding = this.faxCompressionOptions.HasFlag(FaxCompressionOptions.EolPadding);
using var bitReader = new T4BitReader(stream, byteCount, this.Allocator, eolPadding); using var bitReader = new T4BitReader(stream, this.FillOrder, byteCount, this.Allocator, eolPadding);
buffer.Clear(); buffer.Clear();
uint bitsWritten = 0; uint bitsWritten = 0;
uint pixelWritten = 0;
while (bitReader.HasMoreData) while (bitReader.HasMoreData)
{ {
bitReader.ReadNextRun(); bitReader.ReadNextRun();
if (bitReader.RunLength > 0) if (bitReader.RunLength > 0)
{ {
if (bitReader.IsWhiteRun) this.WritePixelRun(buffer, bitReader, bitsWritten);
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue); bitsWritten += bitReader.RunLength;
bitsWritten += bitReader.RunLength; pixelWritten += bitReader.RunLength;
}
else
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue);
bitsWritten += bitReader.RunLength;
}
} }
if (bitReader.IsEndOfScanLine) if (bitReader.IsEndOfScanLine)
@ -78,8 +87,29 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, pad, 0); BitWriterUtils.WriteBits(buffer, (int)bitsWritten, pad, 0);
bitsWritten += pad; bitsWritten += pad;
} }
pixelWritten = 0;
} }
} }
// Edge case for when we are at the last byte, but there are still some unwritten pixels left.
if (pixelWritten > 0 && pixelWritten < this.width)
{
bitReader.ReadNextRun();
this.WritePixelRun(buffer, bitReader, bitsWritten);
}
}
private void WritePixelRun(Span<byte> buffer, T4BitReader bitReader, uint bitsWritten)
{
if (bitReader.IsWhiteRun)
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue);
}
else
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue);
}
} }
/// <inheritdoc/> /// <inheritdoc/>

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

@ -0,0 +1,159 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System.Collections.Generic;
using System.IO;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
/// <summary>
/// Bit reader for reading CCITT T6 compressed fax data.
/// See: Facsimile Coding Schemes and Coding Control Functions for Group 4 Facsimile Apparatus, itu-t recommendation t.6
/// </summary>
internal sealed class T6BitReader : T4BitReader
{
private readonly int maxCodeLength = 12;
private static readonly CcittTwoDimensionalCode None = new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.None, 0);
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len1Codes = new Dictionary<uint, CcittTwoDimensionalCode>()
{
{ 0b1, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Vertical0, 1) }
};
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len3Codes = new Dictionary<uint, CcittTwoDimensionalCode>()
{
{ 0b001, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Horizontal, 3) },
{ 0b010, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalL1, 3) },
{ 0b011, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalR1, 3) }
};
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len4Codes = new Dictionary<uint, CcittTwoDimensionalCode>()
{
{ 0b0001, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Pass, 4) }
};
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len6Codes = new Dictionary<uint, CcittTwoDimensionalCode>()
{
{ 0b000011, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalR2, 6) },
{ 0b000010, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalL2, 6) }
};
private static readonly Dictionary<uint, CcittTwoDimensionalCode> Len7Codes = new Dictionary<uint, CcittTwoDimensionalCode>()
{
{ 0b0000011, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalR3, 7) },
{ 0b0000010, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.VerticalL3, 7) },
{ 0b0000001, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Extensions2D, 7) },
{ 0b0000000, new CcittTwoDimensionalCode(CcittTwoDimensionalCodeType.Extensions1D, 7) }
};
/// <summary>
/// Initializes a new instance of the <see cref="T6BitReader"/> class.
/// </summary>
/// <param name="input">The compressed input stream.</param>
/// <param name="fillOrder">The logical order of bits within a byte.</param>
/// <param name="bytesToRead">The number of bytes to read from the stream.</param>
/// <param name="allocator">The memory allocator.</param>
public T6BitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator)
: base(input, fillOrder, bytesToRead, allocator)
{
}
/// <inheritdoc/>
public override bool HasMoreData => this.Position < (ulong)this.DataLength - 1 || ((uint)(this.BitsRead - 1) < (7 - 1));
/// <summary>
/// Gets or sets the two dimensional code.
/// </summary>
public CcittTwoDimensionalCode Code { get; internal set; }
public bool ReadNextCodeWord()
{
this.Code = None;
this.Reset();
uint value = this.ReadValue(1);
do
{
if (this.CurValueBitsRead > this.maxCodeLength)
{
TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error: invalid code length read");
}
switch (this.CurValueBitsRead)
{
case 1:
if (Len1Codes.ContainsKey(value))
{
this.Code = Len1Codes[value];
return false;
}
break;
case 3:
if (Len3Codes.ContainsKey(value))
{
this.Code = Len3Codes[value];
return false;
}
break;
case 4:
if (Len4Codes.ContainsKey(value))
{
this.Code = Len4Codes[value];
return false;
}
break;
case 6:
if (Len6Codes.ContainsKey(value))
{
this.Code = Len6Codes[value];
return false;
}
break;
case 7:
if (Len7Codes.ContainsKey(value))
{
this.Code = Len7Codes[value];
return false;
}
break;
}
uint currBit = this.ReadValue(1);
value = (value << 1) | currBit;
}
while (!this.IsEndOfScanLine);
if (this.IsEndOfScanLine)
{
return true;
}
return false;
}
/// <summary>
/// No EOL is expected at the start of a run.
/// </summary>
protected override void ReadEolBeforeFirstData()
{
// Nothing to do here.
}
/// <summary>
/// Swaps the white run to black run an vise versa.
/// </summary>
public void SwapColor() => this.IsWhiteRun = !this.IsWhiteRun;
}
}

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

@ -0,0 +1,254 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
{
/// <summary>
/// Class to handle cases where TIFF image data is compressed using CCITT T6 compression.
/// </summary>
internal sealed class T6TiffCompression : TiffBaseDecompressor
{
private readonly bool isWhiteZero;
private readonly byte whiteValue;
private readonly byte blackValue;
private readonly int width;
/// <summary>
/// Initializes a new instance of the <see cref="T6TiffCompression" /> class.
/// </summary>
/// <param name="allocator">The memory allocator.</param>
/// <param name="fillOrder">The logical order of bits within a byte.</param>
/// <param name="width">The image width.</param>
/// <param name="bitsPerPixel">The number of bits per pixel.</param>
/// <param name="photometricInterpretation">The photometric interpretation.</param>
public T6TiffCompression(
MemoryAllocator allocator,
TiffFillOrder fillOrder,
int width,
int bitsPerPixel,
TiffPhotometricInterpretation photometricInterpretation)
: base(allocator, width, bitsPerPixel)
{
this.FillOrder = fillOrder;
this.width = width;
this.isWhiteZero = photometricInterpretation == TiffPhotometricInterpretation.WhiteIsZero;
this.whiteValue = (byte)(this.isWhiteZero ? 0 : 1);
this.blackValue = (byte)(this.isWhiteZero ? 1 : 0);
}
/// <summary>
/// Gets the logical order of bits within a byte.
/// </summary>
private TiffFillOrder FillOrder { get; }
/// <inheritdoc/>
protected override void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer)
{
int height = stripHeight;
using System.Buffers.IMemoryOwner<byte> scanLineBuffer = this.Allocator.Allocate<byte>(this.width * 2);
Span<byte> scanLine = scanLineBuffer.GetSpan().Slice(0, this.width);
Span<byte> referenceScanLineSpan = scanLineBuffer.GetSpan().Slice(this.width, this.width);
using var bitReader = new T6BitReader(stream, this.FillOrder, byteCount, this.Allocator);
var referenceScanLine = new CcittReferenceScanline(this.isWhiteZero, this.width);
uint bitsWritten = 0;
for (int y = 0; y < height; y++)
{
scanLine.Fill(0);
Decode2DScanline(bitReader, this.isWhiteZero, referenceScanLine, scanLine);
bitsWritten = this.WriteScanLine(buffer, scanLine, bitsWritten);
scanLine.CopyTo(referenceScanLineSpan);
referenceScanLine = new CcittReferenceScanline(this.isWhiteZero, referenceScanLineSpan);
}
}
private uint WriteScanLine(Span<byte> buffer, Span<byte> scanLine, uint bitsWritten)
{
byte white = (byte)(this.isWhiteZero ? 0 : 255);
for (int i = 0; i < scanLine.Length; i++)
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, 1, scanLine[i] == white ? this.whiteValue : this.blackValue);
bitsWritten++;
}
// Write padding bytes, if necessary.
uint remainder = bitsWritten % 8;
if (remainder != 0)
{
uint padding = 8 - remainder;
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, padding, 0);
bitsWritten += padding;
}
return bitsWritten;
}
private static void Decode2DScanline(T6BitReader bitReader, bool whiteIsZero, CcittReferenceScanline referenceScanline, Span<byte> scanline)
{
int width = scanline.Length;
bitReader.StartNewRow();
// 2D Encoding variables.
int a0 = -1;
byte fillByte = whiteIsZero ? (byte)0 : (byte)255;
// Process every code word in this scanline.
int unpacked = 0;
while (true)
{
// Read next code word and advance pass it.
bool isEol = bitReader.ReadNextCodeWord();
// Special case handling for EOL.
if (isEol)
{
// If a TIFF reader encounters EOFB before the expected number of lines has been extracted,
// it is appropriate to assume that the missing rows consist entirely of white pixels.
scanline.Fill(whiteIsZero ? (byte)0 : (byte)255);
break;
}
// Update 2D Encoding variables.
int b1 = referenceScanline.FindB1(a0, fillByte);
// Switch on the code word.
int a1;
switch (bitReader.Code.Type)
{
case CcittTwoDimensionalCodeType.None:
TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error, could not read a valid code word.");
break;
case CcittTwoDimensionalCodeType.Pass:
int b2 = referenceScanline.FindB2(b1);
scanline.Slice(unpacked, b2 - unpacked).Fill(fillByte);
unpacked = b2;
a0 = b2;
break;
case CcittTwoDimensionalCodeType.Horizontal:
// Decode M(a0a1)
bitReader.ReadNextRun();
int runLength = (int)bitReader.RunLength;
if (runLength > (uint)(scanline.Length - unpacked))
{
TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error");
}
scanline.Slice(unpacked, runLength).Fill(fillByte);
unpacked += runLength;
fillByte = (byte)~fillByte;
// Decode M(a1a2)
bitReader.ReadNextRun();
runLength = (int)bitReader.RunLength;
if (runLength > (uint)(scanline.Length - unpacked))
{
TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error");
}
scanline.Slice(unpacked, runLength).Fill(fillByte);
unpacked += runLength;
fillByte = (byte)~fillByte;
// Prepare next a0
a0 = unpacked;
break;
case CcittTwoDimensionalCodeType.Vertical0:
a1 = b1;
scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte);
unpacked = a1;
a0 = a1;
fillByte = (byte)~fillByte;
bitReader.SwapColor();
break;
case CcittTwoDimensionalCodeType.VerticalR1:
a1 = b1 + 1;
scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte);
unpacked = a1;
a0 = a1;
fillByte = (byte)~fillByte;
bitReader.SwapColor();
break;
case CcittTwoDimensionalCodeType.VerticalR2:
a1 = b1 + 2;
scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte);
unpacked = a1;
a0 = a1;
fillByte = (byte)~fillByte;
bitReader.SwapColor();
break;
case CcittTwoDimensionalCodeType.VerticalR3:
a1 = b1 + 3;
scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte);
unpacked = a1;
a0 = a1;
fillByte = (byte)~fillByte;
bitReader.SwapColor();
break;
case CcittTwoDimensionalCodeType.VerticalL1:
a1 = b1 - 1;
scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte);
unpacked = a1;
a0 = a1;
fillByte = (byte)~fillByte;
bitReader.SwapColor();
break;
case CcittTwoDimensionalCodeType.VerticalL2:
a1 = b1 - 2;
scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte);
unpacked = a1;
a0 = a1;
fillByte = (byte)~fillByte;
bitReader.SwapColor();
break;
case CcittTwoDimensionalCodeType.VerticalL3:
a1 = b1 - 3;
scanline.Slice(unpacked, a1 - unpacked).Fill(fillByte);
unpacked = a1;
a0 = a1;
fillByte = (byte)~fillByte;
bitReader.SwapColor();
break;
default:
throw new NotSupportedException("ccitt extensions are not supported.");
}
// This line is fully unpacked. Should exit and process next line.
if (unpacked == width)
{
break;
}
if (unpacked > width)
{
TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error, unpacked data > width");
}
}
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
}
}

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

@ -2,9 +2,11 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers.Binary;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Tiff.Compression namespace SixLabors.ImageSharp.Formats.Tiff.Compression
@ -19,16 +21,34 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
/// </summary> /// </summary>
/// <param name="pixelBytes">Buffer with decompressed pixel data.</param> /// <param name="pixelBytes">Buffer with decompressed pixel data.</param>
/// <param name="width">The width of the image or strip.</param> /// <param name="width">The width of the image or strip.</param>
/// <param name="bitsPerPixel">Bits per pixel.</param> /// <param name="colorType">The color type of the pixel data.</param>
public static void Undo(Span<byte> pixelBytes, int width, int bitsPerPixel) /// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public static void Undo(Span<byte> pixelBytes, int width, TiffColorType colorType, bool isBigEndian)
{ {
if (bitsPerPixel == 8) switch (colorType)
{
Undo8Bit(pixelBytes, width);
}
else if (bitsPerPixel == 24)
{ {
Undo24Bit(pixelBytes, width); case TiffColorType.BlackIsZero8:
case TiffColorType.WhiteIsZero8:
case TiffColorType.PaletteColor:
UndoGray8Bit(pixelBytes, width);
break;
case TiffColorType.BlackIsZero16:
case TiffColorType.WhiteIsZero16:
UndoGray16Bit(pixelBytes, width, isBigEndian);
break;
case TiffColorType.BlackIsZero32:
case TiffColorType.WhiteIsZero32:
UndoGray32Bit(pixelBytes, width, isBigEndian);
break;
case TiffColorType.Rgb888:
UndoRgb24Bit(pixelBytes, width);
break;
case TiffColorType.Rgb161616:
UndoRgb48Bit(pixelBytes, width, isBigEndian);
break;
case TiffColorType.Rgb323232:
UndoRgb96Bit(pixelBytes, width, isBigEndian);
break;
} }
} }
@ -93,7 +113,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
} }
} }
private static void Undo8Bit(Span<byte> pixelBytes, int width) private static void UndoGray8Bit(Span<byte> pixelBytes, int width)
{ {
int rowBytesCount = width; int rowBytesCount = width;
int height = pixelBytes.Length / rowBytesCount; int height = pixelBytes.Length / rowBytesCount;
@ -110,7 +130,95 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
} }
} }
private static void Undo24Bit(Span<byte> pixelBytes, int width) private static void UndoGray16Bit(Span<byte> pixelBytes, int width, bool isBigEndian)
{
int rowBytesCount = width * 2;
int height = pixelBytes.Length / rowBytesCount;
if (isBigEndian)
{
for (int y = 0; y < height; y++)
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
ushort pixelValue = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
offset += 2;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 2);
ushort diff = TiffUtils.ConvertToUShortBigEndian(rowSpan);
pixelValue += diff;
BinaryPrimitives.WriteUInt16BigEndian(rowSpan, pixelValue);
offset += 2;
}
}
}
else
{
for (int y = 0; y < height; y++)
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
ushort pixelValue = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
offset += 2;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 2);
ushort diff = TiffUtils.ConvertToUShortLittleEndian(rowSpan);
pixelValue += diff;
BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, pixelValue);
offset += 2;
}
}
}
}
private static void UndoGray32Bit(Span<byte> pixelBytes, int width, bool isBigEndian)
{
int rowBytesCount = width * 4;
int height = pixelBytes.Length / rowBytesCount;
if (isBigEndian)
{
for (int y = 0; y < height; y++)
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
uint pixelValue = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
offset += 4;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 4);
uint diff = TiffUtils.ConvertToUIntBigEndian(rowSpan);
pixelValue += diff;
BinaryPrimitives.WriteUInt32BigEndian(rowSpan, pixelValue);
offset += 4;
}
}
}
else
{
for (int y = 0; y < height; y++)
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
uint pixelValue = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
offset += 4;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 4);
uint diff = TiffUtils.ConvertToUIntLittleEndian(rowSpan);
pixelValue += diff;
BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, pixelValue);
offset += 4;
}
}
}
}
private static void UndoRgb24Bit(Span<byte> pixelBytes, int width)
{ {
int rowBytesCount = width * 3; int rowBytesCount = width * 3;
int height = pixelBytes.Length / rowBytesCount; int height = pixelBytes.Length / rowBytesCount;
@ -134,5 +242,157 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
} }
} }
} }
private static void UndoRgb48Bit(Span<byte> pixelBytes, int width, bool isBigEndian)
{
int rowBytesCount = width * 6;
int height = pixelBytes.Length / rowBytesCount;
if (isBigEndian)
{
for (int y = 0; y < height; y++)
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
ushort r = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
offset += 2;
ushort g = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
offset += 2;
ushort b = TiffUtils.ConvertToUShortBigEndian(rowBytes.Slice(offset, 2));
offset += 2;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 2);
ushort deltaR = TiffUtils.ConvertToUShortBigEndian(rowSpan);
r += deltaR;
BinaryPrimitives.WriteUInt16BigEndian(rowSpan, r);
offset += 2;
rowSpan = rowBytes.Slice(offset, 2);
ushort deltaG = TiffUtils.ConvertToUShortBigEndian(rowSpan);
g += deltaG;
BinaryPrimitives.WriteUInt16BigEndian(rowSpan, g);
offset += 2;
rowSpan = rowBytes.Slice(offset, 2);
ushort deltaB = TiffUtils.ConvertToUShortBigEndian(rowSpan);
b += deltaB;
BinaryPrimitives.WriteUInt16BigEndian(rowSpan, b);
offset += 2;
}
}
}
else
{
for (int y = 0; y < height; y++)
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
ushort r = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
offset += 2;
ushort g = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
offset += 2;
ushort b = TiffUtils.ConvertToUShortLittleEndian(rowBytes.Slice(offset, 2));
offset += 2;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 2);
ushort deltaR = TiffUtils.ConvertToUShortLittleEndian(rowSpan);
r += deltaR;
BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, r);
offset += 2;
rowSpan = rowBytes.Slice(offset, 2);
ushort deltaG = TiffUtils.ConvertToUShortLittleEndian(rowSpan);
g += deltaG;
BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, g);
offset += 2;
rowSpan = rowBytes.Slice(offset, 2);
ushort deltaB = TiffUtils.ConvertToUShortLittleEndian(rowSpan);
b += deltaB;
BinaryPrimitives.WriteUInt16LittleEndian(rowSpan, b);
offset += 2;
}
}
}
}
private static void UndoRgb96Bit(Span<byte> pixelBytes, int width, bool isBigEndian)
{
int rowBytesCount = width * 12;
int height = pixelBytes.Length / rowBytesCount;
if (isBigEndian)
{
for (int y = 0; y < height; y++)
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
uint r = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
offset += 4;
uint g = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
offset += 4;
uint b = TiffUtils.ConvertToUIntBigEndian(rowBytes.Slice(offset, 4));
offset += 4;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 4);
uint deltaR = TiffUtils.ConvertToUIntBigEndian(rowSpan);
r += deltaR;
BinaryPrimitives.WriteUInt32BigEndian(rowSpan, r);
offset += 4;
rowSpan = rowBytes.Slice(offset, 4);
uint deltaG = TiffUtils.ConvertToUIntBigEndian(rowSpan);
g += deltaG;
BinaryPrimitives.WriteUInt32BigEndian(rowSpan, g);
offset += 4;
rowSpan = rowBytes.Slice(offset, 4);
uint deltaB = TiffUtils.ConvertToUIntBigEndian(rowSpan);
b += deltaB;
BinaryPrimitives.WriteUInt32BigEndian(rowSpan, b);
offset += 4;
}
}
}
else
{
for (int y = 0; y < height; y++)
{
int offset = 0;
Span<byte> rowBytes = pixelBytes.Slice(y * rowBytesCount, rowBytesCount);
uint r = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
offset += 4;
uint g = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
offset += 4;
uint b = TiffUtils.ConvertToUIntLittleEndian(rowBytes.Slice(offset, 4));
offset += 4;
for (int x = 1; x < width; x++)
{
Span<byte> rowSpan = rowBytes.Slice(offset, 4);
uint deltaR = TiffUtils.ConvertToUIntLittleEndian(rowSpan);
r += deltaR;
BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, r);
offset += 4;
rowSpan = rowBytes.Slice(offset, 4);
uint deltaG = TiffUtils.ConvertToUIntLittleEndian(rowSpan);
g += deltaG;
BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, g);
offset += 4;
rowSpan = rowBytes.Slice(offset, 4);
uint deltaB = TiffUtils.ConvertToUIntLittleEndian(rowSpan);
b += deltaB;
BinaryPrimitives.WriteUInt32LittleEndian(rowSpan, b);
offset += 4;
}
}
}
}
} }
} }

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

@ -3,7 +3,6 @@
using System; using System;
using System.IO; using System.IO;
using SixLabors.ImageSharp.Formats.Tiff.Constants; using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
@ -15,8 +14,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
/// </summary> /// </summary>
internal abstract class TiffBaseDecompressor : TiffBaseCompression internal abstract class TiffBaseDecompressor : TiffBaseCompression
{ {
protected TiffBaseDecompressor(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None) /// <summary>
: base(allocator, width, bitsPerPixel, predictor) /// Initializes a new instance of the <see cref="TiffBaseDecompressor"/> class.
/// </summary>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="width">The width of the image.</param>
/// <param name="bitsPerPixel">The bits per pixel.</param>
/// <param name="predictor">The predictor.</param>
protected TiffBaseDecompressor(MemoryAllocator memoryAllocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None)
: base(memoryAllocator, width, bitsPerPixel, predictor)
{ {
} }
@ -26,8 +32,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
/// <param name="stream">The <see cref="Stream" /> to read image data from.</param> /// <param name="stream">The <see cref="Stream" /> to read image data from.</param>
/// <param name="stripOffset">The strip offset of stream.</param> /// <param name="stripOffset">The strip offset of stream.</param>
/// <param name="stripByteCount">The number of bytes to read from the input stream.</param> /// <param name="stripByteCount">The number of bytes to read from the input stream.</param>
/// <param name="stripHeight">The height of the strip.</param>
/// <param name="buffer">The output buffer for uncompressed data.</param> /// <param name="buffer">The output buffer for uncompressed data.</param>
public void Decompress(BufferedReadStream stream, uint stripOffset, uint stripByteCount, Span<byte> buffer) public void Decompress(BufferedReadStream stream, uint stripOffset, uint stripByteCount, int stripHeight, Span<byte> buffer)
{ {
if (stripByteCount > int.MaxValue) if (stripByteCount > int.MaxValue)
{ {
@ -35,7 +42,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
} }
stream.Seek(stripOffset, SeekOrigin.Begin); stream.Seek(stripOffset, SeekOrigin.Begin);
this.Decompress(stream, (int)stripByteCount, buffer); this.Decompress(stream, (int)stripByteCount, stripHeight, buffer);
if (stripOffset + stripByteCount < stream.Position) if (stripOffset + stripByteCount < stream.Position)
{ {
@ -48,7 +55,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
/// </summary> /// </summary>
/// <param name="stream">The <see cref="Stream" /> to read image data from.</param> /// <param name="stream">The <see cref="Stream" /> to read image data from.</param>
/// <param name="byteCount">The number of bytes to read from the input stream.</param> /// <param name="byteCount">The number of bytes to read from the input stream.</param>
/// <param name="stripHeight">The height of the strip.</param>
/// <param name="buffer">The output buffer for uncompressed data.</param> /// <param name="buffer">The output buffer for uncompressed data.</param>
protected abstract void Decompress(BufferedReadStream stream, int byteCount, Span<byte> buffer); protected abstract void Decompress(BufferedReadStream stream, int byteCount, int stripHeight, Span<byte> buffer);
} }
} }

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

@ -25,7 +25,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
// The following compression types are not implemented in the encoder and will default to no compression instead. // The following compression types are not implemented in the encoder and will default to no compression instead.
case TiffCompression.ItuTRecT43: case TiffCompression.ItuTRecT43:
case TiffCompression.ItuTRecT82: case TiffCompression.ItuTRecT82:
case TiffCompression.Jpeg:
case TiffCompression.OldJpeg: case TiffCompression.OldJpeg:
case TiffCompression.OldDeflate: case TiffCompression.OldDeflate:
case TiffCompression.None: case TiffCompression.None:
@ -34,6 +33,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
return new NoCompressor(output, allocator, width, bitsPerPixel); return new NoCompressor(output, allocator, width, bitsPerPixel);
case TiffCompression.Jpeg:
DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set");
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new TiffJpegCompressor(output, allocator, width, bitsPerPixel);
case TiffCompression.PackBits: case TiffCompression.PackBits:
DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set"); DebugGuard.IsTrue(compressionLevel == DeflateCompressionLevel.DefaultCompression, "No deflate compression level is expected to be set");
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression"); DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");

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

Loading…
Cancel
Save