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

8
.github/workflows/code-coverage.yml

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

18
ImageSharp.sln

@ -654,43 +654,25 @@ Global
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug-InnerLoop|Any CPU = Debug-InnerLoop|Any CPU
Release|Any CPU = Release|Any CPU
Release-InnerLoop|Any CPU = Release-InnerLoop|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{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-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.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.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.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.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.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.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.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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

1
src/Directory.Build.props

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

21
src/ImageSharp/Diagnostics/MemoryDiagnostics.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Threading;
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>
/// Gets a value indicating the total number of memory resource objects leaked to the finalizer.
/// </summary>
@ -54,11 +65,17 @@ namespace SixLabors.ImageSharp.Diagnostics
internal static bool UndisposedAllocationSubscribed => Volatile.Read(ref undisposedAllocationSubscriptionCounter) > 0;
internal static void IncrementTotalUndisposedAllocationCount() =>
internal static void IncrementTotalUndisposedAllocationCount()
{
Interlocked.Increment(ref totalUndisposedAllocationCount);
MemoryAllocated?.Invoke();
}
internal static void DecrementTotalUndisposedAllocationCount() =>
internal static void DecrementTotalUndisposedAllocationCount()
{
Interlocked.Decrement(ref totalUndisposedAllocationCount);
MemoryReleased?.Invoke();
}
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)
where TPixel : unmanaged, IPixel<TPixel>
{
Image<TPixel> image = null;
try
{
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();
@ -193,8 +194,14 @@ namespace SixLabors.ImageSharp.Formats.Bmp
}
catch (IndexOutOfRangeException e)
{
image?.Dispose();
throw new ImageFormatException("Bitmap does not have a valid format.", e);
}
catch
{
image?.Dispose();
throw;
}
}
/// <inheritdoc />
@ -323,12 +330,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref colors[colorIdx * 4]));
break;
case RleSkippedPixelHandling.Transparent:
color.FromVector4(Vector4.Zero);
color.FromScaledVector4(Vector4.Zero);
break;
// Default handling for skipped pixels is black (which is what System.Drawing is also doing).
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;
}
}
@ -395,12 +402,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
color.FromBgr24(Unsafe.As<byte, Bgr24>(ref bufferSpan[idx]));
break;
case RleSkippedPixelHandling.Transparent:
color.FromVector4(Vector4.Zero);
color.FromScaledVector4(Vector4.Zero);
break;
// Default handling for skipped pixels is black (which is what System.Drawing is also doing).
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;
}
}
@ -1127,7 +1134,7 @@ namespace SixLabors.ImageSharp.Formats.Bmp
g * invMaxValueGreen,
b * invMaxValueBlue,
alpha);
color.FromVector4(vector4);
color.FromScaledVector4(vector4);
}
else
{

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

@ -221,7 +221,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
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);
}
@ -231,7 +235,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
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);
if (this.imageDescriptor.Height == 0 || this.imageDescriptor.Width == 0)
@ -245,7 +253,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
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);
}

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/>
@ -210,6 +212,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.rgbBuffer?.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.QuantizationTables = new Block8x8F[4];
this.scanDecoder = huffmanScanDecoder;
if (tableBytes.Length < 4)
{
JpegThrowHelper.ThrowInvalidImageContentException("Not enough data to read marker");
}
using var ms = new MemoryStream(tableBytes);
using var stream = new BufferedReadStream(this.Configuration, ms);
// Check for the Start Of Image marker.
stream.Read(this.markerBuffer, 0, 2);
int bytesRead = stream.Read(this.markerBuffer, 0, 2);
var fileMarker = new JpegFileMarker(this.markerBuffer[1], 0);
if (fileMarker.Marker != JpegConstants.Markers.SOI)
{
@ -240,16 +246,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
// Read next marker.
stream.Read(this.markerBuffer, 0, 2);
byte marker = this.markerBuffer[1];
fileMarker = new JpegFileMarker(marker, (int)stream.Position - 2);
bytesRead = stream.Read(this.markerBuffer, 0, 2);
fileMarker = new JpegFileMarker(this.markerBuffer[1], (int)stream.Position - 2);
while (fileMarker.Marker != JpegConstants.Markers.EOI || (fileMarker.Marker == JpegConstants.Markers.EOI && fileMarker.Invalid))
{
if (!fileMarker.Invalid)
{
// Get the marker length.
int remaining = this.ReadUint16(stream) - 2;
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)
{
@ -259,13 +272,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegConstants.Markers.RST7:
break;
case JpegConstants.Markers.DHT:
this.ProcessDefineHuffmanTablesMarker(stream, remaining);
this.ProcessDefineHuffmanTablesMarker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.DQT:
this.ProcessDefineQuantizationTablesMarker(stream, remaining);
this.ProcessDefineQuantizationTablesMarker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.DRI:
this.ProcessDefineRestartIntervalMarker(stream, remaining);
this.ProcessDefineRestartIntervalMarker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.EOI:
return;
@ -273,7 +286,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
}
// 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);
}
}
@ -315,14 +333,22 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (!fileMarker.Invalid)
{
// 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)
{
case JpegConstants.Markers.SOF0:
case JpegConstants.Markers.SOF1:
case JpegConstants.Markers.SOF2:
this.ProcessStartOfFrameMarker(stream, remaining, fileMarker, metadataOnly);
this.ProcessStartOfFrameMarker(stream, markerContentByteSize, fileMarker, metadataOnly);
break;
case JpegConstants.Markers.SOF5:
@ -350,7 +376,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegConstants.Markers.SOS:
if (!metadataOnly)
{
this.ProcessStartOfScanMarker(stream, remaining);
this.ProcessStartOfScanMarker(stream, markerContentByteSize);
break;
}
else
@ -364,41 +390,41 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
if (metadataOnly)
{
stream.Skip(remaining);
stream.Skip(markerContentByteSize);
}
else
{
this.ProcessDefineHuffmanTablesMarker(stream, remaining);
this.ProcessDefineHuffmanTablesMarker(stream, markerContentByteSize);
}
break;
case JpegConstants.Markers.DQT:
this.ProcessDefineQuantizationTablesMarker(stream, remaining);
this.ProcessDefineQuantizationTablesMarker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.DRI:
if (metadataOnly)
{
stream.Skip(remaining);
stream.Skip(markerContentByteSize);
}
else
{
this.ProcessDefineRestartIntervalMarker(stream, remaining);
this.ProcessDefineRestartIntervalMarker(stream, markerContentByteSize);
}
break;
case JpegConstants.Markers.APP0:
this.ProcessApplicationHeaderMarker(stream, remaining);
this.ProcessApplicationHeaderMarker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.APP1:
this.ProcessApp1Marker(stream, remaining);
this.ProcessApp1Marker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.APP2:
this.ProcessApp2Marker(stream, remaining);
this.ProcessApp2Marker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.APP3:
@ -411,20 +437,20 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
case JpegConstants.Markers.APP10:
case JpegConstants.Markers.APP11:
case JpegConstants.Markers.APP12:
stream.Skip(remaining);
stream.Skip(markerContentByteSize);
break;
case JpegConstants.Markers.APP13:
this.ProcessApp13Marker(stream, remaining);
this.ProcessApp13Marker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.APP14:
this.ProcessApp14Marker(stream, remaining);
this.ProcessApp14Marker(stream, markerContentByteSize);
break;
case JpegConstants.Markers.APP15:
case JpegConstants.Markers.COM:
stream.Skip(remaining);
stream.Skip(markerContentByteSize);
break;
case JpegConstants.Markers.DAC:
@ -722,7 +748,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
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);
remaining -= remainingXmpMarkerBytes;
if (ProfileResolver.IsProfile(this.temp, ProfileResolver.XmpMarker))
@ -1260,7 +1293,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
int selectorsBytes = selectorsCount * 2;
if (remaining != 4 + selectorsBytes)
{
JpegThrowHelper.ThrowBadMarker("SOS", remaining);
JpegThrowHelper.ThrowBadMarker(nameof(JpegConstants.Markers.SOS), remaining);
}
// selectorsCount*2 bytes: component index + huffman tables indices
@ -1312,8 +1345,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
component.ACHuffmanTableId = acTableIndex;
}
// 3 bytes: Progressive scan decoding data
stream.Read(this.temp, 0, 3);
// 3 bytes: Progressive scan decoding data.
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];
this.scanDecoder.SpectralStart = spectralStart;
@ -1336,7 +1373,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
[MethodImpl(InliningOptions.ShortMethod)]
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);
}
}

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

@ -25,6 +25,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
[MethodImpl(InliningOptions.ColdPath)]
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)]
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;
}
catch
{
image?.Dispose();
throw;
}
finally
{
this.scanline?.Dispose();
this.previousScanline?.Dispose();
this.nextChunk?.Data?.Dispose();
}
}
@ -472,6 +478,8 @@ namespace SixLabors.ImageSharp.Formats.Png
this.bytesPerSample = this.header.BitDepth / 8;
}
this.previousScanline?.Dispose();
this.scanline?.Dispose();
this.previousScanline = this.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)
{
chunk.Data?.Dispose();
return chunk.Length;
}
@ -1453,6 +1462,9 @@ namespace SixLabors.ImageSharp.Formats.Png
if (validCrc != inputCrc)
{
string chunkTypeName = Encoding.ASCII.GetString(chunkType);
// ensure when throwing we dispose the data back to the memory allocator
chunk.Data?.Dispose();
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);
// 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;
}
@ -81,7 +82,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors
jpegDecoder.ParseStream(stream, scanDecoder, CancellationToken.None);
// 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;
}

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

@ -23,11 +23,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants
/// </summary>
Ccitt1D = 2,
/// <summary>
/// PackBits compression
/// </summary>
PackBits = 32773,
/// <summary>
/// T4-encoding: CCITT T.4 bi-level encoding (see Section 11 of the TIFF 6.0 specification).
/// </summary>
@ -65,27 +60,48 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants
Deflate = 8,
/// <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.
/// </summary>
OldDeflate = 32946,
ItuTRecT82 = 9,
/// <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,
/// if this is chosen.
/// </summary>
ItuTRecT82 = 9,
ItuTRecT43 = 10,
/// <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.
/// </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
L16 l16 = TiffUtils.L16Default;
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(TiffUtils.Vector4Default);
int offset = 0;
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,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(TiffUtils.Vector4Default);
byte[] buffer = new byte[4];
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,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(TiffUtils.Vector4Default);
byte[] buffer = new byte[4];
int offset = 0;
@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4;
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
color.FromVector4(colorVector);
color.FromScaledVector4(colorVector);
pixelRow[x] = color;
}
}
@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4;
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
color.FromVector4(colorVector);
color.FromScaledVector4(colorVector);
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,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(TiffUtils.Vector4Default);
int offset = 0;
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);
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;
}

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 g = colorMap[gOffset + 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;

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
Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(TiffUtils.Vector4Default);
int offset = 0;
@ -55,7 +55,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
ulong b = TiffUtils.ConvertToUShortBigEndian(data.Slice(offset, 2));
offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color);
pixelRow[x] = TiffUtils.ColorFromRgb64(rgba, r, g, b, color);
}
}
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
Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(TiffUtils.Vector4Default);
Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan();
@ -50,7 +50,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 2;
pixelRow[x] = TiffUtils.ColorFromRgba64(rgba, r, g, b, color);
pixelRow[x] = TiffUtils.ColorFromRgb64(rgba, r, g, b, color);
}
}
else
@ -63,7 +63,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
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,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(TiffUtils.Vector4Default);
int offset = 0;
Span<byte> buffer = stackalloc byte[4];
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,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(TiffUtils.Vector4Default);
Span<byte> buffer = stackalloc byte[4];
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,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(TiffUtils.Vector4Default);
int offset = 0;
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,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(TiffUtils.Vector4Default);
Span<byte> redData = data[0].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,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(TiffUtils.Vector4Default);
int offset = 0;
byte[] buffer = new byte[4];
@ -57,7 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4;
var colorVector = new Vector4(r, g, b, 1.0f);
color.FromVector4(colorVector);
color.FromScaledVector4(colorVector);
pixelRow[x] = color;
}
}
@ -78,7 +78,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4;
var colorVector = new Vector4(r, g, b, 1.0f);
color.FromVector4(colorVector);
color.FromScaledVector4(colorVector);
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 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;
}

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

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

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -18,15 +20,23 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
private readonly Configuration configuration;
private readonly MemoryAllocator memoryAllocator;
private readonly TiffExtraSampleType? extraSamplesType;
/// <summary>
/// Initializes a new instance of the <see cref="Rgba16161616TiffColor{TPixel}" /> class.
/// </summary>
/// <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>
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.isBigEndian = isBigEndian;
this.memoryAllocator = memoryAllocator;
this.extraSamplesType = extraSamplesType;
}
/// <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
Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(TiffUtils.Vector4Default);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
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++)
{
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));
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
@ -69,6 +84,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
pixelRow,
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;
}
}

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 TiffExtraSampleType? extraSamplesType;
/// <summary>
/// Initializes a new instance of the <see cref="Rgba16PlanarTiffColor{TPixel}" /> class.
/// </summary>
/// <param name="isBigEndian">if set to <c>true</c> decodes the pixel data as big endian, otherwise as little endian.</param>
public Rgba16PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
/// <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>
public Rgba16PlanarTiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian)
{
this.extraSamplesType = extraSamplesType;
this.isBigEndian = isBigEndian;
}
/// <inheritdoc/>
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
Rgba64 rgba = TiffUtils.Rgba64Default;
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(TiffUtils.Vector4Default);
Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan();
Span<byte> blueData = data[2].GetSpan();
Span<byte> alphaData = data[3].GetSpan();
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0;
for (int y = top; y < top + height; y++)
{
@ -52,7 +60,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
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
@ -62,11 +72,13 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
ulong r = TiffUtils.ConvertToUShortLittleEndian(redData.Slice(offset, 2));
ulong g = TiffUtils.ConvertToUShortLittleEndian(greenData.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;
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 TiffExtraSampleType? extraSamplesType;
/// <summary>
/// Initializes a new instance of the <see cref="Rgba24242424TiffColor{TPixel}" /> class.
/// </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>
public Rgba24242424TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
public Rgba24242424TiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian)
{
this.extraSamplesType = extraSamplesType;
this.isBigEndian = isBigEndian;
}
/// <inheritdoc/>
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,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(TiffUtils.Vector4Default);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0;
Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;
@ -58,7 +68,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
ulong a = TiffUtils.ConvertToUIntBigEndian(buffer);
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
@ -81,7 +93,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
ulong a = TiffUtils.ConvertToUIntLittleEndian(buffer);
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 TiffExtraSampleType? extraSamplesType;
/// <summary>
/// Initializes a new instance of the <see cref="Rgba24PlanarTiffColor{TPixel}" /> class.
/// </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>
public Rgba24PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
public Rgba24PlanarTiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian)
{
this.extraSamplesType = extraSamplesType;
this.isBigEndian = isBigEndian;
}
/// <inheritdoc/>
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,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(TiffUtils.Vector4Default);
Span<byte> buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;
@ -39,6 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
Span<byte> alphaData = data[3].GetSpan();
Span<byte> bufferSpan = buffer.Slice(bufferStartIdx);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0;
for (int y = top; y < top + height; y++)
{
@ -58,7 +66,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
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
@ -76,7 +86,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
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 TiffExtraSampleType? extraSamplesType;
/// <summary>
/// Initializes a new instance of the <see cref="Rgba32323232TiffColor{TPixel}" /> class.
/// </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>
public Rgba32323232TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
public Rgba32323232TiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian)
{
this.extraSamplesType = extraSamplesType;
this.isBigEndian = isBigEndian;
}
/// <inheritdoc/>
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,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(TiffUtils.Vector4Default);
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0;
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));
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
@ -70,7 +81,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
ulong a = TiffUtils.ConvertToUIntLittleEndian(data.Slice(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 TiffExtraSampleType? extraSamplesType;
/// <summary>
/// Initializes a new instance of the <see cref="Rgba32PlanarTiffColor{TPixel}" /> class.
/// </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>
public Rgba32PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
public Rgba32PlanarTiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian)
{
this.extraSamplesType = extraSamplesType;
this.isBigEndian = isBigEndian;
}
/// <inheritdoc/>
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,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(TiffUtils.Vector4Default);
Span<byte> redData = data[0].GetSpan();
Span<byte> greenData = data[1].GetSpan();
Span<byte> blueData = data[2].GetSpan();
Span<byte> alphaData = data[3].GetSpan();
bool hasAssociatedAlpha = this.extraSamplesType.HasValue && this.extraSamplesType == TiffExtraSampleType.AssociatedAlphaData;
int offset = 0;
for (int y = top; y < top + height; y++)
{
@ -51,7 +59,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
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
@ -65,7 +75,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
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.
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Formats.Tiff.Utils;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -15,13 +18,27 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
{
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/>
public override void Decode(ReadOnlySpan<byte> data, Buffer2D<TPixel> pixels, int left, int top, int width, int height)
{
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++)
{
Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
@ -32,6 +49,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
pixelRow,
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;
}
}

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,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(TiffUtils.Vector4Default);
int offset = 0;
byte[] buffer = new byte[4];
@ -62,7 +62,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4;
var colorVector = new Vector4(r, g, b, a);
color.FromVector4(colorVector);
color.FromScaledVector4(colorVector);
pixelRow[x] = color;
}
}
@ -87,7 +87,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4;
var colorVector = new Vector4(r, g, b, a);
color.FromVector4(colorVector);
color.FromScaledVector4(colorVector);
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;
public RgbaPlanarTiffColor(TiffBitsPerSample bitsPerSample)
private readonly TiffExtraSampleType? extraSampleType;
public RgbaPlanarTiffColor(TiffExtraSampleType? extraSampleType, TiffBitsPerSample bitsPerSample)
{
this.bitsPerSampleR = bitsPerSample.Channel0;
this.bitsPerSampleG = bitsPerSample.Channel1;
@ -43,6 +45,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
this.gFactor = (1 << this.bitsPerSampleG) - 1.0f;
this.bFactor = (1 << this.bitsPerSampleB) - 1.0f;
this.aFactor = (1 << this.bitsPerSampleA) - 1.0f;
this.extraSampleType = extraSampleType;
}
/// <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)
{
var color = default(TPixel);
bool hasAssociatedAlpha = this.extraSampleType.HasValue && this.extraSampleType == TiffExtraSampleType.AssociatedAlphaData;
var rBitReader = new BitReader(data[0].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 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;
}

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

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

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

@ -14,6 +14,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
MemoryAllocator memoryAllocator,
TiffColorType colorType,
TiffBitsPerSample bitsPerSample,
TiffExtraSampleType? extraSampleType,
ushort[] colorMap,
Rational[] referenceBlackAndWhite,
Rational[] ycbcrCoefficients,
@ -125,7 +126,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 2,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample);
return new RgbaTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.Rgb333:
DebugGuard.IsTrue(
@ -146,7 +147,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 3,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample);
return new RgbaTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.Rgb444:
DebugGuard.IsTrue(
@ -167,7 +168,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 4,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample);
return new RgbaTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.Rgb555:
DebugGuard.IsTrue(
@ -188,7 +189,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 5,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample);
return new RgbaTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.Rgb666:
DebugGuard.IsTrue(
@ -209,7 +210,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 6,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample);
return new RgbaTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.Rgb888:
DebugGuard.IsTrue(
@ -230,7 +231,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 8,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgba8888TiffColor<TPixel>(configuration);
return new Rgba8888TiffColor<TPixel>(configuration, memoryAllocator, extraSampleType);
case TiffColorType.Rgb101010:
DebugGuard.IsTrue(
@ -251,7 +252,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 10,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample);
return new RgbaTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.Rgb121212:
DebugGuard.IsTrue(
@ -272,7 +273,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 12,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample);
return new RgbaTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.Rgb141414:
DebugGuard.IsTrue(
@ -293,7 +294,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 14,
"bitsPerSample");
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaTiffColor<TPixel>(bitsPerSample);
return new RgbaTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.Rgb161616:
DebugGuard.IsTrue(
@ -314,7 +315,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 16,
"bitsPerSample");
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:
DebugGuard.IsTrue(
@ -335,7 +336,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 24,
"bitsPerSample");
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:
DebugGuard.IsTrue(
@ -356,7 +357,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
&& bitsPerSample.Channel0 == 32,
"bitsPerSample");
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:
DebugGuard.IsTrue(
@ -394,6 +395,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
public static TiffBasePlanarColorDecoder<TPixel> CreatePlanar(
TiffColorType colorType,
TiffBitsPerSample bitsPerSample,
TiffExtraSampleType? extraSampleType,
ushort[] colorMap,
Rational[] referenceBlackAndWhite,
Rational[] ycbcrCoefficients,
@ -408,7 +410,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
case TiffColorType.Rgba8888Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new RgbaPlanarTiffColor<TPixel>(bitsPerSample);
return new RgbaPlanarTiffColor<TPixel>(extraSampleType, bitsPerSample);
case TiffColorType.YCbCrPlanar:
return new YCbCrPlanarTiffColor<TPixel>(referenceBlackAndWhite, ycbcrCoefficients, ycbcrSubSampling);
@ -419,7 +421,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
case TiffColorType.Rgba16161616Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgba16PlanarTiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
return new Rgba16PlanarTiffColor<TPixel>(extraSampleType, byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgb242424Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap");
@ -427,7 +429,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
case TiffColorType.Rgba24242424Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgba24PlanarTiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
return new Rgba24PlanarTiffColor<TPixel>(extraSampleType, byteOrder == ByteOrder.BigEndian);
case TiffColorType.Rgb323232Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap");
@ -435,7 +437,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
case TiffColorType.Rgba32323232Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap");
return new Rgba32PlanarTiffColor<TPixel>(byteOrder == ByteOrder.BigEndian);
return new Rgba32PlanarTiffColor<TPixel>(extraSampleType, byteOrder == ByteOrder.BigEndian);
default:
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
L16 l16 = TiffUtils.L16Default;
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(TiffUtils.Vector4Default);
int offset = 0;
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,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(TiffUtils.Vector4Default);
byte[] buffer = new byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;
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,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(TiffUtils.Vector4Default);
byte[] buffer = new byte[4];
int offset = 0;
@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4;
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
color.FromVector4(colorVector);
color.FromScaledVector4(colorVector);
pixelRow[x] = color;
}
}
@ -59,7 +59,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
offset += 4;
var colorVector = new Vector4(intensity, intensity, intensity, 1.0f);
color.FromVector4(colorVector);
color.FromScaledVector4(colorVector);
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,
// we define our own defaults as a workaround. See: https://github.com/dotnet/runtime/issues/55623
var color = default(TPixel);
color.FromVector4(TiffUtils.Vector4Default);
color.FromScaledVector4(TiffUtils.Vector4Default);
const uint maxValue = 0xFFFFFFFF;
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);
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;
}

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

@ -28,15 +28,6 @@
- The Decoder currently only supports decoding multiframe images, which have the same dimensions.
- 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
| |Encoder|Decoder|Comments |
@ -87,7 +78,7 @@
|Model | Y | Y | |
|StripOffsets | Y | Y | |
|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 | |
|StripByteCounts | Y | Y | |
|MinSampleValue | | | |
@ -105,7 +96,7 @@
|Artist | Y | Y | |
|HostComputer | Y | Y | |
|ColorMap | Y | Y | |
|ExtraSamples | | (Y) | Only UnassociatedAlphaData is supported so far |
|ExtraSamples | | Y | Unspecified alpha data is not supported. |
|Copyright | Y | Y | |
### Extension TIFF Tags

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

@ -118,9 +118,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
public TiffFillOrder FillOrder { get; set; }
/// <summary>
/// Gets or sets the extra samples, which can contain the alpha channel data.
/// Gets or sets the extra samples type.
/// </summary>
public TiffExtraSampleType? ExtraSamples { get; set; }
public TiffExtraSampleType? ExtraSamplesType { get; set; }
/// <summary>
/// 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)
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>>();
foreach (ExifProfile ifd in directories)
try
{
cancellationToken.ThrowIfCancellationRequested();
ImageFrame<TPixel> frame = this.DecodeFrame<TPixel>(ifd, cancellationToken);
frames.Add(frame);
this.inputStream = stream;
var reader = new DirectoryReader(stream, this.Configuration.MemoryAllocator);
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.
ImageFrame<TPixel> root = frames[0];
this.Dimensions = root.Size();
foreach (ImageFrame<TPixel> frame in frames)
{
if (frame.Size() != root.Size())
// TODO: Tiff frames can have different sizes.
ImageFrame<TPixel> root = frames[0];
this.Dimensions = root.Size();
foreach (ImageFrame<TPixel> frame in frames)
{
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/>
@ -240,8 +252,8 @@ namespace SixLabors.ImageSharp.Formats.Tiff
var stripOffsetsArray = (Array)tags.GetValueInternal(ExifTag.StripOffsets).GetValue();
var stripByteCountsArray = (Array)tags.GetValueInternal(ExifTag.StripByteCounts).GetValue();
IMemoryOwner<ulong> stripOffsetsMemory = this.ConvertNumbers(stripOffsetsArray, out Span<ulong> stripOffsets);
IMemoryOwner<ulong> stripByteCountsMemory = this.ConvertNumbers(stripByteCountsArray, out Span<ulong> stripByteCounts);
using IMemoryOwner<ulong> stripOffsetsMemory = this.ConvertNumbers(stripOffsetsArray, out Span<ulong> stripOffsets);
using IMemoryOwner<ulong> stripByteCountsMemory = this.ConvertNumbers(stripByteCountsArray, out Span<ulong> stripByteCounts);
if (this.PlanarConfiguration == TiffPlanarConfiguration.Planar)
{
@ -262,8 +274,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff
cancellationToken);
}
stripOffsetsMemory?.Dispose();
stripByteCountsMemory?.Dispose();
return frame;
}
@ -375,6 +385,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
TiffBasePlanarColorDecoder<TPixel> colorDecoder = TiffColorDecoderFactory<TPixel>.CreatePlanar(
this.ColorType,
this.BitsPerSample,
this.ExtraSamplesType,
this.ColorMap,
this.ReferenceBlackAndWhite,
this.YcbcrCoefficients,
@ -456,6 +467,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff
this.memoryAllocator,
this.ColorType,
this.BitsPerSample,
this.ExtraSamplesType,
this.ColorMap,
this.ReferenceBlackAndWhite,
this.YcbcrCoefficients,

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

@ -39,10 +39,10 @@ namespace SixLabors.ImageSharp.Formats.Tiff
}
var extraSamplesType = (TiffExtraSampleType)extraSamples[0];
options.ExtraSamples = extraSamplesType;
if (extraSamplesType is not TiffExtraSampleType.UnassociatedAlphaData)
options.ExtraSamplesType = extraSamplesType;
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)]
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>
{
rgba.PackedValue = r | (g << 16) | (b << 32) | (0xfffful << 48);
@ -63,12 +63,21 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
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)]
public static TPixel ColorScaleTo24Bit<TPixel>(ulong r, ulong g, ulong b, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
var colorVector = new Vector4(r * Scale24Bit, g * Scale24Bit, b * Scale24Bit, 1.0f);
color.FromVector4(colorVector);
color.FromScaledVector4(colorVector);
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)
where TPixel : unmanaged, IPixel<TPixel>
{
var colorVector = new Vector4(r * Scale24Bit, g * Scale24Bit, b * Scale24Bit, a * Scale24Bit);
color.FromVector4(colorVector);
Vector4 colorVector = new Vector4(r, g, b, a) * Scale24Bit;
color.FromScaledVector4(colorVector);
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)]
public static TPixel ColorScaleTo32Bit<TPixel>(ulong r, ulong g, ulong b, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
{
var colorVector = new Vector4(r * Scale32Bit, g * Scale32Bit, b * Scale32Bit, 1.0f);
color.FromVector4(colorVector);
color.FromScaledVector4(colorVector);
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)
where TPixel : unmanaged, IPixel<TPixel>
{
var colorVector = new Vector4(r * Scale32Bit, g * Scale32Bit, b * Scale32Bit, a * Scale32Bit);
color.FromVector4(colorVector);
Vector4 colorVector = new Vector4(r, g, b, a) * Scale32Bit;
color.FromScaledVector4(colorVector);
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)]
public static TPixel ColorFromL16<TPixel>(L16 l16, ushort intensity, TPixel color)
where TPixel : unmanaged, IPixel<TPixel>
@ -113,7 +138,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
where TPixel : unmanaged, IPixel<TPixel>
{
var colorVector = new Vector4(intensity * Scale24Bit, intensity * Scale24Bit, intensity * Scale24Bit, 1.0f);
color.FromVector4(colorVector);
color.FromScaledVector4(colorVector);
return color;
}
@ -122,7 +147,17 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
where TPixel : unmanaged, IPixel<TPixel>
{
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;
}
@ -139,8 +174,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
return 0;
}
int padding = subSampling - (valueToRoundUp % subSampling);
return padding;
return subSampling - (valueToRoundUp % subSampling);
}
}
}

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)
where TPixel : unmanaged, IPixel<TPixel>
{
this.Metadata = new ImageMetadata();
this.currentStream = stream;
Image<TPixel> image = null;
try
{
this.Metadata = new ImageMetadata();
this.currentStream = stream;
uint fileSize = this.ReadImageHeader();
uint fileSize = this.ReadImageHeader();
using (this.webImageInfo = this.ReadVp8Info())
{
if (this.webImageInfo.Features is { Animation: true })
using (this.webImageInfo = this.ReadVp8Info())
{
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);
}
if (this.webImageInfo.Features is { Animation: true })
{
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);
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
if (this.webImageInfo.Features is { Animation: true })
{
WebpThrowHelper.ThrowNotSupportedException("Animations are not supported");
}
if (this.webImageInfo.IsLossless)
{
var losslessDecoder = new WebpLosslessDecoder(this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.Configuration);
losslessDecoder.Decode(pixels, image.Width, image.Height);
}
else
{
var lossyDecoder = new WebpLossyDecoder(this.webImageInfo.Vp8BitReader, this.memoryAllocator, this.Configuration);
lossyDecoder.Decode(pixels, image.Width, image.Height, this.webImageInfo, this.AlphaData);
}
image = new Image<TPixel>(this.Configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.Metadata);
Buffer2D<TPixel> pixels = image.GetRootFramePixelBuffer();
if (this.webImageInfo.IsLossless)
{
var losslessDecoder = new WebpLosslessDecoder(this.webImageInfo.Vp8LBitReader, this.memoryAllocator, this.Configuration);
losslessDecoder.Decode(pixels, image.Width, image.Height);
}
else
{
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.
this.ReadOptionalMetadata();
// There can be optional chunks after the image data, like EXIF and XMP.
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;
default:
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>
/// 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>
/// <param name="chunkType">The chunk type.</param>
/// <param name="features">The webp image features.</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)
{
int bytesRead;
switch (chunkType)
{
case WebpChunkType.Iccp:
uint iccpChunkSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer);
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;
}
}
this.ReadIccProfile();
break;
case WebpChunkType.Exif:
uint exifChunkSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer);
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;
}
this.ReadExifProfile();
break;
case WebpChunkType.Xmp:
uint xmpChunkSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer);
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;
}
this.ReadXmpProfile();
break;
case WebpChunkType.AnimationParameter:
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));
this.currentStream.Read(this.buffer, 0, 2);
features.AnimationLoopCount = BinaryPrimitives.ReadUInt16LittleEndian(this.buffer);
this.ReadAnimationParameters(features);
return true;
case WebpChunkType.Alpha:
uint alphaChunkSize = WebpChunkParsingUtils.ReadChunkSize(this.currentStream, this.buffer);
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);
this.ReadAlphaData(features, ignoreAlpha);
break;
default:
WebpThrowHelper.ThrowImageFormatException("Unexpected chunk followed VP8X header");
@ -331,22 +274,193 @@ namespace SixLabors.ImageSharp.Formats.Webp
/// <summary>
/// Reads the optional metadata EXIF of XMP profiles, which can follow the image data.
/// </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
// in the VP8X chunk before the image data. Make sure there is still data to read here.
if (this.currentStream.Position == this.currentStream.Length)
byte[] xmpData = new byte[xmpChunkSize];
int bytesRead = this.currentStream.Read(xmpData, 0, (int)xmpChunkSize);
if (bytesRead != xmpChunkSize)
{
// Ignore invalid chunk.
return;
}
WebpChunkType chunkType = WebpChunkParsingUtils.ReadChunkType(this.currentStream, this.buffer);
WebpChunkParsingUtils.ParseOptionalChunks(this.currentStream, chunkType, this.Metadata, this.IgnoreMetadata, this.buffer);
var profile = new XmpProfile(xmpData);
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/>
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
{
/// <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>
/// Cold path optimization for throwing <see cref="ImageFormatException"/>-s
/// </summary>

9
src/ImageSharp/IO/BufferedReadStream.cs

@ -114,6 +114,15 @@ namespace SixLabors.ImageSharp.IO
/// <inheritdoc/>
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>
/// Gets the underlying stream.
/// </summary>

31
src/ImageSharp/ImageSharp.csproj

@ -12,33 +12,23 @@
<PackageProjectUrl>$(RepositoryUrl)</PackageProjectUrl>
<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>
<Configurations>Debug;Release;Debug-InnerLoop;Release-InnerLoop</Configurations>
<Configurations>Debug;Release</Configurations>
</PropertyGroup>
<PropertyGroup>
<!--Bump to V2 prior to tagged release.-->
<MinVerMinimumMajorMinor>2.0</MinVerMinimumMajorMinor>
<!--Bump to V3 prior to tagged release.-->
<MinVerMinimumMajorMinor>3.0</MinVerMinimumMajorMinor>
</PropertyGroup>
<Choose>
<When Condition="$(SIXLABORS_TESTING_PREVIEW) == true">
<PropertyGroup>
<TargetFrameworks>net6.0;net5.0;netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;net472</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>
<TargetFrameworks>net7.0;net6.0</TargetFrameworks>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;net472</TargetFrameworks>
<TargetFrameworks>net6.0</TargetFrameworks>
</PropertyGroup>
</Otherwise>
</Choose>
@ -47,17 +37,6 @@
<None Include="..\..\shared-infrastructure\branding\icons\imagesharp\sixlabors.imagesharp.128.png" Pack="true" PackagePath="" />
</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>
<Compile Update="Formats\Jpeg\Components\Block8x8F.Generated.cs">
<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)
#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 (byte* b = destination)
@ -89,6 +97,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
}
}
}
#endif
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>
/// Returns the thumbnail in the EXIF profile when available.
/// </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)
{
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);
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>
public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius, Rectangle 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>
public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma, Rectangle 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,
Rectangle 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">
/// The 'radius' value representing the size of the area to sample.
/// </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.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>
@ -39,9 +54,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary>
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 />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
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);
}
/// <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>
/// Gets the 1D convolution kernel.
/// </summary>
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/>
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);
}

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="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 Convolution2PassProcessor(
Configuration configuration,
float[] kernel,
bool preserveAlpha,
Image<TPixel> source,
Rectangle sourceRectangle)
Rectangle sourceRectangle,
BorderWrappingMode borderWrapModeX,
BorderWrappingMode borderWrapModeY)
: base(configuration, source, sourceRectangle)
{
this.Kernel = kernel;
this.PreserveAlpha = preserveAlpha;
this.BorderWrapModeX = borderWrapModeX;
this.BorderWrapModeY = borderWrapModeY;
}
/// <summary>
@ -48,6 +54,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary>
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/>
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.
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
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>
/// Initializes a new instance of the <see cref="GaussianBlurProcessor"/> class.
/// </summary>
@ -54,9 +65,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// This should be at least twice the sigma value.
/// </param>
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.Radius = radius;
this.BorderWrapModeX = borderWrapModeX;
this.BorderWrapModeY = borderWrapModeY;
}
/// <summary>
@ -69,9 +103,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary>
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 />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
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);
}
/// <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>
/// Gets the 1D convolution kernel.
/// </summary>
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/>
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);
}

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>
/// Initializes a new instance of the <see cref="GaussianSharpenProcessor"/> class.
/// </summary>
@ -54,9 +65,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// This should be at least twice the sigma value.
/// </param>
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.Radius = radius;
this.BorderWrapModeX = borderWrapModeX;
this.BorderWrapModeY = borderWrapModeY;
}
/// <summary>
@ -69,9 +103,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary>
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 />
public IImageProcessor<TPixel> CreatePixelSpecificProcessor<TPixel>(Configuration configuration, Image<TPixel> source, Rectangle sourceRectangle)
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.
/// </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="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>
public GaussianSharpenProcessor(
@ -24,10 +24,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
GaussianSharpenProcessor definition,
Image<TPixel> source,
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)
{
int kernelSize = (definition.Radius * 2) + 1;
this.Kernel = ConvolutionProcessorHelpers.CreateGaussianSharpenKernel(kernelSize, definition.Sigma);
this.BorderWrapModeX = borderWrapModeX;
this.BorderWrapModeY = borderWrapModeY;
}
/// <summary>
@ -35,10 +57,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// </summary>
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/>
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);
}

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="bounds">The source bounds.</param>
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>
/// 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="bounds">The source bounds.</param>
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.xOffsets = this.allocator.Allocate<int>(bounds.Width * kernelWidth);
@ -49,43 +60,8 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
int minX = bounds.X;
int maxX = bounds.Right - 1;
int radiusY = kernelHeight >> 1;
int radiusX = kernelWidth >> 1;
// 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);
}
this.BuildOffsets(this.yOffsets, bounds.Height, kernelHeight, minY, maxY, yBorderMode);
this.BuildOffsets(this.xOffsets, bounds.Width, kernelWidth, minX, maxX, xBorderMode);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -105,5 +81,105 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
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)
{
// 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:
// - Not reproducable locally
// - Doesn't seem to be triggered by the bulk Numerics.UnPremultiply method but by caller.
// https://github.com/SixLabors/ImageSharp/pull/1591
this.InvokeMacOSX(in rows, span);
this.InvokeMacOS(in rows, span);
return;
}
@ -259,7 +259,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
[ExcludeFromCodeCoverage]
[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;
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)
{
// 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:
// - Not reproducable locally
// - Doesn't seem to be triggered by the bulk Numerics.UnPremultiply method but by caller.
// https://github.com/SixLabors/ImageSharp/pull/1591
this.InvokeMacOSX(in rows, span);
this.InvokeMacOS(in rows, span);
return;
}
@ -258,7 +258,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Transforms
[ExcludeFromCodeCoverage]
[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;
TResampler sampler = this.sampler;

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

@ -9,7 +9,7 @@
<DebugType>portable</DebugType>
<!--Used to hide test project from dotnet test-->
<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): -->
<!--<SignAssembly>false</SignAssembly>-->
</PropertyGroup>
@ -17,17 +17,12 @@
<Choose>
<When Condition="$(SIXLABORS_TESTING_PREVIEW) == true">
<PropertyGroup>
<TargetFrameworks>net6.0;net5.0;netcoreapp3.1;netcoreapp2.1;net472</TargetFrameworks>
</PropertyGroup>
</When>
<When Condition="$(Configuration.EndsWith('InnerLoop')) == true">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1</TargetFrameworks>
<TargetFrameworks>net7.0;net6.0</TargetFrameworks>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<TargetFrameworks>net5.0;netcoreapp3.1;netcoreapp2.1;net472</TargetFrameworks>
<TargetFrameworks>net6.0</TargetFrameworks>
</PropertyGroup>
</Otherwise>
</Choose>

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

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

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

@ -5,7 +5,6 @@ using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
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
{
[Trait("Format", "Bmp")]
[ValidateDisposedMemoryAllocations]
public class BmpDecoderTests
{
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
{
[Trait("Format", "Gif")]
[ValidateDisposedMemoryAllocations]
public class GifDecoderTests
{
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
// 2. DisableFMA - call avx without fma implementation
// 3. DisableAvx - call sse implementation
// 4. DisableSIMD - call Vector4 fallback implementation
// 4. DisableHWIntrinsic - call Vector4 fallback implementation
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
seed,
HwIntrinsics.AllowAll | HwIntrinsics.DisableFMA | HwIntrinsics.DisableAVX | HwIntrinsics.DisableSIMD);
HwIntrinsics.AllowAll | HwIntrinsics.DisableFMA | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
}
// Forward transform
@ -200,11 +200,11 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
// 1. AllowAll - call avx/fma implementation
// 2. DisableFMA - call avx without fma implementation
// 3. DisableAvx - call Vector4 implementation
// 4. DisableSIMD - call scalar fallback implementation
// 4. DisableHWIntrinsic - call scalar fallback implementation
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
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.IndexOutOfRangeException1693B,
TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824C,
TestImages.Jpeg.Issues.Fuzz.NullReferenceException2085,
};
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);
using (var stream = new MemoryStream(testFile.Bytes, false))
{
IImageInfo imageInfo = useIdentify
? ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream, default)
: decoder.Decode<Rgba32>(Configuration.Default, stream, default);
test(imageInfo);
if (useIdentify)
{
IImageInfo imageInfo = ((IImageInfoDetector)decoder).Identify(Configuration.Default, stream, default);
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
[Trait("Format", "Jpg")]
[ValidateDisposedMemoryAllocations]
public partial class JpegDecoderTests
{
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
{
[Trait("Format", "Jpg")]
public class JpegEncoderTests
public partial class JpegEncoderTests
{
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
{
[Trait("Format", "Pbm")]
[ValidateDisposedMemoryAllocations]
public class PbmDecoderTests
{
[Theory]

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

@ -19,6 +19,7 @@ using Xunit;
namespace SixLabors.ImageSharp.Tests.Formats.Png
{
[Trait("Format", "Png")]
[ValidateDisposedMemoryAllocations]
public partial class PngDecoderTests
{
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(
RunTest,
HwIntrinsics.DisableSIMD);
HwIntrinsics.DisableHWIntrinsic);
}
[Fact]
@ -95,7 +95,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.DisableSIMD);
HwIntrinsics.DisableHWIntrinsic);
}
[Fact]
@ -137,7 +137,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.DisableSIMD);
HwIntrinsics.DisableHWIntrinsic);
}
[Fact]
@ -179,7 +179,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Png
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
HwIntrinsics.DisableSIMD);
HwIntrinsics.DisableHWIntrinsic);
}
[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
{
[Trait("Format", "Tga")]
[ValidateDisposedMemoryAllocations]
public class TgaDecoderTests
{
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
{
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)
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
{
[Trait("Format", "Tiff")]
[ValidateDisposedMemoryAllocations]
public class TiffDecoderTests : TiffDecoderBaseTester
{
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)
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]
[WithFile(Flower14BitGray, PixelTypes.Rgba32)]
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)
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]
[WithFile(FlowerRgb888Contiguous, PixelTypes.Rgba32)]
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)
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]
[WithFile(Flower24BitGray, PixelTypes.Rgba32)]
[WithFile(Flower24BitGrayLittleEndian, PixelTypes.Rgba32)]
@ -261,7 +314,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
where TPixel : unmanaged, IPixel<TPixel>
{
// 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();
image.DebugSave(provider);
}
@ -296,6 +349,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
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]
[WithFile(Flower32BitGrayPredictorBigEndian, 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)
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]
[WithFile(FlowerRgb141414Contiguous, 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)
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]
[WithFile(FlowerRgb161616PredictorBigEndian, 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)
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]
[WithFile(FlowerRgb242424Contiguous, 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
{
[Trait("Format", "Webp")]
[ValidateDisposedMemoryAllocations]
public class WebpDecoderTests
{
private static WebpDecoder WebpDecoder => new();

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

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Formats.Webp;
@ -170,5 +171,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Webp
Assert.NotNull(actualExif);
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));
}
else if (TestEnvironment.IsOSX)
else if (TestEnvironment.IsMacOS)
{
Assert.True(RuntimeEnvironment.IsOSPlatform(OSPlatform.OSX));
}

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

@ -14,6 +14,7 @@ namespace SixLabors.ImageSharp.Tests
[Theory]
[InlineData(false)]
[InlineData(true)]
[ValidateDisposedMemoryAllocations]
public void FromPixels(bool useSpan)
{
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)
{
using IDisposable mem = MemoryAllocatorValidator.MonitorAllocations();
Configuration configuration = Configuration.Default.Clone();
configuration.PreferContiguousImageBuffers = true;
IImageEncoder encoder = configuration.ImageFormatsManager.FindEncoder(

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

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

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

@ -58,11 +58,11 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators
[Collection(nameof(NonParallelCollection))]
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.)
[ConditionalFact(nameof(IsNotMacOs))]
[ConditionalFact(nameof(IsNotMacOS))]
public void MultiplePoolInstances_TrimPeriodElapsed_AllAreTrimmed()
{
if (!TestEnvironment.RunsOnCI)

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

@ -245,9 +245,9 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators
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))]
[InlineData(false)]
[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
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;
}
@ -321,9 +321,9 @@ namespace SixLabors.ImageSharp.Tests.Memory.Allocators
[InlineData(600)] // Group of single UniformUnmanagedMemoryPool buffer
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;
}

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

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

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);
using Image<Rgba32> thumbnail = profile.CreateThumbnail<Rgba32>();
using Image thumbnail = profile.CreateThumbnail();
Assert.NotNull(thumbnail);
Assert.Equal(256, thumbnail.Width);
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]

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 =
ImageComparer.TolerantPercentage(TestEnvironment.IsOSX && TestEnvironment.RunsOnCI ? 0.26F : 0.07F);
ImageComparer.TolerantPercentage(TestEnvironment.IsMacOS && TestEnvironment.RunsOnCI ? 0.26F : 0.07F);
[Fact]
public void Resize_PixelAgnostic()

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

Loading…
Cancel
Save