diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index 43a4a1708..16f8ebb06 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.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 }}
diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml
index 2b14f2a4b..3f8a82031 100644
--- a/.github/workflows/code-coverage.yml
+++ b/.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}}"
diff --git a/ImageSharp.sln b/ImageSharp.sln
index 5428f3394..fbf1ca24b 100644
--- a/ImageSharp.sln
+++ b/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
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index d211992a9..faa29865f 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -27,6 +27,7 @@
+
diff --git a/src/ImageSharp/Diagnostics/MemoryDiagnostics.cs b/src/ImageSharp/Diagnostics/MemoryDiagnostics.cs
index d83e737f1..89f18cff6 100644
--- a/src/ImageSharp/Diagnostics/MemoryDiagnostics.cs
+++ b/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
}
}
+ ///
+ /// Fires when ImageSharp allocates memory from a MemoryAllocator
+ ///
+ internal static event Action MemoryAllocated;
+
+ ///
+ /// Fires when ImageSharp releases memory allocated from a MemoryAllocator
+ ///
+ internal static event Action MemoryReleased;
+
///
/// Gets a value indicating the total number of memory resource objects leaked to the finalizer.
///
@@ -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)
{
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
index 41adc1cff..ee0a31280 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
@@ -122,11 +122,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
+ Image image = null;
try
{
int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette);
- var image = new Image(this.Configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata);
+ image = new Image(this.Configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata);
Buffer2D 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;
+ }
}
///
@@ -323,12 +330,12 @@ namespace SixLabors.ImageSharp.Formats.Bmp
color.FromBgr24(Unsafe.As(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(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
{
diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
index dd4f42104..2932cafe2 100644
--- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
+++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs
@@ -221,7 +221,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
///
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
///
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
///
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);
}
diff --git a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs b/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
index 430adeb21..532892e06 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/Decoder/SpectralConverter{TPixel}.cs
+++ b/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;
}
///
@@ -210,6 +212,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.rgbBuffer?.Dispose();
this.paddedProxyPixelRow?.Dispose();
+ this.pixelBuffer?.Dispose();
}
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
index ef4e3ffac..533ffa719 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
+++ b/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);
}
}
diff --git a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs b/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
index b238e45ef..1073ffff7 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
+++ b/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}.");
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index 497dc3967..12770bc52 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/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(this.bytesPerScanline, AllocationOptions.Clean);
this.scanline = this.Configuration.MemoryAllocator.Allocate(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);
}
}
diff --git a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs b/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs
index cfbc32f4f..4e788c76a 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/Decompressors/JpegTiffCompression.cs
+++ b/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;
}
diff --git a/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs b/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs
index 0baf4c89c..765f2c237 100644
--- a/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs
+++ b/src/ImageSharp/Formats/Tiff/Constants/TiffCompression.cs
@@ -23,11 +23,6 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants
///
Ccitt1D = 2,
- ///
- /// PackBits compression
- ///
- PackBits = 32773,
-
///
/// T4-encoding: CCITT T.4 bi-level encoding (see Section 11 of the TIFF 6.0 specification).
///
@@ -65,27 +60,48 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Constants
Deflate = 8,
///
- /// 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.
///
- OldDeflate = 32946,
+ ItuTRecT82 = 9,
///
- /// 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.
///
- ItuTRecT82 = 9,
+ ItuTRecT43 = 10,
///
- /// 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.
+ ///
+ NeXT = 32766,
+
+ ///
+ /// PackBits compression.
+ ///
+ PackBits = 32773,
+
+ ///
+ /// 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.
///
- ItuTRecT43 = 10
+ ThunderScan = 32809,
+
+ ///
+ /// Deflate compression - old.
+ ///
+ /// Note: The TIFF encoder does not support this compression and will default to use no compression instead,
+ /// if this is chosen.
+ ///
+ OldDeflate = 32946,
}
}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs
index 5d910d16e..e60562912 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero16TiffColor{TPixel}.cs
+++ b/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++)
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs
index 4cda95480..7d230dfd5 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero24TiffColor{TPixel}.cs
+++ b/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;
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs
index ee9bf8a9c..c43b121ca 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32FloatTiffColor{TPixel}.cs
+++ b/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;
}
}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs
index 7367a78e3..00e4caef7 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZero32TiffColor{TPixel}.cs
+++ b/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++)
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs
index a40fa7667..59be3f891 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/BlackIsZeroTiffColor{TPixel}.cs
+++ b/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;
}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs
index 29e03c6c6..ad5793084 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/PaletteTiffColor{TPixel}.cs
+++ b/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;
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs
index a4d725bcf..609369011 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb161616TiffColor{TPixel}.cs
+++ b/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
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs
index 1c61b0991..76fed3c93 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb16PlanarTiffColor{TPixel}.cs
+++ b/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 redData = data[0].GetSpan();
Span 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);
}
}
}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs
index 4130ee1a2..addf576e9 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb242424TiffColor{TPixel}.cs
+++ b/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 buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs
index e37fff1e7..2eda3b5af 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb24PlanarTiffColor{TPixel}.cs
+++ b/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 buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs
index bf1e65e1c..02319bfa6 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb323232TiffColor{TPixel}.cs
+++ b/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++)
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs
index cdc6942bd..26f75bfcf 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgb32PlanarTiffColor{TPixel}.cs
+++ b/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 redData = data[0].GetSpan();
Span greenData = data[1].GetSpan();
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs
index 4dc3295a4..7fd98dd50 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbFloat323232TiffColor{TPixel}.cs
+++ b/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;
}
}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs
index 54466e05b..f1ff91382 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbPlanarTiffColor{TPixel}.cs
+++ b/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;
}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs
index 4a887c426..9d037cec7 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbTiffColor{TPixel}.cs
+++ b/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;
}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs
index 7fd8d9879..0340438cb 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16161616TiffColor{TPixel}.cs
+++ b/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;
+
///
/// Initializes a new instance of the class.
///
/// The configuration.
+ /// The memory allocator.
/// if set to true decodes the pixel data as big endian, otherwise as little endian.
- public Rgba16161616TiffColor(Configuration configuration, bool isBigEndian)
+ /// The type of the extra samples.
+ public Rgba16161616TiffColor(Configuration configuration, MemoryAllocator memoryAllocator, TiffExtraSampleType? extraSamplesType, bool isBigEndian)
{
this.configuration = configuration;
this.isBigEndian = isBigEndian;
+ this.memoryAllocator = memoryAllocator;
+ this.extraSamplesType = extraSamplesType;
}
///
@@ -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 vectors = hasAssociatedAlpha ? this.memoryAllocator.Allocate(width) : null;
+ Span vectorsSpan = hasAssociatedAlpha ? vectors.GetSpan() : Span.Empty;
for (int y = top; y < top + height; y++)
{
Span 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.Instance.ToVector4(this.configuration, pixelRow, vectorsSpan);
+ PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorsSpan, pixelRow, PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale);
+ }
+
offset += byteCount;
}
}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs
index 705010ce9..856d810d3 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba16PlanarTiffColor{TPixel}.cs
+++ b/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;
+
///
/// Initializes a new instance of the class.
///
- /// if set to true decodes the pixel data as big endian, otherwise as little endian.
- public Rgba16PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
+ /// The extra samples type.
+ /// If set to true decodes the pixel data as big endian, otherwise as little endian.
+ public Rgba16PlanarTiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian)
+ {
+ this.extraSamplesType = extraSamplesType;
+ this.isBigEndian = isBigEndian;
+ }
///
public override void Decode(IMemoryOwner[] data, Buffer2D 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 redData = data[0].GetSpan();
Span greenData = data[1].GetSpan();
Span blueData = data[2].GetSpan();
Span 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);
}
}
}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs
index 71e1f7abd..2ce30252f 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24242424TiffColor{TPixel}.cs
+++ b/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;
+
///
/// Initializes a new instance of the class.
///
+ /// The type of the extra samples.
/// if set to true decodes the pixel data as big endian, otherwise as little endian.
- public Rgba24242424TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
+ public Rgba24242424TiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian)
+ {
+ this.extraSamplesType = extraSamplesType;
+ this.isBigEndian = isBigEndian;
+ }
///
public override void Decode(ReadOnlySpan data, Buffer2D 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 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);
}
}
}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs
index 03b78c3f8..89172cfe7 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba24PlanarTiffColor{TPixel}.cs
+++ b/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;
+
///
/// Initializes a new instance of the class.
///
+ /// The extra samples type.
/// if set to true decodes the pixel data as big endian, otherwise as little endian.
- public Rgba24PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
+ public Rgba24PlanarTiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian)
+ {
+ this.extraSamplesType = extraSamplesType;
+ this.isBigEndian = isBigEndian;
+ }
///
public override void Decode(IMemoryOwner[] data, Buffer2D 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 buffer = stackalloc byte[4];
int bufferStartIdx = this.isBigEndian ? 1 : 0;
@@ -39,6 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
Span alphaData = data[3].GetSpan();
Span 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);
}
}
}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs
index fbd18ca3e..8ee9eb0bf 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32323232TiffColor{TPixel}.cs
+++ b/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;
+
///
/// Initializes a new instance of the class.
///
+ /// The type of the extra samples.
/// if set to true decodes the pixel data as big endian, otherwise as little endian.
- public Rgba32323232TiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
+ public Rgba32323232TiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian)
+ {
+ this.extraSamplesType = extraSamplesType;
+ this.isBigEndian = isBigEndian;
+ }
///
public override void Decode(ReadOnlySpan data, Buffer2D 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);
}
}
}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs
index e23111159..c98ac1cf0 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba32PlanarTiffColor{TPixel}.cs
+++ b/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;
+
///
/// Initializes a new instance of the class.
///
+ /// The extra samples type.
/// if set to true decodes the pixel data as big endian, otherwise as little endian.
- public Rgba32PlanarTiffColor(bool isBigEndian) => this.isBigEndian = isBigEndian;
+ public Rgba32PlanarTiffColor(TiffExtraSampleType? extraSamplesType, bool isBigEndian)
+ {
+ this.extraSamplesType = extraSamplesType;
+ this.isBigEndian = isBigEndian;
+ }
///
public override void Decode(IMemoryOwner[] data, Buffer2D 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 redData = data[0].GetSpan();
Span greenData = data[1].GetSpan();
Span blueData = data[2].GetSpan();
Span 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);
}
}
}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs
index 491a42fb7..967a68ad0 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/Rgba8888TiffColor{TPixel}.cs
+++ b/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;
+ }
///
public override void Decode(ReadOnlySpan data, Buffer2D 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 vectors = hasAssociatedAlpha ? this.memoryAllocator.Allocate(width) : null;
+ Span vectorsSpan = hasAssociatedAlpha ? vectors.GetSpan() : Span.Empty;
for (int y = top; y < top + height; y++)
{
Span pixelRow = pixels.DangerousGetRowSpan(y).Slice(left, width);
@@ -32,6 +49,12 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
pixelRow,
pixelRow.Length);
+ if (hasAssociatedAlpha)
+ {
+ PixelOperations.Instance.ToVector4(this.configuration, pixelRow, vectorsSpan);
+ PixelOperations.Instance.FromVector4Destructive(this.configuration, vectorsSpan, pixelRow, PixelConversionModifiers.Premultiply | PixelConversionModifiers.Scale);
+ }
+
offset += byteCount;
}
}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs
index 4fb0797dc..f95045ec5 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaFloat32323232TiffColor{TPixel}.cs
+++ b/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;
}
}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs
index f2ccf2ec8..e2dbdfb00 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaPlanarTiffColor{TPixel}.cs
+++ b/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;
}
///
@@ -57,6 +61,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
public override void Decode(IMemoryOwner[] data, Buffer2D 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;
}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs
index 8bec7da89..74b816dbc 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/RgbaTiffColor{TPixel}.cs
+++ b/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;
}
///
@@ -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 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();
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs
index 1c608e303..c95d03946 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/TiffColorDecoderFactory{TPixel}.cs
+++ b/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(bitsPerSample);
+ return new RgbaTiffColor(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(bitsPerSample);
+ return new RgbaTiffColor(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(bitsPerSample);
+ return new RgbaTiffColor(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(bitsPerSample);
+ return new RgbaTiffColor(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(bitsPerSample);
+ return new RgbaTiffColor(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(configuration);
+ return new Rgba8888TiffColor(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(bitsPerSample);
+ return new RgbaTiffColor(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(bitsPerSample);
+ return new RgbaTiffColor(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(bitsPerSample);
+ return new RgbaTiffColor(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(configuration, isBigEndian: byteOrder == ByteOrder.BigEndian);
+ return new Rgba16161616TiffColor(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(isBigEndian: byteOrder == ByteOrder.BigEndian);
+ return new Rgba24242424TiffColor(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(isBigEndian: byteOrder == ByteOrder.BigEndian);
+ return new Rgba32323232TiffColor(extraSampleType, isBigEndian: byteOrder == ByteOrder.BigEndian);
case TiffColorType.RgbFloat323232:
DebugGuard.IsTrue(
@@ -394,6 +395,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
public static TiffBasePlanarColorDecoder 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(bitsPerSample);
+ return new RgbaPlanarTiffColor(extraSampleType, bitsPerSample);
case TiffColorType.YCbCrPlanar:
return new YCbCrPlanarTiffColor(referenceBlackAndWhite, ycbcrCoefficients, ycbcrSubSampling);
@@ -419,7 +421,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.PhotometricInterpretation
case TiffColorType.Rgba16161616Planar:
DebugGuard.IsTrue(colorMap == null, "colorMap");
- return new Rgba16PlanarTiffColor(byteOrder == ByteOrder.BigEndian);
+ return new Rgba16PlanarTiffColor(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(byteOrder == ByteOrder.BigEndian);
+ return new Rgba24PlanarTiffColor(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(byteOrder == ByteOrder.BigEndian);
+ return new Rgba32PlanarTiffColor(extraSampleType, byteOrder == ByteOrder.BigEndian);
default:
throw TiffThrowHelper.InvalidColorType(colorType.ToString());
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs
index 038281c99..d509776d7 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero16TiffColor{TPixel}.cs
+++ b/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++)
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs
index 807023b6b..fbf813078 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero24TiffColor{TPixel}.cs
+++ b/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;
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs
index 71323c7ba..40d1541c5 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32FloatTiffColor{TPixel}.cs
+++ b/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;
}
}
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs
index e433956f0..fd908c1e9 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZero32TiffColor{TPixel}.cs
+++ b/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;
diff --git a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs b/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs
index d692fc789..e1e2ba983 100644
--- a/src/ImageSharp/Formats/Tiff/PhotometricInterpretation/WhiteIsZeroTiffColor{TPixel}.cs
+++ b/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;
}
diff --git a/src/ImageSharp/Formats/Tiff/README.md b/src/ImageSharp/Formats/Tiff/README.md
index aa960b373..8cb327a7b 100644
--- a/src/ImageSharp/Formats/Tiff/README.md
+++ b/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
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
index e5b810738..1cd3d2c0c 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
+++ b/src/ImageSharp/Formats/Tiff/TiffDecoderCore.cs
@@ -118,9 +118,9 @@ namespace SixLabors.ImageSharp.Formats.Tiff
public TiffFillOrder FillOrder { get; set; }
///
- /// Gets or sets the extra samples, which can contain the alpha channel data.
+ /// Gets or sets the extra samples type.
///
- public TiffExtraSampleType? ExtraSamples { get; set; }
+ public TiffExtraSampleType? ExtraSamplesType { get; set; }
///
/// Gets or sets the JPEG tables when jpeg compression is used.
@@ -157,40 +157,52 @@ namespace SixLabors.ImageSharp.Formats.Tiff
public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
- this.inputStream = stream;
- var reader = new DirectoryReader(stream, this.Configuration.MemoryAllocator);
-
- IEnumerable directories = reader.Read();
- this.byteOrder = reader.ByteOrder;
- this.isBigTiff = reader.IsBigTiff;
-
var frames = new List>();
- foreach (ExifProfile ifd in directories)
+ try
{
- cancellationToken.ThrowIfCancellationRequested();
- ImageFrame frame = this.DecodeFrame(ifd, cancellationToken);
- frames.Add(frame);
+ this.inputStream = stream;
+ var reader = new DirectoryReader(stream, this.Configuration.MemoryAllocator);
+
+ IEnumerable 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 frame = this.DecodeFrame(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 root = frames[0];
- this.Dimensions = root.Size();
- foreach (ImageFrame frame in frames)
- {
- if (frame.Size() != root.Size())
+ // TODO: Tiff frames can have different sizes.
+ ImageFrame root = frames[0];
+ this.Dimensions = root.Size();
+ foreach (ImageFrame 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(this.Configuration, metadata, frames);
}
+ catch
+ {
+ foreach (ImageFrame f in frames)
+ {
+ f.Dispose();
+ }
- return new Image(this.Configuration, metadata, frames);
+ throw;
+ }
}
///
@@ -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 stripOffsetsMemory = this.ConvertNumbers(stripOffsetsArray, out Span stripOffsets);
- IMemoryOwner stripByteCountsMemory = this.ConvertNumbers(stripByteCountsArray, out Span stripByteCounts);
+ using IMemoryOwner stripOffsetsMemory = this.ConvertNumbers(stripOffsetsArray, out Span stripOffsets);
+ using IMemoryOwner stripByteCountsMemory = this.ConvertNumbers(stripByteCountsArray, out Span 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 colorDecoder = TiffColorDecoderFactory.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,
diff --git a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs b/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
index 23bc5f15f..6baf71466 100644
--- a/src/ImageSharp/Formats/Tiff/TiffDecoderOptionsParser.cs
+++ b/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.");
}
}
diff --git a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs
index b7d4b6e7c..532423c4f 100644
--- a/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs
+++ b/src/ImageSharp/Formats/Tiff/Utils/TiffUtils.cs
@@ -46,7 +46,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static TPixel ColorFromRgba64(Rgba64 rgba, ulong r, ulong g, ulong b, TPixel color)
+ public static TPixel ColorFromRgb64(Rgba64 rgba, ulong r, ulong g, ulong b, TPixel color)
where TPixel : unmanaged, IPixel
{
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(Rgba64 rgba, ulong r, ulong g, ulong b, ulong a, TPixel color)
+ where TPixel : unmanaged, IPixel
+ {
+ 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(ulong r, ulong g, ulong b, TPixel color)
where TPixel : unmanaged, IPixel
{
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(ulong r, ulong g, ulong b, ulong a, TPixel color)
where TPixel : unmanaged, IPixel
{
- 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(ulong r, ulong g, ulong b, ulong a, TPixel color)
+ where TPixel : unmanaged, IPixel
+ {
+ Vector4 colorVector = new Vector4(r, g, b, a) * Scale24Bit;
+ return UnPremultiply(ref colorVector, color);
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorScaleTo32Bit(ulong r, ulong g, ulong b, TPixel color)
where TPixel : unmanaged, IPixel
{
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(ulong r, ulong g, ulong b, ulong a, TPixel color)
where TPixel : unmanaged, IPixel
{
- 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(ulong r, ulong g, ulong b, ulong a, TPixel color)
+ where TPixel : unmanaged, IPixel
+ {
+ Vector4 colorVector = new Vector4(r, g, b, a) * Scale32Bit;
+ return UnPremultiply(ref colorVector, color);
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TPixel ColorFromL16(L16 l16, ushort intensity, TPixel color)
where TPixel : unmanaged, IPixel
@@ -113,7 +138,7 @@ namespace SixLabors.ImageSharp.Formats.Tiff.Utils
where TPixel : unmanaged, IPixel
{
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
{
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(ref Vector4 vector, TPixel color)
+ where TPixel : unmanaged, IPixel
+ {
+ 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);
}
}
}
diff --git a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
index 252af7867..84ee5d0a2 100644
--- a/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Webp/WebpDecoderCore.cs
@@ -93,37 +93,53 @@ namespace SixLabors.ImageSharp.Formats.Webp
public Image Decode(BufferedReadStream stream, CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
- this.Metadata = new ImageMetadata();
- this.currentStream = stream;
+ Image 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(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(stream, this.webImageInfo.Features, this.webImageInfo.Width, this.webImageInfo.Height, fileSize);
+ }
- var image = new Image(this.Configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.Metadata);
- Buffer2D 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(this.Configuration, (int)this.webImageInfo.Width, (int)this.webImageInfo.Height, this.Metadata);
+ Buffer2D 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.
}
}
///
- /// 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.
///
/// The chunk type.
/// The webp image features.
/// For identify, the alpha data should not be read.
- /// true, if animation chunk was found.
+ /// true, if its a alpha chunk.
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(alphaDataSize);
- Span 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
///
/// Reads the optional metadata EXIF of XMP profiles, which can follow the image data.
///
- private void ReadOptionalMetadata()
+ /// The webp features.
+ 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);
+ }
+ }
+ }
+
+ ///
+ /// Reads the EXIF profile from the stream.
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// Reads the XMP profile the stream.
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// Reads the ICCP chunk from the stream.
+ ///
+ 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;
+ }
+ }
+ }
+
+ ///
+ /// Reads the animation parameters chunk from the stream.
+ ///
+ /// The webp features.
+ 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);
+ }
+
+ ///
+ /// Reads the alpha data chunk data from the stream.
+ ///
+ /// The features.
+ /// if set to true, skips the chunk data.
+ 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(alphaDataSize);
+ Span 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");
}
}
+ ///
+ /// Identifies the chunk type from the chunk.
+ ///
+ ///
+ /// Thrown if the input stream is not valid.
+ ///
+ 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.");
+ }
+
+ ///
+ /// 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.
+ ///
+ /// The chunk size in bytes.
+ 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.");
+ }
+
///
public void Dispose() => this.AlphaData?.Dispose();
}
diff --git a/src/ImageSharp/Formats/Webp/WebpThrowHelper.cs b/src/ImageSharp/Formats/Webp/WebpThrowHelper.cs
index fffdd3410..5f063fd11 100644
--- a/src/ImageSharp/Formats/Webp/WebpThrowHelper.cs
+++ b/src/ImageSharp/Formats/Webp/WebpThrowHelper.cs
@@ -8,6 +8,13 @@ namespace SixLabors.ImageSharp.Formats.Webp
{
internal static class WebpThrowHelper
{
+ ///
+ /// Cold path optimization for throwing 's.
+ ///
+ /// The error message for the exception.
+ [MethodImpl(InliningOptions.ColdPath)]
+ public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage);
+
///
/// Cold path optimization for throwing -s
///
diff --git a/src/ImageSharp/IO/BufferedReadStream.cs b/src/ImageSharp/IO/BufferedReadStream.cs
index 4ab7f312b..2823b8ed6 100644
--- a/src/ImageSharp/IO/BufferedReadStream.cs
+++ b/src/ImageSharp/IO/BufferedReadStream.cs
@@ -114,6 +114,15 @@ namespace SixLabors.ImageSharp.IO
///
public override bool CanWrite { get; } = false;
+ ///
+ /// Gets remaining byte count available to read.
+ ///
+ public long RemainingBytes
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.Length - this.Position;
+ }
+
///
/// Gets the underlying stream.
///
diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj
index 39c85c4f2..7e58607dd 100644
--- a/src/ImageSharp/ImageSharp.csproj
+++ b/src/ImageSharp/ImageSharp.csproj
@@ -12,33 +12,23 @@
$(RepositoryUrl)
Image Resize Crop Gif Jpg Jpeg Bitmap Pbm Png Tga Tiff WebP NetCore
A new, fully featured, fully managed, cross-platform, 2D graphics API for .NET
- Debug;Release;Debug-InnerLoop;Release-InnerLoop
+ Debug;Release
-
- 2.0
+
+ 3.0
- net6.0;net5.0;netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;net472
-
-
-
-
- net5.0;netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;net472
-
-
-
-
- netcoreapp3.1
+ net7.0;net6.0
- netcoreapp3.1;netcoreapp2.1;netstandard2.1;netstandard2.0;net472
+ net6.0
@@ -47,17 +37,6 @@
-
-
-
-
-
-
-
-
-
-
-
True
diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs
index 5fd613b1f..4ec9b3267 100644
--- a/src/ImageSharp/Metadata/Profiles/Exif/ExifEncodedStringHelpers.cs
+++ b/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 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 buffer, out CharacterCode code)
{
diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
index e0ab8ecf9..665e4753a 100644
--- a/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
+++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifProfile.cs
@@ -121,6 +121,14 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
}
}
+ ///
+ /// Returns the thumbnail in the EXIF profile when available.
+ ///
+ ///
+ /// The .
+ ///
+ public Image CreateThumbnail() => this.CreateThumbnail();
+
///
/// Returns the thumbnail in the EXIF profile when available.
///
diff --git a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs
index 9b5e098c8..abfe835b1 100644
--- a/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs
+++ b/src/ImageSharp/Metadata/Profiles/Exif/ExifReader.cs
@@ -202,7 +202,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Exif
protected void ReadValues64(List 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();
diff --git a/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs
index f891ffa97..a7f93e23e 100644
--- a/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/Convolution/BoxBlurExtensions.cs
@@ -39,5 +39,26 @@ namespace SixLabors.ImageSharp.Processing
/// The to allow chaining of operations.
public static IImageProcessingContext BoxBlur(this IImageProcessingContext source, int radius, Rectangle rectangle)
=> source.ApplyProcessor(new BoxBlurProcessor(radius), rectangle);
+
+ ///
+ /// Applies a box blur to the image.
+ ///
+ /// The image this method extends.
+ /// The 'radius' value representing the size of the area to sample.
+ ///
+ /// The structure that specifies the portion of the image object to alter.
+ ///
+ ///
+ /// The to use when mapping the pixels outside of the border, in X direction.
+ ///
+ ///
+ /// The to use when mapping the pixels outside of the border, in Y direction.
+ ///
+ /// The to allow chaining of operations.
+ 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);
+ }
}
}
diff --git a/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs
index bd4fb716d..49af591e9 100644
--- a/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs
+++ b/src/ImageSharp/Processing/Extensions/Convolution/GaussianBlurExtensions.cs
@@ -39,5 +39,26 @@ namespace SixLabors.ImageSharp.Processing
/// The to allow chaining of operations.
public static IImageProcessingContext GaussianBlur(this IImageProcessingContext source, float sigma, Rectangle rectangle)
=> source.ApplyProcessor(new GaussianBlurProcessor(sigma), rectangle);
+
+ ///
+ /// Applies a Gaussian blur to the image.
+ ///
+ /// The image this method extends.
+ /// The 'sigma' value representing the weight of the blur.
+ ///
+ /// The structure that specifies the portion of the image object to alter.
+ ///
+ ///
+ /// The to use when mapping the pixels outside of the border, in X direction.
+ ///
+ ///
+ /// The to use when mapping the pixels outside of the border, in Y direction.
+ ///
+ /// The to allow chaining of operations.
+ 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);
+ }
}
}
diff --git a/src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs b/src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs
index f5b8798f4..5ac9d6909 100644
--- a/src/ImageSharp/Processing/Extensions/Convolution/GaussianSharpenExtensions.cs
+++ b/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);
+
+ ///
+ /// Applies a Gaussian sharpening filter to the image.
+ ///
+ /// The image this method extends.
+ /// The 'sigma' value representing the weight of the blur.
+ ///
+ /// The structure that specifies the portion of the image object to alter.
+ ///
+ ///
+ /// The to use when mapping the pixels outside of the border, in X direction.
+ ///
+ ///
+ /// The to use when mapping the pixels outside of the border, in Y direction.
+ ///
+ /// The to allow chaining of operations.
+ 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);
+ }
}
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/BorderWrappingMode.cs b/src/ImageSharp/Processing/Processors/Convolution/BorderWrappingMode.cs
new file mode 100644
index 000000000..e835fc748
--- /dev/null
+++ b/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
+{
+ ///
+ /// Wrapping mode for the border pixels in convolution processing.
+ ///
+ public enum BorderWrappingMode : byte
+ {
+ /// Repeat the border pixel value: aaaaaa|abcdefgh|hhhhhhh
+ Repeat = 0,
+
+ /// Take values from the opposite edge: cdefgh|abcdefgh|abcdefg
+ Wrap = 1,
+
+ /// Mirror the last few border values: fedcba|abcdefgh|hgfedcb
+ /// This Mode is similar to , but here the very border pixel is repeated.
+ Mirror = 2,
+
+ /// Bounce off the border: fedcb|abcdefgh|gfedcb
+ /// This Mode is similar to , but here the very border pixel is not repeated.
+ Bounce = 3
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs
index da6b96718..a622739fd 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor.cs
@@ -21,9 +21,24 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
/// The 'radius' value representing the size of the area to sample.
///
- public BoxBlurProcessor(int radius)
+ /// The to use when mapping the pixels outside of the border, in X direction.
+ /// The to use when mapping the pixels outside of the border, in Y direction.
+ public BoxBlurProcessor(int radius, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
{
this.Radius = radius;
+ this.BorderWrapModeX = borderWrapModeX;
+ this.BorderWrapModeY = borderWrapModeY;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The 'radius' value representing the size of the area to sample.
+ ///
+ public BoxBlurProcessor(int radius)
+ : this(radius, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat)
+ {
}
///
@@ -39,9 +54,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
public int Radius { get; }
+ ///
+ /// Gets the to use when mapping the pixels outside of the border, in X direction.
+ ///
+ public BorderWrappingMode BorderWrapModeX { get; }
+
+ ///
+ /// Gets the to use when mapping the pixels outside of the border, in Y direction.
+ ///
+ public BorderWrappingMode BorderWrapModeY { get; }
+
///
public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle)
where TPixel : unmanaged, IPixel
- => new BoxBlurProcessor(configuration, this, source, sourceRectangle);
+ => new BoxBlurProcessor(configuration, this, source, sourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
}
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs
index 5beadb0ce..ceebdf15a 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/BoxBlurProcessor{TPixel}.cs
@@ -27,15 +27,49 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
this.Kernel = CreateBoxKernel(kernelSize);
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The configuration which allows altering default behaviour or extending the library.
+ /// The defining the processor parameters.
+ /// The source for the current processor instance.
+ /// The source area to process for the current processor instance.
+ /// The to use when mapping the pixels outside of the border, in X direction.
+ /// The to use when mapping the pixels outside of the border, in Y direction.
+ public BoxBlurProcessor(
+ Configuration configuration,
+ BoxBlurProcessor definition,
+ Image 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;
+ }
+
///
/// Gets the 1D convolution kernel.
///
public float[] Kernel { get; }
+ ///
+ /// Gets the to use when mapping the pixels outside of the border, in X direction.
+ ///
+ public BorderWrappingMode BorderWrapModeX { get; }
+
+ ///
+ /// Gets the to use when mapping the pixels outside of the border, in Y direction.
+ ///
+ public BorderWrappingMode BorderWrapModeY { get; }
+
///
protected override void OnFrameApply(ImageFrame source)
{
- using var processor = new Convolution2PassProcessor(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle);
+ using var processor = new Convolution2PassProcessor(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
processor.Apply(source);
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
index fa58422dc..2fc0a5fe8 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/Convolution2PassProcessor{TPixel}.cs
@@ -26,16 +26,22 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// Whether the convolution filter is applied to alpha as well as the color channels.
/// The source for the current processor instance.
/// The source area to process for the current processor instance.
+ /// The to use when mapping the pixels outside of the border, in X direction.
+ /// The to use when mapping the pixels outside of the border, in Y direction.
public Convolution2PassProcessor(
Configuration configuration,
float[] kernel,
bool preserveAlpha,
Image 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;
}
///
@@ -48,6 +54,16 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
public bool PreserveAlpha { get; }
+ ///
+ /// Gets the to use when mapping the pixels outside of the border, in X direction.
+ ///
+ public BorderWrappingMode BorderWrapModeX { get; }
+
+ ///
+ /// Gets the to use when mapping the pixels outside of the border, in Y direction.
+ ///
+ public BorderWrappingMode BorderWrapModeY { get; }
+
///
protected override void OnFrameApply(ImageFrame 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(
diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs
index 1fa65b62c..3af9791dc 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor.cs
@@ -32,6 +32,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The 'sigma' value representing the weight of the blur.
+ /// The to use when mapping the pixels outside of the border, in X direction.
+ /// The to use when mapping the pixels outside of the border, in Y direction.
+ public GaussianBlurProcessor(float sigma, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
+ : this(sigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(sigma), borderWrapModeX, borderWrapModeY)
+ {
+ }
+
///
/// Initializes a new instance of the class.
///
@@ -54,9 +65,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// This should be at least twice the sigma value.
///
public GaussianBlurProcessor(float sigma, int radius)
+ : this(sigma, radius, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The 'sigma' value representing the weight of the blur.
+ ///
+ ///
+ /// The 'radius' value representing the size of the area to sample.
+ /// This should be at least twice the sigma value.
+ ///
+ ///
+ /// The to use when mapping the pixels outside of the border, in X direction.
+ ///
+ ///
+ /// The to use when mapping the pixels outside of the border, in Y direction.
+ ///
+ public GaussianBlurProcessor(float sigma, int radius, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
{
this.Sigma = sigma;
this.Radius = radius;
+ this.BorderWrapModeX = borderWrapModeX;
+ this.BorderWrapModeY = borderWrapModeY;
}
///
@@ -69,9 +103,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
public int Radius { get; }
+ ///
+ /// Gets the to use when mapping the pixels outside of the border, in X direction.
+ ///
+ public BorderWrappingMode BorderWrapModeX { get; }
+
+ ///
+ /// Gets the to use when mapping the pixels outside of the border, in Y direction.
+ ///
+ public BorderWrappingMode BorderWrapModeY { get; }
+
///
public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle)
where TPixel : unmanaged, IPixel
- => new GaussianBlurProcessor(configuration, this, source, sourceRectangle);
+ => new GaussianBlurProcessor(configuration, this, source, sourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
}
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs
index 4ade01f91..16b05b8bb 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/GaussianBlurProcessor{TPixel}.cs
+++ b/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);
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The configuration which allows altering default behaviour or extending the library.
+ /// The defining the processor parameters.
+ /// The source for the current processor instance.
+ /// The source area to process for the current processor instance.
+ /// The to use when mapping the pixels outside of the border, in X direction.
+ /// The to use when mapping the pixels outside of the border, in Y direction.
+ public GaussianBlurProcessor(
+ Configuration configuration,
+ GaussianBlurProcessor definition,
+ Image 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;
+ }
+
///
/// Gets the 1D convolution kernel.
///
public float[] Kernel { get; }
+ ///
+ /// Gets the to use when mapping the pixels outside of the border, in X direction.
+ ///
+ public BorderWrappingMode BorderWrapModeX { get; }
+
+ ///
+ /// Gets the to use when mapping the pixels outside of the border, in Y direction.
+ ///
+ public BorderWrappingMode BorderWrapModeY { get; }
+
///
protected override void OnFrameApply(ImageFrame source)
{
- using var processor = new Convolution2PassProcessor(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle);
+ using var processor = new Convolution2PassProcessor(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
processor.Apply(source);
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs
index 7e1f02906..98c897c21 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor.cs
@@ -32,6 +32,17 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
{
}
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The 'sigma' value representing the weight of the blur.
+ /// The to use when mapping the pixels outside of the border, in X direction.
+ /// The to use when mapping the pixels outside of the border, in Y direction.
+ public GaussianSharpenProcessor(float sigma, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
+ : this(sigma, ConvolutionProcessorHelpers.GetDefaultGaussianRadius(sigma), borderWrapModeX, borderWrapModeY)
+ {
+ }
+
///
/// Initializes a new instance of the class.
///
@@ -54,9 +65,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// This should be at least twice the sigma value.
///
public GaussianSharpenProcessor(float sigma, int radius)
+ : this(sigma, radius, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The 'sigma' value representing the weight of the blur.
+ ///
+ ///
+ /// The 'radius' value representing the size of the area to sample.
+ /// This should be at least twice the sigma value.
+ ///
+ ///
+ /// The to use when mapping the pixels outside of the border, in X direction.
+ ///
+ ///
+ /// The to use when mapping the pixels outside of the border, in Y direction.
+ ///
+ public GaussianSharpenProcessor(float sigma, int radius, BorderWrappingMode borderWrapModeX, BorderWrappingMode borderWrapModeY)
{
this.Sigma = sigma;
this.Radius = radius;
+ this.BorderWrapModeX = borderWrapModeX;
+ this.BorderWrapModeY = borderWrapModeY;
}
///
@@ -69,9 +103,19 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
public int Radius { get; }
+ ///
+ /// Gets the to use when mapping the pixels outside of the border, in X direction.
+ ///
+ public BorderWrappingMode BorderWrapModeX { get; }
+
+ ///
+ /// Gets the to use when mapping the pixels outside of the border, in Y direction.
+ ///
+ public BorderWrappingMode BorderWrapModeY { get; }
+
///
public IImageProcessor CreatePixelSpecificProcessor(Configuration configuration, Image source, Rectangle sourceRectangle)
where TPixel : unmanaged, IPixel
- => new GaussianSharpenProcessor(configuration, this, source, sourceRectangle);
+ => new GaussianSharpenProcessor(configuration, this, source, sourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
}
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs
index 73aaaec18..bddaab233 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/GaussianSharpenProcessor{TPixel}.cs
@@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// Initializes a new instance of the class.
///
/// The configuration which allows altering default behaviour or extending the library.
- /// The defining the processor parameters.
+ /// The defining the processor parameters.
/// The source for the current processor instance.
/// The source area to process for the current processor instance.
public GaussianSharpenProcessor(
@@ -24,10 +24,32 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
GaussianSharpenProcessor definition,
Image source,
Rectangle sourceRectangle)
+ : this(configuration, definition, source, sourceRectangle, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The configuration which allows altering default behaviour or extending the library.
+ /// The defining the processor parameters.
+ /// The source for the current processor instance.
+ /// The source area to process for the current processor instance.
+ /// The to use when mapping the pixels outside of the border, in X direction.
+ /// The to use when mapping the pixels outside of the border, in Y direction.
+ public GaussianSharpenProcessor(
+ Configuration configuration,
+ GaussianSharpenProcessor definition,
+ Image 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;
}
///
@@ -35,10 +57,20 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
///
public float[] Kernel { get; }
+ ///
+ /// Gets the to use when mapping the pixels outside of the border, in X direction.
+ ///
+ public BorderWrappingMode BorderWrapModeX { get; }
+
+ ///
+ /// Gets the to use when mapping the pixels outside of the border, in Y direction.
+ ///
+ public BorderWrappingMode BorderWrapModeY { get; }
+
///
protected override void OnFrameApply(ImageFrame source)
{
- using var processor = new Convolution2PassProcessor(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle);
+ using var processor = new Convolution2PassProcessor(this.Configuration, this.Kernel, false, this.Source, this.SourceRectangle, this.BorderWrapModeX, this.BorderWrapModeY);
processor.Apply(source);
}
diff --git a/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs b/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs
index 904b599f7..98a4ca357 100644
--- a/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs
+++ b/src/ImageSharp/Processing/Processors/Convolution/KernelSamplingMap.cs
@@ -31,7 +31,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Convolution
/// The convolution kernel.
/// The source bounds.
public void BuildSamplingOffsetMap(DenseMatrix kernel, Rectangle bounds)
- => this.BuildSamplingOffsetMap(kernel.Rows, kernel.Columns, bounds);
+ => this.BuildSamplingOffsetMap(kernel.Rows, kernel.Columns, bounds, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat);
///
/// 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
/// The width (number of columns) of the convolution kernel to use.
/// The source bounds.
public void BuildSamplingOffsetMap(int kernelHeight, int kernelWidth, Rectangle bounds)
+ => this.BuildSamplingOffsetMap(kernelHeight, kernelWidth, bounds, BorderWrappingMode.Repeat, BorderWrappingMode.Repeat);
+
+ ///
+ /// Builds a map of the sampling offsets for the kernel clamped by the given bounds.
+ ///
+ /// The height (number of rows) of the convolution kernel to use.
+ /// The width (number of columns) of the convolution kernel to use.
+ /// The source bounds.
+ /// The wrapping mode on the horizontal borders.
+ /// The wrapping mode on the vertical borders.
+ public void BuildSamplingOffsetMap(int kernelHeight, int kernelWidth, Rectangle bounds, BorderWrappingMode xBorderMode, BorderWrappingMode yBorderMode)
{
this.yOffsets = this.allocator.Allocate(bounds.Height * kernelHeight);
this.xOffsets = this.allocator.Allocate(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 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 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 offsets, int boundsSize, int kernelSize, int min, int max, BorderWrappingMode borderMode)
+ {
+ int radius = kernelSize >> 1;
+ Span 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 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;
+ }
+ }
+ }
}
}
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs
index f6fadca33..f74cf5949 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Linear/AffineTransformProcessor{TPixel}.cs
+++ b/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 span)
+ private void InvokeMacOS(in RowInterval rows, Span span)
{
Matrix3x2 matrix = this.matrix;
TResampler sampler = this.sampler;
diff --git a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs
index 26b970d8b..9bb9c113d 100644
--- a/src/ImageSharp/Processing/Processors/Transforms/Linear/ProjectiveTransformProcessor{TPixel}.cs
+++ b/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 span)
+ public void InvokeMacOS(in RowInterval rows, Span span)
{
Matrix4x4 matrix = this.matrix;
TResampler sampler = this.sampler;
diff --git a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
index 9a9274199..24f618d11 100644
--- a/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
+++ b/tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
@@ -9,7 +9,7 @@
portable
false
- Debug;Release;Debug-InnerLoop;Release-InnerLoop
+ Debug;Release
@@ -17,17 +17,12 @@
- net6.0;net5.0;netcoreapp3.1;netcoreapp2.1;net472
-
-
-
-
- netcoreapp3.1
+ net7.0;net6.0
- net5.0;netcoreapp3.1;netcoreapp2.1;net472
+ net6.0
diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj
index 6ff5a4cc7..492ce36b8 100644
--- a/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj
+++ b/tests/ImageSharp.Tests.ProfilingSandbox/ImageSharp.Tests.ProfilingSandbox.csproj
@@ -12,24 +12,19 @@
false
false
- Debug;Release;Debug-InnerLoop;Release-InnerLoop
+ Debug;Release
false
- net6.0;net5.0;netcoreapp3.1;netcoreapp2.1;net472
-
-
-
-
- netcoreapp3.1
+ net7.0;net6.0
- net5.0;netcoreapp3.1;netcoreapp2.1;net472
+ net6.0
diff --git a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs b/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs
index 95e64b153..0c7b157b2 100644
--- a/tests/ImageSharp.Tests.ProfilingSandbox/LoadResizeSaveParallelMemoryStress.cs
+++ b/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;
diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
index 71d0ab34a..43ec45a34 100644
--- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs
+++ b/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;
diff --git a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
index 42bdb3ba9..ffe053f6a 100644
--- a/tests/ImageSharp.Tests/Formats/Gif/GifDecoderTests.cs
+++ b/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;
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs
index 9c467a1cc..a40ae2af5 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs
+++ b/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);
}
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
index 70cbc3af7..d5e0f081b 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
+++ b/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 CustomToleranceValues = new()
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs
index 9864d62b8..d9915f17d 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs
+++ b/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(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(Configuration.Default, stream, default);
+ test(img);
+ }
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
index faf40ccf7..1faa6f0f4 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
+++ b/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;
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.Metadata.cs
new file mode 100644
index 000000000..2bbce6cb1
--- /dev/null
+++ b/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(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ Exception ex = Record.Exception(() =>
+ {
+ var encoder = new JpegEncoder();
+ var stream = new MemoryStream();
+
+ using Image image = provider.GetImage(JpegDecoder);
+ image.Save(stream, encoder);
+ });
+
+ Assert.Null(ex);
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
index 18eae9fbd..d860836e0 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegEncoderTests.cs
+++ b/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();
diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs
index 97237bca5..eb3bc8c9a 100644
--- a/tests/ImageSharp.Tests/Formats/Pbm/PbmDecoderTests.cs
+++ b/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]
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
index 752036126..a4fcf63ba 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
+++ b/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;
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs
index 11e3fbb23..90fa5777b 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderFilterTests.cs
+++ b/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]
diff --git a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs
index 55dc2ecdd..e83c5a98c 100644
--- a/tests/ImageSharp.Tests/Formats/Tga/TgaDecoderTests.cs
+++ b/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();
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs
index aeadae255..7e45edb6d 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderBaseTester.cs
+++ b/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(TestImageProvider provider, IImageDecoder referenceDecoder = null, bool useExactComparer = true, float compareTolerance = 0.001f)
where TPixel : unmanaged, IPixel
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
index bd300f799..9460f3a35 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/TiffDecoderTests.cs
+++ b/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(TestImageProvider provider)
where TPixel : unmanaged, IPixel => TestTiffDecoder(provider);
+ [Theory]
+ [WithFile(Rgba3BitAssociatedAlpha, PixelTypes.Rgba32)]
+ public void TiffDecoder_CanDecode_12Bit_WithAssociatedAlpha(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ 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 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(TestImageProvider provider)
@@ -226,6 +244,24 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_20Bit_WithUnassociatedAlpha(TestImageProvider provider)
where TPixel : unmanaged, IPixel => TestTiffDecoder(provider);
+ [Theory]
+ [WithFile(Rgba5BitAssociatedAlpha, PixelTypes.Rgba32)]
+ public void TiffDecoder_CanDecode_20Bit_WithAssociatedAlpha(TestImageProvider provider)
+
+ where TPixel : unmanaged, IPixel
+ {
+ 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 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(TestImageProvider provider)
@@ -236,6 +272,23 @@ namespace SixLabors.ImageSharp.Tests.Formats.Tiff
public void TiffDecoder_CanDecode_24Bit_WithUnassociatedAlpha(TestImageProvider provider)
where TPixel : unmanaged, IPixel => TestTiffDecoder(provider);
+ [Theory]
+ [WithFile(Rgba6BitAssociatedAlpha, PixelTypes.Rgba32)]
+ public void TiffDecoder_CanDecode_24Bit_WithAssociatedAlpha(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ 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 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
{
// 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 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(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ 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 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(TestImageProvider provider)
where TPixel : unmanaged, IPixel => TestTiffDecoder(provider);
+ [Theory]
+ [WithFile(Rgba10BitAssociatedAlphaBigEndian, PixelTypes.Rgba32)]
+ [WithFile(Rgba10BitAssociatedAlphaLittleEndian, PixelTypes.Rgba32)]
+ public void TiffDecoder_CanDecode_40Bit_WithAssociatedAlpha(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ 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 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(TestImageProvider provider)
where TPixel : unmanaged, IPixel => TestTiffDecoder(provider);
+ [Theory]
+ [WithFile(Rgba12BitAssociatedAlphaBigEndian, PixelTypes.Rgba32)]
+ [WithFile(Rgba12BitAssociatedAlphaLittleEndian, PixelTypes.Rgba32)]
+ public void TiffDecoder_CanDecode_48Bit_WithAssociatedAlpha(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ 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 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(TestImageProvider provider)
where TPixel : unmanaged, IPixel => TestTiffDecoder(provider);
+ [Theory]
+ [WithFile(Rgba14BitAssociatedAlphaBigEndian, PixelTypes.Rgba32)]
+ [WithFile(Rgba14BitAssociatedAlphaLittleEndian, PixelTypes.Rgba32)]
+ public void TiffDecoder_CanDecode_56Bit_WithAssociatedAlpha(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ 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 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)]
diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs
index 5e878f780..f5f472bf3 100644
--- a/tests/ImageSharp.Tests/Formats/WebP/WebpDecoderTests.cs
+++ b/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();
diff --git a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs b/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs
index 0064e6da7..35d285d0d 100644
--- a/tests/ImageSharp.Tests/Formats/WebP/WebpMetaDataTests.cs
+++ b/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(TestImageProvider provider)
+ where TPixel : unmanaged, IPixel
+ {
+ Exception ex = Record.Exception(() =>
+ {
+ using Image image = provider.GetImage();
+ });
+ Assert.Null(ex);
+ }
}
}
diff --git a/tests/ImageSharp.Tests/Helpers/RuntimeEnvironmentTests.cs b/tests/ImageSharp.Tests/Helpers/RuntimeEnvironmentTests.cs
index 8074b8b15..5246f8f91 100644
--- a/tests/ImageSharp.Tests/Helpers/RuntimeEnvironmentTests.cs
+++ b/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));
}
diff --git a/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs b/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs
index a4044b906..7683ee688 100644
--- a/tests/ImageSharp.Tests/Image/ImageTests.LoadPixelData.cs
+++ b/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, };
diff --git a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs b/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs
index 357d02be4..ce1f902e5 100644
--- a/tests/ImageSharp.Tests/Image/LargeImageIntegrationTests.cs
+++ b/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(
diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj
index 28c778787..a4f1de17b 100644
--- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj
+++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj
@@ -6,23 +6,18 @@
SixLabors.ImageSharp.Tests
AnyCPU;x64;x86
SixLabors.ImageSharp.Tests
- Debug;Release;Debug-InnerLoop;Release-InnerLoop
+ Debug;Release
- net6.0;net5.0;netcoreapp3.1;netcoreapp2.1;net472
-
-
-
-
- netcoreapp3.1
+ net7.0;net6.0
- net5.0;netcoreapp3.1;netcoreapp2.1;net472
+ net6.0
@@ -47,7 +42,6 @@
-
diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs
index f748b7feb..d1a8e25bb 100644
--- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.Trim.cs
+++ b/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)
diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs
index 7d98eff61..2976bf7fd 100644
--- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedMemoryPoolTests.cs
+++ b/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)]
diff --git a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs b/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs
index 414f991f7..0520c3c1f 100644
--- a/tests/ImageSharp.Tests/Memory/Allocators/UniformUnmanagedPoolMemoryAllocatorTests.cs
+++ b/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;
}
diff --git a/tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs b/tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs
index f58136f73..5b6938df1 100644
--- a/tests/ImageSharp.Tests/Memory/Buffer2DTests.SwapOrCopyContent.cs
+++ b/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 a = this.MemoryAllocator.Allocate2D(10, 5, AllocationOptions.Clean))
- using (Buffer2D b = this.MemoryAllocator.Allocate2D(3, 7, AllocationOptions.Clean))
+ using (Buffer2D a = this.memoryAllocator.Allocate2D(10, 5, AllocationOptions.Clean))
+ using (Buffer2D b = this.memoryAllocator.Allocate2D(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.Wrap(new int[100]);
using var dest = new Buffer2D(destData, 10, 10);
- using (Buffer2D source = this.MemoryAllocator.Allocate2D(10, 10, AllocationOptions.Clean))
+ using (Buffer2D source = this.memoryAllocator.Allocate2D(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 a = this.MemoryAllocator.Allocate2D(48, 2);
- using Buffer2D b = this.MemoryAllocator.Allocate2D(50, 2);
+ this.memoryAllocator.BufferCapacityInBytes = sizeof(int) * 50;
+ using Buffer2D a = this.memoryAllocator.Allocate2D(48, 2);
+ using Buffer2D b = this.memoryAllocator.Allocate2D(50, 2);
Memory a0 = a.FastMemoryGroup[0];
Memory a1 = a.FastMemoryGroup[1];
@@ -90,8 +90,8 @@ namespace SixLabors.ImageSharp.Tests.Memory
[Fact]
public void WhenBothAreMemoryOwners_ShouldReplaceViews()
{
- using Buffer2D a = this.MemoryAllocator.Allocate2D(100, 1);
- using Buffer2D b = this.MemoryAllocator.Allocate2D(100, 2);
+ using Buffer2D a = this.memoryAllocator.Allocate2D(100, 1);
+ using Buffer2D b = this.memoryAllocator.Allocate2D(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(data);
using var dest = new Buffer2D(MemoryGroup.Wrap(destOwner.Memory), 21, 1);
- using Buffer2D source = this.MemoryAllocator.Allocate2D(21, 1);
+ using Buffer2D source = this.memoryAllocator.Allocate2D(21, 1);
source.FastMemoryGroup[0].Span[10] = color;
@@ -145,7 +145,7 @@ namespace SixLabors.ImageSharp.Tests.Memory
using var destOwner = new TestMemoryManager(data);
using var dest = new Buffer2D(MemoryGroup.Wrap(destOwner.Memory), 21, 1);
- using Buffer2D source = this.MemoryAllocator.Allocate2D(22, 1);
+ using Buffer2D source = this.memoryAllocator.Allocate2D(22, 1);
source.FastMemoryGroup[0].Span[10] = color;
diff --git a/tests/ImageSharp.Tests/MemoryAllocatorValidator.cs b/tests/ImageSharp.Tests/MemoryAllocatorValidator.cs
new file mode 100644
index 000000000..13664ee9b
--- /dev/null
+++ b/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 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();
+ }
+ }
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs
index a85985227..1668b7a12 100644
--- a/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs
+++ b/tests/ImageSharp.Tests/Metadata/Profiles/Exif/ExifProfileTests.cs
@@ -354,10 +354,15 @@ namespace SixLabors.ImageSharp.Tests.Metadata.Profiles.Exif
TestProfile(profile);
- using Image thumbnail = profile.CreateThumbnail();
+ using Image thumbnail = profile.CreateThumbnail();
Assert.NotNull(thumbnail);
Assert.Equal(256, thumbnail.Width);
Assert.Equal(170, thumbnail.Height);
+
+ using Image genericThumbnail = profile.CreateThumbnail();
+ Assert.NotNull(genericThumbnail);
+ Assert.Equal(256, genericThumbnail.Width);
+ Assert.Equal(170, genericThumbnail.Height);
}
[Fact]
diff --git a/tests/ImageSharp.Tests/Processing/Convolution/KernelSamplingMapTest.cs b/tests/ImageSharp.Tests/Processing/Convolution/KernelSamplingMapTest.cs
new file mode 100644
index 000000000..64e1ea2cb
--- /dev/null
+++ b/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);
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs b/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
index b1ff3df08..9d766e058 100644
--- a/tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
+++ b/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()
diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs
index 0550978fd..760e6a5ef 100644
--- a/tests/ImageSharp.Tests/TestImages.cs
+++ b/tests/ImageSharp.Tests/TestImages.cs
@@ -264,6 +264,7 @@ namespace SixLabors.ImageSharp.Tests
public const string InvalidIptcTag = "Jpg/issues/Issue1942InvalidIptcTag.jpg";
public const string Issue2057App1Parsing = "Jpg/issues/Issue2057-App1Parsing.jpg";
public const string ExifNullArrayTag = "Jpg/issues/issue-2056-exif-null-array.jpg";
+ public const string ValidExifArgumentNullExceptionOnEncode = "Jpg/issues/Issue2087-exif-null-reference-on-encode.jpg";
public static class Fuzz
{
@@ -292,6 +293,7 @@ namespace SixLabors.ImageSharp.Tests
public const string AccessViolationException922 = "Jpg/issues/fuzz/Issue922-AccessViolationException.jpg";
public const string IndexOutOfRangeException1693A = "Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-A.jpg";
public const string IndexOutOfRangeException1693B = "Jpg/issues/fuzz/Issue1693-IndexOutOfRangeException-B.jpg";
+ public const string NullReferenceException2085 = "Jpg/issues/fuzz/Issue2085-NullReferenceException.jpg";
}
}
@@ -627,6 +629,8 @@ namespace SixLabors.ImageSharp.Tests
public static class Lossy
{
public const string Earth = "Webp/earth_lossy.webp";
+ public const string WithExif = "Webp/exif_lossy.webp";
+ public const string WithExifNotEnoughData = "Webp/exif_lossy_not_enough_data.webp";
public const string WithIccp = "Webp/lossy_with_iccp.webp";
public const string WithXmp = "Webp/xmp_lossy.webp";
public const string BikeSmall = "Webp/bike_lossy_small.webp";
@@ -856,20 +860,33 @@ namespace SixLabors.ImageSharp.Tests
// Images with alpha channel.
public const string Rgba2BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha2bit.tiff";
public const string Rgba3BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha3bit.tiff";
+ public const string Rgba3BitAssociatedAlpha = "Tiff/RgbaAssociatedAlpha3bit.tiff";
public const string Rgba4BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha4bit.tiff";
+ public const string Rgba4BitAassociatedAlpha = "Tiff/RgbaAssociatedAlpha4bit.tiff";
public const string Rgba5BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha5bit.tiff";
+ public const string Rgba5BitAssociatedAlpha = "Tiff/RgbaAssociatedAlpha5bit.tiff";
public const string Rgba6BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha6bit.tiff";
+ public const string Rgba6BitAssociatedAlpha = "Tiff/RgbaAssociatedAlpha6bit.tiff";
public const string Rgba8BitUnassociatedAlpha = "Tiff/RgbaUnassociatedAlpha8bit.tiff";
+ public const string Rgba8BitAssociatedAlpha = "Tiff/RgbaAlpha8bit.tiff";
public const string Rgba8BitUnassociatedAlphaWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor8bit.tiff";
public const string Rgba8BitPlanarUnassociatedAlpha = "Tiff/RgbaUnassociatedAlphaPlanar8bit.tiff";
public const string Rgba10BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha10bit_msb.tiff";
public const string Rgba10BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha10bit_lsb.tiff";
+ public const string Rgba10BitAssociatedAlphaBigEndian = "Tiff/RgbaAssociatedAlpha10bit_msb.tiff";
+ public const string Rgba10BitAssociatedAlphaLittleEndian = "Tiff/RgbaAssociatedAlpha10bit_lsb.tiff";
public const string Rgba12BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha12bit_msb.tiff";
public const string Rgba12BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha12bit_lsb.tiff";
+ public const string Rgba12BitAssociatedAlphaBigEndian = "Tiff/RgbaAssociatedAlpha12bit_msb.tiff";
+ public const string Rgba12BitAssociatedAlphaLittleEndian = "Tiff/RgbaAssociatedAlpha12bit_lsb.tiff";
public const string Rgba14BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha14bit_msb.tiff";
public const string Rgba14BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha14bit_lsb.tiff";
+ public const string Rgba14BitAssociatedAlphaBigEndian = "Tiff/RgbaAssociatedAlpha14bit_msb.tiff";
+ public const string Rgba14BitAssociatedAlphaLittleEndian = "Tiff/RgbaAssociatedAlpha14bit_lsb.tiff";
public const string Rgba16BitUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlpha16bit_msb.tiff";
public const string Rgba16BitUnassociatedAlphaLittleEndian = "Tiff/RgbaUnassociatedAlpha16bit_lsb.tiff";
+ public const string Rgba16BitAssociatedAlphaBigEndian = "Tiff/RgbaAssociatedAlpha16bit_msb.tiff";
+ public const string Rgba16BitAssociatedAlphaLittleEndian = "Tiff/RgbaAssociatedAlpha16bit_lsb.tiff";
public const string Rgba16BitUnassociatedAlphaBigEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor16bit_msb.tiff";
public const string Rgba16BitUnassociatedAlphaLittleEndianWithPredictor = "Tiff/RgbaUnassociatedAlphaPredictor16bit_lsb.tiff";
public const string Rgba16BitPlanarUnassociatedAlphaBigEndian = "Tiff/RgbaUnassociatedAlphaPlanar16bit_msb.tiff";
diff --git a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs
index 0d2f3fcef..fc0374bbd 100644
--- a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs
@@ -356,10 +356,6 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities
var key = (HwIntrinsics)Enum.Parse(typeof(HwIntrinsics), intrinsic);
switch (intrinsic)
{
- case nameof(HwIntrinsics.DisableSIMD):
- features.Add(key, "FeatureSIMD");
- break;
-
case nameof(HwIntrinsics.AllowAll):
// Not a COMPlus value. We filter in calling method.
@@ -390,23 +386,22 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities
{
// Use flags so we can pass multiple values without using params.
// Don't base on 0 or use inverse for All as that doesn't translate to string values.
- DisableSIMD = 1 << 0,
- DisableHWIntrinsic = 1 << 1,
- DisableSSE = 1 << 2,
- DisableSSE2 = 1 << 3,
- DisableAES = 1 << 4,
- DisablePCLMULQDQ = 1 << 5,
- DisableSSE3 = 1 << 6,
- DisableSSSE3 = 1 << 7,
- DisableSSE41 = 1 << 8,
- DisableSSE42 = 1 << 9,
- DisablePOPCNT = 1 << 10,
- DisableAVX = 1 << 11,
- DisableFMA = 1 << 12,
- DisableAVX2 = 1 << 13,
- DisableBMI1 = 1 << 14,
- DisableBMI2 = 1 << 15,
- DisableLZCNT = 1 << 16,
- AllowAll = 1 << 17
+ DisableHWIntrinsic = 1 << 0,
+ DisableSSE = 1 << 1,
+ DisableSSE2 = 1 << 2,
+ DisableAES = 1 << 3,
+ DisablePCLMULQDQ = 1 << 4,
+ DisableSSE3 = 1 << 5,
+ DisableSSSE3 = 1 << 6,
+ DisableSSE41 = 1 << 7,
+ DisableSSE42 = 1 << 8,
+ DisablePOPCNT = 1 << 9,
+ DisableAVX = 1 << 10,
+ DisableFMA = 1 << 11,
+ DisableAVX2 = 1 << 12,
+ DisableBMI1 = 1 << 13,
+ DisableBMI2 = 1 << 14,
+ DisableLZCNT = 1 << 15,
+ AllowAll = 1 << 16
}
}
diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs
index e1cbf12ac..220b78e48 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs
@@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
sb.Append(Environment.NewLine);
- // TODO: We should add OSX.
+ // TODO: We should add macOS.
sb.AppendFormat("Test Environment OS : {0}", TestEnvironment.IsWindows ? "Windows" : "Linux");
sb.Append(Environment.NewLine);
diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs
index 1801d6b59..fa2584674 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparingUtils.cs
@@ -26,7 +26,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
}
var testFile = TestFile.Create(path);
- Image magickImage = DecodeWithMagick(new FileInfo(testFile.FullPath));
+ using Image magickImage = DecodeWithMagick(new FileInfo(testFile.FullPath));
if (useExactComparer)
{
ImageComparer.Exact.VerifySimilarity(magickImage, image);
diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs
index e2e7d73bc..63c5ce31a 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs
@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
+using SixLabors.ImageSharp.Diagnostics;
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@@ -158,8 +159,13 @@ namespace SixLabors.ImageSharp.Tests
return this.LoadImage(decoder);
}
- var key = new Key(this.PixelType, this.FilePath, decoder);
+ // do not cache so we can track allocation correctly when validating memory
+ if (MemoryAllocatorValidator.MonitoringAllocations)
+ {
+ return this.LoadImage(decoder);
+ }
+ var key = new Key(this.PixelType, this.FilePath, decoder);
Image cachedImage = Cache.GetOrAdd(key, _ => this.LoadImage(decoder));
return cachedImage.Clone(this.Configuration);
diff --git a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs
index 3ccaf2ba3..df584173e 100644
--- a/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/TestEnvironment.cs
@@ -110,7 +110,7 @@ namespace SixLabors.ImageSharp.Tests
internal static bool IsLinux => RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
- internal static bool IsOSX => RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
+ internal static bool IsMacOS => RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
internal static bool IsMono => Type.GetType("Mono.Runtime") != null; // https://stackoverflow.com/a/721194
diff --git a/tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs b/tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs
index a2f36c85a..6dd59a750 100644
--- a/tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/Tests/FeatureTestRunnerTests.cs
@@ -16,10 +16,10 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests
public class FeatureTestRunnerTests
{
public static TheoryData Intrinsics =>
- new TheoryData
+ new()
{
{ HwIntrinsics.DisableAES | HwIntrinsics.AllowAll, new string[] { "EnableAES", "AllowAll" } },
- { HwIntrinsics.DisableSIMD | HwIntrinsics.DisableHWIntrinsic, new string[] { "FeatureSIMD", "EnableHWIntrinsic" } },
+ { HwIntrinsics.DisableHWIntrinsic, new string[] { "EnableHWIntrinsic" } },
{ HwIntrinsics.DisableSSE42 | HwIntrinsics.DisableAVX, new string[] { "EnableSSE42", "EnableAVX" } }
};
@@ -55,14 +55,6 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests
HwIntrinsics.AllowAll);
}
- [Fact]
- public void CanLimitHwIntrinsicSIMDFeatures()
- {
- FeatureTestRunner.RunWithHwIntrinsicsFeature(
- () => Assert.False(Vector.IsHardwareAccelerated),
- HwIntrinsics.DisableSIMD);
- }
-
#if SUPPORTS_RUNTIME_INTRINSICS
[Fact]
public void CanLimitHwIntrinsicBaseFeatures()
@@ -101,9 +93,6 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests
switch ((HwIntrinsics)Enum.Parse(typeof(HwIntrinsics), intrinsic))
{
- case HwIntrinsics.DisableSIMD:
- Assert.False(Vector.IsHardwareAccelerated);
- break;
#if SUPPORTS_RUNTIME_INTRINSICS
case HwIntrinsics.DisableHWIntrinsic:
Assert.False(Sse.IsSupported);
@@ -206,9 +195,6 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.Tests
switch ((HwIntrinsics)Enum.Parse(typeof(HwIntrinsics), intrinsic))
{
- case HwIntrinsics.DisableSIMD:
- Assert.False(Vector.IsHardwareAccelerated, nameof(Vector.IsHardwareAccelerated));
- break;
#if SUPPORTS_RUNTIME_INTRINSICS
case HwIntrinsics.DisableHWIntrinsic:
Assert.False(Sse.IsSupported);
diff --git a/tests/ImageSharp.Tests/ValidateDisposedMemoryAllocationsAttribute.cs b/tests/ImageSharp.Tests/ValidateDisposedMemoryAllocationsAttribute.cs
new file mode 100644
index 000000000..65ed990dd
--- /dev/null
+++ b/tests/ImageSharp.Tests/ValidateDisposedMemoryAllocationsAttribute.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Six Labors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Diagnostics;
+using System.Reflection;
+using Xunit.Sdk;
+
+namespace SixLabors.ImageSharp.Tests
+{
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
+ public class ValidateDisposedMemoryAllocationsAttribute : BeforeAfterTestAttribute
+ {
+ private readonly int expected = 0;
+
+ public ValidateDisposedMemoryAllocationsAttribute()
+ : this(0)
+ {
+ }
+
+ public ValidateDisposedMemoryAllocationsAttribute(int expected)
+ => this.expected = expected;
+
+ public override void Before(MethodInfo methodUnderTest)
+ => MemoryAllocatorValidator.MonitorAllocations();
+
+ public override void After(MethodInfo methodUnderTest)
+ {
+ MemoryAllocatorValidator.ValidateAllocations(this.expected);
+ MemoryAllocatorValidator.StopMonitoringAllocations();
+ }
+ }
+}
diff --git a/tests/Images/Input/Jpg/issues/Issue2087-exif-null-reference-on-encode.jpg b/tests/Images/Input/Jpg/issues/Issue2087-exif-null-reference-on-encode.jpg
new file mode 100644
index 000000000..e95ef7a73
--- /dev/null
+++ b/tests/Images/Input/Jpg/issues/Issue2087-exif-null-reference-on-encode.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4d41a41180a3371d0c4a724b40a4c86f6f975dab6be9da96964a484818770394
+size 30715
diff --git a/tests/Images/Input/Jpg/issues/fuzz/Issue2085-NullReferenceException.jpg b/tests/Images/Input/Jpg/issues/fuzz/Issue2085-NullReferenceException.jpg
new file mode 100644
index 000000000..8a680ff6a
--- /dev/null
+++ b/tests/Images/Input/Jpg/issues/fuzz/Issue2085-NullReferenceException.jpg
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d478beff34179fda26238a44434607c276f55438ee96824c5af8c0188d358d8d
+size 234
diff --git a/tests/Images/Input/Tiff/RgbaAlpha8bit.tiff b/tests/Images/Input/Tiff/RgbaAlpha8bit.tiff
new file mode 100644
index 000000000..d0d9f2d1e
--- /dev/null
+++ b/tests/Images/Input/Tiff/RgbaAlpha8bit.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f8bc77670ea12cd163fbf21fa7dd6d6c10e3b8c1afc83f26618f92303d0cbfcf
+size 235478
diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha10bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha10bit_lsb.tiff
new file mode 100644
index 000000000..01ff24afc
--- /dev/null
+++ b/tests/Images/Input/Tiff/RgbaAssociatedAlpha10bit_lsb.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:1a9878183ebe84506810ea2edc6dc95cd76780f3847ac3a614ce2dcfb355ea9f
+size 294278
diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha10bit_msb.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha10bit_msb.tiff
new file mode 100644
index 000000000..a0eabbea2
--- /dev/null
+++ b/tests/Images/Input/Tiff/RgbaAssociatedAlpha10bit_msb.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4b95334e1e4b78f5106cacd66d6dc9ad15d023c5449e9562b7489982c5336715
+size 294278
diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha12bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha12bit_lsb.tiff
new file mode 100644
index 000000000..934895707
--- /dev/null
+++ b/tests/Images/Input/Tiff/RgbaAssociatedAlpha12bit_lsb.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4b1aa42b51bd5474cbf333d9a0b89634198250d3eb5de4cf3af2a8d846395bf0
+size 353078
diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha12bit_msb.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha12bit_msb.tiff
new file mode 100644
index 000000000..50e9d9c83
--- /dev/null
+++ b/tests/Images/Input/Tiff/RgbaAssociatedAlpha12bit_msb.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9a146f0bfcac158dfda1ba307737b39d472a95926ff172bc8c798557f2dd1e1b
+size 353078
diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha14bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha14bit_lsb.tiff
new file mode 100644
index 000000000..b58f097c1
--- /dev/null
+++ b/tests/Images/Input/Tiff/RgbaAssociatedAlpha14bit_lsb.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3190269edb721175e8f3788528bd84aee3a12b2c7fb970c02c98822452fc81b0
+size 411878
diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha14bit_msb.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha14bit_msb.tiff
new file mode 100644
index 000000000..cfe66071d
--- /dev/null
+++ b/tests/Images/Input/Tiff/RgbaAssociatedAlpha14bit_msb.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4d65b9b1172d125fece197c2bf332bff9074db2b443e74d1973a14dbe45865e8
+size 411878
diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha16bit_lsb.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha16bit_lsb.tiff
new file mode 100644
index 000000000..a6199845a
--- /dev/null
+++ b/tests/Images/Input/Tiff/RgbaAssociatedAlpha16bit_lsb.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5ee223455c3e2f748d4dc9a1446047adb3c547878b815504c785497d8c059d34
+size 470678
diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha16bit_msb.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha16bit_msb.tiff
new file mode 100644
index 000000000..3bc030c50
--- /dev/null
+++ b/tests/Images/Input/Tiff/RgbaAssociatedAlpha16bit_msb.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9ea0206508fe23dfb8653ac1706894e54a4ad980b3bf75b68e5deb9f2838a1de
+size 470678
diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha3bit.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha3bit.tiff
new file mode 100644
index 000000000..7a87e5ef8
--- /dev/null
+++ b/tests/Images/Input/Tiff/RgbaAssociatedAlpha3bit.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:03d1050a606b6fa9e909f54991ed12b1aa96b739e5f69f4b1bae7c903b1236ef
+size 88478
diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha4bit.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha4bit.tiff
new file mode 100644
index 000000000..2679e5829
--- /dev/null
+++ b/tests/Images/Input/Tiff/RgbaAssociatedAlpha4bit.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:41481c59d9b63ca946d9d2cf2ccc599d1e48ef6fdfa00926c1e6d5cdfd35eb47
+size 117878
diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha5bit.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha5bit.tiff
new file mode 100644
index 000000000..1cdb3d475
--- /dev/null
+++ b/tests/Images/Input/Tiff/RgbaAssociatedAlpha5bit.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:4bc3b2b8031593ff9a79d7caccd2ed614cf8d788dedbefbd27d0a4f88399be4e
+size 147278
diff --git a/tests/Images/Input/Tiff/RgbaAssociatedAlpha6bit.tiff b/tests/Images/Input/Tiff/RgbaAssociatedAlpha6bit.tiff
new file mode 100644
index 000000000..3184bd90b
--- /dev/null
+++ b/tests/Images/Input/Tiff/RgbaAssociatedAlpha6bit.tiff
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a9c1c64ce9a002337c3f6d332b4a5bb3016718b672f9c95d8792b013545553c2
+size 176678
diff --git a/tests/Images/Input/Webp/exif_lossy.webp b/tests/Images/Input/Webp/exif_lossy.webp
index 35e454b96..5d6db3800 100644
--- a/tests/Images/Input/Webp/exif_lossy.webp
+++ b/tests/Images/Input/Webp/exif_lossy.webp
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:fdf4e9b20af4168f4177d33f7f502906343bbaaae2af9b90e1531bd4452b317b
-size 40765
+oid sha256:5c53967bfefcfece8cd4411740c1c394e75864ca61a7a9751df3b28e727c0205
+size 68646
diff --git a/tests/Images/Input/Webp/exif_lossy_not_enough_data.webp b/tests/Images/Input/Webp/exif_lossy_not_enough_data.webp
new file mode 100644
index 000000000..35e454b96
--- /dev/null
+++ b/tests/Images/Input/Webp/exif_lossy_not_enough_data.webp
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:fdf4e9b20af4168f4177d33f7f502906343bbaaae2af9b90e1531bd4452b317b
+size 40765