Browse Source

Merge branch 'master' into webp

pull/1552/head
James Jackson-South 5 years ago
committed by GitHub
parent
commit
30ea4ed81b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 141
      .github/workflows/build-and-test.yml
  2. 5
      Directory.Build.props
  3. 2
      shared-infrastructure
  4. 14
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
  5. 10
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter.cs
  6. 8
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
  7. 79
      src/ImageSharp/Formats/Jpeg/Components/Encoder/HuffmanScanEncoder.cs
  8. 10
      src/ImageSharp/Formats/Jpeg/Components/Encoder/QuantIndex.cs
  9. 114
      src/ImageSharp/Formats/Jpeg/Components/Encoder/RgbForwardConverter{TPixel}.cs
  10. 8
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter420{TPixel}.cs
  11. 8
      src/ImageSharp/Formats/Jpeg/Components/Encoder/YCbCrForwardConverter444{TPixel}.cs
  12. 8
      src/ImageSharp/Formats/Jpeg/IJpegEncoderOptions.cs
  13. 49
      src/ImageSharp/Formats/Jpeg/JpegColorType.cs
  14. 55
      src/ImageSharp/Formats/Jpeg/JpegConstants.cs
  15. 174
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  16. 15
      src/ImageSharp/Formats/Jpeg/JpegEncoder.cs
  17. 255
      src/ImageSharp/Formats/Jpeg/JpegEncoderCore.cs
  18. 23
      src/ImageSharp/Formats/Jpeg/JpegSubsample.cs
  19. 7
      src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
  20. 2
      src/ImageSharp/Formats/Tiff/Compression/Compressors/PackBitsWriter.cs
  21. 49
      src/ImageSharp/Formats/Tiff/Compression/Compressors/TiffJpegCompressor.cs
  22. 154
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittReferenceScanline.cs
  23. 27
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCode.cs
  24. 73
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/CcittTwoDimensionalCodeType.cs
  25. 4
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/DeflateTiffCompression.cs
  26. 95
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs
  27. 4
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/LzwTiffCompression.cs
  28. 73
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanBitReader.cs
  29. 34
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/ModifiedHuffmanTiffCompression.cs
  30. 5
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/NoneTiffCompression.cs
  31. 4
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/PackBitsTiffCompression.cs
  32. 33
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/RgbJpegSpectralConverter.cs
  33. 375
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4BitReader.cs
  34. 14
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T4TiffCompression.cs
  35. 160
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6BitReader.cs
  36. 254
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/T6TiffCompression.cs
  37. 19
      src/ImageSharp/Formats/Tiff/Compression/TiffBaseDecompressor.cs
  38. 6
      src/ImageSharp/Formats/Tiff/Compression/TiffCompressorFactory.cs
  39. 14
      src/ImageSharp/Formats/Tiff/Compression/TiffDecoderCompressionType.cs
  40. 10
      src/ImageSharp/Formats/Tiff/Compression/TiffDecompressorsFactory.cs
  41. 4
      src/ImageSharp/Formats/Tiff/README.md
  42. BIN
      src/ImageSharp/Formats/Tiff/TIFF-AdobeTechNote-22032002.pdf
  43. 52
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  44. 15
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
  45. 17
      src/ImageSharp/Formats/Tiff/TiffEncoderCore.cs
  46. 3
      src/ImageSharp/Formats/Tiff/TiffEncoderEntriesCollector.cs
  47. 28
      src/ImageSharp/Image.FromBytes.cs
  48. 18
      src/ImageSharp/Image.FromFile.cs
  49. 51
      src/ImageSharp/Image.FromStream.cs
  50. 5
      src/ImageSharp/ImageSharp.csproj
  51. 2
      src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs
  52. 4
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs
  53. 6
      tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
  54. 6
      tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj
  55. 59
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs
  56. 17
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  57. 190
      tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
  58. 2
      tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs
  59. 6
      tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs
  60. 2
      tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
  61. 2
      tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs
  62. 8
      tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs
  63. 9
      tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs
  64. 23
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  65. 7
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
  66. 7
      tests/ImageSharp.Tests/ImageSharp.Tests.csproj
  67. 14
      tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs
  68. 16
      tests/ImageSharp.Tests/TestImages.cs
  69. 3
      tests/Images/Input/Jpg/baseline/arithmetic_coding.jpg
  70. 3
      tests/Images/Input/Jpg/baseline/jpeg-rgb.jpg
  71. 3
      tests/Images/Input/Jpg/baseline/jpeg410.jpg
  72. 3
      tests/Images/Input/Jpg/baseline/jpeg411.jpg
  73. 3
      tests/Images/Input/Jpg/baseline/jpeg422.jpg
  74. 3
      tests/Images/Input/Jpg/baseline/lossless.jpg
  75. 3
      tests/Images/Input/Jpg/progressive/arithmetic_progressive.jpg
  76. 4
      tests/Images/Input/Tiff/Calliphora_bicolor_uncompressed.tiff
  77. 4
      tests/Images/Input/Tiff/Calliphora_ccitt_fax3.tiff
  78. 4
      tests/Images/Input/Tiff/Calliphora_ccitt_fax4.tiff
  79. 4
      tests/Images/Input/Tiff/Calliphora_gray_deflate.tiff
  80. 4
      tests/Images/Input/Tiff/Calliphora_gray_deflate_predictor.tiff
  81. 4
      tests/Images/Input/Tiff/Calliphora_gray_lzw_predictor.tiff
  82. 4
      tests/Images/Input/Tiff/Calliphora_grayscale_uncompressed.tiff
  83. 4
      tests/Images/Input/Tiff/Calliphora_huffman_rle.tiff
  84. 4
      tests/Images/Input/Tiff/Calliphora_palette_uncompressed.tiff
  85. 4
      tests/Images/Input/Tiff/Calliphora_rgb_deflate_predictor.tiff
  86. 4
      tests/Images/Input/Tiff/Calliphora_rgb_jpeg.tiff
  87. 3
      tests/Images/Input/Tiff/Calliphora_rgb_lzw.tif
  88. 3
      tests/Images/Input/Tiff/Calliphora_rgb_lzw.tiff
  89. 4
      tests/Images/Input/Tiff/Calliphora_rgb_lzw_predictor.tiff
  90. 4
      tests/Images/Input/Tiff/Calliphora_rgb_packbits.tiff
  91. 4
      tests/Images/Input/Tiff/Calliphora_rgb_uncompressed.tiff
  92. 3
      tests/Images/Input/Tiff/basi3p02_fax4.tiff
  93. 3
      tests/Images/Input/Tiff/basi3p02_fax4_lowerOrderBitsFirst.tiff
  94. 3
      tests/Images/Input/Tiff/basi3p02_fax4_minisblack.tiff
  95. 3
      tests/Images/Input/Tiff/rgb_jpegcompressed_nojpegtable.tiff
  96. 3
      tests/Images/Input/Tiff/rgb_jpegcompressed_stripped.tiff
  97. 0
      tests/Images/Input/Tiff/rgb_jpegcompression.tiff
  98. 3
      tests/Images/Input/Tiff/ycbcr_jpegcompressed.tiff

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

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.

2
shared-infrastructure

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

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

@ -38,10 +38,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary> /// </summary>
private int restartInterval; 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;
/// <summary> /// <summary>
@ -54,14 +58,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary> /// </summary>
private readonly HuffmanTable[] acHuffmanTables; private readonly HuffmanTable[] acHuffmanTables;
// The unzig data. /// <summary>
/// The unzig data.
/// </summary>
private ZigZag dctZigZag; private ZigZag dctZigZag;
private HuffmanScanBuffer scanBuffer; private HuffmanScanBuffer scanBuffer;
private readonly SpectralConverter spectralConverter; private readonly SpectralConverter spectralConverter;
private CancellationToken cancellationToken; 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.

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

@ -1,6 +1,8 @@
// 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 SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
/// <summary> /// <summary>
@ -30,5 +32,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// Actual stride height depends on the subsampling factor of the given component. /// Actual stride height depends on the subsampling factor of the given component.
/// </remarks> /// </remarks>
public abstract void ConvertStrideBaseline(); 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>
public virtual JpegColorConverter GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverter.GetConverter(jpegData.ColorSpace, frame.Precision);
} }
} }

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

@ -11,12 +11,12 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
internal sealed class SpectralConverter<TPixel> : SpectralConverter, IDisposable internal class SpectralConverter<TPixel> : SpectralConverter, IDisposable
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
private readonly Configuration configuration; private readonly Configuration configuration;
private CancellationToken cancellationToken; private readonly CancellationToken cancellationToken;
private JpegComponentPostProcessor[] componentProcessors; private JpegComponentPostProcessor[] componentProcessors;
@ -59,6 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
} }
} }
/// <inheritdoc/>
public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData) public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData)
{ {
MemoryAllocator allocator = this.configuration.MemoryAllocator; MemoryAllocator allocator = this.configuration.MemoryAllocator;
@ -85,9 +86,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.rgbaBuffer = allocator.Allocate<Vector4>(frame.PixelWidth); this.rgbaBuffer = allocator.Allocate<Vector4>(frame.PixelWidth);
// color converter from Rgba32 to TPixel // color converter from Rgba32 to TPixel
this.colorConverter = JpegColorConverter.GetConverter(jpegData.ColorSpace, frame.Precision); this.colorConverter = this.GetColorConverter(frame, jpegData);
} }
/// <inheritdoc/>
public override void ConvertStrideBaseline() public override void ConvertStrideBaseline()
{ {
// Convert next pixel stride using single spectral `stride' // Convert next pixel stride using single spectral `stride'

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

@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
private int emitLen = 0; private int emitLen = 0;
/// <summary> /// <summary>
/// Emmited bits 'micro buffer' before being transfered to the <see cref="emitBuffer"/>. /// Emitted bits 'micro buffer' before being transferred to the <see cref="emitBuffer"/>.
/// </summary> /// </summary>
private int accumulatedBits; private int accumulatedBits;
@ -58,18 +58,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// </summary> /// </summary>
private readonly Stream target; private readonly Stream target;
public HuffmanScanEncoder(Stream outputStream) public HuffmanScanEncoder(Stream outputStream) => this.target = outputStream;
{
this.target = outputStream;
}
/// <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>
@ -128,8 +125,8 @@ 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>
@ -196,7 +193,7 @@ 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>
@ -234,6 +231,64 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
this.FlushInternalBuffer(); this.FlushInternalBuffer();
} }
/// <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="luminanceQuantTable">Luminance 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 luminanceQuantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
this.huffmanTables = HuffmanLut.TheHuffmanLut;
var unzig = ZigZag.CreateUnzigTable();
// 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 luminanceQuantTable,
ref unzig);
prevDCG = this.WriteBlock(
QuantIndex.Luminance,
prevDCG,
ref pixelConverter.G,
ref luminanceQuantTable,
ref unzig);
prevDCB = this.WriteBlock(
QuantIndex.Luminance,
prevDCB,
ref pixelConverter.B,
ref luminanceQuantTable,
ref unzig);
}
}
this.FlushInternalBuffer();
}
/// <summary> /// <summary>
/// Writes a block of pixel data using the given quantization table, /// Writes a block of pixel data using the given quantization table,
/// returning the post-quantized DC value of the DCT-transformed block. /// returning the post-quantized DC value of the DCT-transformed block.
@ -437,7 +492,7 @@ 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
@ -449,7 +504,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
// 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

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

@ -1,21 +1,21 @@
// 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, 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)
{ {

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

@ -16,13 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
public int? Quality { get; set; } public int? Quality { get; set; }
/// <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>

174
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;
@ -136,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)
{ {
@ -200,6 +201,67 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
return new ImageInfo(new PixelTypeInfo(this.Frame.BitsPerPixel), pixelSize.Width, pixelSize.Height, this.Metadata); return new ImageInfo(new PixelTypeInfo(this.Frame.BitsPerPixel), pixelSize.Width, pixelSize.Height, this.Metadata);
} }
/// <summary>
/// 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>
/// <param name="tableBytes">The table bytes.</param>
/// <param name="huffmanScanDecoder">The scan decoder.</param>
public void LoadTables(byte[] tableBytes, HuffmanScanDecoder huffmanScanDecoder)
{
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.
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.");
}
// Read next marker.
stream.Read(this.markerBuffer, 0, 2);
byte marker = this.markerBuffer[1];
fileMarker = new JpegFileMarker(marker, (int)stream.Position - 2);
while (fileMarker.Marker != JpegConstants.Markers.EOI || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid))
{
if (!fileMarker.Invalid)
{
// 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> /// <summary>
/// Parses the input stream for file markers. /// Parses the input stream for file markers.
/// </summary> /// </summary>
@ -225,7 +287,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
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]; 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
@ -236,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)
@ -247,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, remaining, cancellationToken); this.ProcessStartOfScanMarker(stream, remaining);
break; break;
} }
else else
@ -326,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;
} }
} }
@ -345,10 +433,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
/// <summary> /// <summary>
/// Returns the correct colorspace based on the image component count and the jpeg frame components. /// 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(byte componentCount, JpegComponent[] components) private JpegColorSpace DeduceJpegColorSpace(byte componentCount)
{ {
if (componentCount == 1) if (componentCount == 1)
{ {
@ -363,7 +452,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
// If the component Id's are R, G, B in ASCII the colorspace is RGB and not YCbCr. // If the component Id's are R, G, B in ASCII the colorspace is RGB and not YCbCr.
if (components[2].Id == 66 && components[1].Id == 71 && components[0].Id == 82) if (this.Components[2].Id == 66 && this.Components[1].Id == 71 && this.Components[0].Id == 82)
{ {
return JpegColorSpace.RGB; return JpegColorSpace.RGB;
} }
@ -384,6 +473,64 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
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>
@ -582,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>
@ -797,7 +944,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
/// <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>
@ -889,9 +1036,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
index += componentBytes; index += componentBytes;
} }
this.ColorSpace = this.DeduceJpegColorSpace(componentCount, this.Frame.Components); this.ColorSpace = this.DeduceJpegColorSpace(componentCount);
this.Metadata.GetJpegMetadata().ColorType = this.DeduceJpegColorType();
this.Metadata.GetJpegMetadata().ColorType = this.ColorSpace == JpegColorSpace.Grayscale ? JpegColorType.Luminance : JpegColorType.YCbCr;
if (!metadataOnly) if (!metadataOnly)
{ {
@ -989,7 +1135,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary> /// <summary>
/// Processes the SOS (Start of scan marker). /// Processes the SOS (Start of scan marker).
/// </summary> /// </summary>
private void ProcessStartOfScanMarker(BufferedReadStream stream, int remaining, CancellationToken cancellationToken) private void ProcessStartOfScanMarker(BufferedReadStream stream, int remaining)
{ {
if (this.Frame is null) if (this.Frame is null)
{ {

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

@ -16,14 +16,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <inheritdoc/> /// <inheritdoc/>
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>
@ -36,7 +29,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); this.InitializeColorType(image);
encoder.Encode(image, stream); encoder.Encode(image, stream);
} }
@ -52,7 +45,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); this.InitializeColorType(image);
return encoder.EncodeAsync(image, stream, cancellationToken); return encoder.EncodeAsync(image, stream, cancellationToken);
} }
@ -75,7 +68,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
bool isGrayscale = bool isGrayscale =
typeof(TPixel) == typeof(L8) || typeof(TPixel) == typeof(L16) || typeof(TPixel) == typeof(L8) || typeof(TPixel) == typeof(L16) ||
typeof(TPixel) == typeof(La16) || typeof(TPixel) == typeof(La32); typeof(TPixel) == typeof(La16) || typeof(TPixel) == typeof(La32);
this.ColorType = isGrayscale ? JpegColorType.Luminance : JpegColorType.YCbCr; this.ColorType = isGrayscale ? JpegColorType.Luminance : JpegColorType.YCbCrRatio420;
} }
} }
} }

255
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,12 +51,15 @@ 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; if (IsSupportedColorType(options.ColorType))
{
this.colorType = options.ColorType;
}
} }
/// <summary> /// <summary>
@ -88,49 +86,72 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
ImageMetadata metadata = image.Metadata; ImageMetadata metadata = image.Metadata;
JpegMetadata jpegMetadata = metadata.GetJpegMetadata(); JpegMetadata jpegMetadata = metadata.GetJpegMetadata();
// If the color type was not specified by the user, preserve the color type of the input image, if it's a supported color type.
if (!this.colorType.HasValue && IsSupportedColorType(jpegMetadata.ColorType))
{
this.colorType = jpegMetadata.ColorType;
}
// 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();
// TODO: Right now encoder writes both quantization tables for grayscale images - we shouldn't do that // TODO: Right now encoder writes both quantization tables for grayscale images - we shouldn't do that
// Initialize the quantization tables. // Initialize the quantization tables.
this.InitQuantizationTables(componentCount, jpegMetadata, out Block8x8F luminanceQuantTable, out Block8x8F chrominanceQuantTable); this.InitQuantizationTables(componentCount, jpegMetadata, out Block8x8F luminanceQuantTable, out Block8x8F chrominanceQuantTable);
// Write the Start Of Image marker. // Write the Start Of Image marker.
this.WriteApplicationHeader(metadata); this.WriteStartOfImage();
// Do not write APP0 marker for RGB colorspace.
if (this.colorType != JpegColorType.Rgb)
{
this.WriteJfifApplicationHeader(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); var scanEncoder = new HuffmanScanEncoder(stream);
if (this.colorType == JpegColorType.Luminance) if (this.colorType == JpegColorType.Luminance)
{ {
// luminance quantization table only // luminance quantization table only.
scanEncoder.EncodeGrayscale(image, ref luminanceQuantTable, cancellationToken); scanEncoder.EncodeGrayscale(image, ref luminanceQuantTable, cancellationToken);
} }
else else
{ {
// luminance and chrominance quantization tables // luminance and chrominance quantization tables.
switch (this.subsample) switch (this.colorType)
{ {
case JpegSubsample.Ratio444: case JpegColorType.YCbCrRatio444:
case JpegColorType.Luminance:
scanEncoder.Encode444(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken); scanEncoder.Encode444(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken);
break; break;
case JpegSubsample.Ratio420: case JpegColorType.YCbCrRatio420:
scanEncoder.Encode420(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken); scanEncoder.Encode420(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken);
break; break;
case JpegColorType.Rgb:
scanEncoder.EncodeRgb(image, ref luminanceQuantTable, cancellationToken);
break;
} }
} }
@ -141,12 +162,32 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
/// <summary> /// <summary>
/// Writes data to "Define Quantization Tables" block for QuantIndex /// 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;
@ -157,52 +198,60 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
/// <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>
@ -212,7 +261,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,
@ -249,7 +298,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);
@ -265,6 +314,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>
@ -343,7 +421,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;
@ -385,7 +463,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)
{ {
@ -405,7 +483,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;
@ -478,22 +556,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 = stackalloc byte[]
{ {
0x22, 0x22,
0x11, 0x11,
0x11 0x11
}; };
Span<byte> chroma = stackalloc byte[] ReadOnlySpan<byte> chroma = stackalloc byte[]
{ {
0x00, 0x00,
0x01, 0x01,
@ -511,17 +592,29 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
else else
{ {
switch (this.subsample) switch (this.colorType)
{ {
case JpegSubsample.Ratio444: case JpegColorType.YCbCrRatio444:
case JpegColorType.Rgb:
subsamples = stackalloc byte[] subsamples = stackalloc byte[]
{ {
0x11, 0x11,
0x11, 0x11,
0x11 0x11
}; };
if (this.colorType == JpegColorType.Rgb)
{
chroma = stackalloc byte[]
{
0x00,
0x00,
0x00
};
}
break; break;
case JpegSubsample.Ratio420: case JpegColorType.YCbCrRatio420:
subsamples = stackalloc byte[] subsamples = stackalloc byte[]
{ {
0x22, 0x22,
@ -545,10 +638,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);
@ -557,26 +652,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 = stackalloc 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 = stackalloc 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",
@ -597,7 +696,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
} }
@ -633,7 +732,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
/// <summary> /// <summary>
/// Initializes quntization tables. /// Initializes quantization tables.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// We take quality values in a hierarchical order: /// We take quality values in a hierarchical order:
@ -672,9 +771,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
chromaQuality = Numerics.Clamp(chromaQuality, 1, 100); chromaQuality = Numerics.Clamp(chromaQuality, 1, 100);
chrominanceQuantTable = Quantization.ScaleChrominanceTable(chromaQuality); chrominanceQuantTable = Quantization.ScaleChrominanceTable(chromaQuality);
if (!this.subsample.HasValue) if (!this.colorType.HasValue)
{ {
this.subsample = chromaQuality >= 91 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420; this.colorType = chromaQuality >= 91 ? JpegColorType.YCbCrRatio444 : JpegColorType.YCbCrRatio420;
} }
} }
} }

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

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

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

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

@ -18,7 +18,7 @@ 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 bool isBigEndian;
@ -41,7 +41,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)
{ {
long pos = stream.Position; long pos = stream.Position;
using (var deframeStream = new ZlibInflateStream( using (var deframeStream = new ZlibInflateStream(

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

@ -0,0 +1,95 @@
// 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);
using var image = new Image<Rgb24>(this.configuration, spectralConverter.PixelBuffer, new ImageMetadata());
CopyImageBytesToBuffer(buffer, image);
}
else
{
using var image = Image.Load<Rgb24>(stream);
CopyImageBytesToBuffer(buffer, image);
}
}
private static void CopyImageBytesToBuffer(Span<byte> buffer, Image<Rgb24> image)
{
int offset = 0;
for (int y = 0; y < image.Height; y++)
{
Span<Rgb24> pixelRowSpan = image.GetPixelRowSpan(y);
Span<byte> rgbBytes = MemoryMarshal.AsBytes(pixelRowSpan);
rgbBytes.CopyTo(buffer.Slice(offset));
offset += rgbBytes.Length;
}
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
}
}
}

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

@ -12,7 +12,7 @@ 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 bool isBigEndian;
@ -35,7 +35,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)
{ {
var decoder = new TiffLzwDecoder(stream); var decoder = new TiffLzwDecoder(stream);
decoder.DecodePixels(buffer); decoder.DecodePixels(buffer);

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

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

@ -12,7 +12,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;
@ -27,17 +27,23 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <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, TiffFillOrder fillOrder, int width, int bitsPerPixel, TiffPhotometricInterpretation photometricInterpretation) public ModifiedHuffmanTiffCompression(MemoryAllocator allocator, TiffFillOrder fillOrder, int width, int bitsPerPixel, TiffPhotometricInterpretation photometricInterpretation)
: base(allocator, fillOrder, 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, this.FillOrder, 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;
@ -51,20 +57,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);
@ -74,7 +80,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)
{
}
} }
} }

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

@ -11,7 +11,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 +25,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)

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

@ -12,7 +12,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 +28,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/>
public override JpegColorConverter GetColorConverter(JpegFrame frame, IRawJpegData jpegData) => JpegColorConverter.GetConverter(JpegColorSpace.RGB, frame.Precision);
}
}

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

@ -16,31 +16,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// </summary> /// </summary>
internal class T4BitReader : IDisposable internal class T4BitReader : IDisposable
{ {
/// <summary>
/// Number of bits read.
/// </summary>
private int bitsRead;
/// <summary> /// <summary>
/// The logical order of bits within a byte. /// The logical order of bits within a byte.
/// </summary> /// </summary>
private readonly TiffFillOrder fillOrder; private readonly TiffFillOrder fillOrder;
/// <summary>
/// Current value.
/// </summary>
private uint value;
/// <summary>
/// Number of bits read for the current run value.
/// </summary>
private int curValueBitsRead;
/// <summary>
/// Byte position in the buffer.
/// </summary>
private ulong position;
/// <summary> /// <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.
/// </summary> /// </summary>
@ -57,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>()
@ -231,19 +210,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <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, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator, bool eolPadding = false, bool isModifiedHuffman = false)
{ {
this.fillOrder = fillOrder; 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;
@ -257,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>
@ -265,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.
@ -291,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;
} }
} }
@ -315,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();
@ -363,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)
@ -384,7 +367,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
} }
uint 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)
{ {
@ -396,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];
} }
} }
@ -453,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];
} }
} }
@ -516,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];
} }
} }
@ -559,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];
} }
} }
@ -597,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);
} }
} }
@ -648,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);
} }
} }
@ -694,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);
} }
} }
@ -727,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;
uint 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");
} }
} }

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

@ -12,7 +12,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;
@ -31,7 +31,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <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, TiffFillOrder fillOrder, 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;
@ -45,10 +51,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <summary> /// <summary>
/// Gets the logical order of bits within a byte. /// Gets the logical order of bits within a byte.
/// </summary> /// </summary>
protected TiffFillOrder FillOrder { get; } 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))
{ {

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

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

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

@ -15,8 +15,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 +33,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 +43,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 +56,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");

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

@ -29,13 +29,23 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
Lzw = 3, Lzw = 3,
/// <summary> /// <summary>
/// Image data is compressed using T4-encoding: CCITT T.4. /// Image data is compressed using CCITT T.4 fax compression.
/// </summary> /// </summary>
T4 = 4, T4 = 4,
/// <summary>
/// Image data is compressed using CCITT T.6 fax compression.
/// </summary>
T6 = 5,
/// <summary> /// <summary>
/// Image data is compressed using modified huffman compression. /// Image data is compressed using modified huffman compression.
/// </summary> /// </summary>
HuffmanRle = 5, HuffmanRle = 6,
/// <summary>
/// The image data is compressed as a JPEG stream.
/// </summary>
Jpeg = 7,
} }
} }

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

@ -11,6 +11,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
internal static class TiffDecompressorsFactory internal static class TiffDecompressorsFactory
{ {
public static TiffBaseDecompressor Create( public static TiffBaseDecompressor Create(
Configuration configuration,
TiffDecoderCompressionType method, TiffDecoderCompressionType method,
MemoryAllocator allocator, MemoryAllocator allocator,
TiffPhotometricInterpretation photometricInterpretation, TiffPhotometricInterpretation photometricInterpretation,
@ -19,6 +20,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
TiffColorType colorType, TiffColorType colorType,
TiffPredictor predictor, TiffPredictor predictor,
FaxCompressionOptions faxOptions, FaxCompressionOptions faxOptions,
byte[] jpegTables,
TiffFillOrder fillOrder, TiffFillOrder fillOrder,
ByteOrder byteOrder) ByteOrder byteOrder)
{ {
@ -46,10 +48,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
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");
return new T4TiffCompression(allocator, fillOrder, width, bitsPerPixel, faxOptions, photometricInterpretation); return new T4TiffCompression(allocator, fillOrder, width, bitsPerPixel, faxOptions, photometricInterpretation);
case TiffDecoderCompressionType.T6:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new T6TiffCompression(allocator, fillOrder, width, bitsPerPixel, photometricInterpretation);
case TiffDecoderCompressionType.HuffmanRle: case TiffDecoderCompressionType.HuffmanRle:
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");
return new ModifiedHuffmanTiffCompression(allocator, fillOrder, width, bitsPerPixel, photometricInterpretation); return new ModifiedHuffmanTiffCompression(allocator, fillOrder, width, bitsPerPixel, photometricInterpretation);
case TiffDecoderCompressionType.Jpeg:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
return new JpegTiffCompression(configuration, allocator, width, bitsPerPixel, jpegTables, photometricInterpretation);
default: default:
throw TiffThrowHelper.NotSupportedDecompressor(nameof(method)); throw TiffThrowHelper.NotSupportedDecompressor(nameof(method));
} }

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

@ -45,10 +45,10 @@
|Ccitt1D | Y | Y | | |Ccitt1D | Y | Y | |
|PackBits | Y | Y | | |PackBits | Y | Y | |
|CcittGroup3Fax | Y | Y | | |CcittGroup3Fax | Y | Y | |
|CcittGroup4Fax | | | | |CcittGroup4Fax | | Y | |
|Lzw | Y | Y | Based on ImageSharp GIF LZW implementation - this code could be modified to be (i) shared, or (ii) optimised for each case | |Lzw | Y | Y | Based on ImageSharp GIF LZW implementation - this code could be modified to be (i) shared, or (ii) optimised for each case |
|Old Jpeg | | | We should not even try to support this | |Old Jpeg | | | We should not even try to support this |
|Jpeg (Technote 2) | | | | |Jpeg (Technote 2) | Y | Y | |
|Deflate (Technote 2) | Y | Y | Based on PNG Deflate. | |Deflate (Technote 2) | Y | Y | Based on PNG Deflate. |
|Old Deflate (Technote 2) | | Y | | |Old Deflate (Technote 2) | | Y | |

BIN
src/ImageSharp/Formats/Tiff/TIFF-AdobeTechNote-22032002.pdf

Binary file not shown.

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

@ -105,6 +105,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary> /// </summary>
public TiffFillOrder FillOrder { get; set; } public TiffFillOrder FillOrder { get; set; }
/// <summary>
/// Gets or sets the JPEG tables when jpeg compression is used.
/// </summary>
public byte[] JpegTables { get; set; }
/// <summary> /// <summary>
/// Gets or sets the planar configuration type to use when decoding the image. /// Gets or sets the planar configuration type to use when decoding the image.
/// </summary> /// </summary>
@ -144,7 +149,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
var frames = new List<ImageFrame<TPixel>>(); var frames = new List<ImageFrame<TPixel>>();
foreach (ExifProfile ifd in directories) foreach (ExifProfile ifd in directories)
{ {
ImageFrame<TPixel> frame = this.DecodeFrame<TPixel>(ifd); cancellationToken.ThrowIfCancellationRequested();
ImageFrame<TPixel> frame = this.DecodeFrame<TPixel>(ifd, cancellationToken);
frames.Add(frame); frames.Add(frame);
} }
@ -186,10 +192,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="tags">The IFD tags.</param> /// <param name="tags">The IFD tags.</param>
/// <returns> /// <param name="cancellationToken">The token to monitor cancellation.</param>
/// The tiff frame. /// <returns> The tiff frame. </returns>
/// </returns> private ImageFrame<TPixel> DecodeFrame<TPixel>(ExifProfile tags, CancellationToken cancellationToken)
private ImageFrame<TPixel> DecodeFrame<TPixel>(ExifProfile tags)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
ImageFrameMetadata imageFrameMetaData = this.ignoreMetadata ? ImageFrameMetadata imageFrameMetaData = this.ignoreMetadata ?
@ -211,11 +216,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar) if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar)
{ {
this.DecodeStripsPlanar(frame, rowsPerStrip, stripOffsets, stripByteCounts); this.DecodeStripsPlanar(frame, rowsPerStrip, stripOffsets, stripByteCounts, cancellationToken);
} }
else else
{ {
this.DecodeStripsChunky(frame, rowsPerStrip, stripOffsets, stripByteCounts); this.DecodeStripsChunky(frame, rowsPerStrip, stripOffsets, stripByteCounts, cancellationToken);
} }
return frame; return frame;
@ -263,14 +268,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff
} }
/// <summary> /// <summary>
/// Decodes the image data for strip encoded data. /// Decodes the image data for planar encoded pixel data.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="frame">The image frame to decode data into.</param> /// <param name="frame">The image frame to decode data into.</param>
/// <param name="rowsPerStrip">The number of rows per strip of data.</param> /// <param name="rowsPerStrip">The number of rows per strip of data.</param>
/// <param name="stripOffsets">An array of byte offsets to each strip in the image.</param> /// <param name="stripOffsets">An array of byte offsets to each strip in the image.</param>
/// <param name="stripByteCounts">An array of the size of each strip (in bytes).</param> /// <param name="stripByteCounts">An array of the size of each strip (in bytes).</param>
private void DecodeStripsPlanar<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, Number[] stripOffsets, Number[] stripByteCounts) /// <param name="cancellationToken">The token to monitor cancellation.</param>
private void DecodeStripsPlanar<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, Number[] stripOffsets, Number[] stripByteCounts, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
int stripsPerPixel = this.BitsPerSample.Channels; int stripsPerPixel = this.BitsPerSample.Channels;
@ -290,6 +296,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
} }
using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create( using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(
this.Configuration,
this.CompressionType, this.CompressionType,
this.memoryAllocator, this.memoryAllocator,
this.PhotometricInterpretation, this.PhotometricInterpretation,
@ -298,6 +305,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.ColorType, this.ColorType,
this.Predictor, this.Predictor,
this.FaxCompressionOptions, this.FaxCompressionOptions,
this.JpegTables,
this.FillOrder, this.FillOrder,
this.byteOrder); this.byteOrder);
@ -312,12 +320,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff
for (int i = 0; i < stripsPerPlane; i++) for (int i = 0; i < stripsPerPlane; i++)
{ {
cancellationToken.ThrowIfCancellationRequested();
int stripHeight = i < stripsPerPlane - 1 || frame.Height % rowsPerStrip == 0 ? rowsPerStrip : frame.Height % rowsPerStrip; int stripHeight = i < stripsPerPlane - 1 || frame.Height % rowsPerStrip == 0 ? rowsPerStrip : frame.Height % rowsPerStrip;
int stripIndex = i; int stripIndex = i;
for (int planeIndex = 0; planeIndex < stripsPerPixel; planeIndex++) for (int planeIndex = 0; planeIndex < stripsPerPixel; planeIndex++)
{ {
decompressor.Decompress(this.inputStream, (uint)stripOffsets[stripIndex], (uint)stripByteCounts[stripIndex], stripBuffers[planeIndex].GetSpan()); decompressor.Decompress(
this.inputStream,
(uint)stripOffsets[stripIndex],
(uint)stripByteCounts[stripIndex],
stripHeight,
stripBuffers[planeIndex].GetSpan());
stripIndex += stripsPerPlane; stripIndex += stripsPerPlane;
} }
@ -333,7 +348,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff
} }
} }
private void DecodeStripsChunky<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, Number[] stripOffsets, Number[] stripByteCounts) /// <summary>
/// Decodes the image data for chunky encoded pixel data.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="frame">The image frame to decode data into.</param>
/// <param name="rowsPerStrip">The rows per strip.</param>
/// <param name="stripOffsets">The strip offsets.</param>
/// <param name="stripByteCounts">The strip byte counts.</param>
/// <param name="cancellationToken">The token to monitor cancellation.</param>
private void DecodeStripsChunky<TPixel>(ImageFrame<TPixel> frame, int rowsPerStrip, Number[] stripOffsets, Number[] stripByteCounts, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// If the rowsPerStrip has the default value, which is effectively infinity. That is, the entire image is one strip. // If the rowsPerStrip has the default value, which is effectively infinity. That is, the entire image is one strip.
@ -350,6 +374,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
Buffer2D<TPixel> pixels = frame.PixelBuffer; Buffer2D<TPixel> pixels = frame.PixelBuffer;
using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create( using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(
this.Configuration,
this.CompressionType, this.CompressionType,
this.memoryAllocator, this.memoryAllocator,
this.PhotometricInterpretation, this.PhotometricInterpretation,
@ -358,6 +383,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.ColorType, this.ColorType,
this.Predictor, this.Predictor,
this.FaxCompressionOptions, this.FaxCompressionOptions,
this.JpegTables,
this.FillOrder, this.FillOrder,
this.byteOrder); this.byteOrder);
@ -374,6 +400,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++) for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++)
{ {
cancellationToken.ThrowIfCancellationRequested();
int stripHeight = stripIndex < stripOffsets.Length - 1 || frame.Height % rowsPerStrip == 0 int stripHeight = stripIndex < stripOffsets.Length - 1 || frame.Height % rowsPerStrip == 0
? rowsPerStrip ? rowsPerStrip
: frame.Height % rowsPerStrip; : frame.Height % rowsPerStrip;
@ -385,7 +413,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
break; break;
} }
decompressor.Decompress(this.inputStream, (uint)stripOffsets[stripIndex], (uint)stripByteCounts[stripIndex], stripBufferSpan); decompressor.Decompress(this.inputStream, (uint)stripOffsets[stripIndex], (uint)stripByteCounts[stripIndex], stripHeight, stripBufferSpan);
colorDecoder.Decode(stripBufferSpan, pixels, 0, top, frame.Width, stripHeight); colorDecoder.Decode(stripBufferSpan, pixels, 0, top, frame.Width, stripHeight);
} }

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

@ -87,6 +87,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
options.YcbcrCoefficients = exifProfile.GetValue(ExifTag.YCbCrCoefficients)?.Value; options.YcbcrCoefficients = exifProfile.GetValue(ExifTag.YCbCrCoefficients)?.Value;
options.YcbcrSubSampling = exifProfile.GetValue(ExifTag.YCbCrSubsampling)?.Value; options.YcbcrSubSampling = exifProfile.GetValue(ExifTag.YCbCrSubsampling)?.Value;
options.FillOrder = fillOrder; options.FillOrder = fillOrder;
options.JpegTables = exifProfile.GetValue(ExifTag.JPEGTables)?.Value;
options.ParseColorType(exifProfile); options.ParseColorType(exifProfile);
options.ParseCompression(frameMetadata.Compression, exifProfile); options.ParseCompression(frameMetadata.Compression, exifProfile);
@ -418,12 +419,26 @@ namespace SixLabors.ImageSharp.Formats.Tiff
break; break;
} }
case TiffCompression.CcittGroup4Fax:
{
options.CompressionType = TiffDecoderCompressionType.T6;
options.FaxCompressionOptions = exifProfile.GetValue(ExifTag.T4Options) != null ? (FaxCompressionOptions)exifProfile.GetValue(ExifTag.T4Options).Value : FaxCompressionOptions.None;
break;
}
case TiffCompression.Ccitt1D: case TiffCompression.Ccitt1D:
{ {
options.CompressionType = TiffDecoderCompressionType.HuffmanRle; options.CompressionType = TiffDecoderCompressionType.HuffmanRle;
break; break;
} }
case TiffCompression.Jpeg:
{
options.CompressionType = TiffDecoderCompressionType.Jpeg;
break;
}
default: default:
{ {
TiffThrowHelper.ThrowNotSupported($"The specified TIFF compression format {compression} is not supported"); TiffThrowHelper.ThrowNotSupported($"The specified TIFF compression format {compression} is not supported");

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

@ -155,6 +155,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
Image<TPixel> metadataImage = image; Image<TPixel> metadataImage = image;
foreach (ImageFrame<TPixel> frame in image.Frames) foreach (ImageFrame<TPixel> frame in image.Frames)
{ {
cancellationToken.ThrowIfCancellationRequested();
var subfileType = (TiffNewSubfileType)(frame.Metadata.ExifProfile?.GetValue(ExifTag.SubfileType)?.Value ?? (int)TiffNewSubfileType.FullImage); var subfileType = (TiffNewSubfileType)(frame.Metadata.ExifProfile?.GetValue(ExifTag.SubfileType)?.Value ?? (int)TiffNewSubfileType.FullImage);
ifdMarker = this.WriteFrame(writer, frame, image.Metadata, metadataImage, ifdMarker); ifdMarker = this.WriteFrame(writer, frame, image.Metadata, metadataImage, ifdMarker);
@ -223,7 +225,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
entriesCollector, entriesCollector,
(int)this.BitsPerPixel); (int)this.BitsPerPixel);
int rowsPerStrip = this.CalcRowsPerStrip(frame.Height, colorWriter.BytesPerRow); int rowsPerStrip = this.CalcRowsPerStrip(frame.Height, colorWriter.BytesPerRow, this.CompressionType);
colorWriter.Write(compressor, rowsPerStrip); colorWriter.Write(compressor, rowsPerStrip);
@ -245,13 +247,22 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary> /// </summary>
/// <param name="height">The height of the image.</param> /// <param name="height">The height of the image.</param>
/// <param name="bytesPerRow">The number of bytes per row.</param> /// <param name="bytesPerRow">The number of bytes per row.</param>
/// <param name="compression">The compression used.</param>
/// <returns>Number of rows per strip.</returns> /// <returns>Number of rows per strip.</returns>
private int CalcRowsPerStrip(int height, int bytesPerRow) private int CalcRowsPerStrip(int height, int bytesPerRow, TiffCompression? compression)
{ {
DebugGuard.MustBeGreaterThan(height, 0, nameof(height)); DebugGuard.MustBeGreaterThan(height, 0, nameof(height));
DebugGuard.MustBeGreaterThan(bytesPerRow, 0, nameof(bytesPerRow)); DebugGuard.MustBeGreaterThan(bytesPerRow, 0, nameof(bytesPerRow));
int rowsPerStrip = TiffConstants.DefaultStripSize / bytesPerRow; // Jpeg compressed images should be written in one strip.
if (compression is TiffCompression.Jpeg)
{
return height;
}
// If compression is used, change stripSizeInBytes heuristically to a larger value to not write to many strips.
int stripSizeInBytes = compression is TiffCompression.Deflate || compression is TiffCompression.Lzw ? TiffConstants.DefaultStripSize * 2 : TiffConstants.DefaultStripSize;
int rowsPerStrip = stripSizeInBytes / bytesPerRow;
if (rowsPerStrip > 0) if (rowsPerStrip > 0)
{ {

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

@ -396,6 +396,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
case TiffCompression.Ccitt1D: case TiffCompression.Ccitt1D:
return (ushort)TiffCompression.Ccitt1D; return (ushort)TiffCompression.Ccitt1D;
case TiffCompression.Jpeg:
return (ushort)TiffCompression.Jpeg;
} }
return (ushort)TiffCompression.None; return (ushort)TiffCompression.None;

28
src/ImageSharp/Image.FromBytes.cs

@ -102,6 +102,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The data is null.</exception> /// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(byte[] data) public static Image<TPixel> Load<TPixel>(byte[] data)
@ -116,6 +117,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The data is null.</exception> /// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(byte[] data, out IImageFormat format) public static Image<TPixel> Load<TPixel>(byte[] data, out IImageFormat format)
@ -131,6 +133,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data is null.</exception> /// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration configuration, byte[] data) public static Image<TPixel> Load<TPixel>(Configuration configuration, byte[] data)
@ -154,6 +157,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data is null.</exception> /// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration configuration, byte[] data, out IImageFormat format) public static Image<TPixel> Load<TPixel>(Configuration configuration, byte[] data, out IImageFormat format)
@ -175,6 +179,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The data is null.</exception> /// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(byte[] data, IImageDecoder decoder) public static Image<TPixel> Load<TPixel>(byte[] data, IImageDecoder decoder)
@ -198,6 +203,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The data is null.</exception> /// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration configuration, byte[] data, IImageDecoder decoder) public static Image<TPixel> Load<TPixel>(Configuration configuration, byte[] data, IImageDecoder decoder)
@ -216,10 +222,7 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="data">The byte span containing encoded image data to read the header from.</param> /// <param name="data">The byte span containing encoded image data to read the header from.</param>
/// <returns>The format or null if none found.</returns> /// <returns>The format or null if none found.</returns>
public static IImageFormat DetectFormat(ReadOnlySpan<byte> data) public static IImageFormat DetectFormat(ReadOnlySpan<byte> data) => DetectFormat(Configuration.Default, data);
{
return DetectFormat(Configuration.Default, data);
}
/// <summary> /// <summary>
/// By reading the header on the provided byte span this calculates the images format. /// By reading the header on the provided byte span this calculates the images format.
@ -258,6 +261,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data) public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
@ -271,6 +275,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data, out IImageFormat format) public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
@ -284,6 +289,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data, IImageDecoder decoder) public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
@ -298,6 +304,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static unsafe Image<TPixel> Load<TPixel>(Configuration configuration, ReadOnlySpan<byte> data) public static unsafe Image<TPixel> Load<TPixel>(Configuration configuration, ReadOnlySpan<byte> data)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
@ -321,6 +328,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static unsafe Image<TPixel> Load<TPixel>( public static unsafe Image<TPixel> Load<TPixel>(
Configuration configuration, Configuration configuration,
@ -347,6 +355,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static unsafe Image<TPixel> Load<TPixel>( public static unsafe Image<TPixel> Load<TPixel>(
Configuration configuration, Configuration configuration,
@ -372,6 +381,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The data is null.</exception> /// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(byte[] data, out IImageFormat format) public static Image Load(byte[] data, out IImageFormat format)
=> Load(Configuration.Default, data, out format); => Load(Configuration.Default, data, out format);
@ -384,6 +394,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The data is null.</exception> /// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(byte[] data, IImageDecoder decoder) public static Image Load(byte[] data, IImageDecoder decoder)
=> Load(Configuration.Default, data, decoder); => Load(Configuration.Default, data, decoder);
@ -397,6 +408,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The data is null.</exception> /// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(Configuration configuration, byte[] data) public static Image Load(Configuration configuration, byte[] data)
=> Load(configuration, data, out _); => Load(configuration, data, out _);
@ -411,6 +423,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The data is null.</exception> /// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(Configuration configuration, byte[] data, IImageDecoder decoder) public static Image Load(Configuration configuration, byte[] data, IImageDecoder decoder)
{ {
@ -430,6 +443,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The data is null.</exception> /// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(Configuration configuration, byte[] data, out IImageFormat format) public static Image Load(Configuration configuration, byte[] data, out IImageFormat format)
{ {
@ -445,6 +459,7 @@ namespace SixLabors.ImageSharp
/// <param name="data">The byte span containing image data.</param> /// <param name="data">The byte span containing image data.</param>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(ReadOnlySpan<byte> data) public static Image Load(ReadOnlySpan<byte> data)
=> Load(Configuration.Default, data); => Load(Configuration.Default, data);
@ -458,6 +473,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The decoder is null.</exception> /// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(ReadOnlySpan<byte> data, IImageDecoder decoder) public static Image Load(ReadOnlySpan<byte> data, IImageDecoder decoder)
=> Load(Configuration.Default, data, decoder); => Load(Configuration.Default, data, decoder);
@ -470,6 +486,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The decoder is null.</exception> /// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(ReadOnlySpan<byte> data, out IImageFormat format) public static Image Load(ReadOnlySpan<byte> data, out IImageFormat format)
=> Load(Configuration.Default, data, out format); => Load(Configuration.Default, data, out format);
@ -491,7 +508,7 @@ namespace SixLabors.ImageSharp
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</exception> /// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
@ -518,6 +535,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static unsafe Image Load( public static unsafe Image Load(
Configuration configuration, Configuration configuration,

18
src/ImageSharp/Image.FromFile.cs

@ -182,6 +182,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(Configuration configuration, string path) public static Image Load(Configuration configuration, string path)
@ -196,6 +197,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static async Task<Image> LoadAsync( public static async Task<Image> LoadAsync(
@ -219,6 +221,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</exception> /// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(Configuration configuration, string path, IImageDecoder decoder) public static Image Load(Configuration configuration, string path, IImageDecoder decoder)
@ -241,6 +244,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</exception> /// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static Task<Image> LoadAsync(string path, CancellationToken cancellationToken = default) public static Task<Image> LoadAsync(string path, CancellationToken cancellationToken = default)
@ -255,6 +259,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</exception> /// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static Task<Image> LoadAsync(string path, IImageDecoder decoder) public static Task<Image> LoadAsync(string path, IImageDecoder decoder)
@ -269,6 +274,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</exception> /// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
@ -287,6 +293,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</exception> /// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static Task<Image> LoadAsync( public static Task<Image> LoadAsync(
@ -313,6 +320,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</exception> /// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
@ -338,6 +346,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static Task<Image<TPixel>> LoadAsync<TPixel>(string path) public static Task<Image<TPixel>> LoadAsync<TPixel>(string path)
@ -353,6 +362,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
@ -376,6 +386,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</exception> /// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
public static Image Load(string path, IImageDecoder decoder) public static Image Load(string path, IImageDecoder decoder)
@ -388,6 +399,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path) public static Image<TPixel> Load<TPixel>(string path)
@ -402,6 +414,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(string path, out IImageFormat format) public static Image<TPixel> Load<TPixel>(string path, out IImageFormat format)
@ -416,6 +429,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
@ -440,6 +454,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
@ -465,6 +480,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image Load(Configuration configuration, string path, out IImageFormat format) public static Image Load(Configuration configuration, string path, out IImageFormat format)
@ -485,6 +501,7 @@ namespace SixLabors.ImageSharp
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
@ -502,6 +519,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The path is null.</exception> /// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</exception> /// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>

51
src/ImageSharp/Image.FromStream.cs

@ -9,7 +9,6 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.IO; using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp namespace SixLabors.ImageSharp
@ -216,7 +215,7 @@ namespace SixLabors.ImageSharp
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="format">The format type of the decoded image.</param> /// <param name="format">The format type of the decoded image.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
@ -229,7 +228,7 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{ValueTuple}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{ValueTuple}"/> representing the asynchronous operation.</returns>
@ -242,7 +241,7 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
@ -254,7 +253,7 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
@ -268,7 +267,7 @@ namespace SixLabors.ImageSharp
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</exception> /// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns> /// <returns>The <see cref="Image"/>.</returns>
@ -283,7 +282,7 @@ namespace SixLabors.ImageSharp
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</exception> /// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
@ -300,7 +299,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</exception> /// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image"/>.</returns> /// <returns>A new <see cref="Image"/>.</returns>
@ -321,7 +320,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</exception> /// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
@ -346,7 +345,7 @@ namespace SixLabors.ImageSharp
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image"/>.</returns> /// <returns>A new <see cref="Image"/>.</returns>
@ -360,7 +359,7 @@ namespace SixLabors.ImageSharp
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
@ -376,7 +375,7 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
@ -390,7 +389,7 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
@ -405,7 +404,7 @@ namespace SixLabors.ImageSharp
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="format">The format type of the decoded image.</param> /// <param name="format">The format type of the decoded image.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
@ -419,7 +418,7 @@ namespace SixLabors.ImageSharp
/// </summary> /// </summary>
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
@ -434,7 +433,7 @@ namespace SixLabors.ImageSharp
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
@ -450,7 +449,7 @@ namespace SixLabors.ImageSharp
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
@ -471,7 +470,7 @@ namespace SixLabors.ImageSharp
/// <param name="decoder">The decoder.</param> /// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
@ -489,7 +488,7 @@ namespace SixLabors.ImageSharp
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
@ -513,7 +512,7 @@ namespace SixLabors.ImageSharp
/// <param name="stream">The stream containing image information.</param> /// <param name="stream">The stream containing image information.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
@ -530,7 +529,7 @@ namespace SixLabors.ImageSharp
/// <param name="format">The format type of the decoded image.</param> /// <param name="format">The format type of the decoded image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
@ -566,7 +565,7 @@ namespace SixLabors.ImageSharp
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{ValueTuple}"/> representing the asynchronous operation.</returns> /// <returns>A <see cref="Task{ValueTuple}"/> representing the asynchronous operation.</returns>
@ -606,7 +605,7 @@ namespace SixLabors.ImageSharp
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
@ -649,7 +648,7 @@ namespace SixLabors.ImageSharp
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param> /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam> /// <typeparam name="TPixel">The pixel format.</typeparam>
@ -674,7 +673,7 @@ namespace SixLabors.ImageSharp
/// <param name="format">The format type of the decoded image.</param> /// <param name="format">The format type of the decoded image.</param>
/// <exception cref="ArgumentNullException">The configuration is null.</exception> /// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="ArgumentNullException">The stream is null.</exception> /// <exception cref="ArgumentNullException">The stream is null.</exception>
/// <exception cref="NotSupportedException">The stream is not readable.</exception> /// <exception cref="NotSupportedException">The stream is not readable or the image format is not supported.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception> /// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception> /// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns> /// <returns>A new <see cref="Image{TPixel}"/>.</returns>
@ -763,7 +762,7 @@ namespace SixLabors.ImageSharp
} }
// To make sure we don't trigger anything with aspnetcore then we just need to make sure we are // To make sure we don't trigger anything with aspnetcore then we just need to make sure we are
// seekable and we make the copy using CopyToAsync if the stream is seekable then we arn't using // seekable and we make the copy using CopyToAsync if the stream is seekable then we aren't using
// one of the aspnetcore wrapped streams that error on sync api calls and we can use it without // one of the aspnetcore wrapped streams that error on sync api calls and we can use it without
// having to further wrap // having to further wrap
if (stream.CanSeek) if (stream.CanSeek)

5
src/ImageSharp/ImageSharp.csproj

@ -16,6 +16,11 @@
</PropertyGroup> </PropertyGroup>
<Choose> <Choose>
<When Condition="$(SIXLABORS_TESTING_PREVIEW) == true">
<PropertyGroup>
<TargetFrameworks>net6.0;net5.0;netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472</TargetFrameworks>
</PropertyGroup>
</When>
<When Condition="$(SIXLABORS_TESTING) == true"> <When Condition="$(SIXLABORS_TESTING) == true">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net5.0;netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472</TargetFrameworks> <TargetFrameworks>net5.0;netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472</TargetFrameworks>

2
src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs

@ -257,7 +257,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private void AddPixelsToHistogram(ref Vector4 greyValuesBase, ref int histogramBase, int luminanceLevels, int length) private void AddPixelsToHistogram(ref Vector4 greyValuesBase, ref int histogramBase, int luminanceLevels, int length)
{ {
for (int idx = 0; idx < length; idx++) for (nint idx = 0; idx < length; idx++)
{ {
int luminance = ColorNumerics.GetBT709Luminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels); int luminance = ColorNumerics.GetBT709Luminance(ref Unsafe.Add(ref greyValuesBase, idx), luminanceLevels);
Unsafe.Add(ref histogramBase, luminance)++; Unsafe.Add(ref histogramBase, luminance)++;

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

@ -40,8 +40,8 @@ namespace SixLabors.ImageSharp.Benchmarks.Codecs.Jpeg
this.bmpCore = Image.Load<Rgba32>(this.bmpStream); this.bmpCore = Image.Load<Rgba32>(this.bmpStream);
this.bmpCore.Metadata.ExifProfile = null; this.bmpCore.Metadata.ExifProfile = null;
this.encoder420 = new JpegEncoder { Quality = this.Quality, Subsample = JpegSubsample.Ratio420 }; this.encoder420 = new JpegEncoder { Quality = this.Quality, ColorType = JpegColorType.YCbCrRatio420 };
this.encoder444 = new JpegEncoder { Quality = this.Quality, Subsample = JpegSubsample.Ratio444 }; this.encoder444 = new JpegEncoder { Quality = this.Quality, ColorType = JpegColorType.YCbCrRatio444 };
this.bmpStream.Position = 0; this.bmpStream.Position = 0;
this.bmpDrawing = SDImage.FromStream(this.bmpStream); this.bmpDrawing = SDImage.FromStream(this.bmpStream);

6
tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj

@ -9,12 +9,16 @@
<!--Used to hide test project from dotnet test--> <!--Used to hide test project from dotnet test-->
<IsTestProject>false</IsTestProject> <IsTestProject>false</IsTestProject>
<Configurations>Debug;Release;Debug-InnerLoop;Release-InnerLoop</Configurations> <Configurations>Debug;Release;Debug-InnerLoop;Release-InnerLoop</Configurations>
<LangVersion>9</LangVersion>
<!-- Uncomment this to run benchmarks depending on Colorful or Pfim (colorspaces and TGA): --> <!-- Uncomment this to run benchmarks depending on Colorful or Pfim (colorspaces and TGA): -->
<!--<SignAssembly>false</SignAssembly>--> <!--<SignAssembly>false</SignAssembly>-->
</PropertyGroup> </PropertyGroup>
<Choose> <Choose>
<When Condition="$(SIXLABORS_TESTING_PREVIEW) == true">
<PropertyGroup>
<TargetFrameworks>net6.0;net5.0;netcoreapp3.1;netcoreapp2.1;net472</TargetFrameworks>
</PropertyGroup>
</When>
<When Condition="$(Configuration.EndsWith('InnerLoop')) == true"> <When Condition="$(Configuration.EndsWith('InnerLoop')) == true">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netcoreapp3.1</TargetFrameworks> <TargetFrameworks>netcoreapp3.1</TargetFrameworks>

6
tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj

@ -14,10 +14,14 @@
<EnsureNETCoreAppRuntime>false</EnsureNETCoreAppRuntime> <EnsureNETCoreAppRuntime>false</EnsureNETCoreAppRuntime>
<Configurations>Debug;Release;Debug-InnerLoop;Release-InnerLoop</Configurations> <Configurations>Debug;Release;Debug-InnerLoop;Release-InnerLoop</Configurations>
<ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained> <ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
<LangVersion>9</LangVersion>
</PropertyGroup> </PropertyGroup>
<Choose> <Choose>
<When Condition="$(SIXLABORS_TESTING_PREVIEW) == true">
<PropertyGroup>
<TargetFrameworks>net6.0;net5.0;netcoreapp3.1;netcoreapp2.1;net472</TargetFrameworks>
</PropertyGroup>
</When>
<When Condition="$(Configuration.EndsWith('InnerLoop')) == true"> <When Condition="$(Configuration.EndsWith('InnerLoop')) == true">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netcoreapp3.1</TargetFrameworks> <TargetFrameworks>netcoreapp3.1</TargetFrameworks>

59
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs

@ -67,16 +67,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
string imagePath, string imagePath,
int expectedPixelSize, int expectedPixelSize,
bool exifProfilePresent, bool exifProfilePresent,
bool iccProfilePresent) bool iccProfilePresent) => TestMetadataImpl(
{
TestMetadataImpl(
useIdentify, useIdentify,
JpegDecoder, JpegDecoder,
imagePath, imagePath,
expectedPixelSize, expectedPixelSize,
exifProfilePresent, exifProfilePresent,
iccProfilePresent); iccProfilePresent);
}
[Theory] [Theory]
[MemberData(nameof(RatioFiles))] [MemberData(nameof(RatioFiles))]
@ -133,8 +130,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using (var stream = new MemoryStream(testFile.Bytes, false))
{ {
var decoder = new JpegDecoder(); using (Image<Rgba32> image = JpegDecoder.Decode<Rgba32>(Configuration.Default, stream))
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream))
{ {
JpegMetadata meta = image.Metadata.GetJpegMetadata(); JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(quality, meta.Quality); Assert.Equal(quality, meta.Quality);
@ -142,6 +138,42 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
} }
} }
[Theory]
[InlineData(TestImages.Jpeg.Baseline.Floorplan, JpegColorType.Luminance)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg420Small, JpegColorType.YCbCrRatio420)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg444, JpegColorType.YCbCrRatio444)]
[InlineData(TestImages.Jpeg.Baseline.JpegRgb, JpegColorType.Rgb)]
[InlineData(TestImages.Jpeg.Baseline.Cmyk, JpegColorType.Cmyk)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg410, JpegColorType.YCbCrRatio410)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg422, JpegColorType.YCbCrRatio422)]
[InlineData(TestImages.Jpeg.Baseline.Jpeg411, JpegColorType.YCbCrRatio411)]
public void Identify_DetectsCorrectColorType(string imagePath, JpegColorType expectedColorType)
{
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
IImageInfo image = JpegDecoder.Identify(Configuration.Default, stream);
JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(expectedColorType, meta.ColorType);
}
}
[Theory]
[WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32, JpegColorType.Luminance)]
[WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgba32, JpegColorType.YCbCrRatio420)]
[WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgba32, JpegColorType.YCbCrRatio444)]
[WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgba32, JpegColorType.Rgb)]
[WithFile(TestImages.Jpeg.Baseline.Cmyk, PixelTypes.Rgba32, JpegColorType.Cmyk)]
public void Decode_DetectsCorrectColorType<TPixel>(TestImageProvider<TPixel> provider, JpegColorType expectedColorType)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(JpegDecoder))
{
JpegMetadata meta = image.Metadata.GetJpegMetadata();
Assert.Equal(expectedColorType, meta.ColorType);
}
}
private static void TestImageInfo(string imagePath, IImageDecoder decoder, bool useIdentify, Action<IImageInfo> test) private static void TestImageInfo(string imagePath, IImageDecoder decoder, bool useIdentify, Action<IImageInfo> test)
{ {
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
@ -161,9 +193,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
string imagePath, string imagePath,
int expectedPixelSize, int expectedPixelSize,
bool exifProfilePresent, bool exifProfilePresent,
bool iccProfilePresent) bool iccProfilePresent) => TestImageInfo(
{
TestImageInfo(
imagePath, imagePath,
decoder, decoder,
useIdentify, useIdentify,
@ -207,7 +237,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Null(iccProfile); Assert.Null(iccProfile);
} }
}); });
}
[Theory] [Theory]
[InlineData(false)] [InlineData(false)]
@ -237,9 +266,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory] [Theory]
[InlineData(false)] [InlineData(false)]
[InlineData(true)] [InlineData(true)]
public void Decoder_Reads_Correct_Resolution_From_Jfif(bool useIdentify) public void Decoder_Reads_Correct_Resolution_From_Jfif(bool useIdentify) => TestImageInfo(
{
TestImageInfo(
TestImages.Jpeg.Baseline.Floorplan, TestImages.Jpeg.Baseline.Floorplan,
JpegDecoder, JpegDecoder,
useIdentify, useIdentify,
@ -248,14 +275,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(300, imageInfo.Metadata.HorizontalResolution); Assert.Equal(300, imageInfo.Metadata.HorizontalResolution);
Assert.Equal(300, imageInfo.Metadata.VerticalResolution); Assert.Equal(300, imageInfo.Metadata.VerticalResolution);
}); });
}
[Theory] [Theory]
[InlineData(false)] [InlineData(false)]
[InlineData(true)] [InlineData(true)]
public void Decoder_Reads_Correct_Resolution_From_Exif(bool useIdentify) public void Decoder_Reads_Correct_Resolution_From_Exif(bool useIdentify) => TestImageInfo(
{
TestImageInfo(
TestImages.Jpeg.Baseline.Jpeg420Exif, TestImages.Jpeg.Baseline.Jpeg420Exif,
JpegDecoder, JpegDecoder,
useIdentify, useIdentify,
@ -264,6 +288,5 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(72, imageInfo.Metadata.HorizontalResolution); Assert.Equal(72, imageInfo.Metadata.HorizontalResolution);
Assert.Equal(72, imageInfo.Metadata.VerticalResolution); Assert.Equal(72, imageInfo.Metadata.VerticalResolution);
}); });
}
} }
} }

17
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs

@ -74,7 +74,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
byte[] bytes = TestFile.Create(TestImages.Jpeg.Progressive.Progress).Bytes; byte[] bytes = TestFile.Create(TestImages.Jpeg.Progressive.Progress).Bytes;
using var ms = new MemoryStream(bytes); using var ms = new MemoryStream(bytes);
using var bufferedStream = new BufferedReadStream(Configuration.Default, ms); using var bufferedStream = new BufferedReadStream(Configuration.Default, ms);
var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder()); using var decoder = new JpegDecoderCore(Configuration.Default, new JpegDecoder());
using Image<Rgba32> image = decoder.Decode<Rgba32>(bufferedStream, cancellationToken: default); using Image<Rgba32> image = decoder.Decode<Rgba32>(bufferedStream, cancellationToken: default);
// I don't know why these numbers are different. All I know is that the decoder works // I don't know why these numbers are different. All I know is that the decoder works
@ -174,13 +174,26 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
await Assert.ThrowsAsync<TaskCanceledException>(async () => await Image.IdentifyAsync(config, "someFakeFile", cts.Token)); await Assert.ThrowsAsync<TaskCanceledException>(async () => await Image.IdentifyAsync(config, "someFakeFile", cts.Token));
} }
[Theory]
[WithFile(TestImages.Jpeg.Baseline.ArithmeticCoding, PixelTypes.Rgba32)]
[WithFile(TestImages.Jpeg.Baseline.ArithmeticCodingProgressive, PixelTypes.Rgba32)]
[WithFile(TestImages.Jpeg.Baseline.Lossless, PixelTypes.Rgba32)]
public void ThrowsNotSupported_WithUnsupportedJpegs<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
Assert.Throws<NotSupportedException>(() =>
{
using Image<TPixel> image = provider.GetImage(JpegDecoder);
});
}
// https://github.com/SixLabors/ImageSharp/pull/1732 // https://github.com/SixLabors/ImageSharp/pull/1732
[Theory] [Theory]
[WithFile(TestImages.Jpeg.Issues.WrongColorSpace, PixelTypes.Rgba32)] [WithFile(TestImages.Jpeg.Issues.WrongColorSpace, PixelTypes.Rgba32)]
public void Issue1732_DecodesWithRgbColorSpace<TPixel>(TestImageProvider<TPixel> provider) public void Issue1732_DecodesWithRgbColorSpace<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
using (Image<TPixel> image = provider.GetImage(new JpegDecoder())) using (Image<TPixel> image = provider.GetImage(JpegDecoder))
{ {
image.DebugSave(provider); image.DebugSave(provider);
image.CompareToOriginal(provider); image.CompareToOriginal(provider);

190
tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs

@ -24,6 +24,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Trait("Format", "Jpg")] [Trait("Format", "Jpg")]
public class JpegEncoderTests public class JpegEncoderTests
{ {
private static JpegEncoder JpegEncoder => new JpegEncoder();
private static JpegDecoder JpegDecoder => new JpegDecoder();
public static readonly TheoryData<string, int> QualityFiles = public static readonly TheoryData<string, int> QualityFiles =
new TheoryData<string, int> new TheoryData<string, int>
{ {
@ -31,15 +35,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{ TestImages.Jpeg.Progressive.Fb, 75 } { TestImages.Jpeg.Progressive.Fb, 75 }
}; };
public static readonly TheoryData<JpegSubsample, int> BitsPerPixel_Quality = public static readonly TheoryData<JpegColorType, int> BitsPerPixel_Quality =
new TheoryData<JpegSubsample, int> new TheoryData<JpegColorType, int>
{ {
{ JpegSubsample.Ratio420, 40 }, { JpegColorType.YCbCrRatio420, 40 },
{ JpegSubsample.Ratio420, 60 }, { JpegColorType.YCbCrRatio420, 60 },
{ JpegSubsample.Ratio420, 100 }, { JpegColorType.YCbCrRatio420, 100 },
{ JpegSubsample.Ratio444, 40 }, { JpegColorType.YCbCrRatio444, 40 },
{ JpegSubsample.Ratio444, 60 }, { JpegColorType.YCbCrRatio444, 60 },
{ JpegSubsample.Ratio444, 100 }, { JpegColorType.YCbCrRatio444, 100 },
{ JpegColorType.Rgb, 40 },
{ JpegColorType.Rgb, 60 },
{ JpegColorType.Rgb, 100 }
}; };
public static readonly TheoryData<int> Grayscale_Quality = public static readonly TheoryData<int> Grayscale_Quality =
@ -59,17 +66,84 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}; };
[Theory] [Theory]
[MemberData(nameof(QualityFiles))] [WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32, JpegColorType.Luminance)]
public void Encode_PreserveQuality(string imagePath, int quality) [WithFile(TestImages.Jpeg.Baseline.Jpeg444, PixelTypes.Rgba32, JpegColorType.YCbCrRatio444)]
[WithFile(TestImages.Jpeg.Baseline.Jpeg420Small, PixelTypes.Rgba32, JpegColorType.YCbCrRatio420)]
[WithFile(TestImages.Jpeg.Baseline.JpegRgb, PixelTypes.Rgba32, JpegColorType.Rgb)]
public void Encode_PreservesColorType<TPixel>(TestImageProvider<TPixel> provider, JpegColorType expectedColorType)
where TPixel : unmanaged, IPixel<TPixel>
{
// arrange
using Image<TPixel> input = provider.GetImage(JpegDecoder);
using var memoryStream = new MemoryStream();
// act
input.Save(memoryStream, JpegEncoder);
// assert
memoryStream.Position = 0;
using var output = Image.Load<Rgba32>(memoryStream);
JpegMetadata meta = output.Metadata.GetJpegMetadata();
Assert.Equal(expectedColorType, meta.ColorType);
}
[Theory]
[WithFile(TestImages.Jpeg.Baseline.Cmyk, PixelTypes.Rgba32)]
[WithFile(TestImages.Jpeg.Baseline.Jpeg410, PixelTypes.Rgba32)]
[WithFile(TestImages.Jpeg.Baseline.Jpeg411, PixelTypes.Rgba32)]
[WithFile(TestImages.Jpeg.Baseline.Jpeg422, PixelTypes.Rgba32)]
public void Encode_WithUnsupportedColorType_FromInputImage_DefaultsToYCbCr420<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
// arrange
using Image<TPixel> input = provider.GetImage(JpegDecoder);
using var memoryStream = new MemoryStream();
// act
input.Save(memoryStream, new JpegEncoder()
{
Quality = 75
});
// assert
memoryStream.Position = 0;
using var output = Image.Load<Rgba32>(memoryStream);
JpegMetadata meta = output.Metadata.GetJpegMetadata();
Assert.Equal(JpegColorType.YCbCrRatio420, meta.ColorType);
}
[Theory]
[InlineData(JpegColorType.Cmyk)]
[InlineData(JpegColorType.YCbCrRatio410)]
[InlineData(JpegColorType.YCbCrRatio411)]
[InlineData(JpegColorType.YCbCrRatio422)]
public void Encode_WithUnsupportedColorType_DefaultsToYCbCr420(JpegColorType colorType)
{ {
var options = new JpegEncoder(); // arrange
var jpegEncoder = new JpegEncoder() { ColorType = colorType };
using var input = new Image<Rgb24>(10, 10);
using var memoryStream = new MemoryStream();
// act
input.Save(memoryStream, jpegEncoder);
// assert
memoryStream.Position = 0;
using var output = Image.Load<Rgba32>(memoryStream);
JpegMetadata meta = output.Metadata.GetJpegMetadata();
Assert.Equal(JpegColorType.YCbCrRatio420, meta.ColorType);
}
[Theory]
[MemberData(nameof(QualityFiles))]
public void Encode_PreservesQuality(string imagePath, int quality)
{
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateRgba32Image()) using (Image<Rgba32> input = testFile.CreateRgba32Image())
{ {
using (var memStream = new MemoryStream()) using (var memStream = new MemoryStream())
{ {
input.Save(memStream, options); input.Save(memStream, JpegEncoder);
memStream.Position = 0; memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream)) using (var output = Image.Load<Rgba32>(memStream))
@ -83,16 +157,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory] [Theory]
[WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)] [WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)]
public void EncodeBaseline_CalliphoraPartial<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality)
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality);
[Theory]
[WithFile(TestImages.Png.CalliphoraPartial, nameof(BitsPerPixel_Quality), PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 158, 24, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 153, 21, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 600, 400, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 138, 24, PixelTypes.Rgba32)]
public void EncodeBaseline_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality)
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality);
[Theory]
[WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 100, 100, 100, 255, PixelTypes.L8)]
[WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 143, 81, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 7, 5, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 96, 48, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 73, 71, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 73, 71, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 24, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 24, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 46, 8, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 46, 8, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 51, 7, PixelTypes.Rgba32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 51, 7, PixelTypes.Rgba32)]
[WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)] public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality)
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 7, 5, PixelTypes.Rgba32)] where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.12f));
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 600, 400, PixelTypes.Rgba32)]
[WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 100, 100, 100, 255, PixelTypes.L8)]
public void EncodeBaseline_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, JpegSubsample subsample, int quality)
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, subsample, quality);
[Theory] [Theory]
[WithFile(TestImages.Png.BikeGrayscale, nameof(Grayscale_Quality), PixelTypes.L8)] [WithFile(TestImages.Png.BikeGrayscale, nameof(Grayscale_Quality), PixelTypes.L8)]
@ -102,46 +190,51 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.La16, 100)] [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.La16, 100)]
[WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.La32, 100)] [WithSolidFilledImages(1, 1, 100, 100, 100, 255, PixelTypes.La32, 100)]
public void EncodeBaseline_Grayscale<TPixel>(TestImageProvider<TPixel> provider, int quality) public void EncodeBaseline_Grayscale<TPixel>(TestImageProvider<TPixel> provider, int quality)
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, null, quality, JpegColorType.Luminance); where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, JpegColorType.Luminance, quality);
[Theory]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 96, 96, PixelTypes.Rgba32 | PixelTypes.Bgra32)]
public void EncodeBaseline_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality)
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality);
[Theory] [Theory]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 48, PixelTypes.Rgba32 | PixelTypes.Bgra32)] [WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 48, PixelTypes.Rgba32 | PixelTypes.Bgra32)]
public void EncodeBaseline_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider, JpegSubsample subsample, int quality) public void EncodeBaseline_WithSmallImages_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality)
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, subsample, quality); where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.06f));
[Theory] [Theory]
[WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, JpegSubsample.Ratio444)] [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, JpegColorType.YCbCrRatio444)]
[WithTestPatternImages(587, 821, PixelTypes.Rgba32, JpegSubsample.Ratio444)] [WithTestPatternImages(587, 821, PixelTypes.Rgba32, JpegColorType.YCbCrRatio444)]
[WithTestPatternImages(677, 683, PixelTypes.Bgra32, JpegSubsample.Ratio420)] [WithTestPatternImages(677, 683, PixelTypes.Bgra32, JpegColorType.YCbCrRatio420)]
[WithSolidFilledImages(400, 400, "Red", PixelTypes.Bgr24, JpegSubsample.Ratio420)] [WithSolidFilledImages(400, 400, "Red", PixelTypes.Bgr24, JpegColorType.YCbCrRatio420)]
public void EncodeBaseline_WorksWithDiscontiguousBuffers<TPixel>(TestImageProvider<TPixel> provider, JpegSubsample subsample) public void EncodeBaseline_WorksWithDiscontiguousBuffers<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
ImageComparer comparer = subsample == JpegSubsample.Ratio444 ImageComparer comparer = colorType == JpegColorType.YCbCrRatio444
? ImageComparer.TolerantPercentage(0.1f) ? ImageComparer.TolerantPercentage(0.1f)
: ImageComparer.TolerantPercentage(5f); : ImageComparer.TolerantPercentage(5f);
provider.LimitAllocatorBufferCapacity().InBytesSqrt(200); provider.LimitAllocatorBufferCapacity().InBytesSqrt(200);
TestJpegEncoderCore(provider, subsample, 100, JpegColorType.YCbCr, comparer); TestJpegEncoderCore(provider, colorType, 100, comparer);
} }
/// <summary> /// <summary>
/// Anton's SUPER-SCIENTIFIC tolerance threshold calculation /// Anton's SUPER-SCIENTIFIC tolerance threshold calculation
/// </summary> /// </summary>
private static ImageComparer GetComparer(int quality, JpegSubsample? subsample) private static ImageComparer GetComparer(int quality, JpegColorType? colorType)
{ {
float tolerance = 0.015f; // ~1.5% float tolerance = 0.015f; // ~1.5%
if (quality < 50) if (quality < 50)
{ {
tolerance *= 10f; tolerance *= 4.5f;
} }
else if (quality < 75 || subsample == JpegSubsample.Ratio420) else if (quality < 75 || colorType == JpegColorType.YCbCrRatio420)
{ {
tolerance *= 5f; tolerance *= 2.0f;
if (subsample == JpegSubsample.Ratio420) if (colorType == JpegColorType.YCbCrRatio420)
{ {
tolerance *= 2f; tolerance *= 2.0f;
} }
} }
@ -150,9 +243,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
private static void TestJpegEncoderCore<TPixel>( private static void TestJpegEncoderCore<TPixel>(
TestImageProvider<TPixel> provider, TestImageProvider<TPixel> provider,
JpegSubsample? subsample, JpegColorType colorType = JpegColorType.YCbCrRatio420,
int quality = 100, int quality = 100,
JpegColorType colorType = JpegColorType.YCbCr,
ImageComparer comparer = null) ImageComparer comparer = null)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
@ -163,13 +255,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var encoder = new JpegEncoder var encoder = new JpegEncoder
{ {
Subsample = subsample,
Quality = quality, Quality = quality,
ColorType = colorType ColorType = colorType
}; };
string info = $"{subsample}-Q{quality}"; string info = $"{colorType}-Q{quality}";
comparer ??= GetComparer(quality, subsample); comparer ??= GetComparer(quality, colorType);
// Does DebugSave & load reference CompareToReferenceInput(): // Does DebugSave & load reference CompareToReferenceInput():
image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png"); image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png");
@ -225,14 +316,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[MemberData(nameof(RatioFiles))] [MemberData(nameof(RatioFiles))]
public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit) public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{ {
var options = new JpegEncoder();
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateRgba32Image()) using (Image<Rgba32> input = testFile.CreateRgba32Image())
{ {
using (var memStream = new MemoryStream()) using (var memStream = new MemoryStream())
{ {
input.Save(memStream, options); input.Save(memStream, JpegEncoder);
memStream.Position = 0; memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream)) using (var output = Image.Load<Rgba32>(memStream))
@ -253,11 +342,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using var input = new Image<Rgba32>(1, 1); using var input = new Image<Rgba32>(1, 1);
input.Metadata.IptcProfile = new IptcProfile(); input.Metadata.IptcProfile = new IptcProfile();
input.Metadata.IptcProfile.SetValue(IptcTag.Byline, "unit_test"); input.Metadata.IptcProfile.SetValue(IptcTag.Byline, "unit_test");
var encoder = new JpegEncoder();
// act // act
using var memStream = new MemoryStream(); using var memStream = new MemoryStream();
input.Save(memStream, encoder); input.Save(memStream, JpegEncoder);
// assert // assert
memStream.Position = 0; memStream.Position = 0;
@ -275,11 +363,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using var input = new Image<Rgba32>(1, 1); using var input = new Image<Rgba32>(1, 1);
input.Metadata.ExifProfile = new ExifProfile(); input.Metadata.ExifProfile = new ExifProfile();
input.Metadata.ExifProfile.SetValue(ExifTag.Software, "unit_test"); input.Metadata.ExifProfile.SetValue(ExifTag.Software, "unit_test");
var encoder = new JpegEncoder();
// act // act
using var memStream = new MemoryStream(); using var memStream = new MemoryStream();
input.Save(memStream, encoder); input.Save(memStream, JpegEncoder);
// assert // assert
memStream.Position = 0; memStream.Position = 0;
@ -296,11 +383,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
// arrange // arrange
using var input = new Image<Rgba32>(1, 1); using var input = new Image<Rgba32>(1, 1);
input.Metadata.IccProfile = new IccProfile(IccTestDataProfiles.Profile_Random_Array); input.Metadata.IccProfile = new IccProfile(IccTestDataProfiles.Profile_Random_Array);
var encoder = new JpegEncoder();
// act // act
using var memStream = new MemoryStream(); using var memStream = new MemoryStream();
input.Save(memStream, encoder); input.Save(memStream, JpegEncoder);
// assert // assert
memStream.Position = 0; memStream.Position = 0;
@ -312,9 +398,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
} }
[Theory] [Theory]
[InlineData(JpegSubsample.Ratio420)] [InlineData(JpegColorType.YCbCrRatio420)]
[InlineData(JpegSubsample.Ratio444)] [InlineData(JpegColorType.YCbCrRatio444)]
public async Task Encode_IsCancellable(JpegSubsample subsample) public async Task Encode_IsCancellable(JpegColorType colorType)
{ {
var cts = new CancellationTokenSource(); var cts = new CancellationTokenSource();
using var pausedStream = new PausedStream(new MemoryStream()); using var pausedStream = new PausedStream(new MemoryStream());
@ -336,7 +422,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using var image = new Image<Rgba32>(5000, 5000); using var image = new Image<Rgba32>(5000, 5000);
await Assert.ThrowsAsync<TaskCanceledException>(async () => await Assert.ThrowsAsync<TaskCanceledException>(async () =>
{ {
var encoder = new JpegEncoder() { Subsample = subsample }; var encoder = new JpegEncoder() { ColorType = colorType };
await image.SaveAsync(pausedStream, encoder, cts.Token); await image.SaveAsync(pausedStream, encoder, cts.Token);
}); });
} }

2
tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs

@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var clone = (JpegMetadata)meta.DeepClone(); var clone = (JpegMetadata)meta.DeepClone();
clone.Quality = 99; clone.Quality = 99;
clone.ColorType = JpegColorType.YCbCr; clone.ColorType = JpegColorType.YCbCrRatio420;
Assert.False(meta.Quality.Equals(clone.Quality)); Assert.False(meta.Quality.Equals(clone.Quality));
Assert.False(meta.ColorType.Equals(clone.ColorType)); Assert.False(meta.ColorType.Equals(clone.ColorType));

6
tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs

@ -21,10 +21,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Trait("Format", "Jpg")] [Trait("Format", "Jpg")]
public class SpectralJpegTests public class SpectralJpegTests
{ {
public SpectralJpegTests(ITestOutputHelper output) public SpectralJpegTests(ITestOutputHelper output) => this.Output = output;
{
this.Output = output;
}
private ITestOutputHelper Output { get; } private ITestOutputHelper Output { get; }
@ -46,7 +43,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public static readonly string[] AllTestJpegs = BaselineTestJpegs.Concat(ProgressiveTestJpegs).ToArray(); public static readonly string[] AllTestJpegs = BaselineTestJpegs.Concat(ProgressiveTestJpegs).ToArray();
[Theory(Skip = "Debug only, enable manually!")] [Theory(Skip = "Debug only, enable manually!")]
//[Theory]
[WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)] [WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)]
public void Decoder_ParseStream_SaveSpectralResult<TPixel>(TestImageProvider<TPixel> provider) public void Decoder_ParseStream_SaveSpectralResult<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>

2
tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
using var decompressor = new DeflateTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffColorType.BlackIsZero8, TiffPredictor.None, false); using var decompressor = new DeflateTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffColorType.BlackIsZero8, TiffPredictor.None, false);
decompressor.Decompress(stream, 0, (uint)stream.Length, buffer); decompressor.Decompress(stream, 0, (uint)stream.Length, 1, buffer);
Assert.Equal(data, buffer); Assert.Equal(data, buffer);
} }

2
tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs

@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
var buffer = new byte[data.Length]; var buffer = new byte[data.Length];
using var decompressor = new LzwTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffColorType.BlackIsZero8, TiffPredictor.None, false); using var decompressor = new LzwTiffCompression(Configuration.Default.MemoryAllocator, 10, 8, TiffColorType.BlackIsZero8, TiffPredictor.None, false);
decompressor.Decompress(stream, 0, (uint)stream.Length, buffer); decompressor.Decompress(stream, 0, (uint)stream.Length, 1, buffer);
Assert.Equal(data, buffer); Assert.Equal(data, buffer);
} }

8
tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs

@ -17,10 +17,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
[InlineData(new byte[] { 10, 15, 20, 25, 30, 35, 40, 45 }, 5, new byte[] { 10, 15, 20, 25, 30 })] [InlineData(new byte[] { 10, 15, 20, 25, 30, 35, 40, 45 }, 5, new byte[] { 10, 15, 20, 25, 30 })]
public void Decompress_ReadsData(byte[] inputData, uint byteCount, byte[] expectedResult) public void Decompress_ReadsData(byte[] inputData, uint byteCount, byte[] expectedResult)
{ {
var stream = new BufferedReadStream(Configuration.Default, new MemoryStream(inputData)); using var memoryStream = new MemoryStream(inputData);
var buffer = new byte[expectedResult.Length]; using var stream = new BufferedReadStream(Configuration.Default, memoryStream);
byte[] buffer = new byte[expectedResult.Length];
new NoneTiffCompression(default, default, default).Decompress(stream, 0, byteCount, buffer); using var decompressor = new NoneTiffCompression(default, default, default);
decompressor.Decompress(stream, 0, byteCount, 1, buffer);
Assert.Equal(expectedResult, buffer); Assert.Equal(expectedResult, buffer);
} }

9
tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs

@ -26,11 +26,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
[InlineData(new byte[] { 0xFE, 0xAA, 0x02, 0x80, 0x00, 0x2A, 0xFD, 0xAA, 0x03, 0x80, 0x00, 0x2A, 0x22, 0xF7, 0xAA }, new byte[] { 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA })] // Apple PackBits sample [InlineData(new byte[] { 0xFE, 0xAA, 0x02, 0x80, 0x00, 0x2A, 0xFD, 0xAA, 0x03, 0x80, 0x00, 0x2A, 0x22, 0xF7, 0xAA }, new byte[] { 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0x80, 0x00, 0x2A, 0x22, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA })] // Apple PackBits sample
public void Decompress_ReadsData(byte[] inputData, byte[] expectedResult) public void Decompress_ReadsData(byte[] inputData, byte[] expectedResult)
{ {
var stream = new BufferedReadStream(Configuration.Default, new MemoryStream(inputData)); using var memoryStream = new MemoryStream(inputData);
var buffer = new byte[expectedResult.Length]; using var stream = new BufferedReadStream(Configuration.Default, memoryStream);
byte[] buffer = new byte[expectedResult.Length];
using var decompressor = new PackBitsTiffCompression(new ArrayPoolMemoryAllocator(), default, default); using var decompressor = new PackBitsTiffCompression(new ArrayPoolMemoryAllocator(), default, default);
decompressor.Decompress(stream, 0, (uint)inputData.Length, buffer); decompressor.Decompress(stream, 0, (uint)inputData.Length, 1, buffer);
Assert.Equal(expectedResult, buffer); Assert.Equal(expectedResult, buffer);
} }
@ -41,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
{ {
// arrange // arrange
Span<byte> input = inputData.AsSpan(); Span<byte> input = inputData.AsSpan();
var compressed = new byte[expectedResult.Length]; byte[] compressed = new byte[expectedResult.Length];
// act // act
PackBitsWriter.PackBits(input, compressed); PackBitsWriter.PackBits(input, compressed);

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

@ -21,21 +21,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{ {
public static readonly string[] MultiframeTestImages = Multiframes; public static readonly string[] MultiframeTestImages = Multiframes;
public static readonly string[] NotSupportedImages = NotSupported;
private static TiffDecoder TiffDecoder => new TiffDecoder(); private static TiffDecoder TiffDecoder => new TiffDecoder();
private static MagickReferenceDecoder ReferenceDecoder => new MagickReferenceDecoder(); private static MagickReferenceDecoder ReferenceDecoder => new MagickReferenceDecoder();
[Theory] [Theory]
[WithFileCollection(nameof(NotSupportedImages), PixelTypes.Rgba32)] [WithFile(RgbUncompressedTiled, PixelTypes.Rgba32)]
[WithFile(MultiframeDifferentSize, PixelTypes.Rgba32)]
[WithFile(MultiframeDifferentVariants, PixelTypes.Rgba32)]
public void ThrowsNotSupported<TPixel>(TestImageProvider<TPixel> provider) public void ThrowsNotSupported<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => Assert.Throws<NotSupportedException>(() => provider.GetImage(TiffDecoder)); where TPixel : unmanaged, IPixel<TPixel> => Assert.Throws<NotSupportedException>(() => provider.GetImage(TiffDecoder));
[Theory] [Theory]
[InlineData(RgbUncompressed, 24, 256, 256, 300, 300, PixelResolutionUnit.PixelsPerInch)] [InlineData(RgbUncompressed, 24, 256, 256, 300, 300, PixelResolutionUnit.PixelsPerInch)]
[InlineData(SmallRgbDeflate, 24, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)] [InlineData(SmallRgbDeflate, 24, 32, 32, 96, 96, PixelResolutionUnit.PixelsPerInch)]
[InlineData(Calliphora_GrayscaleUncompressed, 8, 804, 1198, 96, 96, PixelResolutionUnit.PixelsPerInch)] [InlineData(Calliphora_GrayscaleUncompressed, 8, 200, 298, 96, 96, PixelResolutionUnit.PixelsPerInch)]
[InlineData(Flower4BitPalette, 4, 73, 43, 72, 72, PixelResolutionUnit.PixelsPerInch)] [InlineData(Flower4BitPalette, 4, 73, 43, 72, 72, PixelResolutionUnit.PixelsPerInch)]
public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit) public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit)
{ {
@ -354,6 +354,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_Fax3Compressed<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_Fax3Compressed<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider); where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Fax4Compressed, PixelTypes.Rgba32)]
[WithFile(Fax4CompressedLowerOrderBitsFirst, PixelTypes.Rgba32)]
[WithFile(Calliphora_Fax4Compressed, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_Fax4Compressed<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory] [Theory]
[WithFile(CcittFax3LowerOrderBitsFirst, PixelTypes.Rgba32)] [WithFile(CcittFax3LowerOrderBitsFirst, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_Compressed_LowerOrderBitsFirst<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_Compressed_LowerOrderBitsFirst<TPixel>(TestImageProvider<TPixel> provider)
@ -366,6 +373,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_PackBitsCompressed<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_PackBitsCompressed<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider); where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(RgbJpegCompressed, PixelTypes.Rgba32)]
[WithFile(RgbWithStripsJpegCompressed, PixelTypes.Rgba32)]
[WithFile(YCbCrJpegCompressed, PixelTypes.Rgba32)]
[WithFile(RgbJpegCompressedNoJpegTable, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_JpegCompressed<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider, useExactComparer: false);
[Theory] [Theory]
[WithFileCollection(nameof(MultiframeTestImages), PixelTypes.Rgba32)] [WithFileCollection(nameof(MultiframeTestImages), PixelTypes.Rgba32)]
public void DecodeMultiframe<TPixel>(TestImageProvider<TPixel> provider) public void DecodeMultiframe<TPixel>(TestImageProvider<TPixel> provider)

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

@ -114,7 +114,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
[InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Ccitt1D, TiffBitsPerPixel.Bit1, TiffCompression.Ccitt1D)] [InlineData(TiffPhotometricInterpretation.BlackIsZero, TiffCompression.Ccitt1D, TiffBitsPerPixel.Bit1, TiffCompression.Ccitt1D)]
[InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.ItuTRecT43, TiffBitsPerPixel.Bit24, TiffCompression.None)] [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.ItuTRecT43, TiffBitsPerPixel.Bit24, TiffCompression.None)]
[InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.ItuTRecT82, TiffBitsPerPixel.Bit24, TiffCompression.None)] [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.ItuTRecT82, TiffBitsPerPixel.Bit24, TiffCompression.None)]
[InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Jpeg, TiffBitsPerPixel.Bit24, TiffCompression.None)] [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.Jpeg, TiffBitsPerPixel.Bit24, TiffCompression.Jpeg)]
[InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.OldDeflate, TiffBitsPerPixel.Bit24, TiffCompression.None)] [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.OldDeflate, TiffBitsPerPixel.Bit24, TiffCompression.None)]
[InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.OldJpeg, TiffBitsPerPixel.Bit24, TiffCompression.None)] [InlineData(TiffPhotometricInterpretation.Rgb, TiffCompression.OldJpeg, TiffBitsPerPixel.Bit24, TiffCompression.None)]
public void EncoderOptions_SetPhotometricInterpretationAndCompression_Works( public void EncoderOptions_SetPhotometricInterpretationAndCompression_Works(
@ -288,6 +288,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffEncoder_EncodeRgb_WithPackBitsCompression_Works<TPixel>(TestImageProvider<TPixel> provider) public void TiffEncoder_EncodeRgb_WithPackBitsCompression_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.PackBits); where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.PackBits);
[Theory]
[WithFile(Calliphora_RgbUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeRgb_WithJpegCompression_Works<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffEncoderCore(provider, TiffBitsPerPixel.Bit24, TiffPhotometricInterpretation.Rgb, TiffCompression.Jpeg, useExactComparer: false, compareTolerance: 0.012f);
[Theory] [Theory]
[WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)] [WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeGray_Works<TPixel>(TestImageProvider<TPixel> provider) public void TiffEncoder_EncodeGray_Works<TPixel>(TestImageProvider<TPixel> provider)

7
tests/ImageSharp.Tests/ImageSharp.Tests.csproj

@ -10,6 +10,11 @@
</PropertyGroup> </PropertyGroup>
<Choose> <Choose>
<When Condition="$(SIXLABORS_TESTING_PREVIEW) == true">
<PropertyGroup>
<TargetFrameworks>net6.0;net5.0;netcoreapp3.1;netcoreapp2.1;net472</TargetFrameworks>
</PropertyGroup>
</When>
<When Condition="$(Configuration.EndsWith('InnerLoop')) == true"> <When Condition="$(Configuration.EndsWith('InnerLoop')) == true">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netcoreapp3.1</TargetFrameworks> <TargetFrameworks>netcoreapp3.1</TargetFrameworks>
@ -21,7 +26,7 @@
</PropertyGroup> </PropertyGroup>
</Otherwise> </Otherwise>
</Choose> </Choose>
<ItemGroup> <ItemGroup>
<InternalsVisibleTo Include="ImageSharp.Tests.ProfilingSandbox" Key="$(SixLaborsPublicKey)" /> <InternalsVisibleTo Include="ImageSharp.Tests.ProfilingSandbox" Key="$(SixLaborsPublicKey)" />
</ItemGroup> </ItemGroup>

14
tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs

@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks
img.Dispose(); img.Dispose();
}, },
#pragma warning disable SA1515 // Single-line comment should be preceded by blank line #pragma warning disable SA1515 // Single-line comment should be preceded by blank line
// ReSharper disable once ExplicitCallerInfoArgument // ReSharper disable once ExplicitCallerInfoArgument
$"Decode {fileName}"); $"Decode {fileName}");
#pragma warning restore SA1515 // Single-line comment should be preceded by blank line #pragma warning restore SA1515 // Single-line comment should be preceded by blank line
} }
@ -92,11 +92,11 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks
// Benchmark, enable manually! // Benchmark, enable manually!
[Theory(Skip = ProfilingSetup.SkipProfilingTests)] [Theory(Skip = ProfilingSetup.SkipProfilingTests)]
[InlineData(1, 75, JpegSubsample.Ratio420)] [InlineData(1, 75, JpegColorType.YCbCrRatio420)]
[InlineData(30, 75, JpegSubsample.Ratio420)] [InlineData(30, 75, JpegColorType.YCbCrRatio420)]
[InlineData(30, 75, JpegSubsample.Ratio444)] [InlineData(30, 75, JpegColorType.YCbCrRatio444)]
[InlineData(30, 100, JpegSubsample.Ratio444)] [InlineData(30, 100, JpegColorType.YCbCrRatio444)]
public void EncodeJpeg(int executionCount, int quality, JpegSubsample subsample) public void EncodeJpeg(int executionCount, int quality, JpegColorType colorType)
{ {
// do not run this on CI even by accident // do not run this on CI even by accident
if (TestEnvironment.RunsOnCI) if (TestEnvironment.RunsOnCI)
@ -118,7 +118,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks
{ {
foreach (Image<Rgba32> img in testImages) foreach (Image<Rgba32> img in testImages)
{ {
var options = new JpegEncoder { Quality = quality, Subsample = subsample }; var options = new JpegEncoder { Quality = quality, ColorType = colorType };
img.Save(ms, options); img.Save(ms, options);
ms.Seek(0, SeekOrigin.Begin); ms.Seek(0, SeekOrigin.Begin);
} }

16
tests/ImageSharp.Tests/TestImages.cs

@ -191,6 +191,10 @@ namespace SixLabors.ImageSharp.Tests
public const string Jpeg420Exif = "Jpg/baseline/jpeg420exif.jpg"; public const string Jpeg420Exif = "Jpg/baseline/jpeg420exif.jpg";
public const string Jpeg444 = "Jpg/baseline/jpeg444.jpg"; public const string Jpeg444 = "Jpg/baseline/jpeg444.jpg";
public const string Jpeg420Small = "Jpg/baseline/jpeg420small.jpg"; public const string Jpeg420Small = "Jpg/baseline/jpeg420small.jpg";
public const string JpegRgb = "Jpg/baseline/jpeg-rgb.jpg";
public const string Jpeg410 = "Jpg/baseline/jpeg410.jpg";
public const string Jpeg411 = "Jpg/baseline/jpeg411.jpg";
public const string Jpeg422 = "Jpg/baseline/jpeg422.jpg";
public const string Testorig420 = "Jpg/baseline/testorig.jpg"; public const string Testorig420 = "Jpg/baseline/testorig.jpg";
public const string MultiScanBaselineCMYK = "Jpg/baseline/MultiScanBaselineCMYK.jpg"; public const string MultiScanBaselineCMYK = "Jpg/baseline/MultiScanBaselineCMYK.jpg";
public const string Ratio1x1 = "Jpg/baseline/ratio-1x1.jpg"; public const string Ratio1x1 = "Jpg/baseline/ratio-1x1.jpg";
@ -201,6 +205,9 @@ namespace SixLabors.ImageSharp.Tests
public const string App13WithEmptyIptc = "Jpg/baseline/iptc-psAPP13-wIPTCempty.jpg"; public const string App13WithEmptyIptc = "Jpg/baseline/iptc-psAPP13-wIPTCempty.jpg";
public const string HistogramEqImage = "Jpg/baseline/640px-Unequalized_Hawkes_Bay_NZ.jpg"; public const string HistogramEqImage = "Jpg/baseline/640px-Unequalized_Hawkes_Bay_NZ.jpg";
public const string ForestBridgeDifferentComponentsQuality = "Jpg/baseline/forest_bridge.jpg"; public const string ForestBridgeDifferentComponentsQuality = "Jpg/baseline/forest_bridge.jpg";
public const string ArithmeticCoding = "Jpg/baseline/arithmetic_coding.jpg";
public const string ArithmeticCodingProgressive = "Jpg/progressive/arithmetic_progressive.jpg";
public const string Lossless = "Jpg/baseline/lossless.jpg";
public static readonly string[] All = public static readonly string[] All =
{ {
@ -726,6 +733,8 @@ namespace SixLabors.ImageSharp.Tests
public const string Calliphora_Fax4Compressed = "Tiff/Calliphora_ccitt_fax4.tiff"; public const string Calliphora_Fax4Compressed = "Tiff/Calliphora_ccitt_fax4.tiff";
public const string Calliphora_HuffmanCompressed = "Tiff/Calliphora_huffman_rle.tiff"; public const string Calliphora_HuffmanCompressed = "Tiff/Calliphora_huffman_rle.tiff";
public const string Calliphora_BiColorUncompressed = "Tiff/Calliphora_bicolor_uncompressed.tiff"; public const string Calliphora_BiColorUncompressed = "Tiff/Calliphora_bicolor_uncompressed.tiff";
public const string Fax4Compressed = "Tiff/basi3p02_fax4.tiff";
public const string Fax4CompressedLowerOrderBitsFirst = "Tiff/basi3p02_fax4_lowerOrderBitsFirst.tiff";
public const string CcittFax3AllTermCodes = "Tiff/ccitt_fax3_all_terminating_codes.tiff"; public const string CcittFax3AllTermCodes = "Tiff/ccitt_fax3_all_terminating_codes.tiff";
public const string CcittFax3AllMakeupCodes = "Tiff/ccitt_fax3_all_makeup_codes.tiff"; public const string CcittFax3AllMakeupCodes = "Tiff/ccitt_fax3_all_makeup_codes.tiff";
@ -744,7 +753,9 @@ namespace SixLabors.ImageSharp.Tests
public const string RgbDeflate = "Tiff/rgb_deflate.tiff"; public const string RgbDeflate = "Tiff/rgb_deflate.tiff";
public const string RgbDeflatePredictor = "Tiff/rgb_deflate_predictor.tiff"; public const string RgbDeflatePredictor = "Tiff/rgb_deflate_predictor.tiff";
public const string RgbDeflateMultistrip = "Tiff/rgb_deflate_multistrip.tiff"; public const string RgbDeflateMultistrip = "Tiff/rgb_deflate_multistrip.tiff";
public const string RgbJpeg = "Tiff/rgb_jpeg.tiff"; public const string RgbJpegCompressed = "Tiff/rgb_jpegcompression.tiff";
public const string RgbWithStripsJpegCompressed = "Tiff/rgb_jpegcompressed_stripped.tiff";
public const string RgbJpegCompressedNoJpegTable = "Tiff/rgb_jpegcompressed_nojpegtable.tiff";
public const string RgbLzwPredictor = "Tiff/rgb_lzw_predictor.tiff"; public const string RgbLzwPredictor = "Tiff/rgb_lzw_predictor.tiff";
public const string RgbLzwNoPredictor = "Tiff/rgb_lzw_no_predictor.tiff"; public const string RgbLzwNoPredictor = "Tiff/rgb_lzw_no_predictor.tiff";
public const string RgbLzwNoPredictorMultistrip = "Tiff/rgb_lzw_noPredictor_multistrip.tiff"; public const string RgbLzwNoPredictorMultistrip = "Tiff/rgb_lzw_noPredictor_multistrip.tiff";
@ -792,6 +803,7 @@ namespace SixLabors.ImageSharp.Tests
public const string RgbYCbCr888Contiguoush2v1 = "Tiff/rgb-ycbcr-contig-08_h2v1.tiff"; public const string RgbYCbCr888Contiguoush2v1 = "Tiff/rgb-ycbcr-contig-08_h2v1.tiff";
public const string RgbYCbCr888Contiguoush2v2 = "Tiff/rgb-ycbcr-contig-08_h2v2.tiff"; public const string RgbYCbCr888Contiguoush2v2 = "Tiff/rgb-ycbcr-contig-08_h2v2.tiff";
public const string RgbYCbCr888Contiguoush4v4 = "Tiff/rgb-ycbcr-contig-08_h4v4.tiff"; public const string RgbYCbCr888Contiguoush4v4 = "Tiff/rgb-ycbcr-contig-08_h4v4.tiff";
public const string YCbCrJpegCompressed = "Tiff/ycbcr_jpegcompressed.tiff";
public const string FlowerRgb444Contiguous = "Tiff/flower-rgb-contig-04.tiff"; public const string FlowerRgb444Contiguous = "Tiff/flower-rgb-contig-04.tiff";
public const string FlowerRgb444Planar = "Tiff/flower-rgb-planar-04.tiff"; public const string FlowerRgb444Planar = "Tiff/flower-rgb-planar-04.tiff";
public const string FlowerRgb222Contiguous = "Tiff/flower-rgb-contig-02.tiff"; public const string FlowerRgb222Contiguous = "Tiff/flower-rgb-contig-02.tiff";
@ -850,8 +862,6 @@ namespace SixLabors.ImageSharp.Tests
public static readonly string[] Multiframes = { MultiframeDeflateWithPreview, MultiframeLzwPredictor /*, MultiFrameDifferentSize, MultiframeDifferentSizeTiled, MultiFrameDifferentVariants,*/ }; public static readonly string[] Multiframes = { MultiframeDeflateWithPreview, MultiframeLzwPredictor /*, MultiFrameDifferentSize, MultiframeDifferentSizeTiled, MultiFrameDifferentVariants,*/ };
public static readonly string[] Metadata = { SampleMetadata }; public static readonly string[] Metadata = { SampleMetadata };
public static readonly string[] NotSupported = { Calliphora_RgbJpeg, RgbJpeg, RgbUncompressedTiled, MultiframeDifferentSize, MultiframeDifferentVariants, Calliphora_Fax4Compressed, Fax4_Motorola };
} }
} }
} }

3
tests/Images/Input/Jpg/baseline/arithmetic_coding.jpg

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

3
tests/Images/Input/Jpg/baseline/jpeg-rgb.jpg

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

3
tests/Images/Input/Jpg/baseline/jpeg410.jpg

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:318338c1a541227632a99cc47b04fc9f6d19c3e8300a76e71136edec2d17a5f5
size 9073

3
tests/Images/Input/Jpg/baseline/jpeg411.jpg

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:70315936e36bb1edf38fc950b7b321f20be5a2592db59bfd28d79dbc391a5aaf
size 4465

3
tests/Images/Input/Jpg/baseline/jpeg422.jpg

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

3
tests/Images/Input/Jpg/baseline/lossless.jpg

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8937e245885e1f280e1843ad48a4349ec1a3f71f86c954229cd44160aeeaaac4
size 209584

3
tests/Images/Input/Jpg/progressive/arithmetic_progressive.jpg

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

4
tests/Images/Input/Tiff/Calliphora_bicolor_uncompressed.tiff

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:ed3b08730e5c34eb8d268f58d1e09efe2605398899bfd726cc3b35de21baa6ff oid sha256:c35902ca485fba441230efa88f794ee5aafa9f75ad5e4a14cb3d592a0a98c538
size 121196 size 7760

4
tests/Images/Input/Tiff/Calliphora_ccitt_fax3.tiff

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:bba35f1e43c8425f3bcfab682efae4d2c00c62f0d8a4b411e646d32047469526 oid sha256:da7d98823c284d92982a88c4a51434bbc140dceac245a8a054c6e41a489d0cc7
size 125802 size 5986

4
tests/Images/Input/Tiff/Calliphora_ccitt_fax4.tiff

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:7a2c95aec08b96bca30af344f7d9952a603a951802ce534a5f2c5f563795cbd2 oid sha256:118e55fe0f0dbb5d2712337ec9456a27407f11c0eb9f7e7e81700d4d84b7db09
size 117704 size 4378

4
tests/Images/Input/Tiff/Calliphora_gray_deflate.tiff

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:2314b31ca9938fa8b11cbabda0b118a90025a45d2931fca9afa131c0d6919aca oid sha256:d4d3541db6b7751d3225599aa822c3376fb27bb9dc2dd28674ef4f78bf426191
size 557717 size 83356

4
tests/Images/Input/Tiff/Calliphora_gray_deflate_predictor.tiff

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:b9576b3a49b84e26938a7e9ded5f43a1a3c3390bf4824803f5aaab8e00c1afb4 oid sha256:d007429701cc20154e84909af6988414001e6e60fba5c995f67b2a04cad6e57b
size 630947 size 41135

4
tests/Images/Input/Tiff/Calliphora_gray_lzw_predictor.tiff

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:3f24fd8f36a4847fcb84a317de4fd2eacd5eb0c58ef4436d33919f0a6658d0d9 oid sha256:649f0b8ad50a9465fdb447c411e33f20a8b367600e7c3af83c8f686f3ab3e6dc
size 698309 size 47143

4
tests/Images/Input/Tiff/Calliphora_grayscale_uncompressed.tiff

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:0283f2be39a151ca3ed19be97ebe4a6b17978ed251dd4d0d568895865fec24c7 oid sha256:5925388b374b75161ef49273e8c85c4f99713e5e3be380ea13a03895f47809ba
size 964588 size 60001

4
tests/Images/Input/Tiff/Calliphora_huffman_rle.tiff

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:1a4f687de9925863b1c9f32f53b6c05fb121f9d7b02ff5869113c4745433f10d oid sha256:f27e758bb72d5e03fdcf0b1c58c417e4a7928cbdf8d432dc5b9a7d8d7ee4d06b
size 124644 size 5668

4
tests/Images/Input/Tiff/Calliphora_palette_uncompressed.tiff

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:b70500348b1af7828c15e7782eaca105ff749136d7c45eb4cab8c5cd5269c3f6 oid sha256:29d4b30265158a7cc651d75130843a7f5a7ebf8b2f0f4bb0cf86c82cbec7f6ec
size 966134 size 61549

4
tests/Images/Input/Tiff/Calliphora_rgb_deflate_predictor.tiff

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:da6e6a35a0bb0f5f2d49e3c5f0eb2deb7118718dd08844f66a6cb72f48b5c489 oid sha256:392e1269220e7e3feb9e2b256e82cce6a2394a5cabef042fdb10def6b24ff165
size 1476294 size 111819

4
tests/Images/Input/Tiff/Calliphora_rgb_jpeg.tiff

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:ba79ffac35e16208406e073492d770d3a77e51a47112aa02ab8fd98b5a8487b2 oid sha256:4baf0f4c462e5bef71ab36f505dfff87a31bd1d25deabccd822a359c1075e08e
size 198564 size 65748

3
tests/Images/Input/Tiff/Calliphora_rgb_lzw.tif

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:53006876fcdc655a794462de57eb6b56f4d0cdd3cb8b752c63328db0eb4aa3c1
size 725085

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

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

4
tests/Images/Input/Tiff/Calliphora_rgb_lzw_predictor.tiff

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:ecb529e5e3e0eca6f5e407b034fa8ba67bb4b9068af9e9b30425b08d30a249c0 oid sha256:993207c34358165af5fac0d0b955b56cd79e9707c1c46344863e01cbc9c7707d
size 1756355 size 126695

4
tests/Images/Input/Tiff/Calliphora_rgb_packbits.tiff

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:59dbb48f10c40cbbd4f5617a9f57536790ce0b9a4cc241dc8d6257095598cb76 oid sha256:81e7456578510c85e5bebe8bc7c5796da6e2cd61f5bbe0a1f6bb46b8aee3d695
size 2891292 size 179949

4
tests/Images/Input/Tiff/Calliphora_rgb_uncompressed.tiff

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:acb46c990af78fcb0e63849f0f26acffe26833d5cfd2fc77a728baf92166eea3 oid sha256:bb25349a7f803aeafc47d8a05deca1a8afdc4bdc5a53e2916f68d5c3e7d8cad3
size 2893218 size 179207

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

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:185ae3c4174b323adcf811d125cd77b71768406845923f50395c0baebff57b7c
size 282

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

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

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

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

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

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6b81013d7b0a29ed1ac9c33e175e0c0e69494b93b2b65b692f16d9ea042b9d5d
size 7759

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

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

0
tests/Images/Input/Tiff/rgb_jpeg.tiff → tests/Images/Input/Tiff/rgb_jpegcompression.tiff

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

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