Browse Source

Merge branch 'master' into jpeg-encoder-optimization

pull/1761/head
Dmitry Pentin 4 years ago
parent
commit
7e4aa46916
  1. 141
      .github/workflows/build-and-test.yml
  2. 5
      Directory.Build.props
  3. 4
      src/ImageSharp/Formats/Jpeg/Components/Block8x8F.cs
  4. 10
      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. 74
      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. 261
      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. 6
      src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs
  52. 2
      src/ImageSharp/Processing/Processors/Normalization/AdaptiveHistogramEqualizationSlidingWindowProcessor{TPixel}.cs
  53. 4
      tests/ImageSharp.Benchmarks/Codecs/Jpeg/EncodeJpeg.cs
  54. 6
      tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
  55. 6
      tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj
  56. 59
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs
  57. 17
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  58. 190
      tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
  59. 2
      tests/ImageSharp.Tests/Formats/Jpg/JpegMetadataTests.cs
  60. 6
      tests/ImageSharp.Tests/Formats/Jpg/SpectralJpegTests.cs
  61. 2
      tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
  62. 2
      tests/ImageSharp.Tests/Formats/Tiff/Compression/LzwTiffCompressionTests.cs
  63. 8
      tests/ImageSharp.Tests/Formats/Tiff/Compression/NoneTiffCompressionTests.cs
  64. 9
      tests/ImageSharp.Tests/Formats/Tiff/Compression/PackBitsTiffCompressionTests.cs
  65. 23
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  66. 7
      tests/ImageSharp.Tests/Formats/Tiff/TiffEncoderTests.cs
  67. 7
      tests/ImageSharp.Tests/ImageSharp.Tests.csproj
  68. 17
      tests/ImageSharp.Tests/Memory/Allocators/ArrayPoolMemoryAllocatorTests.cs
  69. 20
      tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs
  70. 14
      tests/ImageSharp.Tests/ProfilingBenchmarks/JpegProfilingBenchmarks.cs
  71. 16
      tests/ImageSharp.Tests/TestImages.cs
  72. 3
      tests/Images/Input/Jpg/baseline/arithmetic_coding.jpg
  73. 3
      tests/Images/Input/Jpg/baseline/jpeg-rgb.jpg
  74. 3
      tests/Images/Input/Jpg/baseline/jpeg410.jpg
  75. 3
      tests/Images/Input/Jpg/baseline/jpeg411.jpg
  76. 3
      tests/Images/Input/Jpg/baseline/jpeg422.jpg
  77. 3
      tests/Images/Input/Jpg/baseline/lossless.jpg
  78. 3
      tests/Images/Input/Jpg/progressive/arithmetic_progressive.jpg
  79. 4
      tests/Images/Input/Tiff/Calliphora_bicolor_uncompressed.tiff
  80. 4
      tests/Images/Input/Tiff/Calliphora_ccitt_fax3.tiff
  81. 4
      tests/Images/Input/Tiff/Calliphora_ccitt_fax4.tiff
  82. 4
      tests/Images/Input/Tiff/Calliphora_gray_deflate.tiff
  83. 4
      tests/Images/Input/Tiff/Calliphora_gray_deflate_predictor.tiff
  84. 4
      tests/Images/Input/Tiff/Calliphora_gray_lzw_predictor.tiff
  85. 4
      tests/Images/Input/Tiff/Calliphora_grayscale_uncompressed.tiff
  86. 4
      tests/Images/Input/Tiff/Calliphora_huffman_rle.tiff
  87. 4
      tests/Images/Input/Tiff/Calliphora_palette_uncompressed.tiff
  88. 4
      tests/Images/Input/Tiff/Calliphora_rgb_deflate_predictor.tiff
  89. 4
      tests/Images/Input/Tiff/Calliphora_rgb_jpeg.tiff
  90. 3
      tests/Images/Input/Tiff/Calliphora_rgb_lzw.tif
  91. 3
      tests/Images/Input/Tiff/Calliphora_rgb_lzw.tiff
  92. 4
      tests/Images/Input/Tiff/Calliphora_rgb_lzw_predictor.tiff
  93. 4
      tests/Images/Input/Tiff/Calliphora_rgb_packbits.tiff
  94. 4
      tests/Images/Input/Tiff/Calliphora_rgb_uncompressed.tiff
  95. 3
      tests/Images/Input/Tiff/basi3p02_fax4.tiff
  96. 3
      tests/Images/Input/Tiff/basi3p02_fax4_lowerOrderBitsFirst.tiff
  97. 3
      tests/Images/Input/Tiff/basi3p02_fax4_minisblack.tiff
  98. 3
      tests/Images/Input/Tiff/rgb_jpegcompressed_nojpegtable.tiff
  99. 3
      tests/Images/Input/Tiff/rgb_jpegcompressed_stripped.tiff
  100. 0
      tests/Images/Input/Tiff/rgb_jpegcompression.tiff

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

@ -1,19 +1,37 @@
name: Build
on:
push:
branches:
- master
tags:
- "v*"
pull_request:
branches:
- master
push:
branches:
- master
tags:
- "v*"
pull_request:
branches:
- master
jobs:
Build:
strategy:
matrix:
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
framework: net5.0
runtime: -x64
@ -52,37 +70,38 @@ jobs:
codecov: false
runs-on: ${{matrix.options.os}}
if: "!contains(github.event.head_commit.message, '[skip ci]')"
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
- name: Create LFS file list
- name: Git Create LFS FileList
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
id: lfs-cache
with:
path: .git/lfs
key: ${{ runner.os }}-lfs-${{ hashFiles('.lfs-assets-id') }}-v1
- name: Git LFS Pull
- name: Git Pull LFS
run: git lfs pull
- name: Install NuGet
- name: NuGet Install
uses: NuGet/setup-nuget@v1
- name: Setup Git
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
- name: NuGet Setup Cache
uses: actions/cache@v2
id: nuget-cache
with:
@ -90,60 +109,94 @@ jobs:
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }}
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
run: ./ci-build.ps1 "${{matrix.options.framework}}"
env:
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
run: ./ci-test.ps1 "${{matrix.options.os}}" "${{matrix.options.framework}}" "${{matrix.options.runtime}}" "${{matrix.options.codecov}}"
env:
SIXLABORS_TESTING: True
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit
SIXLABORS_TESTING: True
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
uses: actions/upload-artifact@v2
if: failure()
with:
name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip
path: tests/Images/ActualOutput/
name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip
path: tests/Images/ActualOutput/
- name: Update Codecov
- name: Codecov Update
uses: codecov/codecov-action@v1
if: matrix.options.codecov == true && startsWith(github.repository, 'SixLabors')
with:
flags: unittests
flags: unittests
Publish:
needs: [Build]
runs-on: windows-latest
runs-on: ubuntu-latest
if: (github.event_name == 'push')
steps:
- uses: actions/checkout@v2
- name: Install NuGet
uses: NuGet/setup-nuget@v1
- name: Setup Git
- name: Git Config
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: 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
run: ./ci-pack.ps1
- name: Publish to MyGet
- name: MyGet Publish
shell: pwsh
run: |
nuget.exe push .\artifacts\*.nupkg ${{secrets.MYGET_TOKEN}} -Source 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\*.nupkg -k ${{secrets.MYGET_TOKEN}} -s https://www.myget.org/F/sixlabors/api/v2/package
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

5
Directory.Build.props

@ -18,6 +18,11 @@
<!-- Import the shared global .props file -->
<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.
This is easier than setting each project individually.

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

@ -80,7 +80,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
DebugGuard.MustBeBetweenOrEqualTo(idx, 0, Size - 1, nameof(idx));
ref float selfRef = ref Unsafe.As<Block8x8F, float>(ref this);
return Unsafe.Add(ref selfRef, idx);
return Unsafe.Add(ref selfRef, (nint)(uint)idx);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -88,7 +88,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
DebugGuard.MustBeBetweenOrEqualTo(idx, 0, Size - 1, nameof(idx));
ref float selfRef = ref Unsafe.As<Block8x8F, float>(ref this);
Unsafe.Add(ref selfRef, idx) = value;
Unsafe.Add(ref selfRef, (nint)(uint)idx) = value;
}
}

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

@ -38,10 +38,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary>
private int restartInterval;
// How many mcu's are left to do.
/// <summary>
/// How many mcu's are left to do.
/// </summary>
private int todo;
// 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;
/// <summary>
@ -58,7 +62,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
private readonly SpectralConverter spectralConverter;
private CancellationToken cancellationToken;
private readonly CancellationToken cancellationToken;
/// <summary>
/// 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.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder.ColorConverters;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
/// <summary>
@ -30,5 +32,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// Actual stride height depends on the subsampling factor of the given component.
/// </remarks>
public abstract void ConvertStrideBaseline();
/// <summary>
/// Gets the color converter.
/// </summary>
/// <param name="frame">The jpeg frame with the color space to convert to.</param>
/// <param name="jpegData">The raw JPEG data.</param>
/// <returns>The color converter.</returns>
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
{
internal sealed class SpectralConverter<TPixel> : SpectralConverter, IDisposable
internal class SpectralConverter<TPixel> : SpectralConverter, IDisposable
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly Configuration configuration;
private CancellationToken cancellationToken;
private readonly CancellationToken cancellationToken;
private JpegComponentPostProcessor[] componentProcessors;
@ -59,6 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
}
}
/// <inheritdoc/>
public override void InjectFrameData(JpegFrame frame, IRawJpegData jpegData)
{
MemoryAllocator allocator = this.configuration.MemoryAllocator;
@ -85,9 +86,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.rgbaBuffer = allocator.Allocate<Vector4>(frame.PixelWidth);
// 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()
{
// Convert next pixel stride using single spectral `stride'

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

@ -85,7 +85,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
private int emitWriteIndex;
/// <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>
private uint accumulatedBits;
@ -124,8 +124,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="luminanceQuantTable">Luminance quantization table provided by the callee</param>
/// <param name="chrominanceQuantTable">Chrominance quantization table provided by the callee</param>
/// <param name="luminanceQuantTable">Luminance quantization table provided by the callee.</param>
/// <param name="chrominanceQuantTable">Chrominance quantization table provided by the callee.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
public void Encode444<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
@ -184,8 +184,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="luminanceQuantTable">Luminance quantization table provided by the callee</param>
/// <param name="chrominanceQuantTable">Chrominance quantization table provided by the callee</param>
/// <param name="luminanceQuantTable">Luminance quantization table provided by the callee.</param>
/// <param name="chrominanceQuantTable">Chrominance quantization table provided by the callee.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
public void Encode420<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
@ -251,7 +251,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// </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="luminanceQuantTable">Luminance quantization table provided by the callee.</param>
/// <param name="cancellationToken">The token to monitor for cancellation.</param>
public void EncodeGrayscale<TPixel>(Image<TPixel> pixels, ref Block8x8F luminanceQuantTable, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
@ -291,6 +291,64 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
this.FlushRemainingBytes();
}
/// <summary>
/// Encodes the image with no subsampling and keeps the pixel data as Rgb24.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="pixels">The pixel accessor providing access to the image pixels.</param>
/// <param name="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>
/// Writes a block of pixel data using the given quantization table,
/// returning the post-quantized DC value of the DCT-transformed block.
@ -459,7 +517,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");
#if SUPPORTS_BITOPERATIONS
// This should have been implemented as (BitOperations.Log2(value) + 1) as in non-intrinsic implementation
// But internal log2 is implementated like this: (31 - (int)Lzcnt.LeadingZeroCount(value))
// 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
// Lzcnt would return 32 for input value of 0 - no need to check that with branching
@ -471,7 +529,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
// if 0 - return 0 in this case
// 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 can safely shift input value for one bit -> log2(value << 1)
// 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.
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// Enumerates the quantization tables
/// Enumerates the quantization tables.
/// </summary>
internal enum QuantIndex
{
/// <summary>
/// The luminance quantization table index
/// The luminance quantization table index.
/// </summary>
Luminance = 0,
/// <summary>
/// The chrominance quantization table index
/// The chrominance quantization table index.
/// </summary>
Chrominance = 1,
}
}
}

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

@ -0,0 +1,114 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
{
/// <summary>
/// On-stack worker struct to convert TPixel -> Rgb24 of 8x8 pixel blocks.
/// </summary>
/// <typeparam name="TPixel">The pixel type to work on.</typeparam>
internal ref struct RgbForwardConverter<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
/// <summary>
/// Number of pixels processed per single <see cref="Convert(int, int, ref RowOctet{TPixel})"/> call
/// </summary>
private const int PixelsPerSample = 8 * 8;
/// <summary>
/// Total byte size of processed pixels converted from TPixel to <see cref="Rgb24"/>
/// </summary>
private const int RgbSpanByteSize = PixelsPerSample * 3;
/// <summary>
/// <see cref="Size"/> of sampling area from given frame pixel buffer.
/// </summary>
private static readonly Size SampleSize = new Size(8, 8);
/// <summary>
/// The Red component.
/// </summary>
public Block8x8F R;
/// <summary>
/// The Green component.
/// </summary>
public Block8x8F G;
/// <summary>
/// The Blue component.
/// </summary>
public Block8x8F B;
/// <summary>
/// Temporal 64-byte span to hold unconverted TPixel data.
/// </summary>
private readonly Span<TPixel> pixelSpan;
/// <summary>
/// Temporal 64-byte span to hold converted Rgb24 data.
/// </summary>
private readonly Span<Rgb24> rgbSpan;
/// <summary>
/// Sampled pixel buffer size.
/// </summary>
private readonly Size samplingAreaSize;
/// <summary>
/// <see cref="Configuration"/> for internal operations.
/// </summary>
private readonly Configuration config;
public RgbForwardConverter(ImageFrame<TPixel> frame)
{
this.R = default;
this.G = default;
this.B = default;
// temporal pixel buffers
this.pixelSpan = new TPixel[PixelsPerSample].AsSpan();
this.rgbSpan = MemoryMarshal.Cast<byte, Rgb24>(new byte[RgbSpanByteSize + RgbToYCbCrConverterVectorized.AvxCompatibilityPadding].AsSpan());
// frame data
this.samplingAreaSize = new Size(frame.Width, frame.Height);
this.config = frame.GetConfiguration();
}
/// <summary>
/// Converts a 8x8 image area inside 'pixels' at position (x, y) to Rgb24.
/// </summary>
public void Convert(int x, int y, ref RowOctet<TPixel> currentRows)
{
YCbCrForwardConverter<TPixel>.LoadAndStretchEdges(currentRows, this.pixelSpan, new Point(x, y), SampleSize, this.samplingAreaSize);
PixelOperations<TPixel>.Instance.ToRgb24(this.config, this.pixelSpan, this.rgbSpan);
ref Block8x8F redBlock = ref this.R;
ref Block8x8F greenBlock = ref this.G;
ref Block8x8F blueBlock = ref this.B;
CopyToBlock(this.rgbSpan, ref redBlock, ref greenBlock, ref blueBlock);
}
private static void CopyToBlock(Span<Rgb24> rgbSpan, ref Block8x8F redBlock, ref Block8x8F greenBlock, ref Block8x8F blueBlock)
{
ref Rgb24 rgbStart = ref MemoryMarshal.GetReference(rgbSpan);
for (int i = 0; i < Block8x8F.Size; i++)
{
Rgb24 c = Unsafe.Add(ref rgbStart, (nint)(uint)i);
redBlock[i] = c.R;
greenBlock[i] = c.G;
blueBlock[i] = c.B;
}
}
}
}

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

@ -58,22 +58,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Encoder
/// <summary>
/// Temporal 16x8 block to hold TPixel data
/// </summary>
private Span<TPixel> pixelSpan;
private readonly Span<TPixel> pixelSpan;
/// <summary>
/// Temporal RGB block
/// </summary>
private Span<Rgb24> rgbSpan;
private readonly Span<Rgb24> rgbSpan;
/// <summary>
/// Sampled pixel buffer size
/// </summary>
private Size samplingAreaSize;
private readonly Size samplingAreaSize;
/// <summary>
/// <see cref="Configuration"/> for internal operations
/// </summary>
private Configuration config;
private readonly Configuration config;
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>
/// Temporal 64-byte span to hold unconverted TPixel data
/// </summary>
private Span<TPixel> pixelSpan;
private readonly Span<TPixel> pixelSpan;
/// <summary>
/// Temporal 64-byte span to hold converted Rgb24 data
/// </summary>
private Span<Rgb24> rgbSpan;
private readonly Span<Rgb24> rgbSpan;
/// <summary>
/// Sampled pixel buffer size
/// </summary>
private Size samplingAreaSize;
private readonly Size samplingAreaSize;
/// <summary>
/// <see cref="Configuration"/> for internal operations
/// </summary>
private Configuration config;
private readonly Configuration config;
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; }
/// <summary>
/// Gets the subsample ration, 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.
/// Gets the color type, that will be used to encode the image.
/// </summary>
JpegColorType? ColorType { get; }
}

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

@ -10,12 +10,57 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
{
/// <summary>
/// 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>
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>
/// Single channel, luminance.
/// </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>
public const byte APP15 = 0xEF;
/// <summary>
/// Define arithmetic coding conditioning marker.
/// </summary>
public const byte DAC = 0xCC;
/// <summary>
/// The text comment marker
/// </summary>
@ -173,6 +178,56 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
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>
/// Define Huffman Table(s)
/// <remarks>

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

@ -4,6 +4,7 @@
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
@ -136,8 +137,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// Finds the next file marker within the byte stream.
/// </summary>
/// <param name="marker">The buffer to read file markers to</param>
/// <param name="stream">The input stream</param>
/// <param name="marker">The buffer to read file markers to.</param>
/// <param name="stream">The input stream.</param>
/// <returns>The <see cref="JpegFileMarker"/></returns>
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);
}
/// <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>
/// Parses the input stream for file markers.
/// </summary>
@ -225,7 +287,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
stream.Read(this.markerBuffer, 0, 2);
byte marker = this.markerBuffer[1];
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.
// https://github.com/SixLabors/ImageSharp/issues/695
@ -236,7 +298,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (!fileMarker.Invalid)
{
// Get the marker length
// Get the marker length.
int remaining = this.ReadUint16(stream) - 2;
switch (fileMarker.Marker)
@ -247,10 +309,32 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.ProcessStartOfFrameMarker(stream, remaining, fileMarker, metadataOnly);
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:
if (!metadataOnly)
{
this.ProcessStartOfScanMarker(stream, remaining, cancellationToken);
this.ProcessStartOfScanMarker(stream, remaining);
break;
}
else
@ -326,6 +410,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegConstants.Markers.COM:
stream.Skip(remaining);
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>
/// 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>
/// <param name="componentCount">The number of components.</param>
/// <returns>The <see cref="JpegColorSpace"/></returns>
private JpegColorSpace DeduceJpegColorSpace(byte componentCount, JpegComponent[] components)
private JpegColorSpace DeduceJpegColorSpace(byte componentCount)
{
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 (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;
}
@ -384,6 +473,64 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
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>
/// Initializes the EXIF profile.
/// </summary>
@ -582,7 +729,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// 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>
/// <param name="stream">The input stream.</param>
/// <param name="remaining">The remaining bytes in the segment block.</param>
@ -799,7 +946,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
/// <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>
/// <param name="stream">The input stream.</param>
/// <param name="remaining">The remaining bytes in the segment block.</param>
@ -891,9 +1038,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
index += componentBytes;
}
this.ColorSpace = this.DeduceJpegColorSpace(componentCount, this.Frame.Components);
this.Metadata.GetJpegMetadata().ColorType = this.ColorSpace == JpegColorSpace.Grayscale ? JpegColorType.Luminance : JpegColorType.YCbCr;
this.ColorSpace = this.DeduceJpegColorSpace(componentCount);
this.Metadata.GetJpegMetadata().ColorType = this.DeduceJpegColorType();
if (!metadataOnly)
{
@ -991,7 +1137,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// Processes the SOS (Start of scan marker).
/// </summary>
private void ProcessStartOfScanMarker(BufferedReadStream stream, int remaining, CancellationToken cancellationToken)
private void ProcessStartOfScanMarker(BufferedReadStream stream, int remaining)
{
if (this.Frame is null)
{

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

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

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

@ -33,20 +33,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
private readonly byte[] buffer = new byte[20];
/// <summary>
/// Gets or sets the subsampling method to use.
/// </summary>
private JpegSubsample? subsample;
/// <summary>
/// The quality, that will be used to encode the image.
/// </summary>
private readonly int? quality;
/// <summary>
/// Gets or sets the subsampling method to use.
/// Gets or sets the colorspace to use.
/// </summary>
private readonly JpegColorType? colorType;
private JpegColorType? colorType;
/// <summary>
/// The output stream. All attempted writes after the first error become no-ops.
@ -56,12 +51,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// Initializes a new instance of the <see cref="JpegEncoderCore"/> class.
/// </summary>
/// <param name="options">The options</param>
/// <param name="options">The options.</param>
public JpegEncoderCore(IJpegEncoderOptions options)
{
this.quality = options.Quality;
this.subsample = options.Subsample;
this.colorType = options.ColorType;
if (IsSupportedColorType(options.ColorType))
{
this.colorType = options.ColorType;
}
}
/// <summary>
@ -88,30 +86,49 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
ImageMetadata metadata = image.Metadata;
JpegMetadata jpegMetadata = metadata.GetJpegMetadata();
// If the color type was not specified by the user, preserve the color type of the input image, if 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.
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
// Initialize the quantization tables.
this.InitQuantizationTables(componentCount, jpegMetadata, out Block8x8F luminanceQuantTable, out Block8x8F chrominanceQuantTable);
// 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
this.WriteProfiles(metadata);
if (this.colorType == JpegColorType.Rgb)
{
// Write App14 marker to indicate RGB color space.
this.WriteApp14Marker();
}
// Write the quantization tables.
this.WriteDefineQuantizationTables(ref luminanceQuantTable, ref chrominanceQuantTable);
// Write the image dimensions.
this.WriteStartOfFrame(image.Width, image.Height, componentCount);
this.WriteStartOfFrame(image.Width, image.Height, componentCount, componentIds);
// Write the Huffman tables.
this.WriteDefineHuffmanTables(componentCount);
// Write the scan header.
this.WriteStartOfScan(image, componentCount, cancellationToken);
this.WriteStartOfScan(componentCount, componentIds);
// Write the scan compressed data.
if (this.colorType == JpegColorType.Luminance)
@ -121,15 +138,21 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
else
{
// luminance and chrominance quantization tables
switch (this.subsample)
// luminance and chrominance quantization tables.
switch (this.colorType)
{
case JpegSubsample.Ratio444:
case JpegColorType.YCbCrRatio444:
new HuffmanScanEncoder(3, stream).Encode444(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken);
break;
case JpegSubsample.Ratio420:
case JpegColorType.YCbCrRatio420:
new HuffmanScanEncoder(6, stream).Encode420(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken);
break;
case JpegColorType.Luminance:
new HuffmanScanEncoder(1, stream).EncodeGrayscale(image, ref luminanceQuantTable, ref chrominanceQuantTable, cancellationToken);
break;
case JpegColorType.Rgb:
new HuffmanScanEncoder(3, stream).EncodeRgb(image, ref luminanceQuantTable, cancellationToken);
break;
}
}
@ -140,12 +163,32 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
/// <summary>
/// Writes data to "Define Quantization Tables" block for QuantIndex
/// Returns true, if the color type is supported by the encoder.
/// </summary>
/// <param name="colorType">The color type.</param>
/// <returns>true, if color type is supported.</returns>
private static bool IsSupportedColorType(JpegColorType? colorType)
=> 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>
/// <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>
/// <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)
{
dqt[offset++] = (byte)i;
@ -156,52 +199,60 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
/// <summary>
/// Writes the application header containing the JFIF identifier plus extra data.
/// Write the start of image marker.
/// </summary>
/// <param name="meta">The image metadata.</param>
private void WriteApplicationHeader(ImageMetadata meta)
private void WriteStartOfImage()
{
// 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[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
this.buffer[2] = JpegConstants.Markers.XFF;
this.buffer[3] = JpegConstants.Markers.APP0; // Application Marker
this.buffer[4] = 0x00;
this.buffer[5] = 0x10;
this.buffer[6] = 0x4a; // J
this.buffer[0] = JpegConstants.Markers.XFF;
this.buffer[1] = JpegConstants.Markers.APP0; // Application Marker
this.buffer[2] = 0x00;
this.buffer[3] = 0x10;
this.buffer[4] = 0x4a; // J
this.buffer[5] = 0x46; // F
this.buffer[6] = 0x49; // I
this.buffer[7] = 0x46; // F
this.buffer[8] = 0x49; // I
this.buffer[9] = 0x46; // F
this.buffer[10] = 0x00; // = "JFIF",'\0'
this.buffer[11] = 0x01; // versionhi
this.buffer[12] = 0x01; // versionlo
this.buffer[8] = 0x00; // = "JFIF",'\0'
this.buffer[9] = 0x01; // versionhi
this.buffer[10] = 0x01; // versionlo
// Resolution. Big Endian
Span<byte> hResolution = this.buffer.AsSpan(14, 2);
Span<byte> vResolution = this.buffer.AsSpan(16, 2);
Span<byte> hResolution = this.buffer.AsSpan(12, 2);
Span<byte> vResolution = this.buffer.AsSpan(14, 2);
if (meta.ResolutionUnits == PixelResolutionUnit.PixelsPerMeter)
{
// 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(vResolution, (short)Math.Round(UnitConverter.MeterToInch(meta.VerticalResolution)));
}
else
{
// 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(vResolution, (short)Math.Round(meta.VerticalResolution));
}
// No thumbnail
this.buffer[18] = 0x00; // Thumbnail width
this.buffer[19] = 0x00; // Thumbnail height
this.buffer[16] = 0x00; // Thumbnail width
this.buffer[17] = 0x00; // Thumbnail height
this.outputStream.Write(this.buffer, 0, 20);
this.outputStream.Write(this.buffer, 0, 18);
}
/// <summary>
@ -211,7 +262,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
private void WriteDefineHuffmanTables(int componentCount)
{
// Table identifiers.
Span<byte> headers = stackalloc byte[]
ReadOnlySpan<byte> headers = stackalloc byte[]
{
0x00,
0x10,
@ -248,7 +299,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
private void WriteDefineQuantizationTables(ref Block8x8F luminanceQuantTable, ref Block8x8F chrominanceQuantTable)
{
// Marker + quantization table lengths
// Marker + quantization table lengths.
int markerlen = 2 + (QuantizationTableCount * (1 + Block8x8F.Size));
this.WriteMarkerHeader(JpegConstants.Markers.DQT, markerlen);
@ -264,6 +315,35 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
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>
/// Writes the EXIF profile.
/// </summary>
@ -342,7 +422,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
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.AdobeIptcMarker.Length +
2 + 4 + data.Length;
@ -384,7 +464,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// </summary>
/// <param name="iccProfile">The ICC profile to write.</param>
/// <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>
private void WriteIccProfile(IccProfile iccProfile)
{
@ -404,7 +484,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
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 count = dataLength / MaxData;
@ -477,22 +557,25 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
/// <summary>
/// Writes the Start Of Frame (Baseline) marker
/// Writes the Start Of Frame (Baseline) marker.
/// </summary>
/// <param name="width">The width of the image</param>
/// <param name="height">The height of the image</param>
/// <param name="componentCount">The number of components in a pixel</param>
private void WriteStartOfFrame(int width, int height, int componentCount)
/// <param name="width">The width 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="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
Span<byte> subsamples = stackalloc byte[]
ReadOnlySpan<byte> subsamples = new byte[]
{
0x22,
0x11,
0x11
};
Span<byte> chroma = stackalloc byte[]
ReadOnlySpan<byte> chroma = new byte[]
{
0x00,
0x01,
@ -501,7 +584,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (this.colorType == JpegColorType.Luminance)
{
subsamples = stackalloc byte[]
subsamples = new byte[]
{
0x11,
0x00,
@ -510,18 +593,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
else
{
switch (this.subsample)
switch (this.colorType)
{
case JpegSubsample.Ratio444:
subsamples = stackalloc byte[]
case JpegColorType.YCbCrRatio444:
case JpegColorType.Rgb:
subsamples = new byte[]
{
0x11,
0x11,
0x11
};
if (this.colorType == JpegColorType.Rgb)
{
chroma = new byte[]
{
0x00,
0x00,
0x00
};
}
break;
case JpegSubsample.Ratio420:
subsamples = stackalloc byte[]
case JpegColorType.YCbCrRatio420:
subsamples = new byte[]
{
0x22,
0x11,
@ -544,10 +639,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
for (int i = 0; i < componentCount; i++)
{
int i3 = 3 * i;
this.buffer[i3 + 6] = (byte)(i + 1);
this.buffer[i3 + 7] = subsamples[i];
this.buffer[i3 + 8] = chroma[i];
// Component ID.
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);
@ -556,26 +653,30 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <summary>
/// Writes the StartOfScan marker.
/// </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="cancellationToken">The token to monitor for cancellation.</param>
private void WriteStartOfScan<TPixel>(Image<TPixel> image, int componentCount, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
/// <param name="componentIds">The componentId's.</param>
private void WriteStartOfScan(int componentCount, ReadOnlySpan<byte> componentIds)
{
Span<byte> componentId = stackalloc byte[]
{
0x01,
0x02,
0x03
};
Span<byte> huffmanId = 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.
ReadOnlySpan<byte> huffmanId = new byte[]
{
0x00,
0x11,
0x11
};
// Use the same DC/AC tables for all channels for RGB.
if (this.colorType == JpegColorType.Rgb)
{
huffmanId = new byte[]
{
0x00,
0x00,
0x00
};
}
// Write the SOS (Start Of Scan) marker "\xff\xda" followed by 12 bytes:
// - the marker length "\x00\x0c",
// - the number of components "\x03",
@ -596,7 +697,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
for (int i = 0; i < componentCount; 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
}
@ -632,7 +733,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
/// <summary>
/// Initializes quntization tables.
/// Initializes quantization tables.
/// </summary>
/// <remarks>
/// <para>
@ -675,9 +776,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
chromaQuality = Numerics.Clamp(chromaQuality, 1, 100);
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
{
/// <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>
/// Cold path optimization for throwing <see cref="InvalidImageContentException"/>'s.
/// </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)
{
// 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;
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>
/// Note that the 'OldDeflate' compression type is identical to the 'Deflate' compression type.
/// </remarks>
internal class DeflateTiffCompression : TiffBaseDecompressor
internal sealed class DeflateTiffCompression : TiffBaseDecompressor
{
private readonly bool isBigEndian;
@ -41,7 +41,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
/// <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;
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>
/// Class to handle cases where TIFF image data is compressed using LZW compression.
/// </summary>
internal class LzwTiffCompression : TiffBaseDecompressor
internal sealed class LzwTiffCompression : TiffBaseDecompressor
{
private readonly bool isBigEndian;
@ -35,7 +35,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
/// <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);
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>
/// Class to handle cases where TIFF image data is compressed using Modified Huffman Compression.
/// </summary>
internal class ModifiedHuffmanTiffCompression : T4TiffCompression
internal sealed class ModifiedHuffmanTiffCompression : TiffBaseDecompressor
{
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="photometricInterpretation">The photometric interpretation.</param>
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;
this.whiteValue = (byte)(isWhiteZero ? 0 : 1);
this.blackValue = (byte)(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, 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();
uint bitsWritten = 0;
@ -51,20 +57,20 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
if (bitReader.IsWhiteRun)
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.whiteValue);
bitsWritten += bitReader.RunLength;
pixelsWritten += bitReader.RunLength;
}
else
{
BitWriterUtils.WriteBits(buffer, (int)bitsWritten, bitReader.RunLength, this.blackValue);
bitsWritten += bitReader.RunLength;
pixelsWritten += bitReader.RunLength;
}
bitsWritten += bitReader.RunLength;
pixelsWritten += bitReader.RunLength;
}
if (pixelsWritten % this.Width == 0)
if (pixelsWritten == this.Width)
{
bitReader.StartNewRow();
pixelsWritten = 0;
// Write padding bits, if necessary.
uint pad = 8 - (bitsWritten % 8);
@ -74,7 +80,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
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>
/// Class to handle cases where TIFF image data is not compressed.
/// </summary>
internal class NoneTiffCompression : TiffBaseDecompressor
internal sealed class NoneTiffCompression : TiffBaseDecompressor
{
/// <summary>
/// Initializes a new instance of the <see cref="NoneTiffCompression" /> class.
@ -25,7 +25,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
/// <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/>
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>
/// Class to handle cases where TIFF image data is compressed using PackBits compression.
/// </summary>
internal class PackBitsTiffCompression : TiffBaseDecompressor
internal sealed class PackBitsTiffCompression : TiffBaseDecompressor
{
private IMemoryOwner<byte> compressedDataMemory;
@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
/// <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)
{

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>
internal class T4BitReader : IDisposable
{
/// <summary>
/// Number of bits read.
/// </summary>
private int bitsRead;
/// <summary>
/// The logical order of bits within a byte.
/// </summary>
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>
/// Indicates whether its the first line of data which is read from the image.
/// </summary>
@ -57,20 +37,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// </summary>
private bool isStartOfRow;
/// <summary>
/// Indicates whether the modified huffman compression, as specified in the TIFF spec in section 10, is used.
/// </summary>
private readonly bool isModifiedHuffmanRle;
/// <summary>
/// Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false.
/// </summary>
private readonly bool eolPadding;
private readonly int dataLength;
/// <summary>
/// The minimum code length in bits.
/// </summary>
private const int MinCodeLength = 2;
/// <summary>
/// The maximum code length in bits.
/// </summary>
private readonly int maxCodeLength = 13;
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="allocator">The memory allocator.</param>
/// <param name="eolPadding">Indicates, if fill bits have been added as necessary before EOL codes such that EOL always ends on a byte boundary. Defaults to false.</param>
/// <param name="isModifiedHuffman">Indicates, if its the modified huffman code variation. Defaults to false.</param>
public T4BitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator, bool eolPadding = false, bool isModifiedHuffman = false)
public T4BitReader(Stream input, TiffFillOrder fillOrder, int bytesToRead, MemoryAllocator allocator, bool eolPadding = false)
{
this.fillOrder = fillOrder;
this.Data = allocator.Allocate<byte>(bytesToRead);
this.ReadImageDataFromStream(input, bytesToRead);
this.isModifiedHuffmanRle = isModifiedHuffman;
this.dataLength = bytesToRead;
this.bitsRead = 0;
this.value = 0;
this.curValueBitsRead = 0;
this.position = 0;
this.DataLength = bytesToRead;
this.BitsRead = 0;
this.Value = 0;
this.CurValueBitsRead = 0;
this.Position = 0;
this.IsWhiteRun = true;
this.isFirstScanLine = 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>
/// Gets the compressed image data.
/// </summary>
@ -265,23 +267,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <summary>
/// Gets a value indicating whether there is more data to read left.
/// </summary>
public bool HasMoreData
{
get
{
if (this.isModifiedHuffmanRle)
{
return this.position < (ulong)this.dataLength - 1 || (this.bitsRead > 0 && this.bitsRead < 7);
}
return this.position < (ulong)this.dataLength - 1;
}
}
public virtual bool HasMoreData => this.Position < (ulong)this.DataLength - 1;
/// <summary>
/// Gets a value indicating whether the current run is a white pixel run, otherwise its a black pixel run.
/// Gets or sets a value indicating whether the current run is a white pixel run, otherwise its a black pixel run.
/// </summary>
public bool IsWhiteRun { get; private set; }
public bool IsWhiteRun { get; protected set; }
/// <summary>
/// Gets the number of pixels in the current run.
@ -291,16 +282,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <summary>
/// Gets a value indicating whether the end of a pixel row has been reached.
/// </summary>
public bool IsEndOfScanLine
public virtual bool IsEndOfScanLine
{
get
{
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;
}
// Initialize for next run.
this.Reset();
if (this.isFirstScanLine && !this.isModifiedHuffmanRle)
{
// We expect an EOL before the first data.
this.value = this.ReadValue(this.eolPadding ? 16 : 12);
if (!this.IsEndOfScanLine)
{
TiffThrowHelper.ThrowImageFormatException("t4 parsing error: expected start of data marker not found");
}
this.Reset();
}
// We expect an EOL before the first data.
this.ReadEolBeforeFirstData();
// A code word must have at least 2 bits.
this.value = this.ReadValue(MinCodeLength);
this.Value = this.ReadValue(MinCodeLength);
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();
@ -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.
if (this.isStartOfRow && this.IsWhiteRun && this.WhiteTerminatingCodeRunLength() == 0)
{
this.IsWhiteRun = !this.IsWhiteRun;
this.Reset();
this.isStartOfRow = false;
continue;
this.terminationCodeFound = true;
this.RunLength = 0;
break;
}
if (this.IsWhiteRun)
@ -384,7 +367,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
}
uint currBit = this.ReadValue(1);
this.value = (this.value << 1) | currBit;
this.Value = (this.Value << 1) | currBit;
if (this.IsEndOfScanLine)
{
@ -396,55 +379,106 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
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.
this.IsWhiteRun = true;
this.isStartOfRow = true;
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);
if (pad != 8)
this.Value = this.ReadValue(this.eolPadding ? 16 : 12);
if (!this.IsEndOfScanLine)
{
// Skip padding bits, move to next byte.
this.position++;
this.bitsRead = 0;
TiffThrowHelper.ThrowImageFormatException("ccitt compression parsing error: expected start of data marker not found");
}
this.Reset();
}
}
/// <inheritdoc/>
public void Dispose() => this.Data.Dispose();
/// <summary>
/// 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()
{
switch (this.curValueBitsRead)
switch (this.CurValueBitsRead)
{
case 4:
{
return WhiteLen4TermCodes[this.value];
return WhiteLen4TermCodes[this.Value];
}
case 5:
{
return WhiteLen5TermCodes[this.value];
return WhiteLen5TermCodes[this.Value];
}
case 6:
{
return WhiteLen6TermCodes[this.value];
return WhiteLen6TermCodes[this.Value];
}
case 7:
{
return WhiteLen7TermCodes[this.value];
return WhiteLen7TermCodes[this.Value];
}
case 8:
{
return WhiteLen8TermCodes[this.value];
return WhiteLen8TermCodes[this.Value];
}
}
@ -453,61 +487,61 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private uint BlackTerminatingCodeRunLength()
{
switch (this.curValueBitsRead)
switch (this.CurValueBitsRead)
{
case 2:
{
return BlackLen2TermCodes[this.value];
return BlackLen2TermCodes[this.Value];
}
case 3:
{
return BlackLen3TermCodes[this.value];
return BlackLen3TermCodes[this.Value];
}
case 4:
{
return BlackLen4TermCodes[this.value];
return BlackLen4TermCodes[this.Value];
}
case 5:
{
return BlackLen5TermCodes[this.value];
return BlackLen5TermCodes[this.Value];
}
case 6:
{
return BlackLen6TermCodes[this.value];
return BlackLen6TermCodes[this.Value];
}
case 7:
{
return BlackLen7TermCodes[this.value];
return BlackLen7TermCodes[this.Value];
}
case 8:
{
return BlackLen8TermCodes[this.value];
return BlackLen8TermCodes[this.Value];
}
case 9:
{
return BlackLen9TermCodes[this.value];
return BlackLen9TermCodes[this.Value];
}
case 10:
{
return BlackLen10TermCodes[this.value];
return BlackLen10TermCodes[this.Value];
}
case 11:
{
return BlackLen11TermCodes[this.value];
return BlackLen11TermCodes[this.Value];
}
case 12:
{
return BlackLen12TermCodes[this.value];
return BlackLen12TermCodes[this.Value];
}
}
@ -516,41 +550,41 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private uint WhiteMakeupCodeRunLength()
{
switch (this.curValueBitsRead)
switch (this.CurValueBitsRead)
{
case 5:
{
return WhiteLen5MakeupCodes[this.value];
return WhiteLen5MakeupCodes[this.Value];
}
case 6:
{
return WhiteLen6MakeupCodes[this.value];
return WhiteLen6MakeupCodes[this.Value];
}
case 7:
{
return WhiteLen7MakeupCodes[this.value];
return WhiteLen7MakeupCodes[this.Value];
}
case 8:
{
return WhiteLen8MakeupCodes[this.value];
return WhiteLen8MakeupCodes[this.Value];
}
case 9:
{
return WhiteLen9MakeupCodes[this.value];
return WhiteLen9MakeupCodes[this.Value];
}
case 11:
{
return WhiteLen11MakeupCodes[this.value];
return WhiteLen11MakeupCodes[this.Value];
}
case 12:
{
return WhiteLen12MakeupCodes[this.value];
return WhiteLen12MakeupCodes[this.Value];
}
}
@ -559,26 +593,26 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private uint BlackMakeupCodeRunLength()
{
switch (this.curValueBitsRead)
switch (this.CurValueBitsRead)
{
case 10:
{
return BlackLen10MakeupCodes[this.value];
return BlackLen10MakeupCodes[this.Value];
}
case 11:
{
return BlackLen11MakeupCodes[this.value];
return BlackLen11MakeupCodes[this.Value];
}
case 12:
{
return BlackLen12MakeupCodes[this.value];
return BlackLen12MakeupCodes[this.Value];
}
case 13:
{
return BlackLen13MakeupCodes[this.value];
return BlackLen13MakeupCodes[this.Value];
}
}
@ -597,49 +631,41 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private bool IsWhiteMakeupCode()
{
switch (this.curValueBitsRead)
switch (this.CurValueBitsRead)
{
case 5:
{
return WhiteLen5MakeupCodes.ContainsKey(this.value);
return WhiteLen5MakeupCodes.ContainsKey(this.Value);
}
case 6:
{
return WhiteLen6MakeupCodes.ContainsKey(this.value);
return WhiteLen6MakeupCodes.ContainsKey(this.Value);
}
case 7:
{
return WhiteLen7MakeupCodes.ContainsKey(this.value);
return WhiteLen7MakeupCodes.ContainsKey(this.Value);
}
case 8:
{
return WhiteLen8MakeupCodes.ContainsKey(this.value);
return WhiteLen8MakeupCodes.ContainsKey(this.Value);
}
case 9:
{
return WhiteLen9MakeupCodes.ContainsKey(this.value);
return WhiteLen9MakeupCodes.ContainsKey(this.Value);
}
case 11:
{
return WhiteLen11MakeupCodes.ContainsKey(this.value);
return WhiteLen11MakeupCodes.ContainsKey(this.Value);
}
case 12:
{
if (this.isModifiedHuffmanRle)
{
if (this.value == 1)
{
return true;
}
}
return WhiteLen12MakeupCodes.ContainsKey(this.value);
return WhiteLen12MakeupCodes.ContainsKey(this.Value);
}
}
@ -648,34 +674,26 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
private bool IsBlackMakeupCode()
{
switch (this.curValueBitsRead)
switch (this.CurValueBitsRead)
{
case 10:
{
return BlackLen10MakeupCodes.ContainsKey(this.value);
return BlackLen10MakeupCodes.ContainsKey(this.Value);
}
case 11:
{
if (this.isModifiedHuffmanRle)
{
if (this.value == 0)
{
return true;
}
}
return BlackLen11MakeupCodes.ContainsKey(this.value);
return BlackLen11MakeupCodes.ContainsKey(this.Value);
}
case 12:
{
return BlackLen12MakeupCodes.ContainsKey(this.value);
return BlackLen12MakeupCodes.ContainsKey(this.Value);
}
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()
{
switch (this.curValueBitsRead)
switch (this.CurValueBitsRead)
{
case 4:
{
return WhiteLen4TermCodes.ContainsKey(this.value);
return WhiteLen4TermCodes.ContainsKey(this.Value);
}
case 5:
{
return WhiteLen5TermCodes.ContainsKey(this.value);
return WhiteLen5TermCodes.ContainsKey(this.Value);
}
case 6:
{
return WhiteLen6TermCodes.ContainsKey(this.value);
return WhiteLen6TermCodes.ContainsKey(this.Value);
}
case 7:
{
return WhiteLen7TermCodes.ContainsKey(this.value);
return WhiteLen7TermCodes.ContainsKey(this.Value);
}
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()
{
switch (this.curValueBitsRead)
switch (this.CurValueBitsRead)
{
case 2:
{
return BlackLen2TermCodes.ContainsKey(this.value);
return BlackLen2TermCodes.ContainsKey(this.Value);
}
case 3:
{
return BlackLen3TermCodes.ContainsKey(this.value);
return BlackLen3TermCodes.ContainsKey(this.Value);
}
case 4:
{
return BlackLen4TermCodes.ContainsKey(this.value);
return BlackLen4TermCodes.ContainsKey(this.Value);
}
case 5:
{
return BlackLen5TermCodes.ContainsKey(this.value);
return BlackLen5TermCodes.ContainsKey(this.Value);
}
case 6:
{
return BlackLen6TermCodes.ContainsKey(this.value);
return BlackLen6TermCodes.ContainsKey(this.Value);
}
case 7:
{
return BlackLen7TermCodes.ContainsKey(this.value);
return BlackLen7TermCodes.ContainsKey(this.Value);
}
case 8:
{
return BlackLen8TermCodes.ContainsKey(this.value);
return BlackLen8TermCodes.ContainsKey(this.Value);
}
case 9:
{
return BlackLen9TermCodes.ContainsKey(this.value);
return BlackLen9TermCodes.ContainsKey(this.Value);
}
case 10:
{
return BlackLen10TermCodes.ContainsKey(this.value);
return BlackLen10TermCodes.ContainsKey(this.Value);
}
case 11:
{
return BlackLen11TermCodes.ContainsKey(this.value);
return BlackLen11TermCodes.ContainsKey(this.Value);
}
case 12:
{
return BlackLen12TermCodes.ContainsKey(this.value);
return BlackLen12TermCodes.ContainsKey(this.Value);
}
}
return false;
}
private void Reset(bool resetRunLength = true)
{
this.value = 0;
this.curValueBitsRead = 0;
if (resetRunLength)
{
this.RunLength = 0;
}
}
private uint ReadValue(int nBits)
{
Guard.MustBeGreaterThan(nBits, 0, nameof(nBits));
uint v = 0;
int shift = nBits;
while (shift-- > 0)
{
uint bit = this.GetBit();
v |= bit << shift;
this.curValueBitsRead++;
}
return v;
}
private uint GetBit()
{
if (this.bitsRead >= 8)
if (this.BitsRead >= 8)
{
this.LoadNewByte();
}
Span<byte> dataSpan = this.Data.GetSpan();
int shift = 8 - this.bitsRead - 1;
uint bit = (uint)((dataSpan[(int)this.position] & (1 << shift)) != 0 ? 1 : 0);
this.bitsRead++;
int shift = 8 - this.BitsRead - 1;
uint bit = (uint)((dataSpan[(int)this.Position] & (1 << shift)) != 0 ? 1 : 0);
this.BitsRead++;
return bit;
}
private void LoadNewByte()
{
this.position++;
this.bitsRead = 0;
this.Position++;
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>
/// Class to handle cases where TIFF image data is compressed using CCITT T4 compression.
/// </summary>
internal class T4TiffCompression : TiffBaseDecompressor
internal sealed class T4TiffCompression : TiffBaseDecompressor
{
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="faxOptions">Fax compression options.</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)
{
this.faxCompressionOptions = faxOptions;
@ -45,10 +51,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
/// <summary>
/// Gets the logical order of bits within a byte.
/// </summary>
protected TiffFillOrder FillOrder { get; }
private TiffFillOrder FillOrder { get; }
/// <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))
{

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>
internal abstract class TiffBaseDecompressor : TiffBaseCompression
{
protected TiffBaseDecompressor(MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor = TiffPredictor.None)
: base(allocator, width, bitsPerPixel, predictor)
/// <summary>
/// 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="stripOffset">The strip offset of 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>
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)
{
@ -35,7 +43,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
}
stream.Seek(stripOffset, SeekOrigin.Begin);
this.Decompress(stream, (int)stripByteCount, buffer);
this.Decompress(stream, (int)stripByteCount, stripHeight, buffer);
if (stripOffset + stripByteCount < stream.Position)
{
@ -48,7 +56,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
/// </summary>
/// <param name="stream">The <see cref="Stream" /> to read image data from.</param>
/// <param name="byteCount">The number of bytes to read from the input stream.</param>
/// <param name="stripHeight">The height of the strip.</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.
case TiffCompression.ItuTRecT43:
case TiffCompression.ItuTRecT82:
case TiffCompression.Jpeg:
case TiffCompression.OldJpeg:
case TiffCompression.OldDeflate:
case TiffCompression.None:
@ -34,6 +33,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
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:
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");

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

@ -29,13 +29,23 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
Lzw = 3,
/// <summary>
/// Image data is compressed using T4-encoding: CCITT T.4.
/// Image data is compressed using CCITT T.4 fax compression.
/// </summary>
T4 = 4,
/// <summary>
/// Image data is compressed using CCITT T.6 fax compression.
/// </summary>
T6 = 5,
/// <summary>
/// Image data is compressed using modified huffman compression.
/// </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
{
public static TiffBaseDecompressor Create(
Configuration configuration,
TiffDecoderCompressionType method,
MemoryAllocator allocator,
TiffPhotometricInterpretation photometricInterpretation,
@ -19,6 +20,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression
TiffColorType colorType,
TiffPredictor predictor,
FaxCompressionOptions faxOptions,
byte[] jpegTables,
TiffFillOrder fillOrder,
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");
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:
DebugGuard.IsTrue(predictor == TiffPredictor.None, "Predictor should only be used with lzw or deflate compression");
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:
throw TiffThrowHelper.NotSupportedDecompressor(nameof(method));
}

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

@ -45,10 +45,10 @@
|Ccitt1D | Y | Y | |
|PackBits | 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 |
|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. |
|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>
public TiffFillOrder FillOrder { get; set; }
/// <summary>
/// Gets or sets the JPEG tables when jpeg compression is used.
/// </summary>
public byte[] JpegTables { get; set; }
/// <summary>
/// Gets or sets the planar configuration type to use when decoding the image.
/// </summary>
@ -144,7 +149,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
var frames = new List<ImageFrame<TPixel>>();
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);
}
@ -186,10 +192,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="tags">The IFD tags.</param>
/// <returns>
/// The tiff frame.
/// </returns>
private ImageFrame<TPixel> DecodeFrame<TPixel>(ExifProfile tags)
/// <param name="cancellationToken">The token to monitor cancellation.</param>
/// <returns> The tiff frame. </returns>
private ImageFrame<TPixel> DecodeFrame<TPixel>(ExifProfile tags, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel>
{
ImageFrameMetadata imageFrameMetaData = this.ignoreMetadata ?
@ -211,11 +216,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff
if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar)
{
this.DecodeStripsPlanar(frame, rowsPerStrip, stripOffsets, stripByteCounts);
this.DecodeStripsPlanar(frame, rowsPerStrip, stripOffsets, stripByteCounts, cancellationToken);
}
else
{
this.DecodeStripsChunky(frame, rowsPerStrip, stripOffsets, stripByteCounts);
this.DecodeStripsChunky(frame, rowsPerStrip, stripOffsets, stripByteCounts, cancellationToken);
}
return frame;
@ -263,14 +268,15 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
/// <summary>
/// Decodes the image data for strip encoded data.
/// Decodes the image data for planar 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 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="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>
{
int stripsPerPixel = this.BitsPerSample.Channels;
@ -290,6 +296,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(
this.Configuration,
this.CompressionType,
this.memoryAllocator,
this.PhotometricInterpretation,
@ -298,6 +305,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.ColorType,
this.Predictor,
this.FaxCompressionOptions,
this.JpegTables,
this.FillOrder,
this.byteOrder);
@ -312,12 +320,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff
for (int i = 0; i < stripsPerPlane; i++)
{
cancellationToken.ThrowIfCancellationRequested();
int stripHeight = i < stripsPerPlane - 1 || frame.Height % rowsPerStrip == 0 ? rowsPerStrip : frame.Height % rowsPerStrip;
int stripIndex = i;
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;
}
@ -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>
{
// 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;
using TiffBaseDecompressor decompressor = TiffDecompressorsFactory.Create(
this.Configuration,
this.CompressionType,
this.memoryAllocator,
this.PhotometricInterpretation,
@ -358,6 +383,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.ColorType,
this.Predictor,
this.FaxCompressionOptions,
this.JpegTables,
this.FillOrder,
this.byteOrder);
@ -374,6 +400,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
for (int stripIndex = 0; stripIndex < stripOffsets.Length; stripIndex++)
{
cancellationToken.ThrowIfCancellationRequested();
int stripHeight = stripIndex < stripOffsets.Length - 1 || frame.Height % rowsPerStrip == 0
? rowsPerStrip
: frame.Height % rowsPerStrip;
@ -385,7 +413,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
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);
}

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

@ -87,6 +87,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
options.YcbcrCoefficients = exifProfile.GetValue(ExifTag.YCbCrCoefficients)?.Value;
options.YcbcrSubSampling = exifProfile.GetValue(ExifTag.YCbCrSubsampling)?.Value;
options.FillOrder = fillOrder;
options.JpegTables = exifProfile.GetValue(ExifTag.JPEGTables)?.Value;
options.ParseColorType(exifProfile);
options.ParseCompression(frameMetadata.Compression, exifProfile);
@ -418,12 +419,26 @@ namespace SixLabors.ImageSharp.Formats.Tiff
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:
{
options.CompressionType = TiffDecoderCompressionType.HuffmanRle;
break;
}
case TiffCompression.Jpeg:
{
options.CompressionType = TiffDecoderCompressionType.Jpeg;
break;
}
default:
{
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;
foreach (ImageFrame<TPixel> frame in image.Frames)
{
cancellationToken.ThrowIfCancellationRequested();
var subfileType = (TiffNewSubfileType)(frame.Metadata.ExifProfile?.GetValue(ExifTag.SubfileType)?.Value ?? (int)TiffNewSubfileType.FullImage);
ifdMarker = this.WriteFrame(writer, frame, image.Metadata, metadataImage, ifdMarker);
@ -223,7 +225,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
entriesCollector,
(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);
@ -245,13 +247,22 @@ namespace SixLabors.ImageSharp.Formats.Tiff
/// </summary>
/// <param name="height">The height of the image.</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>
private int CalcRowsPerStrip(int height, int bytesPerRow)
private int CalcRowsPerStrip(int height, int bytesPerRow, TiffCompression? compression)
{
DebugGuard.MustBeGreaterThan(height, 0, nameof(height));
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)
{

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

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

28
src/ImageSharp/Image.FromBytes.cs

@ -102,6 +102,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The data is null.</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>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(byte[] data)
@ -116,6 +117,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="ArgumentNullException">The data is null.</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>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
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 data is null.</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>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
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 data is null.</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>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
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>
/// <exception cref="ArgumentNullException">The data is null.</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>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
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 data is null.</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>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image<TPixel> Load<TPixel>(Configuration configuration, byte[] data, IImageDecoder decoder)
@ -216,10 +222,7 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <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>
public static IImageFormat DetectFormat(ReadOnlySpan<byte> data)
{
return DetectFormat(Configuration.Default, data);
}
public static IImageFormat DetectFormat(ReadOnlySpan<byte> data) => DetectFormat(Configuration.Default, data);
/// <summary>
/// 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>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</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>
public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data)
where TPixel : unmanaged, IPixel<TPixel>
@ -271,6 +275,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</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>
public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data, out IImageFormat format)
where TPixel : unmanaged, IPixel<TPixel>
@ -284,6 +289,7 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</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>
public static Image<TPixel> Load<TPixel>(ReadOnlySpan<byte> data, IImageDecoder decoder)
where TPixel : unmanaged, IPixel<TPixel>
@ -298,6 +304,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</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>
public static unsafe Image<TPixel> Load<TPixel>(Configuration configuration, ReadOnlySpan<byte> data)
where TPixel : unmanaged, IPixel<TPixel>
@ -321,6 +328,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</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>
public static unsafe Image<TPixel> Load<TPixel>(
Configuration configuration,
@ -347,6 +355,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</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>
public static unsafe Image<TPixel> Load<TPixel>(
Configuration configuration,
@ -372,6 +381,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>The <see cref="Image"/>.</returns>
public static Image Load(byte[] data, out IImageFormat format)
=> Load(Configuration.Default, data, out format);
@ -384,6 +394,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>The <see cref="Image"/>.</returns>
public static Image Load(byte[] data, IImageDecoder decoder)
=> Load(Configuration.Default, data, decoder);
@ -397,6 +408,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>The <see cref="Image"/>.</returns>
public static Image Load(Configuration configuration, byte[] data)
=> Load(configuration, data, out _);
@ -411,6 +423,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The data is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>The <see cref="Image"/>.</returns>
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="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>The <see cref="Image"/>.</returns>
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>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>The <see cref="Image"/>.</returns>
public static Image Load(ReadOnlySpan<byte> data)
=> Load(Configuration.Default, data);
@ -458,6 +473,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>The <see cref="Image"/>.</returns>
public static Image Load(ReadOnlySpan<byte> data, IImageDecoder decoder)
=> Load(Configuration.Default, data, decoder);
@ -470,6 +486,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The decoder is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>The <see cref="Image"/>.</returns>
public static Image Load(ReadOnlySpan<byte> data, out IImageFormat format)
=> Load(Configuration.Default, data, out format);
@ -491,7 +508,7 @@ namespace SixLabors.ImageSharp
/// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The configuration 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="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns>
@ -518,6 +535,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The configuration is null.</exception>
/// <exception cref="UnknownImageFormatException">Image format not recognised.</exception>
/// <exception cref="InvalidImageContentException">Image contains invalid content.</exception>
/// <exception cref="NotSupportedException">Image format is not supported.</exception>
/// <returns>The <see cref="Image"/>.</returns>
public static unsafe Image Load(
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 path is null.</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>
/// <returns>The <see cref="Image"/>.</returns>
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 path is null.</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>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
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 decoder is null.</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>
/// <returns>The <see cref="Image"/>.</returns>
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 decoder is null.</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>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
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 decoder is null.</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>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
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 decoder is null.</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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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 decoder is null.</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>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
public static Task<Image> LoadAsync(
@ -313,6 +320,7 @@ namespace SixLabors.ImageSharp
/// <exception cref="ArgumentNullException">The path is null.</exception>
/// <exception cref="ArgumentNullException">The decoder is null.</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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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="UnknownImageFormatException">Image format not recognised.</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>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
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 path is null.</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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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 decoder is null.</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>
/// <returns>The <see cref="Image"/>.</returns>
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="UnknownImageFormatException">Image format not recognised.</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>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
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="UnknownImageFormatException">Image format not recognised.</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>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
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 path is null.</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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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 path is null.</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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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 path is null.</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>
/// <returns>A new <see cref="Image{TPixel}"/>.</returns>
public static Image Load(Configuration configuration, string path, out IImageFormat format)
@ -485,6 +501,7 @@ namespace SixLabors.ImageSharp
/// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The path is null.</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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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 decoder is null.</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>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <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 SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.IO;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp
@ -216,7 +215,7 @@ namespace SixLabors.ImageSharp
/// <param name="stream">The stream containing image information.</param>
/// <param name="format">The format type of the decoded image.</param>
/// <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="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns>
@ -229,7 +228,7 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <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="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{ValueTuple}"/> representing the asynchronous operation.</returns>
@ -242,7 +241,7 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <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="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns>
@ -254,7 +253,7 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <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="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
@ -268,7 +267,7 @@ namespace SixLabors.ImageSharp
/// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The stream 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="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>The <see cref="Image"/>.</returns>
@ -283,7 +282,7 @@ namespace SixLabors.ImageSharp
/// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The stream 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="InvalidImageContentException">Image contains invalid content.</exception>
/// <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 stream 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="InvalidImageContentException">Image contains invalid content.</exception>
/// <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 stream 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="InvalidImageContentException">Image contains invalid content.</exception>
/// <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>
/// <exception cref="ArgumentNullException">The configuration 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="InvalidImageContentException">Image contains invalid content.</exception>
/// <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>
/// <exception cref="ArgumentNullException">The configuration 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="InvalidImageContentException">Image contains invalid content.</exception>
/// <returns>A <see cref="Task{Image}"/> representing the asynchronous operation.</returns>
@ -376,7 +375,7 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <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="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
@ -390,7 +389,7 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <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="InvalidImageContentException">Image contains invalid content.</exception>
/// <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="format">The format type of the decoded image.</param>
/// <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="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
@ -419,7 +418,7 @@ namespace SixLabors.ImageSharp
/// </summary>
/// <param name="stream">The stream containing image information.</param>
/// <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="InvalidImageContentException">Image contains invalid content.</exception>
/// <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="decoder">The decoder.</param>
/// <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="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
@ -450,7 +449,7 @@ namespace SixLabors.ImageSharp
/// <param name="decoder">The decoder.</param>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <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="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
@ -471,7 +470,7 @@ namespace SixLabors.ImageSharp
/// <param name="decoder">The decoder.</param>
/// <exception cref="ArgumentNullException">The configuration 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="InvalidImageContentException">Image contains invalid content.</exception>
/// <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>
/// <exception cref="ArgumentNullException">The configuration 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="InvalidImageContentException">Image contains invalid content.</exception>
/// <typeparam name="TPixel">The pixel format.</typeparam>
@ -513,7 +512,7 @@ namespace SixLabors.ImageSharp
/// <param name="stream">The stream containing image information.</param>
/// <exception cref="ArgumentNullException">The configuration 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="InvalidImageContentException">Image contains invalid content.</exception>
/// <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>
/// <exception cref="ArgumentNullException">The configuration 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="InvalidImageContentException">Image contains invalid content.</exception>
/// <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>
/// <exception cref="ArgumentNullException">The configuration 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="InvalidImageContentException">Image contains invalid content.</exception>
/// <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>
/// <exception cref="ArgumentNullException">The configuration 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="InvalidImageContentException">Image contains invalid content.</exception>
/// <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>
/// <exception cref="ArgumentNullException">The configuration 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="InvalidImageContentException">Image contains invalid content.</exception>
/// <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>
/// <exception cref="ArgumentNullException">The configuration 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="InvalidImageContentException">Image contains invalid content.</exception>
/// <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
// 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
// having to further wrap
if (stream.CanSeek)

5
src/ImageSharp/ImageSharp.csproj

@ -16,6 +16,11 @@
</PropertyGroup>
<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">
<PropertyGroup>
<TargetFrameworks>net5.0;netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;netstandard1.3;net472</TargetFrameworks>

6
src/ImageSharp/Memory/Allocators/Internals/ManagedBufferBase.cs

@ -24,7 +24,9 @@ namespace SixLabors.ImageSharp.Memory.Internals
}
void* ptr = (void*)this.pinHandle.AddrOfPinnedObject();
return new MemoryHandle(ptr, this.pinHandle);
// We should only pass pinnable:this, when GCHandle lifetime is managed by the MemoryManager<T> instance.
return new MemoryHandle(ptr, pinnable: this);
}
/// <inheritdoc />
@ -42,4 +44,4 @@ namespace SixLabors.ImageSharp.Memory.Internals
/// <returns>The pinnable <see cref="object"/>.</returns>
protected abstract object GetPinnableObject();
}
}
}

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

@ -257,7 +257,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Normalization
[MethodImpl(InliningOptions.ShortMethod)]
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);
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.Metadata.ExifProfile = null;
this.encoder420 = new JpegEncoder { Quality = this.Quality, Subsample = JpegSubsample.Ratio420 };
this.encoder444 = new JpegEncoder { Quality = this.Quality, Subsample = JpegSubsample.Ratio444 };
this.encoder420 = new JpegEncoder { Quality = this.Quality, ColorType = JpegColorType.YCbCrRatio420 };
this.encoder444 = new JpegEncoder { Quality = this.Quality, ColorType = JpegColorType.YCbCrRatio444 };
this.bmpStream.Position = 0;
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-->
<IsTestProject>false</IsTestProject>
<Configurations>Debug;Release;Debug-InnerLoop;Release-InnerLoop</Configurations>
<LangVersion>9</LangVersion>
<!-- Uncomment this to run benchmarks depending on Colorful or Pfim (colorspaces and TGA): -->
<!--<SignAssembly>false</SignAssembly>-->
</PropertyGroup>
<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">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1</TargetFrameworks>

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

@ -14,10 +14,14 @@
<EnsureNETCoreAppRuntime>false</EnsureNETCoreAppRuntime>
<Configurations>Debug;Release;Debug-InnerLoop;Release-InnerLoop</Configurations>
<ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
<LangVersion>9</LangVersion>
</PropertyGroup>
<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">
<PropertyGroup>
<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,
int expectedPixelSize,
bool exifProfilePresent,
bool iccProfilePresent)
{
TestMetadataImpl(
bool iccProfilePresent) => TestMetadataImpl(
useIdentify,
JpegDecoder,
imagePath,
expectedPixelSize,
exifProfilePresent,
iccProfilePresent);
}
[Theory]
[MemberData(nameof(RatioFiles))]
@ -133,8 +130,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
var decoder = new JpegDecoder();
using (Image<Rgba32> image = decoder.Decode<Rgba32>(Configuration.Default, stream))
using (Image<Rgba32> image = JpegDecoder.Decode<Rgba32>(Configuration.Default, stream))
{
JpegMetadata meta = image.Metadata.GetJpegMetadata();
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)
{
var testFile = TestFile.Create(imagePath);
@ -161,9 +193,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
string imagePath,
int expectedPixelSize,
bool exifProfilePresent,
bool iccProfilePresent)
{
TestImageInfo(
bool iccProfilePresent) => TestImageInfo(
imagePath,
decoder,
useIdentify,
@ -207,7 +237,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Null(iccProfile);
}
});
}
[Theory]
[InlineData(false)]
@ -237,9 +266,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Decoder_Reads_Correct_Resolution_From_Jfif(bool useIdentify)
{
TestImageInfo(
public void Decoder_Reads_Correct_Resolution_From_Jfif(bool useIdentify) => TestImageInfo(
TestImages.Jpeg.Baseline.Floorplan,
JpegDecoder,
useIdentify,
@ -248,14 +275,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(300, imageInfo.Metadata.HorizontalResolution);
Assert.Equal(300, imageInfo.Metadata.VerticalResolution);
});
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void Decoder_Reads_Correct_Resolution_From_Exif(bool useIdentify)
{
TestImageInfo(
public void Decoder_Reads_Correct_Resolution_From_Exif(bool useIdentify) => TestImageInfo(
TestImages.Jpeg.Baseline.Jpeg420Exif,
JpegDecoder,
useIdentify,
@ -264,6 +288,5 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
Assert.Equal(72, imageInfo.Metadata.HorizontalResolution);
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;
using var ms = new MemoryStream(bytes);
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);
// 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));
}
[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
[Theory]
[WithFile(TestImages.Jpeg.Issues.WrongColorSpace, PixelTypes.Rgba32)]
public void Issue1732_DecodesWithRgbColorSpace<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage(new JpegDecoder()))
using (Image<TPixel> image = provider.GetImage(JpegDecoder))
{
image.DebugSave(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")]
public class JpegEncoderTests
{
private static JpegEncoder JpegEncoder => new JpegEncoder();
private static JpegDecoder JpegDecoder => new JpegDecoder();
public static readonly TheoryData<string, int> QualityFiles =
new TheoryData<string, int>
{
@ -31,15 +35,18 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{ TestImages.Jpeg.Progressive.Fb, 75 }
};
public static readonly TheoryData<JpegSubsample, int> BitsPerPixel_Quality =
new TheoryData<JpegSubsample, int>
public static readonly TheoryData<JpegColorType, int> BitsPerPixel_Quality =
new TheoryData<JpegColorType, int>
{
{ JpegSubsample.Ratio420, 40 },
{ JpegSubsample.Ratio420, 60 },
{ JpegSubsample.Ratio420, 100 },
{ JpegSubsample.Ratio444, 40 },
{ JpegSubsample.Ratio444, 60 },
{ JpegSubsample.Ratio444, 100 },
{ JpegColorType.YCbCrRatio420, 40 },
{ JpegColorType.YCbCrRatio420, 60 },
{ JpegColorType.YCbCrRatio420, 100 },
{ JpegColorType.YCbCrRatio444, 40 },
{ JpegColorType.YCbCrRatio444, 60 },
{ JpegColorType.YCbCrRatio444, 100 },
{ JpegColorType.Rgb, 40 },
{ JpegColorType.Rgb, 60 },
{ JpegColorType.Rgb, 100 }
};
public static readonly TheoryData<int> Grayscale_Quality =
@ -59,17 +66,84 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
};
[Theory]
[MemberData(nameof(QualityFiles))]
public void Encode_PreserveQuality(string imagePath, int quality)
[WithFile(TestImages.Jpeg.Baseline.Floorplan, PixelTypes.Rgba32, JpegColorType.Luminance)]
[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);
using (Image<Rgba32> input = testFile.CreateRgba32Image())
{
using (var memStream = new MemoryStream())
{
input.Save(memStream, options);
input.Save(memStream, JpegEncoder);
memStream.Position = 0;
using (var output = Image.Load<Rgba32>(memStream))
@ -83,16 +157,30 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[Theory]
[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), 48, 24, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 46, 8, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 51, 7, PixelTypes.Rgba32)]
[WithSolidFilledImages(nameof(BitsPerPixel_Quality), 1, 1, 255, 100, 50, 255, PixelTypes.Rgba32)]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 7, 5, PixelTypes.Rgba32)]
[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);
public void EncodeBaseline_WithSmallImages_WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality)
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.12f));
[Theory]
[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.La32, 100)]
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]
[WithTestPatternImages(nameof(BitsPerPixel_Quality), 48, 48, PixelTypes.Rgba32 | PixelTypes.Bgra32)]
public void EncodeBaseline_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider, JpegSubsample subsample, int quality)
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, subsample, quality);
public void EncodeBaseline_WithSmallImages_IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType, int quality)
where TPixel : unmanaged, IPixel<TPixel> => TestJpegEncoderCore(provider, colorType, quality, comparer: ImageComparer.Tolerant(0.06f));
[Theory]
[WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, JpegSubsample.Ratio444)]
[WithTestPatternImages(587, 821, PixelTypes.Rgba32, JpegSubsample.Ratio444)]
[WithTestPatternImages(677, 683, PixelTypes.Bgra32, JpegSubsample.Ratio420)]
[WithSolidFilledImages(400, 400, "Red", PixelTypes.Bgr24, JpegSubsample.Ratio420)]
public void EncodeBaseline_WorksWithDiscontiguousBuffers<TPixel>(TestImageProvider<TPixel> provider, JpegSubsample subsample)
[WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32, JpegColorType.YCbCrRatio444)]
[WithTestPatternImages(587, 821, PixelTypes.Rgba32, JpegColorType.YCbCrRatio444)]
[WithTestPatternImages(677, 683, PixelTypes.Bgra32, JpegColorType.YCbCrRatio420)]
[WithSolidFilledImages(400, 400, "Red", PixelTypes.Bgr24, JpegColorType.YCbCrRatio420)]
public void EncodeBaseline_WorksWithDiscontiguousBuffers<TPixel>(TestImageProvider<TPixel> provider, JpegColorType colorType)
where TPixel : unmanaged, IPixel<TPixel>
{
ImageComparer comparer = subsample == JpegSubsample.Ratio444
ImageComparer comparer = colorType == JpegColorType.YCbCrRatio444
? ImageComparer.TolerantPercentage(0.1f)
: ImageComparer.TolerantPercentage(5f);
provider.LimitAllocatorBufferCapacity().InBytesSqrt(200);
TestJpegEncoderCore(provider, subsample, 100, JpegColorType.YCbCr, comparer);
TestJpegEncoderCore(provider, colorType, 100, comparer);
}
/// <summary>
/// Anton's SUPER-SCIENTIFIC tolerance threshold calculation
/// </summary>
private static ImageComparer GetComparer(int quality, JpegSubsample? subsample)
private static ImageComparer GetComparer(int quality, JpegColorType? colorType)
{
float tolerance = 0.015f; // ~1.5%
if (quality < 50)
{
tolerance *= 10f;
tolerance *= 4.5f;
}
else if (quality < 75 || subsample == JpegSubsample.Ratio420)
else if (quality < 75 || colorType == JpegColorType.YCbCrRatio420)
{
tolerance *= 5f;
if (subsample == JpegSubsample.Ratio420)
tolerance *= 2.0f;
if (colorType == JpegColorType.YCbCrRatio420)
{
tolerance *= 2f;
tolerance *= 2.0f;
}
}
@ -150,9 +243,8 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
private static void TestJpegEncoderCore<TPixel>(
TestImageProvider<TPixel> provider,
JpegSubsample? subsample,
JpegColorType colorType = JpegColorType.YCbCrRatio420,
int quality = 100,
JpegColorType colorType = JpegColorType.YCbCr,
ImageComparer comparer = null)
where TPixel : unmanaged, IPixel<TPixel>
{
@ -163,13 +255,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var encoder = new JpegEncoder
{
Subsample = subsample,
Quality = quality,
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():
image.VerifyEncoder(provider, "jpeg", info, encoder, comparer, referenceImageExtension: "png");
@ -225,14 +316,12 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
[MemberData(nameof(RatioFiles))]
public void Encode_PreserveRatio(string imagePath, int xResolution, int yResolution, PixelResolutionUnit resolutionUnit)
{
var options = new JpegEncoder();
var testFile = TestFile.Create(imagePath);
using (Image<Rgba32> input = testFile.CreateRgba32Image())
{
using (var memStream = new MemoryStream())
{
input.Save(memStream, options);
input.Save(memStream, JpegEncoder);
memStream.Position = 0;
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);
input.Metadata.IptcProfile = new IptcProfile();
input.Metadata.IptcProfile.SetValue(IptcTag.Byline, "unit_test");
var encoder = new JpegEncoder();
// act
using var memStream = new MemoryStream();
input.Save(memStream, encoder);
input.Save(memStream, JpegEncoder);
// assert
memStream.Position = 0;
@ -275,11 +363,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
using var input = new Image<Rgba32>(1, 1);
input.Metadata.ExifProfile = new ExifProfile();
input.Metadata.ExifProfile.SetValue(ExifTag.Software, "unit_test");
var encoder = new JpegEncoder();
// act
using var memStream = new MemoryStream();
input.Save(memStream, encoder);
input.Save(memStream, JpegEncoder);
// assert
memStream.Position = 0;
@ -296,11 +383,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
// arrange
using var input = new Image<Rgba32>(1, 1);
input.Metadata.IccProfile = new IccProfile(IccTestDataProfiles.Profile_Random_Array);
var encoder = new JpegEncoder();
// act
using var memStream = new MemoryStream();
input.Save(memStream, encoder);
input.Save(memStream, JpegEncoder);
// assert
memStream.Position = 0;
@ -312,9 +398,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
}
[Theory]
[InlineData(JpegSubsample.Ratio420)]
[InlineData(JpegSubsample.Ratio444)]
public async Task Encode_IsCancellable(JpegSubsample subsample)
[InlineData(JpegColorType.YCbCrRatio420)]
[InlineData(JpegColorType.YCbCrRatio444)]
public async Task Encode_IsCancellable(JpegColorType colorType)
{
var cts = new CancellationTokenSource();
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);
await Assert.ThrowsAsync<TaskCanceledException>(async () =>
{
var encoder = new JpegEncoder() { Subsample = subsample };
var encoder = new JpegEncoder() { ColorType = colorType };
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();
clone.Quality = 99;
clone.ColorType = JpegColorType.YCbCr;
clone.ColorType = JpegColorType.YCbCrRatio420;
Assert.False(meta.Quality.Equals(clone.Quality));
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")]
public class SpectralJpegTests
{
public SpectralJpegTests(ITestOutputHelper output)
{
this.Output = output;
}
public SpectralJpegTests(ITestOutputHelper output) => this.Output = output;
private ITestOutputHelper Output { get; }
@ -46,7 +43,6 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
public static readonly string[] AllTestJpegs = BaselineTestJpegs.Concat(ProgressiveTestJpegs).ToArray();
[Theory(Skip = "Debug only, enable manually!")]
//[Theory]
[WithFileCollection(nameof(AllTestJpegs), PixelTypes.Rgba32)]
public void Decoder_ParseStream_SaveSpectralResult<TPixel>(TestImageProvider<TPixel> provider)
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);
decompressor.Decompress(stream, 0, (uint)stream.Length, buffer);
decompressor.Decompress(stream, 0, (uint)stream.Length, 1, 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];
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);
}

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 })]
public void Decompress_ReadsData(byte[] inputData, uint byteCount, byte[] expectedResult)
{
var stream = new BufferedReadStream(Configuration.Default, new MemoryStream(inputData));
var buffer = new byte[expectedResult.Length];
using var memoryStream = new MemoryStream(inputData);
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);
}

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
public void Decompress_ReadsData(byte[] inputData, byte[] expectedResult)
{
var stream = new BufferedReadStream(Configuration.Default, new MemoryStream(inputData));
var buffer = new byte[expectedResult.Length];
using var memoryStream = new MemoryStream(inputData);
using var stream = new BufferedReadStream(Configuration.Default, memoryStream);
byte[] buffer = new byte[expectedResult.Length];
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);
}
@ -41,7 +42,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff.Compression
{
// arrange
Span<byte> input = inputData.AsSpan();
var compressed = new byte[expectedResult.Length];
byte[] compressed = new byte[expectedResult.Length];
// act
PackBitsWriter.PackBits(input, compressed);

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

@ -23,21 +23,21 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{
public static readonly string[] MultiframeTestImages = Multiframes;
public static readonly string[] NotSupportedImages = NotSupported;
private static TiffDecoder TiffDecoder => new TiffDecoder();
private static MagickReferenceDecoder ReferenceDecoder => new MagickReferenceDecoder();
[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)
where TPixel : unmanaged, IPixel<TPixel> => Assert.Throws<NotSupportedException>(() => provider.GetImage(TiffDecoder));
[Theory]
[InlineData(RgbUncompressed, 24, 256, 256, 300, 300, 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)]
public void Identify(string imagePath, int expectedPixelSize, int expectedWidth, int expectedHeight, double expectedHResolution, double expectedVResolution, PixelResolutionUnit expectedResolutionUnit)
{
@ -356,6 +356,13 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_Fax3Compressed<TPixel>(TestImageProvider<TPixel> 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]
[WithFile(CcittFax3LowerOrderBitsFirst, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_Compressed_LowerOrderBitsFirst<TPixel>(TestImageProvider<TPixel> provider)
@ -368,6 +375,14 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_PackBitsCompressed<TPixel>(TestImageProvider<TPixel> 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]
[WithFileCollection(nameof(MultiframeTestImages), PixelTypes.Rgba32)]
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.Rgb, TiffCompression.ItuTRecT43, 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.OldJpeg, TiffBitsPerPixel.Bit24, TiffCompression.None)]
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)
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]
[WithFile(Calliphora_GrayscaleUncompressed, PixelTypes.Rgba32)]
public void TiffEncoder_EncodeGray_Works<TPixel>(TestImageProvider<TPixel> provider)

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

@ -10,6 +10,11 @@
</PropertyGroup>
<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">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1</TargetFrameworks>
@ -21,7 +26,7 @@
</PropertyGroup>
</Otherwise>
</Choose>
<ItemGroup>
<InternalsVisibleTo Include="ImageSharp.Tests.ProfilingSandbox" Key="$(SixLaborsPublicKey)" />
</ItemGroup>

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

@ -114,6 +114,23 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators
}
}
[Fact]
public unsafe void Allocate_MemoryIsPinnableMultipleTimes()
{
ArrayPoolMemoryAllocator allocator = this.LocalFixture.MemoryAllocator;
using IMemoryOwner<byte> memoryOwner = allocator.Allocate<byte>(100);
using (MemoryHandle pin = memoryOwner.Memory.Pin())
{
Assert.NotEqual(IntPtr.Zero, (IntPtr)pin.Pointer);
}
using (MemoryHandle pin = memoryOwner.Memory.Pin())
{
Assert.NotEqual(IntPtr.Zero, (IntPtr)pin.Pointer);
}
}
[Theory]
[InlineData(false)]
[InlineData(true)]

20
tests/ImageSharp.Tests/Memory/Allocators/SimpleGcMemoryAllocatorTests.cs

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Memory;
using Xunit;
@ -36,9 +37,26 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators
Assert.Equal("length", ex.ParamName);
}
[Fact]
public unsafe void Allocate_MemoryIsPinnableMultipleTimes()
{
SimpleGcMemoryAllocator allocator = this.MemoryAllocator;
using IMemoryOwner<byte> memoryOwner = allocator.Allocate<byte>(100);
using (MemoryHandle pin = memoryOwner.Memory.Pin())
{
Assert.NotEqual(IntPtr.Zero, (IntPtr)pin.Pointer);
}
using (MemoryHandle pin = memoryOwner.Memory.Pin())
{
Assert.NotEqual(IntPtr.Zero, (IntPtr)pin.Pointer);
}
}
[StructLayout(LayoutKind.Explicit, Size = 512)]
private struct BigStruct
{
}
}
}
}

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

@ -70,7 +70,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks
img.Dispose();
},
#pragma warning disable SA1515 // Single-line comment should be preceded by blank line
// ReSharper disable once ExplicitCallerInfoArgument
// ReSharper disable once ExplicitCallerInfoArgument
$"Decode {fileName}");
#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!
[Theory(Skip = ProfilingSetup.SkipProfilingTests)]
[InlineData(1, 75, JpegSubsample.Ratio420)]
[InlineData(30, 75, JpegSubsample.Ratio420)]
[InlineData(30, 75, JpegSubsample.Ratio444)]
[InlineData(30, 100, JpegSubsample.Ratio444)]
public void EncodeJpeg(int executionCount, int quality, JpegSubsample subsample)
[InlineData(1, 75, JpegColorType.YCbCrRatio420)]
[InlineData(30, 75, JpegColorType.YCbCrRatio420)]
[InlineData(30, 75, JpegColorType.YCbCrRatio444)]
[InlineData(30, 100, JpegColorType.YCbCrRatio444)]
public void EncodeJpeg(int executionCount, int quality, JpegColorType colorType)
{
// do not run this on CI even by accident
if (TestEnvironment.RunsOnCI)
@ -118,7 +118,7 @@ namespace SixLabors.ImageSharp.Tests.ProfilingBenchmarks
{
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);
ms.Seek(0, SeekOrigin.Begin);
}

16
tests/ImageSharp.Tests/TestImages.cs

@ -190,6 +190,10 @@ namespace SixLabors.ImageSharp.Tests
public const string Jpeg420Exif = "Jpg/baseline/jpeg420exif.jpg";
public const string Jpeg444 = "Jpg/baseline/jpeg444.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 MultiScanBaselineCMYK = "Jpg/baseline/MultiScanBaselineCMYK.jpg";
public const string Ratio1x1 = "Jpg/baseline/ratio-1x1.jpg";
@ -200,6 +204,9 @@ namespace SixLabors.ImageSharp.Tests
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 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 =
{
@ -535,6 +542,8 @@ namespace SixLabors.ImageSharp.Tests
public const string Calliphora_Fax4Compressed = "Tiff/Calliphora_ccitt_fax4.tiff";
public const string Calliphora_HuffmanCompressed = "Tiff/Calliphora_huffman_rle.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 CcittFax3AllMakeupCodes = "Tiff/ccitt_fax3_all_makeup_codes.tiff";
@ -553,7 +562,9 @@ namespace SixLabors.ImageSharp.Tests
public const string RgbDeflate = "Tiff/rgb_deflate.tiff";
public const string RgbDeflatePredictor = "Tiff/rgb_deflate_predictor.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 RgbLzwNoPredictor = "Tiff/rgb_lzw_no_predictor.tiff";
public const string RgbLzwNoPredictorMultistrip = "Tiff/rgb_lzw_noPredictor_multistrip.tiff";
@ -601,6 +612,7 @@ namespace SixLabors.ImageSharp.Tests
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 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 FlowerRgb444Planar = "Tiff/flower-rgb-planar-04.tiff";
public const string FlowerRgb222Contiguous = "Tiff/flower-rgb-contig-02.tiff";
@ -659,8 +671,6 @@ namespace SixLabors.ImageSharp.Tests
public static readonly string[] Multiframes = { MultiframeDeflateWithPreview, MultiframeLzwPredictor /*, MultiFrameDifferentSize, MultiframeDifferentSizeTiled, MultiFrameDifferentVariants,*/ };
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
oid sha256:ed3b08730e5c34eb8d268f58d1e09efe2605398899bfd726cc3b35de21baa6ff
size 121196
oid sha256:c35902ca485fba441230efa88f794ee5aafa9f75ad5e4a14cb3d592a0a98c538
size 7760

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ba79ffac35e16208406e073492d770d3a77e51a47112aa02ab8fd98b5a8487b2
size 198564
oid sha256:4baf0f4c462e5bef71ab36f505dfff87a31bd1d25deabccd822a359c1075e08e
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
oid sha256:ecb529e5e3e0eca6f5e407b034fa8ba67bb4b9068af9e9b30425b08d30a249c0
size 1756355
oid sha256:993207c34358165af5fac0d0b955b56cd79e9707c1c46344863e01cbc9c7707d
size 126695

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

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

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

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:acb46c990af78fcb0e63849f0f26acffe26833d5cfd2fc77a728baf92166eea3
size 2893218
oid sha256:bb25349a7f803aeafc47d8a05deca1a8afdc4bdc5a53e2916f68d5c3e7d8cad3
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

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

Loading…
Cancel
Save