Browse Source

Merge remote-tracking branch 'origin/main' into bp/webpanimation

# Conflicts:
#	src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
#	tests/ImageSharp.Tests/TestImages.cs
pull/1985/head
Brian Popow 4 years ago
parent
commit
51160a4656
  1. 50
      .github/workflows/build-and-test.yml
  2. 8
      .github/workflows/code-coverage.yml
  3. 18
      ImageSharp.sln
  4. 1
      src/Directory.Build.props
  5. 21
      src/ImageSharp/Diagnostics/MemoryDiagnostics.cs
  6. 19
      src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
  7. 18
      src/ImageSharp/Formats/Gif/GifDecoderCore.cs
  8. 5
      src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
  9. 100
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  10. 3
      src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
  11. 12
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  12. 6
      src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs
  13. 42
      src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs
  14. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs
  15. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs
  16. 6
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs
  17. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs
  18. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs
  19. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs
  20. 4
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs
  21. 6
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs
  22. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs
  23. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs
  24. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs
  25. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs
  26. 6
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs
  27. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs
  28. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs
  29. 27
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs
  30. 24
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs
  31. 22
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs
  32. 20
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs
  33. 21
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs
  34. 20
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs
  35. 25
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs
  36. 6
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs
  37. 18
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs
  38. 20
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs
  39. 34
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs
  40. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs
  41. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs
  42. 6
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs
  43. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs
  44. 2
      src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs
  45. 13
      src/ImageSharp/Formats/Tiff/README.md
  46. 70
      src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
  47. 6
      src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
  48. 56
      src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs
  49. 340
      src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
  50. 7
      src/ImageSharp/Formats/Webp/WebpThrowHelper.cs
  51. 9
      src/ImageSharp/IO/BufferedReadStream.cs
  52. 31
      src/ImageSharp/ImageSharp.csproj
  53. 9
      src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs
  54. 8
      src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
  55. 2
      src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs
  56. 21
      src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs
  57. 21
      src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs
  58. 21
      src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs
  59. 25
      src/ImageSharp/Processing/Processors/Convolution/BorderWrappingMode.cs
  60. 29
      src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs
  61. 36
      src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs
  62. 20
      src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
  63. 46
      src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs
  64. 36
      src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs
  65. 46
      src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs
  66. 36
      src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs
  67. 152
      src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs
  68. 6
      src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs
  69. 6
      src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs
  70. 11
      tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
  71. 11
      tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj
  72. 1
      tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs
  73. 1
      tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
  74. 1
      tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
  75. 8
      tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs
  76. 1
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
  77. 15
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs
  78. 1
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  79. 33
      tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs
  80. 2
      tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
  81. 1
      tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs
  82. 1
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
  83. 8
      tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs
  84. 1
      tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs
  85. 4
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs
  86. 126
      tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
  87. 1
      tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs
  88. 13
      tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs
  89. 2
      tests/ImageSharp.Tests/Helpers/RuntimeEnvironmentTests.cs
  90. 1
      tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs
  91. 2
      tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs
  92. 12
      tests/ImageSharp.Tests/ImageSharp.Tests.csproj
  93. 6
      tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs
  94. 4
      tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs
  95. 8
      tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs
  96. 22
      tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs
  97. 77
      tests/ImageSharp.Tests/MemoryAllocatorValidator.cs
  98. 7
      tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs
  99. 421
      tests/ImageSharp.Tests/Processing/Convolution/KernelSamplingMapTest.cs
  100. 2
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs

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

@ -15,59 +15,38 @@ jobs:
matrix: matrix:
options: options:
- os: ubuntu-latest - os: ubuntu-latest
framework: net6.0 framework: net7.0
sdk: 6.0.x sdk: 7.0.x
sdk-preview: true sdk-preview: true
runtime: -x64 runtime: -x64
codecov: false codecov: false
- os: macos-latest - os: macos-latest
framework: net6.0 framework: net7.0
sdk: 6.0.x sdk: 7.0.x
sdk-preview: true sdk-preview: true
runtime: -x64 runtime: -x64
codecov: false codecov: false
- os: windows-latest - os: windows-latest
framework: net6.0 framework: net7.0
sdk: 6.0.x sdk: 7.0.x
sdk-preview: true sdk-preview: true
runtime: -x64 runtime: -x64
codecov: false codecov: false
- os: ubuntu-latest - os: ubuntu-latest
framework: net5.0 framework: net6.0
runtime: -x64 sdk: 6.0.x
codecov: false
- os: macos-latest
framework: net5.0
runtime: -x64
codecov: false
- os: windows-latest
framework: net5.0
runtime: -x64
codecov: false
- os: ubuntu-latest
framework: netcoreapp3.1
runtime: -x64 runtime: -x64
codecov: false codecov: false
- os: macos-latest - os: macos-latest
framework: netcoreapp3.1 framework: net6.0
runtime: -x64 sdk: 6.0.x
codecov: false
- os: windows-latest
framework: netcoreapp3.1
runtime: -x64
codecov: false
- os: windows-latest
framework: netcoreapp2.1
runtime: -x64 runtime: -x64
codecov: false codecov: false
- os: windows-latest - os: windows-latest
framework: net472 framework: net6.0
sdk: 6.0.x
runtime: -x64 runtime: -x64
codecov: false codecov: false
- os: windows-latest
framework: net472
runtime: -x86
codecov: false
runs-on: ${{matrix.options.os}} runs-on: ${{matrix.options.os}}
@ -112,11 +91,10 @@ jobs:
- name: DotNet Setup - name: DotNet Setup
uses: actions/setup-dotnet@v1 uses: actions/setup-dotnet@v1
with: with:
include-prerelease: true
dotnet-version: | dotnet-version: |
7.0.x
6.0.x 6.0.x
5.0.x
3.1.x
2.1.x
- name: DotNet Build - name: DotNet Build
if: ${{ matrix.options.sdk-preview != true }} if: ${{ matrix.options.sdk-preview != true }}

8
.github/workflows/code-coverage.yml

@ -10,7 +10,7 @@ jobs:
matrix: matrix:
options: options:
- os: ubuntu-latest - os: ubuntu-latest
framework: netcoreapp3.1 framework: net6.0
runtime: -x64 runtime: -x64
codecov: true codecov: true
@ -54,6 +54,12 @@ jobs:
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }} key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', '**/*.props', '**/*.targets') }}
restore-keys: ${{ runner.os }}-nuget- restore-keys: ${{ runner.os }}-nuget-
- name: DotNet Setup
uses: actions/setup-dotnet@v1
with:
dotnet-version: |
6.0.x
- name: DotNet Build - name: DotNet Build
shell: pwsh shell: pwsh
run: ./ci-build.ps1 "${{matrix.options.framework}}" run: ./ci-build.ps1 "${{matrix.options.framework}}"

18
ImageSharp.sln

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

1
src/Directory.Build.props

@ -27,6 +27,7 @@
<InternalsVisibleTo Include="ImageSharp.Benchmarks" Key="$(SixLaborsPublicKey)" /> <InternalsVisibleTo Include="ImageSharp.Benchmarks" Key="$(SixLaborsPublicKey)" />
<InternalsVisibleTo Include="ImageSharp.Tests.ProfilingSandbox" Key="$(SixLaborsPublicKey)" /> <InternalsVisibleTo Include="ImageSharp.Tests.ProfilingSandbox" Key="$(SixLaborsPublicKey)" />
<InternalsVisibleTo Include="SixLabors.ImageSharp.Tests" Key="$(SixLaborsPublicKey)" /> <InternalsVisibleTo Include="SixLabors.ImageSharp.Tests" Key="$(SixLaborsPublicKey)" />
<InternalsVisibleTo Include="SixLabors.ImageSharp.Drawing.Tests" Key="$(SixLaborsPublicKey)" />
</ItemGroup> </ItemGroup>
</Project> </Project>

21
src/ImageSharp/Diagnostics/MemoryDiagnostics.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.Threading; using System.Threading;
namespace SixLabors.ImageSharp.Diagnostics namespace SixLabors.ImageSharp.Diagnostics
@ -47,6 +48,16 @@ namespace SixLabors.ImageSharp.Diagnostics
} }
} }
/// <summary>
/// Fires when ImageSharp allocates memory from a MemoryAllocator
/// </summary>
internal static event Action MemoryAllocated;
/// <summary>
/// Fires when ImageSharp releases memory allocated from a MemoryAllocator
/// </summary>
internal static event Action MemoryReleased;
/// <summary> /// <summary>
/// Gets a value indicating the total number of memory resource objects leaked to the finalizer. /// Gets a value indicating the total number of memory resource objects leaked to the finalizer.
/// </summary> /// </summary>
@ -54,11 +65,17 @@ namespace SixLabors.ImageSharp.Diagnostics
internal static bool UndisposedAllocationSubscribed => Volatile.Read(ref undisposedAllocationSubscriptionCounter) > 0; internal static bool UndisposedAllocationSubscribed => Volatile.Read(ref undisposedAllocationSubscriptionCounter) > 0;
internal static void IncrementTotalUndisposedAllocationCount() => internal static void IncrementTotalUndisposedAllocationCount()
{
Interlocked.Increment(ref totalUndisposedAllocationCount); Interlocked.Increment(ref totalUndisposedAllocationCount);
MemoryAllocated?.Invoke();
}
internal static void DecrementTotalUndisposedAllocationCount() => internal static void DecrementTotalUndisposedAllocationCount()
{
Interlocked.Decrement(ref totalUndisposedAllocationCount); Interlocked.Decrement(ref totalUndisposedAllocationCount);
MemoryReleased?.Invoke();
}
internal static void RaiseUndisposedMemoryResource(string allocationStackTrace) internal static void RaiseUndisposedMemoryResource(string allocationStackTrace)
{ {

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

@ -122,11 +122,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken) public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
Image<TPixel> image = null;
try try
{ {
int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette); int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette);
var image = new Image<TPixel>(this.Configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata); image = new Image<TPixel>(this.Configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata);
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer(); Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
@ -193,8 +194,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp
} }
catch (IndexOutOfRangeException e) catch (IndexOutOfRangeException e)
{ {
image?.Dispose();
throw new ImageFormatException("Bitmap does not have a valid format.", e); throw new ImageFormatException("Bitmap does not have a valid format.", e);
} }
catch
{
image?.Dispose();
throw;
}
} }
/// <inheritdoc /> /// <inheritdoc />
@ -323,12 +330,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref colors[colorIdx * 4])); color.FromBgr24(Unsafe.As<byte, Bgr24>(ref colors[colorIdx * 4]));
break; break;
case RleSkippedPixelHandling.Transparent: case RleSkippedPixelHandling.Transparent:
color.FromVector4(Vector4.Zero); color.FromScaledVector4(Vector4.Zero);
break; break;
// Default handling for skipped pixels is black (which is what System.Drawing is also doing). // Default handling for skipped pixels is black (which is what System.Drawing is also doing).
default: default:
color.FromVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f)); color.FromScaledVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f));
break; break;
} }
} }
@ -395,12 +402,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref bufferSpan[idx])); color.FromBgr24(Unsafe.As<byte, Bgr24>(ref bufferSpan[idx]));
break; break;
case RleSkippedPixelHandling.Transparent: case RleSkippedPixelHandling.Transparent:
color.FromVector4(Vector4.Zero); color.FromScaledVector4(Vector4.Zero);
break; break;
// Default handling for skipped pixels is black (which is what System.Drawing is also doing). // Default handling for skipped pixels is black (which is what System.Drawing is also doing).
default: default:
color.FromVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f)); color.FromScaledVector4(new Vector4(0.0f, 0.0f, 0.0f, 1.0f));
break; break;
} }
} }
@ -1127,7 +1134,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
g * invMaxValueGreen, g * invMaxValueGreen,
b * invMaxValueBlue, b * invMaxValueBlue,
alpha); alpha);
color.FromVector4(vector4); color.FromScaledVector4(vector4);
} }
else else
{ {

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

@ -221,7 +221,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary> /// </summary>
private void ReadGraphicalControlExtension() private void ReadGraphicalControlExtension()
{ {
this.stream.Read(this.buffer, 0, 6); int bytesRead = this.stream.Read(this.buffer, 0, 6);
if (bytesRead != 6)
{
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the graphic control extension");
}
this.graphicsControlExtension = GifGraphicControlExtension.Parse(this.buffer); this.graphicsControlExtension = GifGraphicControlExtension.Parse(this.buffer);
} }
@ -231,7 +235,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary> /// </summary>
private void ReadImageDescriptor() private void ReadImageDescriptor()
{ {
this.stream.Read(this.buffer, 0, 9); int bytesRead = this.stream.Read(this.buffer, 0, 9);
if (bytesRead != 9)
{
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the image descriptor");
}
this.imageDescriptor = GifImageDescriptor.Parse(this.buffer); this.imageDescriptor = GifImageDescriptor.Parse(this.buffer);
if (this.imageDescriptor.Height == 0 || this.imageDescriptor.Width == 0) if (this.imageDescriptor.Height == 0 || this.imageDescriptor.Width == 0)
@ -245,7 +253,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary> /// </summary>
private void ReadLogicalScreenDescriptor() private void ReadLogicalScreenDescriptor()
{ {
this.stream.Read(this.buffer, 0, 7); int bytesRead = this.stream.Read(this.buffer, 0, 7);
if (bytesRead != 7)
{
GifThrowHelper.ThrowInvalidImageContentException("Not enough data to read the logical screen descriptor");
}
this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer); this.logicalScreenDescriptor = GifLogicalScreenDescriptor.Parse(this.buffer);
} }

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

@ -95,7 +95,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
} }
} }
return this.pixelBuffer; var buffer = this.pixelBuffer;
this.pixelBuffer = null;
return buffer;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -210,6 +212,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.rgbBuffer?.Dispose(); this.rgbBuffer?.Dispose();
this.paddedProxyPixelRow?.Dispose(); this.paddedProxyPixelRow?.Dispose();
this.pixelBuffer?.Dispose();
} }
} }
} }

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

@ -228,11 +228,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
this.Metadata = new ImageMetadata(); this.Metadata = new ImageMetadata();
this.QuantizationTables = new Block8x8F[4]; this.QuantizationTables = new Block8x8F[4];
this.scanDecoder = huffmanScanDecoder; this.scanDecoder = huffmanScanDecoder;
if (tableBytes.Length < 4)
{
JpegThrowHelper.ThrowInvalidImageContentException("Not enough data to read marker");
}
using var ms = new MemoryStream(tableBytes); using var ms = new MemoryStream(tableBytes);
using var stream = new BufferedReadStream(this.Configuration, ms); using var stream = new BufferedReadStream(this.Configuration, ms);
// Check for the Start Of Image marker. // Check for the Start Of Image marker.
stream.Read(this.markerBuffer, 0, 2); int bytesRead = stream.Read(this.markerBuffer, 0, 2);
var fileMarker = new JpegFileMarker(this.markerBuffer[1], 0); var fileMarker = new JpegFileMarker(this.markerBuffer[1], 0);
if (fileMarker.Marker != JpegConstants.Markers.SOI) if (fileMarker.Marker != JpegConstants.Markers.SOI)
{ {
@ -240,16 +246,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
// Read next marker. // Read next marker.
stream.Read(this.markerBuffer, 0, 2); bytesRead = stream.Read(this.markerBuffer, 0, 2);
byte marker = this.markerBuffer[1]; fileMarker = new JpegFileMarker(this.markerBuffer[1], (int)stream.Position - 2);
fileMarker = new JpegFileMarker(marker, (int)stream.Position - 2);
while (fileMarker.Marker != JpegConstants.Markers.EOI || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid)) while (fileMarker.Marker != JpegConstants.Markers.EOI || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid))
{ {
if (!fileMarker.Invalid) if (!fileMarker.Invalid)
{ {
// Get the marker length. // Get the marker length.
int remaining = this.ReadUint16(stream) - 2; int markerContentByteSize = this.ReadUint16(stream) - 2;
// Check whether stream actually has enought bytes to read
// markerContentByteSize is always positive so we cast
// to uint to avoid sign extension
if (stream.RemainingBytes < (uint)markerContentByteSize)
{
JpegThrowHelper.ThrowNotEnoughBytesForMarker(fileMarker.Marker);
}
switch (fileMarker.Marker) switch (fileMarker.Marker)
{ {
@ -259,13 +272,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegConstants.Markers.RST7: case JpegConstants.Markers.RST7:
break; break;
case JpegConstants.Markers.DHT: case JpegConstants.Markers.DHT:
this.ProcessDefineHuffmanTablesMarker(stream, remaining); this.ProcessDefineHuffmanTablesMarker(stream, markerContentByteSize);
break; break;
case JpegConstants.Markers.DQT: case JpegConstants.Markers.DQT:
this.ProcessDefineQuantizationTablesMarker(stream, remaining); this.ProcessDefineQuantizationTablesMarker(stream, markerContentByteSize);
break; break;
case JpegConstants.Markers.DRI: case JpegConstants.Markers.DRI:
this.ProcessDefineRestartIntervalMarker(stream, remaining); this.ProcessDefineRestartIntervalMarker(stream, markerContentByteSize);
break; break;
case JpegConstants.Markers.EOI: case JpegConstants.Markers.EOI:
return; return;
@ -273,7 +286,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
} }
// Read next marker. // Read next marker.
stream.Read(this.markerBuffer, 0, 2); bytesRead = stream.Read(this.markerBuffer, 0, 2);
if (bytesRead != 2)
{
JpegThrowHelper.ThrowInvalidImageContentException("Not enough data to read marker");
}
fileMarker = new JpegFileMarker(this.markerBuffer[1], 0); fileMarker = new JpegFileMarker(this.markerBuffer[1], 0);
} }
} }
@ -315,14 +333,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (!fileMarker.Invalid) if (!fileMarker.Invalid)
{ {
// Get the marker length. // Get the marker length.
int remaining = this.ReadUint16(stream) - 2; int markerContentByteSize = this.ReadUint16(stream) - 2;
// Check whether stream actually has enought bytes to read
// markerContentByteSize is always positive so we cast
// to uint to avoid sign extension
if (stream.RemainingBytes < (uint)markerContentByteSize)
{
JpegThrowHelper.ThrowNotEnoughBytesForMarker(fileMarker.Marker);
}
switch (fileMarker.Marker) switch (fileMarker.Marker)
{ {
case JpegConstants.Markers.SOF0: case JpegConstants.Markers.SOF0:
case JpegConstants.Markers.SOF1: case JpegConstants.Markers.SOF1:
case JpegConstants.Markers.SOF2: case JpegConstants.Markers.SOF2:
this.ProcessStartOfFrameMarker(stream, remaining, fileMarker, metadataOnly); this.ProcessStartOfFrameMarker(stream, markerContentByteSize, fileMarker, metadataOnly);
break; break;
case JpegConstants.Markers.SOF5: case JpegConstants.Markers.SOF5:
@ -350,7 +376,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegConstants.Markers.SOS: case JpegConstants.Markers.SOS:
if (!metadataOnly) if (!metadataOnly)
{ {
this.ProcessStartOfScanMarker(stream, remaining); this.ProcessStartOfScanMarker(stream, markerContentByteSize);
break; break;
} }
else else
@ -364,41 +390,41 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (metadataOnly) if (metadataOnly)
{ {
stream.Skip(remaining); stream.Skip(markerContentByteSize);
} }
else else
{ {
this.ProcessDefineHuffmanTablesMarker(stream, remaining); this.ProcessDefineHuffmanTablesMarker(stream, markerContentByteSize);
} }
break; break;
case JpegConstants.Markers.DQT: case JpegConstants.Markers.DQT:
this.ProcessDefineQuantizationTablesMarker(stream, remaining); this.ProcessDefineQuantizationTablesMarker(stream, markerContentByteSize);
break; break;
case JpegConstants.Markers.DRI: case JpegConstants.Markers.DRI:
if (metadataOnly) if (metadataOnly)
{ {
stream.Skip(remaining); stream.Skip(markerContentByteSize);
} }
else else
{ {
this.ProcessDefineRestartIntervalMarker(stream, remaining); this.ProcessDefineRestartIntervalMarker(stream, markerContentByteSize);
} }
break; break;
case JpegConstants.Markers.APP0: case JpegConstants.Markers.APP0:
this.ProcessApplicationHeaderMarker(stream, remaining); this.ProcessApplicationHeaderMarker(stream, markerContentByteSize);
break; break;
case JpegConstants.Markers.APP1: case JpegConstants.Markers.APP1:
this.ProcessApp1Marker(stream, remaining); this.ProcessApp1Marker(stream, markerContentByteSize);
break; break;
case JpegConstants.Markers.APP2: case JpegConstants.Markers.APP2:
this.ProcessApp2Marker(stream, remaining); this.ProcessApp2Marker(stream, markerContentByteSize);
break; break;
case JpegConstants.Markers.APP3: case JpegConstants.Markers.APP3:
@ -411,20 +437,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegConstants.Markers.APP10: case JpegConstants.Markers.APP10:
case JpegConstants.Markers.APP11: case JpegConstants.Markers.APP11:
case JpegConstants.Markers.APP12: case JpegConstants.Markers.APP12:
stream.Skip(remaining); stream.Skip(markerContentByteSize);
break; break;
case JpegConstants.Markers.APP13: case JpegConstants.Markers.APP13:
this.ProcessApp13Marker(stream, remaining); this.ProcessApp13Marker(stream, markerContentByteSize);
break; break;
case JpegConstants.Markers.APP14: case JpegConstants.Markers.APP14:
this.ProcessApp14Marker(stream, remaining); this.ProcessApp14Marker(stream, markerContentByteSize);
break; break;
case JpegConstants.Markers.APP15: case JpegConstants.Markers.APP15:
case JpegConstants.Markers.COM: case JpegConstants.Markers.COM:
stream.Skip(remaining); stream.Skip(markerContentByteSize);
break; break;
case JpegConstants.Markers.DAC: case JpegConstants.Markers.DAC:
@ -722,7 +748,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker.Slice(0, ExifMarkerLength))) if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker.Slice(0, ExifMarkerLength)))
{ {
int remainingXmpMarkerBytes = XmpMarkerLength - ExifMarkerLength; const int remainingXmpMarkerBytes = XmpMarkerLength - ExifMarkerLength;
if (remaining < remainingXmpMarkerBytes || this.IgnoreMetadata)
{
// Skip the application header length.
stream.Skip(remaining);
return;
}
stream.Read(this.temp, ExifMarkerLength, remainingXmpMarkerBytes); stream.Read(this.temp, ExifMarkerLength, remainingXmpMarkerBytes);
remaining -= remainingXmpMarkerBytes; remaining -= remainingXmpMarkerBytes;
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker)) if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker))
@ -1260,7 +1293,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
int selectorsBytes = selectorsCount * 2; int selectorsBytes = selectorsCount * 2;
if (remaining != 4 + selectorsBytes) if (remaining != 4 + selectorsBytes)
{ {
JpegThrowHelper.ThrowBadMarker("SOS", remaining); JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.SOS), remaining);
} }
// selectorsCount*2 bytes: component index + huffman tables indices // selectorsCount*2 bytes: component index + huffman tables indices
@ -1312,8 +1345,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
component.ACHuffmanTableId = acTableIndex; component.ACHuffmanTableId = acTableIndex;
} }
// 3 bytes: Progressive scan decoding data // 3 bytes: Progressive scan decoding data.
stream.Read(this.temp, 0, 3); int bytesRead = stream.Read(this.temp, 0, 3);
if (bytesRead != 3)
{
JpegThrowHelper.ThrowInvalidImageContentException("Not enough data to read progressive scan decoding data");
}
int spectralStart = this.temp[0]; int spectralStart = this.temp[0];
this.scanDecoder.SpectralStart = spectralStart; this.scanDecoder.SpectralStart = spectralStart;
@ -1336,7 +1373,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private ushort ReadUint16(BufferedReadStream stream) private ushort ReadUint16(BufferedReadStream stream)
{ {
stream.Read(this.markerBuffer, 0, 2); int bytesRead = stream.Read(this.markerBuffer, 0, 2);
if (bytesRead != 2)
{
JpegThrowHelper.ThrowInvalidImageContentException("jpeg stream does not contain enough data, could not read ushort.");
}
return BinaryPrimitives.ReadUInt16BigEndian(this.markerBuffer); return BinaryPrimitives.ReadUInt16BigEndian(this.markerBuffer);
} }
} }

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

@ -25,6 +25,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
[MethodImpl(InliningOptions.ColdPath)] [MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadMarker(string marker, int length) => throw new InvalidImageContentException($"Marker {marker} has bad length {length}."); public static void ThrowBadMarker(string marker, int length) => throw new InvalidImageContentException($"Marker {marker} has bad length {length}.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNotEnoughBytesForMarker(byte marker) => throw new InvalidImageContentException($"Input stream does not have enough bytes to parse declared contents of the {marker:X2} marker.");
[MethodImpl(InliningOptions.ColdPath)] [MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadQuantizationTableIndex(int index) => throw new InvalidImageContentException($"Bad Quantization Table index {index}."); public static void ThrowBadQuantizationTableIndex(int index) => throw new InvalidImageContentException($"Bad Quantization Table index {index}.");

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

@ -227,10 +227,16 @@ namespace SixLabors.ImageSharp.Formats.Png
return image; return image;
} }
catch
{
image?.Dispose();
throw;
}
finally finally
{ {
this.scanline?.Dispose(); this.scanline?.Dispose();
this.previousScanline?.Dispose(); this.previousScanline?.Dispose();
this.nextChunk?.Data?.Dispose();
} }
} }
@ -472,6 +478,8 @@ namespace SixLabors.ImageSharp.Formats.Png
this.bytesPerSample = this.header.BitDepth / 8; this.bytesPerSample = this.header.BitDepth / 8;
} }
this.previousScanline?.Dispose();
this.scanline?.Dispose();
this.previousScanline = this.memoryAllocator.Allocate<byte>(this.bytesPerScanline, AllocationOptions.Clean); this.previousScanline = this.memoryAllocator.Allocate<byte>(this.bytesPerScanline, AllocationOptions.Clean);
this.scanline = this.Configuration.MemoryAllocator.Allocate<byte>(this.bytesPerScanline, AllocationOptions.Clean); this.scanline = this.Configuration.MemoryAllocator.Allocate<byte>(this.bytesPerScanline, AllocationOptions.Clean);
} }
@ -1359,6 +1367,7 @@ namespace SixLabors.ImageSharp.Formats.Png
{ {
if (chunk.Type == PngChunkType.Data) if (chunk.Type == PngChunkType.Data)
{ {
chunk.Data?.Dispose();
return chunk.Length; return chunk.Length;
} }
@ -1453,6 +1462,9 @@ namespace SixLabors.ImageSharp.Formats.Png
if (validCrc != inputCrc) if (validCrc != inputCrc)
{ {
string chunkTypeName = Encoding.ASCII.GetString(chunkType); string chunkTypeName = Encoding.ASCII.GetString(chunkType);
// ensure when throwing we dispose the data back to the memory allocator
chunk.Data?.Dispose();
PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName); PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName);
} }
} }

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

@ -65,7 +65,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
jpegDecoder.ParseStream(stream, scanDecoderGray, CancellationToken.None); jpegDecoder.ParseStream(stream, scanDecoderGray, CancellationToken.None);
// TODO: Should we pass through the CancellationToken from the tiff decoder? // TODO: Should we pass through the CancellationToken from the tiff decoder?
CopyImageBytesToBuffer(buffer, spectralConverterGray.GetPixelBuffer(CancellationToken.None)); using var decompressedBuffer = spectralConverterGray.GetPixelBuffer(CancellationToken.None);
CopyImageBytesToBuffer(buffer, decompressedBuffer);
break; break;
} }
@ -81,7 +82,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
jpegDecoder.ParseStream(stream, scanDecoder, CancellationToken.None); jpegDecoder.ParseStream(stream, scanDecoder, CancellationToken.None);
// TODO: Should we pass through the CancellationToken from the tiff decoder? // TODO: Should we pass through the CancellationToken from the tiff decoder?
CopyImageBytesToBuffer(buffer, spectralConverter.GetPixelBuffer(CancellationToken.None)); using var decompressedBuffer = spectralConverter.GetPixelBuffer(CancellationToken.None);
CopyImageBytesToBuffer(buffer, decompressedBuffer);
break; break;
} }

42
src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs

@ -23,11 +23,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants
/// </summary> /// </summary>
Ccitt1D = 2, Ccitt1D = 2,
/// <summary>
/// PackBits compression
/// </summary>
PackBits = 32773,
/// <summary> /// <summary>
/// T4-encoding: CCITT T.4 bi-level encoding (see Section 11 of the TIFF 6.0 specification). /// T4-encoding: CCITT T.4 bi-level encoding (see Section 11 of the TIFF 6.0 specification).
/// </summary> /// </summary>
@ -65,27 +60,48 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants
Deflate = 8, Deflate = 8,
/// <summary> /// <summary>
/// Deflate compression - old. /// ITU-T Rec. T.82 coding, applying ITU-T Rec. T.85 (JBIG) (see RFC2301).
/// ///
/// Note: The TIFF encoder does not support this compression and will default to use no compression instead, /// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead,
/// if this is chosen. /// if this is chosen.
/// </summary> /// </summary>
OldDeflate = 32946, ItuTRecT82 = 9,
/// <summary> /// <summary>
/// ITU-T Rec. T.82 coding, applying ITU-T Rec. T.85 (JBIG) (see RFC2301). /// ITU-T Rec. T.43 representation, using ITU-T Rec. T.82 (JBIG) (see RFC2301).
/// ///
/// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead, /// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead,
/// if this is chosen. /// if this is chosen.
/// </summary> /// </summary>
ItuTRecT82 = 9, ItuTRecT43 = 10,
/// <summary> /// <summary>
/// ITU-T Rec. T.43 representation, using ITU-T Rec. T.82 (JBIG) (see RFC2301). /// NeXT 2-bit Grey Scale compression algorithm.
/// ///
/// Note: The TIFF encoder does not yet support this compression and will default to use no compression instead, /// Note: The TIFF encoder does not support this compression and will default to use no compression instead,
/// if this is chosen.
/// </summary>
NeXT = 32766,
/// <summary>
/// PackBits compression.
/// </summary>
PackBits = 32773,
/// <summary>
/// ThunderScan 4-bit compression.
///
/// Note: The TIFF encoder does not support this compression and will default to use no compression instead,
/// if this is chosen. /// if this is chosen.
/// </summary> /// </summary>
ItuTRecT43 = 10 ThunderScan = 32809,
/// <summary>
/// Deflate compression - old.
///
/// Note: The TIFF encoder does not support this compression and will default to use no compression instead,
/// if this is chosen.
/// </summary>
OldDeflate = 32946,
} }
} }

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs

@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
L16 l16 = TiffUtils.L16Default; L16 l16 = TiffUtils.L16Default;
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
int offset = 0; int offset = 0;
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs

@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
byte[] buffer = new byte[4]; byte[] buffer = new byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0; int bufferStartIdx = this.isBigEndian ? 1 : 0;

6
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
byte[] buffer = new byte[4]; byte[] buffer = new byte[4];
int offset = 0; int offset = 0;
@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4; offset += 4;
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f); var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
pixelRow[x] = color; pixelRow[x] = color;
} }
} }
@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4; offset += 4;
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f); var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
pixelRow[x] = color; pixelRow[x] = color;
} }
} }

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs

@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
int offset = 0; int offset = 0;
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)

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

@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
int value = bitReader.ReadBits(this.bitsPerSample0); int value = bitReader.ReadBits(this.bitsPerSample0);
float intensity = value / this.factor; float intensity = value / this.factor;
color.FromVector4(new Vector4(intensity, intensity, intensity, 1.0f)); color.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1.0f));
pixelRow[x] = color; pixelRow[x] = color;
} }

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

@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
float r = colorMap[rOffset + i] / 65535F; float r = colorMap[rOffset + i] / 65535F;
float g = colorMap[gOffset + i] / 65535F; float g = colorMap[gOffset + i] / 65535F;
float b = colorMap[bOffset + i] / 65535F; float b = colorMap[bOffset + i] / 65535F;
palette[i].FromVector4(new Vector4(r, g, b, 1.0f)); palette[i].FromScaledVector4(new Vector4(r, g, b, 1.0f));
} }
return palette; return palette;

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

@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
Rgba64 rgba = TiffUtils.Rgba64Default; Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
int offset = 0; int offset = 0;
@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
ulong b = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); ulong b = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
offset += 2; offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color); pixelRow[x] = TiffUtils.ColorFromRgb64(rgba, r, g, b, color);
} }
} }
else else

6
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs

@ -30,7 +30,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
Rgba64 rgba = TiffUtils.Rgba64Default; Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
Span<byte> redData = data[0].GetSpan(); Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan(); Span<byte> greenData = data[1].GetSpan();
@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 2; offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color); pixelRow[x] = TiffUtils.ColorFromRgb64(rgba, r, g, b, color);
} }
} }
else else
@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 2; offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color); pixelRow[x] = TiffUtils.ColorFromRgb64(rgba, r, g, b, color);
} }
} }
} }

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs

@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
int offset = 0; int offset = 0;
Span<byte> buffer = stackalloc byte[4]; Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0; int bufferStartIdx = this.isBigEndian ? 1 : 0;

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
Span<byte> buffer = stackalloc byte[4]; Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0; int bufferStartIdx = this.isBigEndian ? 1 : 0;

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs

@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
int offset = 0; int offset = 0;
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
Span<byte> redData = data[0].GetSpan(); Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan(); Span<byte> greenData = data[1].GetSpan();

6
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
int offset = 0; int offset = 0;
byte[] buffer = new byte[4]; byte[] buffer = new byte[4];
@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4; offset += 4;
var colorVector = new Vector4(r, g, b, 1.0f); var colorVector = new Vector4(r, g, b, 1.0f);
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
pixelRow[x] = color; pixelRow[x] = color;
} }
} }
@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4; offset += 4;
var colorVector = new Vector4(r, g, b, 1.0f); var colorVector = new Vector4(r, g, b, 1.0f);
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
pixelRow[x] = color; pixelRow[x] = color;
} }
} }

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

@ -65,7 +65,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
float g = gBitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; float g = gBitReader.ReadBits(this.bitsPerSampleG) / this.gFactor;
float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor;
color.FromVector4(new Vector4(r, g, b, 1.0f)); color.FromScaledVector4(new Vector4(r, g, b, 1.0f));
pixelRow[x] = color; pixelRow[x] = color;
} }

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

@ -54,7 +54,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
float g = bitReader.ReadBits(this.bitsPerSampleG) / this.gFactor; float g = bitReader.ReadBits(this.bitsPerSampleG) / this.gFactor;
float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor;
color.FromVector4(new Vector4(r, g, b, 1.0f)); color.FromScaledVector4(new Vector4(r, g, b, 1.0f));
pixelRow[x] = color; pixelRow[x] = color;
} }

27
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils; using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -18,15 +20,23 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
private readonly Configuration configuration; private readonly Configuration configuration;
private readonly MemoryAllocator memoryAllocator;
private readonly TiffExtraSampleType? extraSamplesType;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Rgba16161616TiffColor{TPixel}" /> class. /// Initializes a new instance of the <see cref="Rgba16161616TiffColor{TPixel}" /> class.
/// </summary> /// </summary>
/// <param name="configuration">The configuration.</param> /// <param name="configuration">The configuration.</param>
/// <param name="memoryAllocator">The memory allocator.</param>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param> /// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgba16161616TiffColor(Configuration configuration, bool isBigEndian) /// <param name="extraSamplesType">The type of the extra samples.</param>
public Rgba16161616TiffColor(Configuration configuration, MemoryAllocator memoryAllocator, TiffExtraSampleType? extraSamplesType, bool isBigEndian)
{ {
this.configuration = configuration; this.configuration = configuration;
this.isBigEndian = isBigEndian; this.isBigEndian = isBigEndian;
this.memoryAllocator = memoryAllocator;
this.extraSamplesType = extraSamplesType;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -36,10 +46,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
Rgba64 rgba = TiffUtils.Rgba64Default; Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0; int offset = 0;
using IMemoryOwner<Vector4> vectors = hasAssociatedAlpha ? this.memoryAllocator.Allocate<Vector4>(width) : null;
Span<Vector4> vectorsSpan = hasAssociatedAlpha ? vectors.GetSpan() : Span<Vector4>.Empty;
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)
{ {
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
@ -57,7 +70,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
ulong a = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2)); ulong a = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
offset += 2; offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color); pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorFromRgba64Premultiplied(rgba, r, g, b, a, color) :
TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color);
} }
} }
else else
@ -69,6 +84,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
pixelRow, pixelRow,
pixelRow.Length); pixelRow.Length);
if (hasAssociatedAlpha)
{
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, pixelRow, vectorsSpan);
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, vectorsSpan, pixelRow, PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale);
}
offset += byteCount; offset += byteCount;
} }
} }

24
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs

@ -17,11 +17,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{ {
private readonly bool isBigEndian; private readonly bool isBigEndian;
private readonly TiffExtraSampleType? extraSamplesType;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Rgba16PlanarTiffColor{TPixel}" /> class. /// Initializes a new instance of the <see cref="Rgba16PlanarTiffColor{TPixel}" /> class.
/// </summary> /// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param> /// <param name="extraSamplesType">The extra samples type.</param>
public Rgba16PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; /// <param name="isBigEndian">If set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgba16PlanarTiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian)
{
this.extraSamplesType = extraSamplesType;
this.isBigEndian = isBigEndian;
}
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
@ -30,13 +37,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
Rgba64 rgba = TiffUtils.Rgba64Default; Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
Span<byte> redData = data[0].GetSpan(); Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan(); Span<byte> greenData = data[1].GetSpan();
Span<byte> blueData = data[2].GetSpan(); Span<byte> blueData = data[2].GetSpan();
Span<byte> alphaData = data[3].GetSpan(); Span<byte> alphaData = data[3].GetSpan();
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0; int offset = 0;
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)
{ {
@ -52,7 +60,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 2; offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color); pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorFromRgba64Premultiplied(rgba, r, g, b, a, color) :
TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color);
} }
} }
else else
@ -62,11 +72,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
ulong r = TiffUtils.ConvertToUShortLittleEndian(redData.Slice(offset, 2)); ulong r = TiffUtils.ConvertToUShortLittleEndian(redData.Slice(offset, 2));
ulong g = TiffUtils.ConvertToUShortLittleEndian(greenData.Slice(offset, 2)); ulong g = TiffUtils.ConvertToUShortLittleEndian(greenData.Slice(offset, 2));
ulong b = TiffUtils.ConvertToUShortLittleEndian(blueData.Slice(offset, 2)); ulong b = TiffUtils.ConvertToUShortLittleEndian(blueData.Slice(offset, 2));
ulong a = TiffUtils.ConvertToUShortBigEndian(alphaData.Slice(offset, 2)); ulong a = TiffUtils.ConvertToUShortLittleEndian(alphaData.Slice(offset, 2));
offset += 2; offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color); pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorFromRgba64Premultiplied(rgba, r, g, b, a, color) :
TiffUtils.ColorFromRgba64(rgba, r, g, b, a, color);
} }
} }
} }

22
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs

@ -16,11 +16,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{ {
private readonly bool isBigEndian; private readonly bool isBigEndian;
private readonly TiffExtraSampleType? extraSamplesType;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Rgba24242424TiffColor{TPixel}" /> class. /// Initializes a new instance of the <see cref="Rgba24242424TiffColor{TPixel}" /> class.
/// </summary> /// </summary>
/// <param name="extraSamplesType">The type of the extra samples.</param>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param> /// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgba24242424TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; public Rgba24242424TiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian)
{
this.extraSamplesType = extraSamplesType;
this.isBigEndian = isBigEndian;
}
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
@ -28,8 +35,11 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0; int offset = 0;
Span<byte> buffer = stackalloc byte[4]; Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0; int bufferStartIdx = this.isBigEndian ? 1 : 0;
@ -58,7 +68,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
ulong a = TiffUtils.ConvertToUIntBigEndian(buffer); ulong a = TiffUtils.ConvertToUIntBigEndian(buffer);
offset += 3; offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorScaleTo24BitPremultiplied(r, g, b, a, color) :
TiffUtils.ColorScaleTo24Bit(r, g, b, a, color);
} }
} }
else else
@ -81,7 +93,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
ulong a = TiffUtils.ConvertToUIntLittleEndian(buffer); ulong a = TiffUtils.ConvertToUIntLittleEndian(buffer);
offset += 3; offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorScaleTo24BitPremultiplied(r, g, b, a, color) :
TiffUtils.ColorScaleTo24Bit(r, g, b, a, color);
} }
} }
} }

20
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs

@ -17,11 +17,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{ {
private readonly bool isBigEndian; private readonly bool isBigEndian;
private readonly TiffExtraSampleType? extraSamplesType;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Rgba24PlanarTiffColor{TPixel}" /> class. /// Initializes a new instance of the <see cref="Rgba24PlanarTiffColor{TPixel}" /> class.
/// </summary> /// </summary>
/// <param name="extraSamplesType">The extra samples type.</param>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param> /// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgba24PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; public Rgba24PlanarTiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian)
{
this.extraSamplesType = extraSamplesType;
this.isBigEndian = isBigEndian;
}
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
@ -29,7 +36,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
Span<byte> buffer = stackalloc byte[4]; Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0; int bufferStartIdx = this.isBigEndian ? 1 : 0;
@ -39,6 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
Span<byte> alphaData = data[3].GetSpan(); Span<byte> alphaData = data[3].GetSpan();
Span<byte> bufferSpan = buffer.Slice(bufferStartIdx); Span<byte> bufferSpan = buffer.Slice(bufferStartIdx);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0; int offset = 0;
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)
{ {
@ -58,7 +66,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 3; offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorScaleTo24BitPremultiplied(r, g, b, a, color) :
TiffUtils.ColorScaleTo24Bit(r, g, b, a, color);
} }
} }
else else
@ -76,7 +86,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 3; offset += 3;
pixelRow[x] = TiffUtils.ColorScaleTo24Bit(r, g, b, a, color); pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorScaleTo24BitPremultiplied(r, g, b, a, color) :
TiffUtils.ColorScaleTo24Bit(r, g, b, a, color);
} }
} }
} }

21
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs

@ -16,11 +16,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{ {
private readonly bool isBigEndian; private readonly bool isBigEndian;
private readonly TiffExtraSampleType? extraSamplesType;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Rgba32323232TiffColor{TPixel}" /> class. /// Initializes a new instance of the <see cref="Rgba32323232TiffColor{TPixel}" /> class.
/// </summary> /// </summary>
/// <param name="extraSamplesType">The type of the extra samples.</param>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param> /// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgba32323232TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; public Rgba32323232TiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian)
{
this.extraSamplesType = extraSamplesType;
this.isBigEndian = isBigEndian;
}
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
@ -28,7 +35,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0; int offset = 0;
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)
@ -51,7 +60,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
ulong a = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4)); ulong a = TiffUtils.ConvertToUIntBigEndian(data.Slice(offset, 4));
offset += 4; offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorScaleTo32BitPremultiplied(r, g, b, a, color) :
TiffUtils.ColorScaleTo32Bit(r, g, b, a, color);
} }
} }
else else
@ -70,7 +81,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
ulong a = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4)); ulong a = TiffUtils.ConvertToUIntLittleEndian(data.Slice(offset, 4));
offset += 4; offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorScaleTo32BitPremultiplied(r, g, b, a, color) :
TiffUtils.ColorScaleTo32Bit(r, g, b, a, color);
} }
} }
} }

20
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs

@ -17,11 +17,18 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{ {
private readonly bool isBigEndian; private readonly bool isBigEndian;
private readonly TiffExtraSampleType? extraSamplesType;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Rgba32PlanarTiffColor{TPixel}" /> class. /// Initializes a new instance of the <see cref="Rgba32PlanarTiffColor{TPixel}" /> class.
/// </summary> /// </summary>
/// <param name="extraSamplesType">The extra samples type.</param>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param> /// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgba32PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian; public Rgba32PlanarTiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian)
{
this.extraSamplesType = extraSamplesType;
this.isBigEndian = isBigEndian;
}
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
@ -29,13 +36,14 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
Span<byte> redData = data[0].GetSpan(); Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan(); Span<byte> greenData = data[1].GetSpan();
Span<byte> blueData = data[2].GetSpan(); Span<byte> blueData = data[2].GetSpan();
Span<byte> alphaData = data[3].GetSpan(); Span<byte> alphaData = data[3].GetSpan();
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0; int offset = 0;
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)
{ {
@ -51,7 +59,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4; offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorScaleTo32BitPremultiplied(r, g, b, a, color) :
TiffUtils.ColorScaleTo32Bit(r, g, b, a, color);
} }
} }
else else
@ -65,7 +75,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4; offset += 4;
pixelRow[x] = TiffUtils.ColorScaleTo32Bit(r, g, b, a, color); pixelRow[x] = hasAssociatedAlpha ?
TiffUtils.ColorScaleTo32BitPremultiplied(r, g, b, a, color) :
TiffUtils.ColorScaleTo32Bit(r, g, b, a, color);
} }
} }
} }

25
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs

@ -2,6 +2,9 @@
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -15,13 +18,27 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{ {
private readonly Configuration configuration; private readonly Configuration configuration;
public Rgba8888TiffColor(Configuration configuration) => this.configuration = configuration; private readonly MemoryAllocator memoryAllocator;
private readonly TiffExtraSampleType? extraSamplesType;
public Rgba8888TiffColor(Configuration configuration, MemoryAllocator memoryAllocator, TiffExtraSampleType? extraSamplesType)
{
this.configuration = configuration;
this.memoryAllocator = memoryAllocator;
this.extraSamplesType = extraSamplesType;
}
/// <inheritdoc/> /// <inheritdoc/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{ {
int offset = 0; int offset = 0;
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
var color = default(TPixel);
color.FromScaledVector4(TiffUtils.Vector4Default);
using IMemoryOwner<Vector4> vectors = hasAssociatedAlpha ? this.memoryAllocator.Allocate<Vector4>(width) : null;
Span<Vector4> vectorsSpan = hasAssociatedAlpha ? vectors.GetSpan() : Span<Vector4>.Empty;
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)
{ {
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
@ -32,6 +49,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
pixelRow, pixelRow,
pixelRow.Length); pixelRow.Length);
if (hasAssociatedAlpha)
{
PixelOperations<TPixel>.Instance.ToVector4(this.configuration, pixelRow, vectorsSpan);
PixelOperations<TPixel>.Instance.FromVector4Destructive(this.configuration, vectorsSpan, pixelRow, PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale);
}
offset += byteCount; offset += byteCount;
} }
} }

6
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
int offset = 0; int offset = 0;
byte[] buffer = new byte[4]; byte[] buffer = new byte[4];
@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4; offset += 4;
var colorVector = new Vector4(r, g, b, a); var colorVector = new Vector4(r, g, b, a);
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
pixelRow[x] = color; pixelRow[x] = color;
} }
} }
@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4; offset += 4;
var colorVector = new Vector4(r, g, b, a); var colorVector = new Vector4(r, g, b, a);
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
pixelRow[x] = color; pixelRow[x] = color;
} }
} }

18
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs

@ -32,7 +32,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
private readonly ushort bitsPerSampleA; private readonly ushort bitsPerSampleA;
public RgbaPlanarTiffColor(TiffBitsPerSample bitsPerSample) private readonly TiffExtraSampleType? extraSampleType;
public RgbaPlanarTiffColor(TiffExtraSampleType? extraSampleType, TiffBitsPerSample bitsPerSample)
{ {
this.bitsPerSampleR = bitsPerSample.Channel0; this.bitsPerSampleR = bitsPerSample.Channel0;
this.bitsPerSampleG = bitsPerSample.Channel1; this.bitsPerSampleG = bitsPerSample.Channel1;
@ -43,6 +45,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; this.gFactor = (1 << this.bitsPerSampleG) - 1.0f;
this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; this.bFactor = (1 << this.bitsPerSampleB) - 1.0f;
this.aFactor = (1 << this.bitsPerSampleA) - 1.0f; this.aFactor = (1 << this.bitsPerSampleA) - 1.0f;
this.extraSampleType = extraSampleType;
} }
/// <summary> /// <summary>
@ -57,6 +61,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height) public override void Decode(IMemoryOwner<byte>[] data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{ {
var color = default(TPixel); var color = default(TPixel);
bool hasAssociatedAlpha = this.extraSampleType.HasValue && this.extraSampleType == TiffExtraSampleType.AssociatedAlphaData;
var rBitReader = new BitReader(data[0].GetSpan()); var rBitReader = new BitReader(data[0].GetSpan());
var gBitReader = new BitReader(data[1].GetSpan()); var gBitReader = new BitReader(data[1].GetSpan());
@ -73,7 +78,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; float b = bBitReader.ReadBits(this.bitsPerSampleB) / this.bFactor;
float a = aBitReader.ReadBits(this.bitsPerSampleA) / this.aFactor; float a = aBitReader.ReadBits(this.bitsPerSampleA) / this.aFactor;
color.FromVector4(new Vector4(r, g, b, a)); var vec = new Vector4(r, g, b, a);
if (hasAssociatedAlpha)
{
color = TiffUtils.UnPremultiply(ref vec, color);
}
else
{
color.FromScaledVector4(vec);
}
pixelRow[x] = color; pixelRow[x] = color;
} }

20
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs

@ -31,7 +31,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
private readonly ushort bitsPerSampleA; private readonly ushort bitsPerSampleA;
public RgbaTiffColor(TiffBitsPerSample bitsPerSample) private readonly TiffExtraSampleType? extraSamplesType;
public RgbaTiffColor(TiffExtraSampleType? extraSampleType, TiffBitsPerSample bitsPerSample)
{ {
this.bitsPerSampleR = bitsPerSample.Channel0; this.bitsPerSampleR = bitsPerSample.Channel0;
this.bitsPerSampleG = bitsPerSample.Channel1; this.bitsPerSampleG = bitsPerSample.Channel1;
@ -42,6 +44,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
this.gFactor = (1 << this.bitsPerSampleG) - 1.0f; this.gFactor = (1 << this.bitsPerSampleG) - 1.0f;
this.bFactor = (1 << this.bitsPerSampleB) - 1.0f; this.bFactor = (1 << this.bitsPerSampleB) - 1.0f;
this.aFactor = (1 << this.bitsPerSampleA) - 1.0f; this.aFactor = (1 << this.bitsPerSampleA) - 1.0f;
this.extraSamplesType = extraSampleType;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -51,6 +55,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
var bitReader = new BitReader(data); var bitReader = new BitReader(data);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)
{ {
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width); Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
@ -61,8 +67,16 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor; float b = bitReader.ReadBits(this.bitsPerSampleB) / this.bFactor;
float a = bitReader.ReadBits(this.bitsPerSampleB) / this.aFactor; float a = bitReader.ReadBits(this.bitsPerSampleB) / this.aFactor;
color.FromVector4(new Vector4(r, g, b, a)); var vec = new Vector4(r, g, b, a);
pixelRow[x] = color; if (hasAssociatedAlpha)
{
pixelRow[x] = TiffUtils.UnPremultiply(ref vec, color);
}
else
{
color.FromScaledVector4(vec);
pixelRow[x] = color;
}
} }
bitReader.NextRow(); bitReader.NextRow();

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

@ -14,6 +14,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
MemoryAllocator memoryAllocator, MemoryAllocator memoryAllocator,
TiffColorType colorType, TiffColorType colorType,
TiffBitsPerSample bitsPerSample, TiffBitsPerSample bitsPerSample,
TiffExtraSampleType? extraSampleType,
ushort[] colorMap, ushort[] colorMap,
Rational[] referenceBlackAndWhite, Rational[] referenceBlackAndWhite,
Rational[] ycbcrCoefficients, Rational[] ycbcrCoefficients,
@ -125,7 +126,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 2, && bitsPerSample.Channel0 == 2,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample); return new RgbaTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.Rgb333: case TiffColorType.Rgb333:
DebugGuard.IsTrue( DebugGuard.IsTrue(
@ -146,7 +147,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 3, && bitsPerSample.Channel0 == 3,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample); return new RgbaTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.Rgb444: case TiffColorType.Rgb444:
DebugGuard.IsTrue( DebugGuard.IsTrue(
@ -167,7 +168,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 4, && bitsPerSample.Channel0 == 4,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample); return new RgbaTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.Rgb555: case TiffColorType.Rgb555:
DebugGuard.IsTrue( DebugGuard.IsTrue(
@ -188,7 +189,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 5, && bitsPerSample.Channel0 == 5,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample); return new RgbaTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.Rgb666: case TiffColorType.Rgb666:
DebugGuard.IsTrue( DebugGuard.IsTrue(
@ -209,7 +210,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 6, && bitsPerSample.Channel0 == 6,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample); return new RgbaTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.Rgb888: case TiffColorType.Rgb888:
DebugGuard.IsTrue( DebugGuard.IsTrue(
@ -230,7 +231,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 8, && bitsPerSample.Channel0 == 8,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgba8888TiffColor<TPixel>(configuration); return new Rgba8888TiffColor<TPixel>(configuration, memoryAllocator, extraSampleType);
case TiffColorType.Rgb101010: case TiffColorType.Rgb101010:
DebugGuard.IsTrue( DebugGuard.IsTrue(
@ -251,7 +252,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 10, && bitsPerSample.Channel0 == 10,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample); return new RgbaTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.Rgb121212: case TiffColorType.Rgb121212:
DebugGuard.IsTrue( DebugGuard.IsTrue(
@ -272,7 +273,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 12, && bitsPerSample.Channel0 == 12,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample); return new RgbaTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.Rgb141414: case TiffColorType.Rgb141414:
DebugGuard.IsTrue( DebugGuard.IsTrue(
@ -293,7 +294,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 14, && bitsPerSample.Channel0 == 14,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample); return new RgbaTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.Rgb161616: case TiffColorType.Rgb161616:
DebugGuard.IsTrue( DebugGuard.IsTrue(
@ -314,7 +315,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 16, && bitsPerSample.Channel0 == 16,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgba16161616TiffColor<TPixel>(configuration, isBigEndian: byteOrder == ByteOrder.BigEndian); return new Rgba16161616TiffColor<TPixel>(configuration, memoryAllocator, extraSampleType, isBigEndian: byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgb242424: case TiffColorType.Rgb242424:
DebugGuard.IsTrue( DebugGuard.IsTrue(
@ -335,7 +336,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 24, && bitsPerSample.Channel0 == 24,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgba24242424TiffColor<TPixel>(isBigEndian: byteOrder == ByteOrder.BigEndian); return new Rgba24242424TiffColor<TPixel>(extraSampleType, isBigEndian: byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgb323232: case TiffColorType.Rgb323232:
DebugGuard.IsTrue( DebugGuard.IsTrue(
@ -356,7 +357,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 32, && bitsPerSample.Channel0 == 32,
"bitsPerSample"); "bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgba32323232TiffColor<TPixel>(isBigEndian: byteOrder == ByteOrder.BigEndian); return new Rgba32323232TiffColor<TPixel>(extraSampleType, isBigEndian: byteOrder == ByteOrder.BigEndian);
case TiffColorType.RgbFloat323232: case TiffColorType.RgbFloat323232:
DebugGuard.IsTrue( DebugGuard.IsTrue(
@ -394,6 +395,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
public static TiffBasePlanarColorDecoder<TPixel> CreatePlanar( public static TiffBasePlanarColorDecoder<TPixel> CreatePlanar(
TiffColorType colorType, TiffColorType colorType,
TiffBitsPerSample bitsPerSample, TiffBitsPerSample bitsPerSample,
TiffExtraSampleType? extraSampleType,
ushort[] colorMap, ushort[] colorMap,
Rational[] referenceBlackAndWhite, Rational[] referenceBlackAndWhite,
Rational[] ycbcrCoefficients, Rational[] ycbcrCoefficients,
@ -408,7 +410,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
case TiffColorType.Rgba8888Planar: case TiffColorType.Rgba8888Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaPlanarTiffColor<TPixel>(bitsPerSample); return new RgbaPlanarTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.YCbCrPlanar: case TiffColorType.YCbCrPlanar:
return new YCbCrPlanarTiffColor<TPixel>(referenceBlackAndWhite, ycbcrCoefficients, ycbcrSubSampling); return new YCbCrPlanarTiffColor<TPixel>(referenceBlackAndWhite, ycbcrCoefficients, ycbcrSubSampling);
@ -419,7 +421,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
case TiffColorType.Rgba16161616Planar: case TiffColorType.Rgba16161616Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgba16PlanarTiffColor<TPixel>(byteOrder == ByteOrder.BigEndian); return new Rgba16PlanarTiffColor<TPixel>(extraSampleType, byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgb242424Planar: case TiffColorType.Rgb242424Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
@ -427,7 +429,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
case TiffColorType.Rgba24242424Planar: case TiffColorType.Rgba24242424Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgba24PlanarTiffColor<TPixel>(byteOrder == ByteOrder.BigEndian); return new Rgba24PlanarTiffColor<TPixel>(extraSampleType, byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgb323232Planar: case TiffColorType.Rgb323232Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
@ -435,7 +437,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
case TiffColorType.Rgba32323232Planar: case TiffColorType.Rgba32323232Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap"); DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgba32PlanarTiffColor<TPixel>(byteOrder == ByteOrder.BigEndian); return new Rgba32PlanarTiffColor<TPixel>(extraSampleType, byteOrder == ByteOrder.BigEndian);
default: default:
throw TiffThrowHelper.InvalidColorType(colorType.ToString()); throw TiffThrowHelper.InvalidColorType(colorType.ToString());

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
L16 l16 = TiffUtils.L16Default; L16 l16 = TiffUtils.L16Default;
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
int offset = 0; int offset = 0;
for (int y = top; y < top + height; y++) for (int y = top; y < top + height; y++)

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs

@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
byte[] buffer = new byte[4]; byte[] buffer = new byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0; int bufferStartIdx = this.isBigEndian ? 1 : 0;
const uint maxValue = 0xFFFFFF; const uint maxValue = 0xFFFFFF;

6
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs

@ -29,7 +29,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
byte[] buffer = new byte[4]; byte[] buffer = new byte[4];
int offset = 0; int offset = 0;
@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4; offset += 4;
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f); var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
pixelRow[x] = color; pixelRow[x] = color;
} }
} }
@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4; offset += 4;
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f); var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
pixelRow[x] = color; pixelRow[x] = color;
} }
} }

2
src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs

@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
// Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those, // Note: due to an issue with netcore 2.1 and default values and unpredictable behavior with those,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623 // we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel); var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default); color.FromScaledVector4(TiffUtils.Vector4Default);
const uint maxValue = 0xFFFFFFFF; const uint maxValue = 0xFFFFFFFF;
int offset = 0; int offset = 0;

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

@ -40,7 +40,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
int value = bitReader.ReadBits(this.bitsPerSample0); int value = bitReader.ReadBits(this.bitsPerSample0);
float intensity = 1.0f - (value / this.factor); float intensity = 1.0f - (value / this.factor);
color.FromVector4(new Vector4(intensity, intensity, intensity, 1.0f)); color.FromScaledVector4(new Vector4(intensity, intensity, intensity, 1.0f));
pixelRow[x] = color; pixelRow[x] = color;
} }

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

@ -28,15 +28,6 @@
- The Decoder currently only supports decoding multiframe images, which have the same dimensions. - The Decoder currently only supports decoding multiframe images, which have the same dimensions.
- Some compression formats are not yet supported. See the list below. - Some compression formats are not yet supported. See the list below.
### Deviations from the TIFF spec (to be fixed)
- Decoder
- A Baseline TIFF reader must skip over extra components (e.g. RGB with 4 samples per pixels)
- NB: Need to handle this for both planar and chunky data
- If the SampleFormat field is present and not 1 - fail gracefully if you cannot handle this
- Compression=None should treat 16/32-BitsPerSample for all samples as SHORT/LONG (for byte order and padding rows)
- Check Planar format data - is this encoded as strips in order RGBRGBRGB or RRRGGGBBB?
### Compression Formats ### Compression Formats
| |Encoder|Decoder|Comments | | |Encoder|Decoder|Comments |
@ -87,7 +78,7 @@
|Model | Y | Y | | |Model | Y | Y | |
|StripOffsets | Y | Y | | |StripOffsets | Y | Y | |
|Orientation | | - | Ignore. Many readers ignore this tag. | |Orientation | | - | Ignore. Many readers ignore this tag. |
|SamplesPerPixel | Y | - | Currently ignored, as can be inferred from count of BitsPerSample | |SamplesPerPixel | Y | - | Currently ignored, as can be inferred from count of BitsPerSample. |
|RowsPerStrip | Y | Y | | |RowsPerStrip | Y | Y | |
|StripByteCounts | Y | Y | | |StripByteCounts | Y | Y | |
|MinSampleValue | | | | |MinSampleValue | | | |
@ -105,7 +96,7 @@
|Artist | Y | Y | | |Artist | Y | Y | |
|HostComputer | Y | Y | | |HostComputer | Y | Y | |
|ColorMap | Y | Y | | |ColorMap | Y | Y | |
|ExtraSamples | | (Y) | Only UnassociatedAlphaData is supported so far | |ExtraSamples | | Y | Unspecified alpha data is not supported. |
|Copyright | Y | Y | | |Copyright | Y | Y | |
### Extension TIFF Tags ### Extension TIFF Tags

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

@ -118,9 +118,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
public TiffFillOrder FillOrder { get; set; } public TiffFillOrder FillOrder { get; set; }
/// <summary> /// <summary>
/// Gets or sets the extra samples, which can contain the alpha channel data. /// Gets or sets the extra samples type.
/// </summary> /// </summary>
public TiffExtraSampleType? ExtraSamples { get; set; } public TiffExtraSampleType? ExtraSamplesType { get; set; }
/// <summary> /// <summary>
/// Gets or sets the JPEG tables when jpeg compression is used. /// Gets or sets the JPEG tables when jpeg compression is used.
@ -157,40 +157,52 @@ namespace SixLabors.ImageSharp.Formats.Tiff
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken) public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
this.inputStream = stream;
var reader = new DirectoryReader(stream, this.Configuration.MemoryAllocator);
IEnumerable<ExifProfile> directories = reader.Read();
this.byteOrder = reader.ByteOrder;
this.isBigTiff = reader.IsBigTiff;
var frames = new List<ImageFrame<TPixel>>(); var frames = new List<ImageFrame<TPixel>>();
foreach (ExifProfile ifd in directories) try
{ {
cancellationToken.ThrowIfCancellationRequested(); this.inputStream = stream;
ImageFrame<TPixel> frame = this.DecodeFrame<TPixel>(ifd, cancellationToken); var reader = new DirectoryReader(stream, this.Configuration.MemoryAllocator);
frames.Add(frame);
IEnumerable<ExifProfile> directories = reader.Read();
this.byteOrder = reader.ByteOrder;
this.isBigTiff = reader.IsBigTiff;
if (this.decodingMode is FrameDecodingMode.First) foreach (ExifProfile ifd in directories)
{ {
break; cancellationToken.ThrowIfCancellationRequested();
ImageFrame<TPixel> frame = this.DecodeFrame<TPixel>(ifd, cancellationToken);
frames.Add(frame);
if (this.decodingMode is FrameDecodingMode.First)
{
break;
}
} }
}
ImageMetadata metadata = TiffDecoderMetadataCreator.Create(frames, this.ignoreMetadata, reader.ByteOrder, reader.IsBigTiff); ImageMetadata metadata = TiffDecoderMetadataCreator.Create(frames, this.ignoreMetadata, reader.ByteOrder, reader.IsBigTiff);
// TODO: Tiff frames can have different sizes. // TODO: Tiff frames can have different sizes.
ImageFrame<TPixel> root = frames[0]; ImageFrame<TPixel> root = frames[0];
this.Dimensions = root.Size(); this.Dimensions = root.Size();
foreach (ImageFrame<TPixel> frame in frames) foreach (ImageFrame<TPixel> frame in frames)
{
if (frame.Size() != root.Size())
{ {
TiffThrowHelper.ThrowNotSupported("Images with different sizes are not supported"); if (frame.Size() != root.Size())
{
TiffThrowHelper.ThrowNotSupported("Images with different sizes are not supported");
}
} }
return new Image<TPixel>(this.Configuration, metadata, frames);
} }
catch
{
foreach (ImageFrame<TPixel> f in frames)
{
f.Dispose();
}
return new Image<TPixel>(this.Configuration, metadata, frames); throw;
}
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -240,8 +252,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
var stripOffsetsArray = (Array)tags.GetValueInternal(ExifTag.StripOffsets).GetValue(); var stripOffsetsArray = (Array)tags.GetValueInternal(ExifTag.StripOffsets).GetValue();
var stripByteCountsArray = (Array)tags.GetValueInternal(ExifTag.StripByteCounts).GetValue(); var stripByteCountsArray = (Array)tags.GetValueInternal(ExifTag.StripByteCounts).GetValue();
IMemoryOwner<ulong> stripOffsetsMemory = this.ConvertNumbers(stripOffsetsArray, out Span<ulong> stripOffsets); using IMemoryOwner<ulong> stripOffsetsMemory = this.ConvertNumbers(stripOffsetsArray, out Span<ulong> stripOffsets);
IMemoryOwner<ulong> stripByteCountsMemory = this.ConvertNumbers(stripByteCountsArray, out Span<ulong> stripByteCounts); using IMemoryOwner<ulong> stripByteCountsMemory = this.ConvertNumbers(stripByteCountsArray, out Span<ulong> stripByteCounts);
if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar) if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar)
{ {
@ -262,8 +274,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff
cancellationToken); cancellationToken);
} }
stripOffsetsMemory?.Dispose();
stripByteCountsMemory?.Dispose();
return frame; return frame;
} }
@ -375,6 +385,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
TiffBasePlanarColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar( TiffBasePlanarColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(
this.ColorType, this.ColorType,
this.BitsPerSample, this.BitsPerSample,
this.ExtraSamplesType,
this.ColorMap, this.ColorMap,
this.ReferenceBlackAndWhite, this.ReferenceBlackAndWhite,
this.YcbcrCoefficients, this.YcbcrCoefficients,
@ -456,6 +467,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.memoryAllocator, this.memoryAllocator,
this.ColorType, this.ColorType,
this.BitsPerSample, this.BitsPerSample,
this.ExtraSamplesType,
this.ColorMap, this.ColorMap,
this.ReferenceBlackAndWhite, this.ReferenceBlackAndWhite,
this.YcbcrCoefficients, this.YcbcrCoefficients,

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

@ -39,10 +39,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff
} }
var extraSamplesType = (TiffExtraSampleType)extraSamples[0]; var extraSamplesType = (TiffExtraSampleType)extraSamples[0];
options.ExtraSamples = extraSamplesType; options.ExtraSamplesType = extraSamplesType;
if (extraSamplesType is not TiffExtraSampleType.UnassociatedAlphaData) if (extraSamplesType is not (TiffExtraSampleType.UnassociatedAlphaData or TiffExtraSampleType.AssociatedAlphaData))
{ {
TiffThrowHelper.ThrowNotSupported("Decoding Tiff images with ExtraSamples is only supported with UnassociatedAlphaData."); TiffThrowHelper.ThrowNotSupported("Decoding Tiff images with ExtraSamples is not supported with UnspecifiedData.");
} }
} }

56
src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs

@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorFromRgba64<TPixel>(Rgba64 rgba, ulong r, ulong g, ulong b, TPixel color) public static TPixel ColorFromRgb64<TPixel>(Rgba64 rgba, ulong r, ulong g, ulong b, TPixel color)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
rgba.PackedValue = r | (g << 16) | (b << 32) | (0xfffful << 48); rgba.PackedValue = r | (g << 16) | (b << 32) | (0xfffful << 48);
@ -63,12 +63,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
return color; return color;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorFromRgba64Premultiplied<TPixel>(Rgba64 rgba, ulong r, ulong g, ulong b, ulong a, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
rgba.PackedValue = r | (g << 16) | (b << 32) | (a << 48);
var vec = rgba.ToVector4();
return UnPremultiply(ref vec, color);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo24Bit<TPixel>(ulong r, ulong g, ulong b, TPixel color) public static TPixel ColorScaleTo24Bit<TPixel>(ulong r, ulong g, ulong b, TPixel color)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var colorVector = new Vector4(r * Scale24Bit, g * Scale24Bit, b * Scale24Bit, 1.0f); var colorVector = new Vector4(r * Scale24Bit, g * Scale24Bit, b * Scale24Bit, 1.0f);
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
return color; return color;
} }
@ -76,17 +85,25 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
public static TPixel ColorScaleTo24Bit<TPixel>(ulong r, ulong g, ulong b, ulong a, TPixel color) public static TPixel ColorScaleTo24Bit<TPixel>(ulong r, ulong g, ulong b, ulong a, TPixel color)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var colorVector = new Vector4(r * Scale24Bit, g * Scale24Bit, b * Scale24Bit, a * Scale24Bit); Vector4 colorVector = new Vector4(r, g, b, a) * Scale24Bit;
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
return color; return color;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo24BitPremultiplied<TPixel>(ulong r, ulong g, ulong b, ulong a, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
Vector4 colorVector = new Vector4(r, g, b, a) * Scale24Bit;
return UnPremultiply(ref colorVector, color);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo32Bit<TPixel>(ulong r, ulong g, ulong b, TPixel color) public static TPixel ColorScaleTo32Bit<TPixel>(ulong r, ulong g, ulong b, TPixel color)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var colorVector = new Vector4(r * Scale32Bit, g * Scale32Bit, b * Scale32Bit, 1.0f); var colorVector = new Vector4(r * Scale32Bit, g * Scale32Bit, b * Scale32Bit, 1.0f);
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
return color; return color;
} }
@ -94,11 +111,19 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
public static TPixel ColorScaleTo32Bit<TPixel>(ulong r, ulong g, ulong b, ulong a, TPixel color) public static TPixel ColorScaleTo32Bit<TPixel>(ulong r, ulong g, ulong b, ulong a, TPixel color)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var colorVector = new Vector4(r * Scale32Bit, g * Scale32Bit, b * Scale32Bit, a * Scale32Bit); Vector4 colorVector = new Vector4(r, g, b, a) * Scale32Bit;
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
return color; return color;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo32BitPremultiplied<TPixel>(ulong r, ulong g, ulong b, ulong a, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
Vector4 colorVector = new Vector4(r, g, b, a) * Scale32Bit;
return UnPremultiply(ref colorVector, color);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorFromL16<TPixel>(L16 l16, ushort intensity, TPixel color) public static TPixel ColorFromL16<TPixel>(L16 l16, ushort intensity, TPixel color)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
@ -113,7 +138,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var colorVector = new Vector4(intensity * Scale24Bit, intensity * Scale24Bit, intensity * Scale24Bit, 1.0f); var colorVector = new Vector4(intensity * Scale24Bit, intensity * Scale24Bit, intensity * Scale24Bit, 1.0f);
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
return color; return color;
} }
@ -122,7 +147,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
var colorVector = new Vector4(intensity * Scale32Bit, intensity * Scale32Bit, intensity * Scale32Bit, 1.0f); var colorVector = new Vector4(intensity * Scale32Bit, intensity * Scale32Bit, intensity * Scale32Bit, 1.0f);
color.FromVector4(colorVector); color.FromScaledVector4(colorVector);
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel UnPremultiply<TPixel>(ref Vector4 vector, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
Numerics.UnPremultiply(ref vector);
color.FromScaledVector4(vector);
return color; return color;
} }
@ -139,8 +174,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
return 0; return 0;
} }
int padding = subSampling - (valueToRoundUp % subSampling); return subSampling - (valueToRoundUp % subSampling);
return padding;
} }
} }
} }

340
src/ImageSharp/Formats/Webp/WebpDecoderCore.cs

@ -93,37 +93,53 @@ namespace SixLabors.ImageSharp.Formats.Webp
public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken) public Image<TPixel> Decode<TPixel>(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
this.Metadata = new ImageMetadata(); Image<TPixel> image = null;
this.currentStream = stream; try
{
this.Metadata = new ImageMetadata();
this.currentStream = stream;
uint fileSize = this.ReadImageHeader(); uint fileSize = this.ReadImageHeader();
using (this.webImageInfo = this.ReadVp8Info()) using (this.webImageInfo = this.ReadVp8Info())
{
if (this.webImageInfo.Features is { Animation: true })
{ {
using var animationDecoder = new WebpAnimationDecoder(this.memoryAllocator, this.Configuration, this.DecodingMode); if (this.webImageInfo.Features is { Animation: true })
return animationDecoder.Decode<TPixel>(stream, this.webImageInfo.Features, this.webImageInfo.Width, this.webImageInfo.Height, fileSize); {
} using var animationDecoder = new WebpAnimationDecoder(this.memoryAllocator, this.Configuration, this.DecodingMode);
return animationDecoder.Decode<TPixel>(stream, this.webImageInfo.Features, this.webImageInfo.Width, this.webImageInfo.Height, fileSize);
}
var image = new Image<TPixel>(this.Configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.Metadata); if (this.webImageInfo.Features is { Animation: true })
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer(); {
WebpThrowHelper.ThrowNotSupportedException("Animations are not supported");
}
if (this.webImageInfo.IsLossless) image = new Image<TPixel>(this.Configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.Metadata);
{ Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
var losslessDecoder = new WebpLosslessDecoder(this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.Configuration); if (this.webImageInfo.IsLossless)
losslessDecoder.Decode(pixels, image.Width, image.Height); {
} var losslessDecoder = new WebpLosslessDecoder(this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.Configuration);
else losslessDecoder.Decode(pixels, image.Width, image.Height);
{ }
var lossyDecoder = new WebpLossyDecoder(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.Configuration); else
lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo, this.AlphaData); {
} var lossyDecoder = new WebpLossyDecoder(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.Configuration);
lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo, this.AlphaData);
}
// There can be optional chunks after the image data, like EXIF and XMP. // There can be optional chunks after the image data, like EXIF and XMP.
this.ReadOptionalMetadata(); if (this.webImageInfo.Features != null)
{
this.ParseOptionalChunks(this.webImageInfo.Features);
}
return image; return image;
}
}
catch
{
image?.Dispose();
throw;
} }
} }
@ -212,113 +228,40 @@ namespace SixLabors.ImageSharp.Formats.Webp
return webpInfos; return webpInfos;
default: default:
WebpThrowHelper.ThrowImageFormatException("Unrecognized VP8 header"); WebpThrowHelper.ThrowImageFormatException("Unrecognized VP8 header");
return new WebpImageInfo(); // This return will never be reached, because throw helper will throw an exception. return
new WebpImageInfo(); // this return will never be reached, because throw helper will throw an exception.
} }
} }
/// <summary> /// <summary>
/// Parses optional VP8X chunks, which can be ICCP, ANIM or ALPH chunks. /// Parses optional VP8X chunks, which can be ICCP, XMP, ANIM or ALPH chunks.
/// </summary> /// </summary>
/// <param name="chunkType">The chunk type.</param> /// <param name="chunkType">The chunk type.</param>
/// <param name="features">The webp image features.</param> /// <param name="features">The webp image features.</param>
/// <param name="ignoreAlpha">For identify, the alpha data should not be read.</param> /// <param name="ignoreAlpha">For identify, the alpha data should not be read.</param>
/// <returns>true, if animation chunk was found.</returns> /// <returns>true, if its a alpha chunk.</returns>
private bool ParseOptionalExtendedChunks(WebpChunkType chunkType, WebpFeatures features, bool ignoreAlpha) private bool ParseOptionalExtendedChunks(WebpChunkType chunkType, WebpFeatures features, bool ignoreAlpha)
{ {
int bytesRead;
switch (chunkType) switch (chunkType)
{ {
case WebpChunkType.Iccp: case WebpChunkType.Iccp:
uint iccpChunkSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer); this.ReadIccProfile();
if (this.IgnoreMetadata)
{
this.currentStream.Skip((int)iccpChunkSize);
}
else
{
byte[] iccpData = new byte[iccpChunkSize];
bytesRead = this.currentStream.Read(iccpData, 0, (int)iccpChunkSize);
if (bytesRead != iccpChunkSize)
{
WebpThrowHelper.ThrowImageFormatException("Could not read enough data for ICCP profile");
}
var profile = new IccProfile(iccpData);
if (profile.CheckIsValid())
{
this.Metadata.IccProfile = profile;
}
}
break; break;
case WebpChunkType.Exif: case WebpChunkType.Exif:
uint exifChunkSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer); this.ReadExifProfile();
if (this.IgnoreMetadata)
{
this.currentStream.Skip((int)exifChunkSize);
}
else
{
byte[] exifData = new byte[exifChunkSize];
bytesRead = this.currentStream.Read(exifData, 0, (int)exifChunkSize);
if (bytesRead != exifChunkSize)
{
WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the EXIF profile");
}
var profile = new ExifProfile(exifData);
this.Metadata.ExifProfile = profile;
}
break; break;
case WebpChunkType.Xmp: case WebpChunkType.Xmp:
uint xmpChunkSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer); this.ReadXmpProfile();
if (this.IgnoreMetadata)
{
this.currentStream.Skip((int)xmpChunkSize);
}
else
{
byte[] xmpData = new byte[xmpChunkSize];
bytesRead = this.currentStream.Read(xmpData, 0, (int)xmpChunkSize);
if (bytesRead != xmpChunkSize)
{
WebpThrowHelper.ThrowImageFormatException("Could not read enough data for the XMP profile");
}
var profile = new XmpProfile(xmpData);
this.Metadata.XmpProfile = profile;
}
break; break;
case WebpChunkType.AnimationParameter: case WebpChunkType.AnimationParameter:
features.Animation = true; this.ReadAnimationParameters(features);
uint animationChunkSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer);
byte blue = (byte)this.currentStream.ReadByte();
byte green = (byte)this.currentStream.ReadByte();
byte red = (byte)this.currentStream.ReadByte();
byte alpha = (byte)this.currentStream.ReadByte();
features.AnimationBackgroundColor = new Color(new Rgba32(red, green, blue, alpha));
this.currentStream.Read(this.buffer, 0, 2);
features.AnimationLoopCount = BinaryPrimitives.ReadUInt16LittleEndian(this.buffer);
return true; return true;
case WebpChunkType.Alpha: case WebpChunkType.Alpha:
uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer); this.ReadAlphaData(features, ignoreAlpha);
if (ignoreAlpha)
{
this.currentStream.Skip((int)alphaChunkSize);
break;
}
features.AlphaChunkHeader = (byte)this.currentStream.ReadByte();
int alphaDataSize = (int)(alphaChunkSize - 1);
this.AlphaData = this.memoryAllocator.Allocate<byte>(alphaDataSize);
Span<byte> alphaData = this.AlphaData.GetSpan();
this.currentStream.Read(alphaData, 0, alphaDataSize);
break; break;
default: default:
WebpThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header"); WebpThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header");
@ -331,22 +274,193 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// <summary> /// <summary>
/// Reads the optional metadata EXIF of XMP profiles, which can follow the image data. /// Reads the optional metadata EXIF of XMP profiles, which can follow the image data.
/// </summary> /// </summary>
private void ReadOptionalMetadata() /// <param name="features">The webp features.</param>
private void ParseOptionalChunks(WebpFeatures features)
{
if (this.IgnoreMetadata || (features.ExifProfile == false && features.XmpMetaData == false))
{
return;
}
long streamLength = this.currentStream.Length;
while (this.currentStream.Position < streamLength)
{
// Read chunk header.
WebpChunkType chunkType = this.ReadChunkType();
if (chunkType == WebpChunkType.Exif && this.Metadata.ExifProfile == null)
{
this.ReadExifProfile();
}
else if (chunkType == WebpChunkType.Xmp && this.Metadata.XmpProfile == null)
{
this.ReadXmpProfile();
}
else
{
// Skip duplicate XMP or EXIF chunk.
uint chunkLength = this.ReadChunkSize();
this.currentStream.Skip((int)chunkLength);
}
}
}
/// <summary>
/// Reads the EXIF profile from the stream.
/// </summary>
private void ReadExifProfile()
{
uint exifChunkSize = this.ReadChunkSize();
if (this.IgnoreMetadata)
{
this.currentStream.Skip((int)exifChunkSize);
}
else
{
byte[] exifData = new byte[exifChunkSize];
int bytesRead = this.currentStream.Read(exifData, 0, (int)exifChunkSize);
if (bytesRead != exifChunkSize)
{
// Ignore invalid chunk.
return;
}
var profile = new ExifProfile(exifData);
this.Metadata.ExifProfile = profile;
}
}
/// <summary>
/// Reads the XMP profile the stream.
/// </summary>
private void ReadXmpProfile()
{ {
if (!this.IgnoreMetadata && this.webImageInfo.Features != null && (this.webImageInfo.Features.ExifProfile || this.webImageInfo.Features.XmpMetaData)) uint xmpChunkSize = this.ReadChunkSize();
if (this.IgnoreMetadata)
{
this.currentStream.Skip((int)xmpChunkSize);
}
else
{ {
// The spec states, that the EXIF and XMP should come after the image data, but it seems some encoders store them byte[] xmpData = new byte[xmpChunkSize];
// in the VP8X chunk before the image data. Make sure there is still data to read here. int bytesRead = this.currentStream.Read(xmpData, 0, (int)xmpChunkSize);
if (this.currentStream.Position == this.currentStream.Length) if (bytesRead != xmpChunkSize)
{ {
// Ignore invalid chunk.
return; return;
} }
WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(this.currentStream, this.buffer); var profile = new XmpProfile(xmpData);
WebpChunkParsingUtils.ParseOptionalChunks(this.currentStream, chunkType, this.Metadata, this.IgnoreMetadata, this.buffer); this.Metadata.XmpProfile = profile;
}
}
/// <summary>
/// Reads the ICCP chunk from the stream.
/// </summary>
private void ReadIccProfile()
{
uint iccpChunkSize = this.ReadChunkSize();
if (this.IgnoreMetadata)
{
this.currentStream.Skip((int)iccpChunkSize);
}
else
{
byte[] iccpData = new byte[iccpChunkSize];
int bytesRead = this.currentStream.Read(iccpData, 0, (int)iccpChunkSize);
if (bytesRead != iccpChunkSize)
{
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the iccp chunk");
}
var profile = new IccProfile(iccpData);
if (profile.CheckIsValid())
{
this.Metadata.IccProfile = profile;
}
}
}
/// <summary>
/// Reads the animation parameters chunk from the stream.
/// </summary>
/// <param name="features">The webp features.</param>
private void ReadAnimationParameters(WebpFeatures features)
{
features.Animation = true;
uint animationChunkSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer);
byte blue = (byte)this.currentStream.ReadByte();
byte green = (byte)this.currentStream.ReadByte();
byte red = (byte)this.currentStream.ReadByte();
byte alpha = (byte)this.currentStream.ReadByte();
features.AnimationBackgroundColor = new Color(new Rgba32(red, green, blue, alpha));
int bytesRead = this.currentStream.Read(this.buffer, 0, 2);
if (bytesRead != 2)
{
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the animation loop count");
}
features.AnimationLoopCount = BinaryPrimitives.ReadUInt16LittleEndian(this.buffer);
}
/// <summary>
/// Reads the alpha data chunk data from the stream.
/// </summary>
/// <param name="features">The features.</param>
/// <param name="ignoreAlpha">if set to true, skips the chunk data.</param>
private void ReadAlphaData(WebpFeatures features, bool ignoreAlpha)
{
uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer);
if (ignoreAlpha)
{
this.currentStream.Skip((int)alphaChunkSize);
return;
}
features.AlphaChunkHeader = (byte)this.currentStream.ReadByte();
int alphaDataSize = (int)(alphaChunkSize - 1);
this.AlphaData = this.memoryAllocator.Allocate<byte>(alphaDataSize);
Span<byte> alphaData = this.AlphaData.GetSpan();
int bytesRead = this.currentStream.Read(alphaData, 0, alphaDataSize);
if (bytesRead != alphaDataSize)
{
WebpThrowHelper.ThrowInvalidImageContentException("Not enough data to read the alpha data from the stream");
} }
} }
/// <summary>
/// Identifies the chunk type from the chunk.
/// </summary>
/// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid.
/// </exception>
private WebpChunkType ReadChunkType()
{
if (this.currentStream.Read(this.buffer, 0, 4) == 4)
{
var chunkType = (WebpChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer);
return chunkType;
}
throw new ImageFormatException("Invalid Webp data.");
}
/// <summary>
/// Reads the chunk size. If Chunk Size is odd, a single padding byte will be added to the payload,
/// so the chunk size will be increased by 1 in those cases.
/// </summary>
/// <returns>The chunk size in bytes.</returns>
private uint ReadChunkSize()
{
if (this.currentStream.Read(this.buffer, 0, 4) == 4)
{
uint chunkSize = BinaryPrimitives.ReadUInt32LittleEndian(this.buffer);
return (chunkSize % 2 == 0) ? chunkSize : chunkSize + 1;
}
throw new ImageFormatException("Invalid Webp data.");
}
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() => this.AlphaData?.Dispose(); public void Dispose() => this.AlphaData?.Dispose();
} }

7
src/ImageSharp/Formats/Webp/WebpThrowHelper.cs

@ -8,6 +8,13 @@ namespace SixLabors.ImageSharp.Formats.Webp
{ {
internal static class WebpThrowHelper internal static class WebpThrowHelper
{ {
/// <summary>
/// Cold path optimization for throwing <see cref="InvalidImageContentException"/>'s.
/// </summary>
/// <param name="errorMessage">The error message for the exception.</param>
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage);
/// <summary> /// <summary>
/// Cold path optimization for throwing <see cref="ImageFormatException"/>-s /// Cold path optimization for throwing <see cref="ImageFormatException"/>-s
/// </summary> /// </summary>

9
src/ImageSharp/IO/BufferedReadStream.cs

@ -114,6 +114,15 @@ namespace SixLabors.ImageSharp.IO
/// <inheritdoc/> /// <inheritdoc/>
public override bool CanWrite { get; } = false; public override bool CanWrite { get; } = false;
/// <summary>
/// Gets remaining byte count available to read.
/// </summary>
public long RemainingBytes
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => this.Length - this.Position;
}
/// <summary> /// <summary>
/// Gets the underlying stream. /// Gets the underlying stream.
/// </summary> /// </summary>

31
src/ImageSharp/ImageSharp.csproj

@ -12,33 +12,23 @@
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl> <PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
<PackageTags>Image Resize Crop Gif Jpg Jpeg Bitmap Pbm Png Tga Tiff WebP NetCore</PackageTags> <PackageTags>Image Resize Crop Gif Jpg Jpeg Bitmap Pbm Png Tga Tiff WebP NetCore</PackageTags>
<Description>A new, fully featured, fully managed, cross-platform, 2D graphics API for .NET</Description> <Description>A new, fully featured, fully managed, cross-platform, 2D graphics API for .NET</Description>
<Configurations>Debug;Release;Debug-InnerLoop;Release-InnerLoop</Configurations> <Configurations>Debug;Release</Configurations>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<!--Bump to V2 prior to tagged release.--> <!--Bump to V3 prior to tagged release.-->
<MinVerMinimumMajorMinor>2.0</MinVerMinimumMajorMinor> <MinVerMinimumMajorMinor>3.0</MinVerMinimumMajorMinor>
</PropertyGroup> </PropertyGroup>
<Choose> <Choose>
<When Condition="$(SIXLABORS_TESTING_PREVIEW) == true"> <When Condition="$(SIXLABORS_TESTING_PREVIEW) == true">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0;net5.0;netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;net472</TargetFrameworks> <TargetFrameworks>net7.0;net6.0</TargetFrameworks>
</PropertyGroup>
</When>
<When Condition="$(SIXLABORS_TESTING) == true">
<PropertyGroup>
<TargetFrameworks>net5.0;netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;net472</TargetFrameworks>
</PropertyGroup>
</When>
<When Condition="$(Configuration.EndsWith('InnerLoop')) == true">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
</When> </When>
<Otherwise> <Otherwise>
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;net472</TargetFrameworks> <TargetFrameworks>net6.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
</Otherwise> </Otherwise>
</Choose> </Choose>
@ -47,17 +37,6 @@
<None Include="..\..\shared-infrastructure\branding\icons\imagesharp\sixlabors.imagesharp.128.png" Pack="true" PackagePath="" /> <None Include="..\..\shared-infrastructure\branding\icons\imagesharp\sixlabors.imagesharp.128.png" Pack="true" PackagePath="" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="5.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" />
</ItemGroup>
<ItemGroup Condition=" $(TargetFramework.StartsWith('netstandard')) OR '$(TargetFramework)' == 'net472'">
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
<PackageReference Include="System.Buffers" Version="4.5.1" />
<PackageReference Include="System.Memory" Version="4.5.4" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Update="Formats\Jpeg\Components\Block8x8F.Generated.cs"> <Compile Update="Formats\Jpeg\Components\Block8x8F.Generated.cs">
<DesignTime>True</DesignTime> <DesignTime>True</DesignTime>

9
src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs

@ -80,7 +80,15 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
} }
public static unsafe int Write(Encoding encoding, string value, Span<byte> destination) public static unsafe int Write(Encoding encoding, string value, Span<byte> destination)
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER || NET
=> encoding.GetBytes(value.AsSpan(), destination);
#else
{ {
if (value.Length == 0)
{
return 0;
}
fixed (char* c = value) fixed (char* c = value)
{ {
fixed (byte* b = destination) fixed (byte* b = destination)
@ -89,6 +97,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
} }
} }
} }
#endif
private static bool TryDetect(ReadOnlySpan<byte> buffer, out CharacterCode code) private static bool TryDetect(ReadOnlySpan<byte> buffer, out CharacterCode code)
{ {

8
src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs

@ -121,6 +121,14 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
} }
} }
/// <summary>
/// Returns the thumbnail in the EXIF profile when available.
/// </summary>
/// <returns>
/// The <see cref="Image"/>.
/// </returns>
public Image CreateThumbnail() => this.CreateThumbnail<Rgba32>();
/// <summary> /// <summary>
/// Returns the thumbnail in the EXIF profile when available. /// Returns the thumbnail in the EXIF profile when available.
/// </summary> /// </summary>

2
src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs

@ -202,7 +202,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
protected void ReadValues64(List<IExifValue> values, ulong offset) protected void ReadValues64(List<IExifValue> values, ulong offset)
{ {
DebugGuard.MustBeLessThanOrEqualTo(offset, (ulong)this.data.Length, "By spec UInt64.MaxValue is supported, but .Net Stream.Length can Int64.MaxValue."); DebugGuard.MustBeLessThanOrEqualTo(offset, (ulong)this.data.Length, "By spec UInt64.MaxValue is supported, but .NET Stream.Length can Int64.MaxValue.");
this.Seek(offset); this.Seek(offset);
ulong count = this.ReadUInt64(); ulong count = this.ReadUInt64();

21
src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs

@ -39,5 +39,26 @@ namespace SixLabors.ImageSharp.Processing
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns> /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius, Rectangle rectangle) public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius, Rectangle rectangle)
=> source.ApplyProcessor(new BoxBlurProcessor(radius), rectangle); => source.ApplyProcessor(new BoxBlurProcessor(radius), rectangle);
/// <summary>
/// Applies a box blur to the image.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="radius">The 'radius' value representing the size of the area to sample.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="borderWrapModeX">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </param>
/// <param name="borderWrapModeY">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius, Rectangle rectangle, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
{
var processor = new BoxBlurProcessor(radius, borderWrapModeX, borderWrapModeY);
return source.ApplyProcessor(processor, rectangle);
}
} }
} }

21
src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs

@ -39,5 +39,26 @@ namespace SixLabors.ImageSharp.Processing
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns> /// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma, Rectangle rectangle) public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma, Rectangle rectangle)
=> source.ApplyProcessor(new GaussianBlurProcessor(sigma), rectangle); => source.ApplyProcessor(new GaussianBlurProcessor(sigma), rectangle);
/// <summary>
/// Applies a Gaussian blur to the image.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="borderWrapModeX">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </param>
/// <param name="borderWrapModeY">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma, Rectangle rectangle, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
{
var processor = new GaussianBlurProcessor(sigma, borderWrapModeX, borderWrapModeY);
return source.ApplyProcessor(processor, rectangle);
}
} }
} }

21
src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs

@ -42,5 +42,26 @@ namespace SixLabors.ImageSharp.Processing
float sigma, float sigma,
Rectangle rectangle) => Rectangle rectangle) =>
source.ApplyProcessor(new GaussianSharpenProcessor(sigma), rectangle); source.ApplyProcessor(new GaussianSharpenProcessor(sigma), rectangle);
/// <summary>
/// Applies a Gaussian sharpening filter to the image.
/// </summary>
/// <param name="source">The image this method extends.</param>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="borderWrapModeX">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </param>
/// <param name="borderWrapModeY">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </param>
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
public static IImageProcessingContext GaussianSharpen(this IImageProcessingContext source, float sigma, Rectangle rectangle, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
{
var processor = new GaussianSharpenProcessor(sigma, borderWrapModeX, borderWrapModeY);
return source.ApplyProcessor(processor, rectangle);
}
} }
} }

25
src/ImageSharp/Processing/Processors/Convolution/BorderWrappingMode.cs

@ -0,0 +1,25 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
/// <summary>
/// Wrapping mode for the border pixels in convolution processing.
/// </summary>
public enum BorderWrappingMode : byte
{
/// <summary>Repeat the border pixel value: aaaaaa|abcdefgh|hhhhhhh</summary>
Repeat = 0,
/// <summary>Take values from the opposite edge: cdefgh|abcdefgh|abcdefg</summary>
Wrap = 1,
/// <summary>Mirror the last few border values: fedcba|abcdefgh|hgfedcb</summary>
/// <remarks>This Mode is similar to <see cref="Bounce"/>, but here the very border pixel is repeated.</remarks>
Mirror = 2,
/// <summary>Bounce off the border: fedcb|abcdefgh|gfedcb</summary>
/// <remarks>This Mode is similar to <see cref="Mirror"/>, but here the very border pixel is not repeated.</remarks>
Bounce = 3
}
}

29
src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs

@ -21,9 +21,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <param name="radius"> /// <param name="radius">
/// The 'radius' value representing the size of the area to sample. /// The 'radius' value representing the size of the area to sample.
/// </param> /// </param>
public BoxBlurProcessor(int radius) /// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
public BoxBlurProcessor(int radius, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
{ {
this.Radius = radius; this.Radius = radius;
this.BorderWrapModeX = borderWrapModeX;
this.BorderWrapModeY = borderWrapModeY;
}
/// <summary>
/// Initializes a new instance of the <see cref="BoxBlurProcessor"/> class.
/// </summary>
/// <param name="radius">
/// The 'radius' value representing the size of the area to sample.
/// </param>
public BoxBlurProcessor(int radius)
: this(radius, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat)
{
} }
/// <summary> /// <summary>
@ -39,9 +54,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary> /// </summary>
public int Radius { get; } public int Radius { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </summary>
public BorderWrappingMode BorderWrapModeX { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </summary>
public BorderWrappingMode BorderWrapModeY { get; }
/// <inheritdoc /> /// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle) public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> new BoxBlurProcessor<TPixel>(configuration, this, source, sourceRectangle); => new BoxBlurProcessor<TPixel>(configuration, this, source, sourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
} }
} }

36
src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs

@ -27,15 +27,49 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
this.Kernel = CreateBoxKernel(kernelSize); this.Kernel = CreateBoxKernel(kernelSize);
} }
/// <summary>
/// Initializes a new instance of the <see cref="BoxBlurProcessor{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="definition">The <see cref="BoxBlurProcessor"/> defining the processor parameters.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
public BoxBlurProcessor(
Configuration configuration,
BoxBlurProcessor definition,
Image<TPixel> source,
Rectangle sourceRectangle,
BorderWrappingMode borderWrapModeX,
BorderWrappingMode borderWrapModeY)
: base(configuration, source, sourceRectangle)
{
int kernelSize = (definition.Radius * 2) + 1;
this.Kernel = CreateBoxKernel(kernelSize);
this.BorderWrapModeX = borderWrapModeX;
this.BorderWrapModeY = borderWrapModeY;
}
/// <summary> /// <summary>
/// Gets the 1D convolution kernel. /// Gets the 1D convolution kernel.
/// </summary> /// </summary>
public float[] Kernel { get; } public float[] Kernel { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </summary>
public BorderWrappingMode BorderWrapModeX { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </summary>
public BorderWrappingMode BorderWrapModeY { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle); using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
processor.Apply(source); processor.Apply(source);
} }

20
src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs

@ -26,16 +26,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param> /// <param name="preserveAlpha">Whether the convolution filter is applied to alpha as well as the color channels.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param> /// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param> /// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
public Convolution2PassProcessor( public Convolution2PassProcessor(
Configuration configuration, Configuration configuration,
float[] kernel, float[] kernel,
bool preserveAlpha, bool preserveAlpha,
Image<TPixel> source, Image<TPixel> source,
Rectangle sourceRectangle) Rectangle sourceRectangle,
BorderWrappingMode borderWrapModeX,
BorderWrappingMode borderWrapModeY)
: base(configuration, source, sourceRectangle) : base(configuration, source, sourceRectangle)
{ {
this.Kernel = kernel; this.Kernel = kernel;
this.PreserveAlpha = preserveAlpha; this.PreserveAlpha = preserveAlpha;
this.BorderWrapModeX = borderWrapModeX;
this.BorderWrapModeY = borderWrapModeY;
} }
/// <summary> /// <summary>
@ -48,6 +54,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary> /// </summary>
public bool PreserveAlpha { get; } public bool PreserveAlpha { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </summary>
public BorderWrappingMode BorderWrapModeX { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </summary>
public BorderWrappingMode BorderWrapModeY { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
@ -63,7 +79,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
// the two 1D kernels represent, and reuse it across both convolution steps, like in the bokeh blur. // the two 1D kernels represent, and reuse it across both convolution steps, like in the bokeh blur.
using var mapXY = new KernelSamplingMap(this.Configuration.MemoryAllocator); using var mapXY = new KernelSamplingMap(this.Configuration.MemoryAllocator);
mapXY.BuildSamplingOffsetMap(this.Kernel.Length, this.Kernel.Length, interest); mapXY.BuildSamplingOffsetMap(this.Kernel.Length, this.Kernel.Length, interest, this.BorderWrapModeX, this.BorderWrapModeY);
// Horizontal convolution // Horizontal convolution
var horizontalOperation = new HorizontalConvolutionRowOperation( var horizontalOperation = new HorizontalConvolutionRowOperation(

46
src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs

@ -32,6 +32,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
} }
/// <summary>
/// Initializes a new instance of the <see cref="GaussianBlurProcessor"/> class.
/// </summary>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
public GaussianBlurProcessor(float sigma, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
: this(sigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(sigma), borderWrapModeX, borderWrapModeY)
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="GaussianBlurProcessor"/> class. /// Initializes a new instance of the <see cref="GaussianBlurProcessor"/> class.
/// </summary> /// </summary>
@ -54,9 +65,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// This should be at least twice the sigma value. /// This should be at least twice the sigma value.
/// </param> /// </param>
public GaussianBlurProcessor(float sigma, int radius) public GaussianBlurProcessor(float sigma, int radius)
: this(sigma, radius, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GaussianBlurProcessor"/> class.
/// </summary>
/// <param name="sigma">
/// The 'sigma' value representing the weight of the blur.
/// </param>
/// <param name="radius">
/// The 'radius' value representing the size of the area to sample.
/// This should be at least twice the sigma value.
/// </param>
/// <param name="borderWrapModeX">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </param>
/// <param name="borderWrapModeY">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </param>
public GaussianBlurProcessor(float sigma, int radius, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
{ {
this.Sigma = sigma; this.Sigma = sigma;
this.Radius = radius; this.Radius = radius;
this.BorderWrapModeX = borderWrapModeX;
this.BorderWrapModeY = borderWrapModeY;
} }
/// <summary> /// <summary>
@ -69,9 +103,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary> /// </summary>
public int Radius { get; } public int Radius { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </summary>
public BorderWrappingMode BorderWrapModeX { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </summary>
public BorderWrappingMode BorderWrapModeY { get; }
/// <inheritdoc /> /// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle) public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> new GaussianBlurProcessor<TPixel>(configuration, this, source, sourceRectangle); => new GaussianBlurProcessor<TPixel>(configuration, this, source, sourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
} }
} }

36
src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs

@ -30,15 +30,49 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
this.Kernel = ConvolutionProcessorHelpers.CreateGaussianBlurKernel(kernelSize, definition.Sigma); this.Kernel = ConvolutionProcessorHelpers.CreateGaussianBlurKernel(kernelSize, definition.Sigma);
} }
/// <summary>
/// Initializes a new instance of the <see cref="GaussianBlurProcessor{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="definition">The <see cref="GaussianBlurProcessor"/> defining the processor parameters.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
public GaussianBlurProcessor(
Configuration configuration,
GaussianBlurProcessor definition,
Image<TPixel> source,
Rectangle sourceRectangle,
BorderWrappingMode borderWrapModeX,
BorderWrappingMode borderWrapModeY)
: base(configuration, source, sourceRectangle)
{
int kernelSize = (definition.Radius * 2) + 1;
this.Kernel = ConvolutionProcessorHelpers.CreateGaussianBlurKernel(kernelSize, definition.Sigma);
this.BorderWrapModeX = borderWrapModeX;
this.BorderWrapModeY = borderWrapModeY;
}
/// <summary> /// <summary>
/// Gets the 1D convolution kernel. /// Gets the 1D convolution kernel.
/// </summary> /// </summary>
public float[] Kernel { get; } public float[] Kernel { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </summary>
public BorderWrappingMode BorderWrapModeX { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </summary>
public BorderWrappingMode BorderWrapModeY { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle); using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
processor.Apply(source); processor.Apply(source);
} }

46
src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs

@ -32,6 +32,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{ {
} }
/// <summary>
/// Initializes a new instance of the <see cref="GaussianSharpenProcessor"/> class.
/// </summary>
/// <param name="sigma">The 'sigma' value representing the weight of the blur.</param>
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
public GaussianSharpenProcessor(float sigma, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
: this(sigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(sigma), borderWrapModeX, borderWrapModeY)
{
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="GaussianSharpenProcessor"/> class. /// Initializes a new instance of the <see cref="GaussianSharpenProcessor"/> class.
/// </summary> /// </summary>
@ -54,9 +65,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// This should be at least twice the sigma value. /// This should be at least twice the sigma value.
/// </param> /// </param>
public GaussianSharpenProcessor(float sigma, int radius) public GaussianSharpenProcessor(float sigma, int radius)
: this(sigma, radius, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GaussianSharpenProcessor"/> class.
/// </summary>
/// <param name="sigma">
/// The 'sigma' value representing the weight of the blur.
/// </param>
/// <param name="radius">
/// The 'radius' value representing the size of the area to sample.
/// This should be at least twice the sigma value.
/// </param>
/// <param name="borderWrapModeX">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </param>
/// <param name="borderWrapModeY">
/// The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </param>
public GaussianSharpenProcessor(float sigma, int radius, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
{ {
this.Sigma = sigma; this.Sigma = sigma;
this.Radius = radius; this.Radius = radius;
this.BorderWrapModeX = borderWrapModeX;
this.BorderWrapModeY = borderWrapModeY;
} }
/// <summary> /// <summary>
@ -69,9 +103,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary> /// </summary>
public int Radius { get; } public int Radius { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </summary>
public BorderWrappingMode BorderWrapModeX { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </summary>
public BorderWrappingMode BorderWrapModeY { get; }
/// <inheritdoc /> /// <inheritdoc />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle) public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
=> new GaussianSharpenProcessor<TPixel>(configuration, this, source, sourceRectangle); => new GaussianSharpenProcessor<TPixel>(configuration, this, source, sourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
} }
} }

36
src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs

@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// Initializes a new instance of the <see cref="GaussianSharpenProcessor{TPixel}"/> class. /// Initializes a new instance of the <see cref="GaussianSharpenProcessor{TPixel}"/> class.
/// </summary> /// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param> /// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="definition">The <see cref="GaussianBlurProcessor"/> defining the processor parameters.</param> /// <param name="definition">The <see cref="GaussianSharpenProcessor"/> defining the processor parameters.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param> /// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param> /// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
public GaussianSharpenProcessor( public GaussianSharpenProcessor(
@ -24,10 +24,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
GaussianSharpenProcessor definition, GaussianSharpenProcessor definition,
Image<TPixel> source, Image<TPixel> source,
Rectangle sourceRectangle) Rectangle sourceRectangle)
: this(configuration, definition, source, sourceRectangle, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="GaussianSharpenProcessor{TPixel}"/> class.
/// </summary>
/// <param name="configuration">The configuration which allows altering default behaviour or extending the library.</param>
/// <param name="definition">The <see cref="GaussianSharpenProcessor"/> defining the processor parameters.</param>
/// <param name="source">The source <see cref="Image{TPixel}"/> for the current processor instance.</param>
/// <param name="sourceRectangle">The source area to process for the current processor instance.</param>
/// <param name="borderWrapModeX">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.</param>
/// <param name="borderWrapModeY">The <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.</param>
public GaussianSharpenProcessor(
Configuration configuration,
GaussianSharpenProcessor definition,
Image<TPixel> source,
Rectangle sourceRectangle,
BorderWrappingMode borderWrapModeX,
BorderWrappingMode borderWrapModeY)
: base(configuration, source, sourceRectangle) : base(configuration, source, sourceRectangle)
{ {
int kernelSize = (definition.Radius * 2) + 1; int kernelSize = (definition.Radius * 2) + 1;
this.Kernel = ConvolutionProcessorHelpers.CreateGaussianSharpenKernel(kernelSize, definition.Sigma); this.Kernel = ConvolutionProcessorHelpers.CreateGaussianSharpenKernel(kernelSize, definition.Sigma);
this.BorderWrapModeX = borderWrapModeX;
this.BorderWrapModeY = borderWrapModeY;
} }
/// <summary> /// <summary>
@ -35,10 +57,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary> /// </summary>
public float[] Kernel { get; } public float[] Kernel { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in X direction.
/// </summary>
public BorderWrappingMode BorderWrapModeX { get; }
/// <summary>
/// Gets the <see cref="BorderWrappingMode"/> to use when mapping the pixels outside of the border, in Y direction.
/// </summary>
public BorderWrappingMode BorderWrapModeY { get; }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source) protected override void OnFrameApply(ImageFrame<TPixel> source)
{ {
using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle); using var processor = new Convolution2PassProcessor<TPixel>(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
processor.Apply(source); processor.Apply(source);
} }

152
src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs

@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <param name="kernel">The convolution kernel.</param> /// <param name="kernel">The convolution kernel.</param>
/// <param name="bounds">The source bounds.</param> /// <param name="bounds">The source bounds.</param>
public void BuildSamplingOffsetMap(DenseMatrix<float> kernel, Rectangle bounds) public void BuildSamplingOffsetMap(DenseMatrix<float> kernel, Rectangle bounds)
=> this.BuildSamplingOffsetMap(kernel.Rows, kernel.Columns, bounds); => this.BuildSamplingOffsetMap(kernel.Rows, kernel.Columns, bounds, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat);
/// <summary> /// <summary>
/// Builds a map of the sampling offsets for the kernel clamped by the given bounds. /// Builds a map of the sampling offsets for the kernel clamped by the given bounds.
@ -40,6 +40,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// <param name="kernelWidth">The width (number of columns) of the convolution kernel to use.</param> /// <param name="kernelWidth">The width (number of columns) of the convolution kernel to use.</param>
/// <param name="bounds">The source bounds.</param> /// <param name="bounds">The source bounds.</param>
public void BuildSamplingOffsetMap(int kernelHeight, int kernelWidth, Rectangle bounds) public void BuildSamplingOffsetMap(int kernelHeight, int kernelWidth, Rectangle bounds)
=> this.BuildSamplingOffsetMap(kernelHeight, kernelWidth, bounds, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat);
/// <summary>
/// Builds a map of the sampling offsets for the kernel clamped by the given bounds.
/// </summary>
/// <param name="kernelHeight">The height (number of rows) of the convolution kernel to use.</param>
/// <param name="kernelWidth">The width (number of columns) of the convolution kernel to use.</param>
/// <param name="bounds">The source bounds.</param>
/// <param name="xBorderMode">The wrapping mode on the horizontal borders.</param>
/// <param name="yBorderMode">The wrapping mode on the vertical borders.</param>
public void BuildSamplingOffsetMap(int kernelHeight, int kernelWidth, Rectangle bounds, BorderWrappingMode xBorderMode, BorderWrappingMode yBorderMode)
{ {
this.yOffsets = this.allocator.Allocate<int>(bounds.Height * kernelHeight); this.yOffsets = this.allocator.Allocate<int>(bounds.Height * kernelHeight);
this.xOffsets = this.allocator.Allocate<int>(bounds.Width * kernelWidth); this.xOffsets = this.allocator.Allocate<int>(bounds.Width * kernelWidth);
@ -49,43 +60,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
int minX = bounds.X; int minX = bounds.X;
int maxX = bounds.Right - 1; int maxX = bounds.Right - 1;
int radiusY = kernelHeight >> 1; this.BuildOffsets(this.yOffsets, bounds.Height, kernelHeight, minY, maxY, yBorderMode);
int radiusX = kernelWidth >> 1; this.BuildOffsets(this.xOffsets, bounds.Width, kernelWidth, minX, maxX, xBorderMode);
// Calculate the y and x sampling offsets clamped to the given rectangle.
// While this isn't a hotpath we still dip into unsafe to avoid the span bounds
// checks as the can potentially be looping over large arrays.
Span<int> ySpan = this.yOffsets.GetSpan();
ref int ySpanBase = ref MemoryMarshal.GetReference(ySpan);
for (int row = 0; row < bounds.Height; row++)
{
int rowBase = row * kernelHeight;
for (int y = 0; y < kernelHeight; y++)
{
Unsafe.Add(ref ySpanBase, rowBase + y) = row + y + minY - radiusY;
}
}
if (kernelHeight > 1)
{
Numerics.Clamp(ySpan, minY, maxY);
}
Span<int> xSpan = this.xOffsets.GetSpan();
ref int xSpanBase = ref MemoryMarshal.GetReference(xSpan);
for (int column = 0; column < bounds.Width; column++)
{
int columnBase = column * kernelWidth;
for (int x = 0; x < kernelWidth; x++)
{
Unsafe.Add(ref xSpanBase, columnBase + x) = column + x + minX - radiusX;
}
}
if (kernelWidth > 1)
{
Numerics.Clamp(xSpan, minX, maxX);
}
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -105,5 +81,105 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
this.isDisposed = true; this.isDisposed = true;
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void BuildOffsets(IMemoryOwner<int> offsets, int boundsSize, int kernelSize, int min, int max, BorderWrappingMode borderMode)
{
int radius = kernelSize >> 1;
Span<int> span = offsets.GetSpan();
ref int spanBase = ref MemoryMarshal.GetReference(span);
for (int chunk = 0; chunk < boundsSize; chunk++)
{
int chunkBase = chunk * kernelSize;
for (int i = 0; i < kernelSize; i++)
{
Unsafe.Add(ref spanBase, chunkBase + i) = chunk + i + min - radius;
}
}
this.CorrectBorder(span, kernelSize, min, max, borderMode);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void CorrectBorder(Span<int> span, int kernelSize, int min, int max, BorderWrappingMode borderMode)
{
var affectedSize = (kernelSize >> 1) * kernelSize;
ref int spanBase = ref MemoryMarshal.GetReference(span);
if (affectedSize > 0)
{
switch (borderMode)
{
case BorderWrappingMode.Repeat:
Numerics.Clamp(span.Slice(0, affectedSize), min, max);
Numerics.Clamp(span.Slice(span.Length - affectedSize), min, max);
break;
case BorderWrappingMode.Mirror:
var min2dec = min + min - 1;
for (int i = 0; i < affectedSize; i++)
{
var value = span[i];
if (value < min)
{
span[i] = min2dec - value;
}
}
var max2inc = max + max + 1;
for (int i = span.Length - affectedSize; i < span.Length; i++)
{
var value = span[i];
if (value > max)
{
span[i] = max2inc - value;
}
}
break;
case BorderWrappingMode.Bounce:
var min2 = min + min;
for (int i = 0; i < affectedSize; i++)
{
var value = span[i];
if (value < min)
{
span[i] = min2 - value;
}
}
var max2 = max + max;
for (int i = span.Length - affectedSize; i < span.Length; i++)
{
var value = span[i];
if (value > max)
{
span[i] = max2 - value;
}
}
break;
case BorderWrappingMode.Wrap:
var diff = max - min + 1;
for (int i = 0; i < affectedSize; i++)
{
var value = span[i];
if (value < min)
{
span[i] = diff + value;
}
}
for (int i = span.Length - affectedSize; i < span.Length; i++)
{
var value = span[i];
if (value > max)
{
span[i] = value - diff;
}
}
break;
}
}
}
} }
} }

6
src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs

@ -187,12 +187,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
&& RuntimeEnvironment.IsNetCore) && RuntimeEnvironment.IsNetCore)
{ {
// There's something wrong with the JIT in .NET Core 3.1 on certain // There's something wrong with the JIT in .NET Core 3.1 on certain
// MacOSX machines so we have to use different pipelines. // macOS machines so we have to use different pipelines.
// It's: // It's:
// - Not reproducable locally // - Not reproducable locally
// - Doesn't seem to be triggered by the bulk Numerics.UnPremultiply method but by caller. // - Doesn't seem to be triggered by the bulk Numerics.UnPremultiply method but by caller.
// https://github.com/SixLabors/ImageSharp/pull/1591 // https://github.com/SixLabors/ImageSharp/pull/1591
this.InvokeMacOSX(in rows, span); this.InvokeMacOS(in rows, span);
return; return;
} }
@ -259,7 +259,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private void InvokeMacOSX(in RowInterval rows, Span<Vector4> span) private void InvokeMacOS(in RowInterval rows, Span<Vector4> span)
{ {
Matrix3x2 matrix = this.matrix; Matrix3x2 matrix = this.matrix;
TResampler sampler = this.sampler; TResampler sampler = this.sampler;

6
src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs

@ -186,12 +186,12 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
&& RuntimeEnvironment.IsNetCore) && RuntimeEnvironment.IsNetCore)
{ {
// There's something wrong with the JIT in .NET Core 3.1 on certain // There's something wrong with the JIT in .NET Core 3.1 on certain
// MacOSX machines so we have to use different pipelines. // macOS machines so we have to use different pipelines.
// It's: // It's:
// - Not reproducable locally // - Not reproducable locally
// - Doesn't seem to be triggered by the bulk Numerics.UnPremultiply method but by caller. // - Doesn't seem to be triggered by the bulk Numerics.UnPremultiply method but by caller.
// https://github.com/SixLabors/ImageSharp/pull/1591 // https://github.com/SixLabors/ImageSharp/pull/1591
this.InvokeMacOSX(in rows, span); this.InvokeMacOS(in rows, span);
return; return;
} }
@ -258,7 +258,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void InvokeMacOSX(in RowInterval rows, Span<Vector4> span) public void InvokeMacOS(in RowInterval rows, Span<Vector4> span)
{ {
Matrix4x4 matrix = this.matrix; Matrix4x4 matrix = this.matrix;
TResampler sampler = this.sampler; TResampler sampler = this.sampler;

11
tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj

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

11
tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj

@ -12,24 +12,19 @@
<!--Used to hide test project from dotnet test--> <!--Used to hide test project from dotnet test-->
<IsTestProject>false</IsTestProject> <IsTestProject>false</IsTestProject>
<EnsureNETCoreAppRuntime>false</EnsureNETCoreAppRuntime> <EnsureNETCoreAppRuntime>false</EnsureNETCoreAppRuntime>
<Configurations>Debug;Release;Debug-InnerLoop;Release-InnerLoop</Configurations> <Configurations>Debug;Release</Configurations>
<ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained> <ValidateExecutableReferencesMatchSelfContained>false</ValidateExecutableReferencesMatchSelfContained>
</PropertyGroup> </PropertyGroup>
<Choose> <Choose>
<When Condition="$(SIXLABORS_TESTING_PREVIEW) == true"> <When Condition="$(SIXLABORS_TESTING_PREVIEW) == true">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0;net5.0;netcoreapp3.1;netcoreapp2.1;net472</TargetFrameworks> <TargetFrameworks>net7.0;net6.0</TargetFrameworks>
</PropertyGroup>
</When>
<When Condition="$(Configuration.EndsWith('InnerLoop')) == true">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
</When> </When>
<Otherwise> <Otherwise>
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net5.0;netcoreapp3.1;netcoreapp2.1;net472</TargetFrameworks> <TargetFrameworks>net6.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
</Otherwise> </Otherwise>
</Choose> </Choose>

1
tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs

@ -5,7 +5,6 @@ using System;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using CommandLine; using CommandLine;

1
tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs

@ -20,6 +20,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Bmp;
namespace SixLabors.ImageSharp.Tests.Formats.Bmp namespace SixLabors.ImageSharp.Tests.Formats.Bmp
{ {
[Trait("Format", "Bmp")] [Trait("Format", "Bmp")]
[ValidateDisposedMemoryAllocations]
public class BmpDecoderTests public class BmpDecoderTests
{ {
public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector; public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Bgra32 | PixelTypes.RgbaVector;

1
tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs

@ -18,6 +18,7 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Gif namespace SixLabors.ImageSharp.Tests.Formats.Gif
{ {
[Trait("Format", "Gif")] [Trait("Format", "Gif")]
[ValidateDisposedMemoryAllocations]
public class GifDecoderTests public class GifDecoderTests
{ {
private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32;

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

@ -149,11 +149,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
// 1. AllowAll - call avx/fma implementation // 1. AllowAll - call avx/fma implementation
// 2. DisableFMA - call avx without fma implementation // 2. DisableFMA - call avx without fma implementation
// 3. DisableAvx - call sse implementation // 3. DisableAvx - call sse implementation
// 4. DisableSIMD - call Vector4 fallback implementation // 4. DisableHWIntrinsic - call Vector4 fallback implementation
FeatureTestRunner.RunWithHwIntrinsicsFeature( FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest, RunTest,
seed, seed,
HwIntrinsics.AllowAll | HwIntrinsics.DisableFMA | HwIntrinsics.DisableAVX | HwIntrinsics.DisableSIMD); HwIntrinsics.AllowAll | HwIntrinsics.DisableFMA | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
} }
// Forward transform // Forward transform
@ -200,11 +200,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
// 1. AllowAll - call avx/fma implementation // 1. AllowAll - call avx/fma implementation
// 2. DisableFMA - call avx without fma implementation // 2. DisableFMA - call avx without fma implementation
// 3. DisableAvx - call Vector4 implementation // 3. DisableAvx - call Vector4 implementation
// 4. DisableSIMD - call scalar fallback implementation // 4. DisableHWIntrinsic - call scalar fallback implementation
FeatureTestRunner.RunWithHwIntrinsicsFeature( FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest, RunTest,
seed, seed,
HwIntrinsics.AllowAll | HwIntrinsics.DisableFMA | HwIntrinsics.DisableAVX | HwIntrinsics.DisableSIMD); HwIntrinsics.AllowAll | HwIntrinsics.DisableFMA | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
} }
} }
} }

1
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs

@ -105,6 +105,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693A, TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693A,
TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693B, TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693B,
TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824C, TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824C,
TestImages.Jpeg.Issues.Fuzz.NullReferenceException2085,
}; };
private static readonly Dictionary<string, float> CustomToleranceValues = new() private static readonly Dictionary<string, float> CustomToleranceValues = new()

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

@ -179,11 +179,16 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
var testFile = TestFile.Create(imagePath); var testFile = TestFile.Create(imagePath);
using (var stream = new MemoryStream(testFile.Bytes, false)) using (var stream = new MemoryStream(testFile.Bytes, false))
{ {
IImageInfo imageInfo = useIdentify if (useIdentify)
? ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream, default) {
: decoder.Decode<Rgba32>(Configuration.Default, stream, default); IImageInfo imageInfo = ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream, default);
test(imageInfo);
test(imageInfo); }
else
{
using var img = decoder.Decode<Rgba32>(Configuration.Default, stream, default);
test(img);
}
} }
} }

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

@ -22,6 +22,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{ {
// TODO: Scatter test cases into multiple test classes // TODO: Scatter test cases into multiple test classes
[Trait("Format", "Jpg")] [Trait("Format", "Jpg")]
[ValidateDisposedMemoryAllocations]
public partial class JpegDecoderTests public partial class JpegDecoderTests
{ {
public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.Bgr24 | PixelTypes.RgbaVector; public const PixelTypes CommonNonDefaultPixelTypes = PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.Bgr24 | PixelTypes.RgbaVector;

33
tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs

@ -0,0 +1,33 @@
// 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.PixelFormats;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{
[Trait("Format", "Jpg")]
public partial class JpegEncoderTests
{
[Theory]
[WithFile(TestImages.Jpeg.Issues.ValidExifArgumentNullExceptionOnEncode, PixelTypes.Rgba32)]
public void Encode_WithValidExifProfile_DoesNotThrowException<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
Exception ex = Record.Exception(() =>
{
var encoder = new JpegEncoder();
var stream = new MemoryStream();
using Image<TPixel> image = provider.GetImage(JpegDecoder);
image.Save(stream, encoder);
});
Assert.Null(ex);
}
}
}

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

@ -20,7 +20,7 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Jpg namespace SixLabors.ImageSharp.Tests.Formats.Jpg
{ {
[Trait("Format", "Jpg")] [Trait("Format", "Jpg")]
public class JpegEncoderTests public partial class JpegEncoderTests
{ {
private static JpegEncoder JpegEncoder => new(); private static JpegEncoder JpegEncoder => new();

1
tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs

@ -11,6 +11,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Pbm;
namespace SixLabors.ImageSharp.Tests.Formats.Pbm namespace SixLabors.ImageSharp.Tests.Formats.Pbm
{ {
[Trait("Format", "Pbm")] [Trait("Format", "Pbm")]
[ValidateDisposedMemoryAllocations]
public class PbmDecoderTests public class PbmDecoderTests
{ {
[Theory] [Theory]

1
tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs

@ -19,6 +19,7 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Png namespace SixLabors.ImageSharp.Tests.Formats.Png
{ {
[Trait("Format", "Png")] [Trait("Format", "Png")]
[ValidateDisposedMemoryAllocations]
public partial class PngDecoderTests public partial class PngDecoderTests
{ {
private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32; private const PixelTypes TestPixelTypes = PixelTypes.Rgba32 | PixelTypes.RgbaVector | PixelTypes.Argb32;

8
tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs

@ -39,7 +39,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
FeatureTestRunner.RunWithHwIntrinsicsFeature( FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest, RunTest,
HwIntrinsics.DisableSIMD); HwIntrinsics.DisableHWIntrinsic);
} }
[Fact] [Fact]
@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
FeatureTestRunner.RunWithHwIntrinsicsFeature( FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest, RunTest,
HwIntrinsics.DisableSIMD); HwIntrinsics.DisableHWIntrinsic);
} }
[Fact] [Fact]
@ -137,7 +137,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
FeatureTestRunner.RunWithHwIntrinsicsFeature( FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest, RunTest,
HwIntrinsics.DisableSIMD); HwIntrinsics.DisableHWIntrinsic);
} }
[Fact] [Fact]
@ -179,7 +179,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
FeatureTestRunner.RunWithHwIntrinsicsFeature( FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest, RunTest,
HwIntrinsics.DisableSIMD); HwIntrinsics.DisableHWIntrinsic);
} }
[Fact] [Fact]

1
tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs

@ -16,6 +16,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tga;
namespace SixLabors.ImageSharp.Tests.Formats.Tga namespace SixLabors.ImageSharp.Tests.Formats.Tga
{ {
[Trait("Format", "Tga")] [Trait("Format", "Tga")]
[ValidateDisposedMemoryAllocations]
public class TgaDecoderTests public class TgaDecoderTests
{ {
private static TgaDecoder TgaDecoder => new(); private static TgaDecoder TgaDecoder => new();

4
tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs

@ -12,9 +12,9 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{ {
public abstract class TiffDecoderBaseTester public abstract class TiffDecoderBaseTester
{ {
protected static TiffDecoder TiffDecoder => new TiffDecoder(); protected static TiffDecoder TiffDecoder => new();
protected static MagickReferenceDecoder ReferenceDecoder => new MagickReferenceDecoder(); protected static MagickReferenceDecoder ReferenceDecoder => new();
protected static void TestTiffDecoder<TPixel>(TestImageProvider<TPixel> provider, IImageDecoder referenceDecoder = null, bool useExactComparer = true, float compareTolerance = 0.001f) protected static void TestTiffDecoder<TPixel>(TestImageProvider<TPixel> provider, IImageDecoder referenceDecoder = null, bool useExactComparer = true, float compareTolerance = 0.001f)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>

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

@ -14,6 +14,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Tiff;
namespace SixLabors.ImageSharp.Tests.Formats.Tiff namespace SixLabors.ImageSharp.Tests.Formats.Tiff
{ {
[Trait("Format", "Tiff")] [Trait("Format", "Tiff")]
[ValidateDisposedMemoryAllocations]
public class TiffDecoderTests : TiffDecoderBaseTester public class TiffDecoderTests : TiffDecoderBaseTester
{ {
public static readonly string[] MultiframeTestImages = Multiframes; public static readonly string[] MultiframeTestImages = Multiframes;
@ -187,6 +188,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_12Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_12Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider); where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Rgba3BitAssociatedAlpha, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_12Bit_WithAssociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
if (TestEnvironment.IsMacOS)
{
// Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though.
using Image<TPixel> image = provider.GetImage(TiffDecoder);
image.DebugSave(provider);
return;
}
// Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues.
TestTiffDecoder(provider, useExactComparer: false);
}
[Theory] [Theory]
[WithFile(Flower14BitGray, PixelTypes.Rgba32)] [WithFile(Flower14BitGray, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_14Bit_Gray<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_14Bit_Gray<TPixel>(TestImageProvider<TPixel> provider)
@ -226,6 +244,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_20Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_20Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider); where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Rgba5BitAssociatedAlpha, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_20Bit_WithAssociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
if (TestEnvironment.IsMacOS)
{
// Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though.
using Image<TPixel> image = provider.GetImage(TiffDecoder);
image.DebugSave(provider);
return;
}
// Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues.
TestTiffDecoder(provider, useExactComparer: false);
}
[Theory] [Theory]
[WithFile(FlowerRgb888Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb888Contiguous, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_24Bit<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_24Bit<TPixel>(TestImageProvider<TPixel> provider)
@ -236,6 +272,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_24Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_24Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider); where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Rgba6BitAssociatedAlpha, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_24Bit_WithAssociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
if (TestEnvironment.IsMacOS)
{
// Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though.
using Image<TPixel> image = provider.GetImage(TiffDecoder);
image.DebugSave(provider);
return;
}
// Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues.
TestTiffDecoder(provider, useExactComparer: false);
}
[Theory] [Theory]
[WithFile(Flower24BitGray, PixelTypes.Rgba32)] [WithFile(Flower24BitGray, PixelTypes.Rgba32)]
[WithFile(Flower24BitGrayLittleEndian, PixelTypes.Rgba32)] [WithFile(Flower24BitGrayLittleEndian, PixelTypes.Rgba32)]
@ -261,7 +314,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// Note: The image from MagickReferenceDecoder does not look right, maybe we are doing something wrong // Note: The image from MagickReferenceDecoder does not look right, maybe we are doing something wrong
// converting the pixel data from Magick.Net to our format with YCbCr? // converting the pixel data from Magick.NET to our format with YCbCr?
using Image<TPixel> image = provider.GetImage(); using Image<TPixel> image = provider.GetImage();
image.DebugSave(provider); image.DebugSave(provider);
} }
@ -296,6 +349,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
image.DebugSave(provider); image.DebugSave(provider);
} }
[Theory]
[WithFile(Rgba8BitAssociatedAlpha, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_32Bit_WithAssociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
if (TestEnvironment.IsMacOS)
{
// Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though.
using Image<TPixel> image = provider.GetImage(TiffDecoder);
image.DebugSave(provider);
return;
}
// Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues.
TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.004F);
}
[Theory] [Theory]
[WithFile(Flower32BitGrayPredictorBigEndian, PixelTypes.Rgba32)] [WithFile(Flower32BitGrayPredictorBigEndian, PixelTypes.Rgba32)]
[WithFile(Flower32BitGrayPredictorLittleEndian, PixelTypes.Rgba32)] [WithFile(Flower32BitGrayPredictorLittleEndian, PixelTypes.Rgba32)]
@ -318,6 +388,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_40Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_40Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider); where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Rgba10BitAssociatedAlphaBigEndian, PixelTypes.Rgba32)]
[WithFile(Rgba10BitAssociatedAlphaLittleEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_40Bit_WithAssociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
if (TestEnvironment.IsMacOS)
{
// Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though.
using Image<TPixel> image = provider.GetImage(TiffDecoder);
image.DebugSave(provider);
return;
}
// Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues.
TestTiffDecoder(provider, useExactComparer: false);
}
[Theory] [Theory]
[WithFile(FlowerRgb141414Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb141414Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerRgb141414Planar, PixelTypes.Rgba32)] [WithFile(FlowerRgb141414Planar, PixelTypes.Rgba32)]
@ -339,6 +427,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_48Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_48Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider); where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Rgba12BitAssociatedAlphaBigEndian, PixelTypes.Rgba32)]
[WithFile(Rgba12BitAssociatedAlphaLittleEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_48Bit_WithAssociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
if (TestEnvironment.IsMacOS)
{
// Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though.
using Image<TPixel> image = provider.GetImage(TiffDecoder);
image.DebugSave(provider);
return;
}
// Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues.
TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.0002f);
}
[Theory] [Theory]
[WithFile(FlowerRgb161616PredictorBigEndian, PixelTypes.Rgba32)] [WithFile(FlowerRgb161616PredictorBigEndian, PixelTypes.Rgba32)]
[WithFile(FlowerRgb161616PredictorLittleEndian, PixelTypes.Rgba32)] [WithFile(FlowerRgb161616PredictorLittleEndian, PixelTypes.Rgba32)]
@ -351,6 +457,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_56Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider) public void TiffDecoder_CanDecode_56Bit_WithUnassociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider); where TPixel : unmanaged, IPixel<TPixel> => TestTiffDecoder(provider);
[Theory]
[WithFile(Rgba14BitAssociatedAlphaBigEndian, PixelTypes.Rgba32)]
[WithFile(Rgba14BitAssociatedAlphaLittleEndian, PixelTypes.Rgba32)]
public void TiffDecoder_CanDecode_56Bit_WithAssociatedAlpha<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
if (TestEnvironment.IsMacOS)
{
// Only debug save on OSX: For some reason the reference image has a difference of 50%. The imagesharp output file looks ok though.
using Image<TPixel> image = provider.GetImage(TiffDecoder);
image.DebugSave(provider);
return;
}
// Note: Using tolerant comparer here, because there is a small difference to the reference decoder probably due to floating point rounding issues.
TestTiffDecoder(provider, useExactComparer: false, compareTolerance: 0.0002f);
}
[Theory] [Theory]
[WithFile(FlowerRgb242424Contiguous, PixelTypes.Rgba32)] [WithFile(FlowerRgb242424Contiguous, PixelTypes.Rgba32)]
[WithFile(FlowerRgb242424ContiguousLittleEndian, PixelTypes.Rgba32)] [WithFile(FlowerRgb242424ContiguousLittleEndian, PixelTypes.Rgba32)]

1
tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs

@ -15,6 +15,7 @@ using static SixLabors.ImageSharp.Tests.TestImages.Webp;
namespace SixLabors.ImageSharp.Tests.Formats.Webp namespace SixLabors.ImageSharp.Tests.Formats.Webp
{ {
[Trait("Format", "Webp")] [Trait("Format", "Webp")]
[ValidateDisposedMemoryAllocations]
public class WebpDecoderTests public class WebpDecoderTests
{ {
private static WebpDecoder WebpDecoder => new(); private static WebpDecoder WebpDecoder => new();

13
tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors. // Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System;
using System.IO; using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats.Webp; using SixLabors.ImageSharp.Formats.Webp;
@ -170,5 +171,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
Assert.NotNull(actualExif); Assert.NotNull(actualExif);
Assert.Equal(expectedExif.Values.Count, actualExif.Values.Count); Assert.Equal(expectedExif.Values.Count, actualExif.Values.Count);
} }
[Theory]
[WithFile(TestImages.Webp.Lossy.WithExifNotEnoughData, PixelTypes.Rgba32)]
public void WebpDecoder_IgnoresInvalidExifChunk<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : unmanaged, IPixel<TPixel>
{
Exception ex = Record.Exception(() =>
{
using Image<TPixel> image = provider.GetImage();
});
Assert.Null(ex);
}
} }
} }

2
tests/ImageSharp.Tests/Helpers/RuntimeEnvironmentTests.cs

@ -28,7 +28,7 @@ namespace SixLabors.ImageSharp.Tests.Helpers
{ {
Assert.True(RuntimeEnvironment.IsOSPlatform(OSPlatform.Linux)); Assert.True(RuntimeEnvironment.IsOSPlatform(OSPlatform.Linux));
} }
else if (TestEnvironment.IsOSX) else if (TestEnvironment.IsMacOS)
{ {
Assert.True(RuntimeEnvironment.IsOSPlatform(OSPlatform.OSX)); Assert.True(RuntimeEnvironment.IsOSPlatform(OSPlatform.OSX));
} }

1
tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs

@ -14,6 +14,7 @@ namespace SixLabors.ImageSharp.Tests
[Theory] [Theory]
[InlineData(false)] [InlineData(false)]
[InlineData(true)] [InlineData(true)]
[ValidateDisposedMemoryAllocations]
public void FromPixels(bool useSpan) public void FromPixels(bool useSpan)
{ {
Rgba32[] data = { Color.Black, Color.White, Color.White, Color.Black, }; Rgba32[] data = { Color.Black, Color.White, Color.White, Color.Black, };

2
tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs

@ -55,6 +55,8 @@ namespace SixLabors.ImageSharp.Tests
static void RunTest(string formatInner) static void RunTest(string formatInner)
{ {
using IDisposable mem = MemoryAllocatorValidator.MonitorAllocations();
Configuration configuration = Configuration.Default.Clone(); Configuration configuration = Configuration.Default.Clone();
configuration.PreferContiguousImageBuffers = true; configuration.PreferContiguousImageBuffers = true;
IImageEncoder encoder = configuration.ImageFormatsManager.FindEncoder( IImageEncoder encoder = configuration.ImageFormatsManager.FindEncoder(

12
tests/ImageSharp.Tests/ImageSharp.Tests.csproj

@ -6,23 +6,18 @@
<AssemblyName>SixLabors.ImageSharp.Tests</AssemblyName> <AssemblyName>SixLabors.ImageSharp.Tests</AssemblyName>
<Platforms>AnyCPU;x64;x86</Platforms> <Platforms>AnyCPU;x64;x86</Platforms>
<RootNamespace>SixLabors.ImageSharp.Tests</RootNamespace> <RootNamespace>SixLabors.ImageSharp.Tests</RootNamespace>
<Configurations>Debug;Release;Debug-InnerLoop;Release-InnerLoop</Configurations> <Configurations>Debug;Release</Configurations>
</PropertyGroup> </PropertyGroup>
<Choose> <Choose>
<When Condition="$(SIXLABORS_TESTING_PREVIEW) == true"> <When Condition="$(SIXLABORS_TESTING_PREVIEW) == true">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0;net5.0;netcoreapp3.1;netcoreapp2.1;net472</TargetFrameworks> <TargetFrameworks>net7.0;net6.0</TargetFrameworks>
</PropertyGroup>
</When>
<When Condition="$(Configuration.EndsWith('InnerLoop')) == true">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
</When> </When>
<Otherwise> <Otherwise>
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net5.0;netcoreapp3.1;netcoreapp2.1;net472</TargetFrameworks> <TargetFrameworks>net6.0</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
</Otherwise> </Otherwise>
</Choose> </Choose>
@ -47,7 +42,6 @@
<PackageReference Include="runtime.osx.10.10-x64.CoreCompat.System.Drawing" Condition="'$(IsOSX)'=='true'" /> <PackageReference Include="runtime.osx.10.10-x64.CoreCompat.System.Drawing" Condition="'$(IsOSX)'=='true'" />
<PackageReference Include="SharpZipLib" /> <PackageReference Include="SharpZipLib" />
<PackageReference Include="System.Drawing.Common" /> <PackageReference Include="System.Drawing.Common" />
<PackageReference Include="System.IO.Compression" Condition="'$(TargetFramework)' == 'net472'" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

6
tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs

@ -58,11 +58,11 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators
[Collection(nameof(NonParallelCollection))] [Collection(nameof(NonParallelCollection))]
public class NonParallel public class NonParallel
{ {
public static readonly bool IsNotMacOs = !TestEnvironment.IsOSX; public static readonly bool IsNotMacOS = !TestEnvironment.IsMacOS;
// TODO: Investigate failures on MacOS. All handles are released after GC. // TODO: Investigate failures on macOS. All handles are released after GC.
// (It seems to happen more consistently on .NET 6.) // (It seems to happen more consistently on .NET 6.)
[ConditionalFact(nameof(IsNotMacOs))] [ConditionalFact(nameof(IsNotMacOS))]
public void MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed() public void MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed()
{ {
if (!TestEnvironment.RunsOnCI) if (!TestEnvironment.RunsOnCI)

4
tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs

@ -245,9 +245,9 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators
cleanup.Register(b1); cleanup.Register(b1);
} }
public static readonly bool IsNotMacOS = !TestEnvironment.IsOSX; public static readonly bool IsNotMacOS = !TestEnvironment.IsMacOS;
// TODO: Investigate MacOS failures // TODO: Investigate macOS failures
[ConditionalTheory(nameof(IsNotMacOS))] [ConditionalTheory(nameof(IsNotMacOS))]
[InlineData(false)] [InlineData(false)]
[InlineData(true)] [InlineData(true)]

8
tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs

@ -259,9 +259,9 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators
[InlineData(1200)] // Group of two UniformUnmanagedMemoryPool buffers [InlineData(1200)] // Group of two UniformUnmanagedMemoryPool buffers
public void AllocateMemoryGroup_Finalization_ReturnsToPool(int length) public void AllocateMemoryGroup_Finalization_ReturnsToPool(int length)
{ {
if (TestEnvironment.IsOSX) if (TestEnvironment.IsMacOS)
{ {
// Skip on OSX: https://github.com/SixLabors/ImageSharp/issues/1887 // Skip on macOS: https://github.com/SixLabors/ImageSharp/issues/1887
return; return;
} }
@ -321,9 +321,9 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators
[InlineData(600)] // Group of single UniformUnmanagedMemoryPool buffer [InlineData(600)] // Group of single UniformUnmanagedMemoryPool buffer
public void AllocateSingleMemoryOwner_Finalization_ReturnsToPool(int length) public void AllocateSingleMemoryOwner_Finalization_ReturnsToPool(int length)
{ {
if (TestEnvironment.IsOSX) if (TestEnvironment.IsMacOS)
{ {
// Skip on OSX: https://github.com/SixLabors/ImageSharp/issues/1887 // Skip on macOS: https://github.com/SixLabors/ImageSharp/issues/1887
return; return;
} }

22
tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs

@ -13,13 +13,13 @@ namespace SixLabors.ImageSharp.Tests.Memory
{ {
public class SwapOrCopyContent public class SwapOrCopyContent
{ {
private readonly TestMemoryAllocator MemoryAllocator = new TestMemoryAllocator(); private readonly TestMemoryAllocator memoryAllocator = new TestMemoryAllocator();
[Fact] [Fact]
public void SwapOrCopyContent_WhenBothAllocated() public void SwapOrCopyContent_WhenBothAllocated()
{ {
using (Buffer2D<int> a = this.MemoryAllocator.Allocate2D<int>(10, 5, AllocationOptions.Clean)) using (Buffer2D<int> a = this.memoryAllocator.Allocate2D<int>(10, 5, AllocationOptions.Clean))
using (Buffer2D<int> b = this.MemoryAllocator.Allocate2D<int>(3, 7, AllocationOptions.Clean)) using (Buffer2D<int> b = this.memoryAllocator.Allocate2D<int>(3, 7, AllocationOptions.Clean))
{ {
a[1, 3] = 666; a[1, 3] = 666;
b[1, 3] = 444; b[1, 3] = 444;
@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
using var destData = MemoryGroup<int>.Wrap(new int[100]); using var destData = MemoryGroup<int>.Wrap(new int[100]);
using var dest = new Buffer2D<int>(destData, 10, 10); using var dest = new Buffer2D<int>(destData, 10, 10);
using (Buffer2D<int> source = this.MemoryAllocator.Allocate2D<int>(10, 10, AllocationOptions.Clean)) using (Buffer2D<int> source = this.memoryAllocator.Allocate2D<int>(10, 10, AllocationOptions.Clean))
{ {
source[0, 0] = 1; source[0, 0] = 1;
dest[0, 0] = 2; dest[0, 0] = 2;
@ -68,9 +68,9 @@ namespace SixLabors.ImageSharp.Tests.Memory
[Fact] [Fact]
public void WhenBothAreMemoryOwners_ShouldSwap() public void WhenBothAreMemoryOwners_ShouldSwap()
{ {
this.MemoryAllocator.BufferCapacityInBytes = sizeof(int) * 50; this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * 50;
using Buffer2D<int> a = this.MemoryAllocator.Allocate2D<int>(48, 2); using Buffer2D<int> a = this.memoryAllocator.Allocate2D<int>(48, 2);
using Buffer2D<int> b = this.MemoryAllocator.Allocate2D<int>(50, 2); using Buffer2D<int> b = this.memoryAllocator.Allocate2D<int>(50, 2);
Memory<int> a0 = a.FastMemoryGroup[0]; Memory<int> a0 = a.FastMemoryGroup[0];
Memory<int> a1 = a.FastMemoryGroup[1]; Memory<int> a1 = a.FastMemoryGroup[1];
@ -90,8 +90,8 @@ namespace SixLabors.ImageSharp.Tests.Memory
[Fact] [Fact]
public void WhenBothAreMemoryOwners_ShouldReplaceViews() public void WhenBothAreMemoryOwners_ShouldReplaceViews()
{ {
using Buffer2D<int> a = this.MemoryAllocator.Allocate2D<int>(100, 1); using Buffer2D<int> a = this.memoryAllocator.Allocate2D<int>(100, 1);
using Buffer2D<int> b = this.MemoryAllocator.Allocate2D<int>(100, 2); using Buffer2D<int> b = this.memoryAllocator.Allocate2D<int>(100, 2);
a.FastMemoryGroup[0].Span[42] = 1; a.FastMemoryGroup[0].Span[42] = 1;
b.FastMemoryGroup[0].Span[33] = 2; b.FastMemoryGroup[0].Span[33] = 2;
@ -121,7 +121,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
using var destOwner = new TestMemoryManager<Rgba32>(data); using var destOwner = new TestMemoryManager<Rgba32>(data);
using var dest = new Buffer2D<Rgba32>(MemoryGroup<Rgba32>.Wrap(destOwner.Memory), 21, 1); using var dest = new Buffer2D<Rgba32>(MemoryGroup<Rgba32>.Wrap(destOwner.Memory), 21, 1);
using Buffer2D<Rgba32> source = this.MemoryAllocator.Allocate2D<Rgba32>(21, 1); using Buffer2D<Rgba32> source = this.memoryAllocator.Allocate2D<Rgba32>(21, 1);
source.FastMemoryGroup[0].Span[10] = color; source.FastMemoryGroup[0].Span[10] = color;
@ -145,7 +145,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
using var destOwner = new TestMemoryManager<Rgba32>(data); using var destOwner = new TestMemoryManager<Rgba32>(data);
using var dest = new Buffer2D<Rgba32>(MemoryGroup<Rgba32>.Wrap(destOwner.Memory), 21, 1); using var dest = new Buffer2D<Rgba32>(MemoryGroup<Rgba32>.Wrap(destOwner.Memory), 21, 1);
using Buffer2D<Rgba32> source = this.MemoryAllocator.Allocate2D<Rgba32>(22, 1); using Buffer2D<Rgba32> source = this.memoryAllocator.Allocate2D<Rgba32>(22, 1);
source.FastMemoryGroup[0].Span[10] = color; source.FastMemoryGroup[0].Span[10] = color;

77
tests/ImageSharp.Tests/MemoryAllocatorValidator.cs

@ -0,0 +1,77 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Threading;
using SixLabors.ImageSharp.Diagnostics;
using Xunit;
namespace SixLabors.ImageSharp.Tests
{
public static class MemoryAllocatorValidator
{
private static readonly AsyncLocal<TestMemoryDiagnostics> LocalInstance = new();
public static bool MonitoringAllocations => LocalInstance.Value != null;
static MemoryAllocatorValidator()
{
MemoryDiagnostics.MemoryAllocated += MemoryDiagnostics_MemoryAllocated;
MemoryDiagnostics.MemoryReleased += MemoryDiagnostics_MemoryReleased;
}
private static void MemoryDiagnostics_MemoryReleased()
{
TestMemoryDiagnostics backing = LocalInstance.Value;
if (backing != null)
{
backing.TotalRemainingAllocated--;
}
}
private static void MemoryDiagnostics_MemoryAllocated()
{
TestMemoryDiagnostics backing = LocalInstance.Value;
if (backing != null)
{
backing.TotalAllocated++;
backing.TotalRemainingAllocated++;
}
}
public static TestMemoryDiagnostics MonitorAllocations()
{
var diag = new TestMemoryDiagnostics();
LocalInstance.Value = diag;
return diag;
}
public static void StopMonitoringAllocations() => LocalInstance.Value = null;
public static void ValidateAllocations(int expectedAllocationCount = 0)
=> LocalInstance.Value?.Validate(expectedAllocationCount);
public class TestMemoryDiagnostics : IDisposable
{
public int TotalAllocated { get; set; }
public int TotalRemainingAllocated { get; set; }
public void Validate(int expectedAllocationCount)
{
var count = this.TotalRemainingAllocated;
var pass = expectedAllocationCount == count;
Assert.True(pass, $"Expected a {expectedAllocationCount} undisposed buffers but found {count}");
}
public void Dispose()
{
this.Validate(0);
if (LocalInstance.Value == this)
{
StopMonitoringAllocations();
}
}
}
}
}

7
tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs

@ -354,10 +354,15 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif
TestProfile(profile); TestProfile(profile);
using Image<Rgba32> thumbnail = profile.CreateThumbnail<Rgba32>(); using Image thumbnail = profile.CreateThumbnail();
Assert.NotNull(thumbnail); Assert.NotNull(thumbnail);
Assert.Equal(256, thumbnail.Width); Assert.Equal(256, thumbnail.Width);
Assert.Equal(170, thumbnail.Height); Assert.Equal(170, thumbnail.Height);
using Image<Rgba32> genericThumbnail = profile.CreateThumbnail<Rgba32>();
Assert.NotNull(genericThumbnail);
Assert.Equal(256, genericThumbnail.Width);
Assert.Equal(170, genericThumbnail.Height);
} }
[Fact] [Fact]

421
tests/ImageSharp.Tests/Processing/Convolution/KernelSamplingMapTest.cs

@ -0,0 +1,421 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.Processing.Processors.Convolution;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Processing.Convolution
{
[Trait("Category", "Processors")]
public class KernelSamplingMapTest
{
[Fact]
public void KernalSamplingMap_Kernel5Image7x7RepeatBorder()
{
var kernelSize = new Size(5, 5);
var bounds = new Rectangle(0, 0, 7, 7);
var mode = BorderWrappingMode.Repeat;
int[] expected =
{
0, 0, 0, 1, 2,
0, 0, 1, 2, 3,
0, 1, 2, 3, 4,
1, 2, 3, 4, 5,
2, 3, 4, 5, 6,
3, 4, 5, 6, 6,
4, 5, 6, 6, 6,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel5Image7x7BounceBorder()
{
var kernelSize = new Size(5, 5);
var bounds = new Rectangle(0, 0, 7, 7);
var mode = BorderWrappingMode.Bounce;
int[] expected =
{
2, 1, 0, 1, 2,
1, 0, 1, 2, 3,
0, 1, 2, 3, 4,
1, 2, 3, 4, 5,
2, 3, 4, 5, 6,
3, 4, 5, 6, 5,
4, 5, 6, 5, 4,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel5Image7x7MirrorBorder()
{
var kernelSize = new Size(5, 5);
var bounds = new Rectangle(0, 0, 7, 7);
var mode = BorderWrappingMode.Mirror;
int[] expected =
{
1, 0, 0, 1, 2,
0, 0, 1, 2, 3,
0, 1, 2, 3, 4,
1, 2, 3, 4, 5,
2, 3, 4, 5, 6,
3, 4, 5, 6, 6,
4, 5, 6, 6, 5,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel5Image7x7WrapBorder()
{
var kernelSize = new Size(5, 5);
var bounds = new Rectangle(0, 0, 7, 7);
var mode = BorderWrappingMode.Wrap;
int[] expected =
{
5, 6, 0, 1, 2,
6, 0, 1, 2, 3,
0, 1, 2, 3, 4,
1, 2, 3, 4, 5,
2, 3, 4, 5, 6,
3, 4, 5, 6, 0,
4, 5, 6, 0, 1,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel5Image9x9BounceBorder()
{
var kernelSize = new Size(5, 5);
var bounds = new Rectangle(1, 1, 9, 9);
var mode = BorderWrappingMode.Bounce;
int[] expected =
{
3, 2, 1, 2, 3,
2, 1, 2, 3, 4,
1, 2, 3, 4, 5,
2, 3, 4, 5, 6,
3, 4, 5, 6, 7,
4, 5, 6, 7, 8,
5, 6, 7, 8, 9,
6, 7, 8, 9, 8,
7, 8, 9, 8, 7,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel5Image9x9MirrorBorder()
{
var kernelSize = new Size(5, 5);
var bounds = new Rectangle(1, 1, 9, 9);
var mode = BorderWrappingMode.Mirror;
int[] expected =
{
2, 1, 1, 2, 3,
1, 1, 2, 3, 4,
1, 2, 3, 4, 5,
2, 3, 4, 5, 6,
3, 4, 5, 6, 7,
4, 5, 6, 7, 8,
5, 6, 7, 8, 9,
6, 7, 8, 9, 9,
7, 8, 9, 9, 8,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel5Image9x9WrapBorder()
{
var kernelSize = new Size(5, 5);
var bounds = new Rectangle(1, 1, 9, 9);
var mode = BorderWrappingMode.Wrap;
int[] expected =
{
8, 9, 1, 2, 3,
9, 1, 2, 3, 4,
1, 2, 3, 4, 5,
2, 3, 4, 5, 6,
3, 4, 5, 6, 7,
4, 5, 6, 7, 8,
5, 6, 7, 8, 9,
6, 7, 8, 9, 1,
7, 8, 9, 1, 2,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel5Image7x7RepeatBorderTile()
{
var kernelSize = new Size(5, 5);
var bounds = new Rectangle(2, 2, 7, 7);
var mode = BorderWrappingMode.Repeat;
int[] expected =
{
2, 2, 2, 3, 4,
2, 2, 3, 4, 5,
2, 3, 4, 5, 6,
3, 4, 5, 6, 7,
4, 5, 6, 7, 8,
5, 6, 7, 8, 8,
6, 7, 8, 8, 8,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel5Image7x7BounceBorderTile()
{
var kernelSize = new Size(5, 5);
var bounds = new Rectangle(2, 2, 7, 7);
var mode = BorderWrappingMode.Bounce;
int[] expected =
{
4, 3, 2, 3, 4,
3, 2, 3, 4, 5,
2, 3, 4, 5, 6,
3, 4, 5, 6, 7,
4, 5, 6, 7, 8,
5, 6, 7, 8, 7,
6, 7, 8, 7, 6,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel5Image7x7MirrorBorderTile()
{
var kernelSize = new Size(5, 5);
var bounds = new Rectangle(2, 2, 7, 7);
var mode = BorderWrappingMode.Mirror;
int[] expected =
{
3, 2, 2, 3, 4,
2, 2, 3, 4, 5,
2, 3, 4, 5, 6,
3, 4, 5, 6, 7,
4, 5, 6, 7, 8,
5, 6, 7, 8, 8,
6, 7, 8, 8, 7,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel5Image7x7WrapBorderTile()
{
var kernelSize = new Size(5, 5);
var bounds = new Rectangle(2, 2, 7, 7);
var mode = BorderWrappingMode.Wrap;
int[] expected =
{
7, 8, 2, 3, 4,
8, 2, 3, 4, 5,
2, 3, 4, 5, 6,
3, 4, 5, 6, 7,
4, 5, 6, 7, 8,
5, 6, 7, 8, 2,
6, 7, 8, 2, 3,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel3Image7x7RepeatBorder()
{
var kernelSize = new Size(3, 3);
var bounds = new Rectangle(0, 0, 7, 7);
var mode = BorderWrappingMode.Repeat;
int[] expected =
{
0, 0, 1,
0, 1, 2,
1, 2, 3,
2, 3, 4,
3, 4, 5,
4, 5, 6,
5, 6, 6,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel3Image7x7BounceBorder()
{
var kernelSize = new Size(3, 3);
var bounds = new Rectangle(0, 0, 7, 7);
var mode = BorderWrappingMode.Bounce;
int[] expected =
{
1, 0, 1,
0, 1, 2,
1, 2, 3,
2, 3, 4,
3, 4, 5,
4, 5, 6,
5, 6, 5,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel3Image7x7MirrorBorder()
{
var kernelSize = new Size(3, 3);
var bounds = new Rectangle(0, 0, 7, 7);
var mode = BorderWrappingMode.Mirror;
int[] expected =
{
0, 0, 1,
0, 1, 2,
1, 2, 3,
2, 3, 4,
3, 4, 5,
4, 5, 6,
5, 6, 6,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel3Image7x7WrapBorder()
{
var kernelSize = new Size(3, 3);
var bounds = new Rectangle(0, 0, 7, 7);
var mode = BorderWrappingMode.Wrap;
int[] expected =
{
6, 0, 1,
0, 1, 2,
1, 2, 3,
2, 3, 4,
3, 4, 5,
4, 5, 6,
5, 6, 0,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel3Image7x7RepeatBorderTile()
{
var kernelSize = new Size(3, 3);
var bounds = new Rectangle(2, 2, 7, 7);
var mode = BorderWrappingMode.Repeat;
int[] expected =
{
2, 2, 3,
2, 3, 4,
3, 4, 5,
4, 5, 6,
5, 6, 7,
6, 7, 8,
7, 8, 8,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel3Image7BounceBorderTile()
{
var kernelSize = new Size(3, 3);
var bounds = new Rectangle(2, 2, 7, 7);
var mode = BorderWrappingMode.Bounce;
int[] expected =
{
3, 2, 3,
2, 3, 4,
3, 4, 5,
4, 5, 6,
5, 6, 7,
6, 7, 8,
7, 8, 7,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel3Image7MirrorBorderTile()
{
var kernelSize = new Size(3, 3);
var bounds = new Rectangle(2, 2, 7, 7);
var mode = BorderWrappingMode.Mirror;
int[] expected =
{
2, 2, 3,
2, 3, 4,
3, 4, 5,
4, 5, 6,
5, 6, 7,
6, 7, 8,
7, 8, 8,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel3Image7x7WrapBorderTile()
{
var kernelSize = new Size(3, 3);
var bounds = new Rectangle(2, 2, 7, 7);
var mode = BorderWrappingMode.Wrap;
int[] expected =
{
8, 2, 3,
2, 3, 4,
3, 4, 5,
4, 5, 6,
5, 6, 7,
6, 7, 8,
7, 8, 2,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, expected, expected);
}
[Fact]
public void KernalSamplingMap_Kernel3Image7x5WrapBorderTile()
{
var kernelSize = new Size(3, 3);
var bounds = new Rectangle(2, 2, 7, 5);
var mode = BorderWrappingMode.Wrap;
int[] xExpected =
{
8, 2, 3,
2, 3, 4,
3, 4, 5,
4, 5, 6,
5, 6, 7,
6, 7, 8,
7, 8, 2,
};
int[] yExpected =
{
6, 2, 3,
2, 3, 4,
3, 4, 5,
4, 5, 6,
5, 6, 2,
};
this.AssertOffsets(kernelSize, bounds, mode, mode, xExpected, yExpected);
}
private void AssertOffsets(Size kernelSize, Rectangle bounds, BorderWrappingMode xBorderMode, BorderWrappingMode yBorderMode, int[] xExpected, int[] yExpected)
{
// Arrange
var map = new KernelSamplingMap(Configuration.Default.MemoryAllocator);
// Act
map.BuildSamplingOffsetMap(kernelSize.Height, kernelSize.Width, bounds, xBorderMode, yBorderMode);
// Assert
var xOffsets = map.GetColumnOffsetSpan().ToArray();
Assert.Equal(xExpected, xOffsets);
var yOffsets = map.GetRowOffsetSpan().ToArray();
Assert.Equal(yExpected, yOffsets);
}
}
}

2
tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs

@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
}; };
private static readonly ImageComparer ValidatorComparer = private static readonly ImageComparer ValidatorComparer =
ImageComparer.TolerantPercentage(TestEnvironment.IsOSX && TestEnvironment.RunsOnCI ? 0.26F : 0.07F); ImageComparer.TolerantPercentage(TestEnvironment.IsMacOS && TestEnvironment.RunsOnCI ? 0.26F : 0.07F);
[Fact] [Fact]
public void Resize_PixelAgnostic() public void Resize_PixelAgnostic()

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

Loading…
Cancel
Save