diff --git a/.gitattributes b/.gitattributes
index f7bd4d061e..7de2c15977 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -85,18 +85,12 @@
###############################################################################
*.basis binary
*.dll binary
-*.eot binary
*.exe binary
-*.otf binary
*.pdf binary
*.ppt binary
*.pptx binary
*.pvr binary
*.snk binary
-*.ttc binary
-*.ttf binary
-*.woff binary
-*.woff2 binary
*.xls binary
*.xlsx binary
###############################################################################
@@ -126,6 +120,7 @@
*.dds filter=lfs diff=lfs merge=lfs -text
*.ktx filter=lfs diff=lfs merge=lfs -text
*.ktx2 filter=lfs diff=lfs merge=lfs -text
+*.astc filter=lfs diff=lfs merge=lfs -text
*.pam filter=lfs diff=lfs merge=lfs -text
*.pbm filter=lfs diff=lfs merge=lfs -text
*.pgm filter=lfs diff=lfs merge=lfs -text
@@ -143,3 +138,12 @@
# Handle ICC files by git lfs
###############################################################################
*.icc filter=lfs diff=lfs merge=lfs -text
+###############################################################################
+# Handle font files by git lfs
+###############################################################################
+*.eot filter=lfs diff=lfs merge=lfs -text
+*.otf filter=lfs diff=lfs merge=lfs -text
+*.ttc filter=lfs diff=lfs merge=lfs -text
+*.ttf filter=lfs diff=lfs merge=lfs -text
+*.woff filter=lfs diff=lfs merge=lfs -text
+*.woff2 filter=lfs diff=lfs merge=lfs -text
diff --git a/ImageSharp.sln b/ImageSharp.sln
index 13dd2fba7e..7a52040374 100644
--- a/ImageSharp.sln
+++ b/ImageSharp.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.0.31903.59
+# Visual Studio Version 18
+VisualStudioVersion = 18.5.11723.231 stable
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_root", "_root", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}"
ProjectSection(SolutionItems) = preProject
@@ -45,6 +45,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp", "src\ImageShar
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{56801022-D71A-4FBE-BC5B-CBA08E2284EC}"
ProjectSection(SolutionItems) = preProject
+ tests\coverlet.runsettings = tests\coverlet.runsettings
tests\Directory.Build.props = tests\Directory.Build.props
tests\Directory.Build.targets = tests\Directory.Build.targets
tests\ImageSharp.Tests.ruleset = tests\ImageSharp.Tests.ruleset
diff --git a/shared-infrastructure b/shared-infrastructure
index a1d3ac2049..7ac5703452 160000
--- a/shared-infrastructure
+++ b/shared-infrastructure
@@ -1 +1 @@
-Subproject commit a1d3ac20494631e3cc13132897573796b0e4ee6d
+Subproject commit 7ac5703452348d9295db31fc0912c2bd9e419dc9
diff --git a/src/ImageSharp/ColorProfiles/WorkingSpaces/GammaWorkingSpace.cs b/src/ImageSharp/ColorProfiles/WorkingSpaces/GammaWorkingSpace.cs
index 91fa426241..a3959f12e4 100644
--- a/src/ImageSharp/ColorProfiles/WorkingSpaces/GammaWorkingSpace.cs
+++ b/src/ImageSharp/ColorProfiles/WorkingSpaces/GammaWorkingSpace.cs
@@ -62,6 +62,7 @@ public sealed class GammaWorkingSpace : RgbWorkingSpace
///
public override int GetHashCode() => HashCode.Combine(
+ typeof(GammaWorkingSpace),
this.WhitePoint,
this.ChromaticityCoordinates,
this.Gamma);
diff --git a/src/ImageSharp/ColorProfiles/WorkingSpaces/RgbWorkingSpace.cs b/src/ImageSharp/ColorProfiles/WorkingSpaces/RgbWorkingSpace.cs
index 97069b856b..b278dd3177 100644
--- a/src/ImageSharp/ColorProfiles/WorkingSpaces/RgbWorkingSpace.cs
+++ b/src/ImageSharp/ColorProfiles/WorkingSpaces/RgbWorkingSpace.cs
@@ -70,8 +70,10 @@ public abstract class RgbWorkingSpace
return true;
}
- if (obj is RgbWorkingSpace other)
+ if (obj.GetType() == this.GetType())
{
+ RgbWorkingSpace other = (RgbWorkingSpace)obj;
+
return this.WhitePoint.Equals(other.WhitePoint)
&& this.ChromaticityCoordinates.Equals(other.ChromaticityCoordinates);
}
@@ -81,5 +83,5 @@ public abstract class RgbWorkingSpace
///
public override int GetHashCode()
- => HashCode.Combine(this.WhitePoint, this.ChromaticityCoordinates);
+ => HashCode.Combine(this.GetType(), this.WhitePoint, this.ChromaticityCoordinates);
}
diff --git a/src/ImageSharp/Common/Tuples/Octet{T}.cs b/src/ImageSharp/Common/Tuples/Octet{T}.cs
deleted file mode 100644
index f73bdd3390..0000000000
--- a/src/ImageSharp/Common/Tuples/Octet{T}.cs
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-namespace SixLabors.ImageSharp.Tuples;
-
-///
-/// Contains 8 element value tuples of various types.
-///
-[StructLayout(LayoutKind.Sequential)]
-internal struct Octet
- where T : unmanaged
-{
- public T V0;
- public T V1;
- public T V2;
- public T V3;
- public T V4;
- public T V5;
- public T V6;
- public T V7;
-
- ///
- public override readonly string ToString()
- {
- return $"Octet<{typeof(T)}>({this.V0},{this.V1},{this.V2},{this.V3},{this.V4},{this.V5},{this.V6},{this.V7})";
- }
-}
-
-///
-/// Extension methods for the type.
-///
-internal static class OctetExtensions
-{
- ///
- /// Loads the fields in a target of from one of type.
- ///
- /// The target of instance.
- /// The source of instance.
- [MethodImpl(InliningOptions.ShortMethod)]
- public static void LoadFrom(ref this Octet destination, ref Octet source)
- {
- destination.V0 = source.V0;
- destination.V1 = source.V1;
- destination.V2 = source.V2;
- destination.V3 = source.V3;
- destination.V4 = source.V4;
- destination.V5 = source.V5;
- destination.V6 = source.V6;
- destination.V7 = source.V7;
- }
-
- ///
- /// Loads the fields in a target of from one of type.
- ///
- /// The target of instance.
- /// The source of instance.
- [MethodImpl(InliningOptions.ShortMethod)]
- public static void LoadFrom(ref this Octet destination, ref Octet source)
- {
- destination.V0 = (byte)source.V0;
- destination.V1 = (byte)source.V1;
- destination.V2 = (byte)source.V2;
- destination.V3 = (byte)source.V3;
- destination.V4 = (byte)source.V4;
- destination.V5 = (byte)source.V5;
- destination.V6 = (byte)source.V6;
- destination.V7 = (byte)source.V7;
- }
-}
diff --git a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs
index 2e60f3a5b9..c24352d576 100644
--- a/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs
+++ b/src/ImageSharp/Formats/Exr/ExrDecoderCore.cs
@@ -91,6 +91,11 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
///
private ExrHeaderAttributes HeaderAttributes { get; set; }
+ ///
+ /// Gets or sets the earliest valid stream position for a scanline chunk.
+ ///
+ private long MinimumChunkOffset { get; set; }
+
///
protected override Image Decode(BufferedReadStream stream, CancellationToken cancellationToken)
{
@@ -100,24 +105,33 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
ExrThrowHelper.ThrowNotSupported($"Compression {this.Compression} is not yet supported");
}
- Image image = new(this.configuration, this.Width, this.Height, this.metadata);
- Buffer2D pixels = image.GetRootFramePixelBuffer();
+ Image image = null;
+ try
+ {
+ image = new Image(this.configuration, this.Width, this.Height, this.metadata);
+ Buffer2D pixels = image.GetRootFramePixelBuffer();
+
+ switch (this.PixelType)
+ {
+ case ExrPixelType.Half:
+ case ExrPixelType.Float:
+ this.DecodeFloatingPointPixelData(stream, pixels, cancellationToken);
+ break;
+ case ExrPixelType.UnsignedInt:
+ this.DecodeUnsignedIntPixelData(stream, pixels, cancellationToken);
+ break;
+ default:
+ ExrThrowHelper.ThrowNotSupported("Pixel type is not supported");
+ break;
+ }
- switch (this.PixelType)
+ return image;
+ }
+ catch
{
- case ExrPixelType.Half:
- case ExrPixelType.Float:
- this.DecodeFloatingPointPixelData(stream, pixels, cancellationToken);
- break;
- case ExrPixelType.UnsignedInt:
- this.DecodeUnsignedIntPixelData(stream, pixels, cancellationToken);
- break;
- default:
- ExrThrowHelper.ThrowNotSupported("Pixel type is not supported");
- break;
+ image?.Dispose();
+ throw;
}
-
- return image;
}
///
@@ -139,9 +153,14 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
where TPixel : unmanaged, IPixel
{
bool hasAlpha = this.HasAlpha();
- uint bytesPerRow = ExrUtils.CalculateBytesPerRow(this.Channels, (uint)this.Width);
+ ulong bytesPerRow = ExrUtils.CalculateBytesPerRow(this.Channels, (uint)this.Width);
uint rowsPerBlock = ExrUtils.RowsPerBlock(this.Compression);
- uint bytesPerBlock = bytesPerRow * rowsPerBlock;
+ ulong bytesPerBlock = bytesPerRow * rowsPerBlock;
+ if (bytesPerBlock > int.MaxValue)
+ {
+ ExrThrowHelper.ThrowInvalidImageContentException("EXR block size exceeds the maximum allowed size.");
+ }
+
int width = this.Width;
int height = this.Height;
int channelCount = this.Channels.Count;
@@ -158,8 +177,8 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
this.Compression,
this.memoryAllocator,
width,
- bytesPerBlock,
- bytesPerRow,
+ (uint)bytesPerBlock,
+ (uint)bytesPerRow,
rowsPerBlock,
channelCount,
this.PixelType);
@@ -170,6 +189,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
ulong rowOffset = this.ReadUnsignedLong(stream);
long nextRowOffsetPosition = stream.Position;
+ this.ValidateChunkOffset(rowOffset, stream);
stream.Position = (long)rowOffset;
uint rowStartIndex = this.ReadUnsignedInteger(stream);
@@ -212,9 +232,14 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
where TPixel : unmanaged, IPixel
{
bool hasAlpha = this.HasAlpha();
- uint bytesPerRow = ExrUtils.CalculateBytesPerRow(this.Channels, (uint)this.Width);
+ ulong bytesPerRow = ExrUtils.CalculateBytesPerRow(this.Channels, (uint)this.Width);
uint rowsPerBlock = ExrUtils.RowsPerBlock(this.Compression);
- uint bytesPerBlock = bytesPerRow * rowsPerBlock;
+ ulong bytesPerBlock = bytesPerRow * rowsPerBlock;
+ if (bytesPerBlock > int.MaxValue)
+ {
+ ExrThrowHelper.ThrowInvalidImageContentException("EXR block size exceeds the maximum allowed size.");
+ }
+
int width = this.Width;
int height = this.Height;
int channelCount = this.Channels.Count;
@@ -231,8 +256,8 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
this.Compression,
this.memoryAllocator,
width,
- bytesPerBlock,
- bytesPerRow,
+ (uint)bytesPerBlock,
+ (uint)bytesPerRow,
rowsPerBlock,
channelCount,
this.PixelType);
@@ -243,6 +268,7 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
ulong rowOffset = this.ReadUnsignedLong(stream);
long nextRowOffsetPosition = stream.Position;
+ this.ValidateChunkOffset(rowOffset, stream);
stream.Position = (long)rowOffset;
uint rowStartIndex = this.ReadUnsignedInteger(stream);
@@ -597,10 +623,38 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
this.HeaderAttributes = this.ParseHeaderAttributes(stream);
- this.Width = this.HeaderAttributes.DataWindow.XMax - this.HeaderAttributes.DataWindow.XMin + 1;
- this.Height = this.HeaderAttributes.DataWindow.YMax - this.HeaderAttributes.DataWindow.YMin + 1;
+ ExrBox2i dataWindow = this.HeaderAttributes.DataWindow;
+ if (dataWindow.XMax < dataWindow.XMin || dataWindow.YMax < dataWindow.YMin)
+ {
+ ExrThrowHelper.ThrowInvalidImageContentException("EXR DataWindow max values must be greater than or equal to min values.");
+ }
+
+ long width = (long)dataWindow.XMax - dataWindow.XMin + 1;
+ long height = (long)dataWindow.YMax - dataWindow.YMin + 1;
+
+ // Decoding stages each row as four color planes, so the width must be bounded
+ // before later width * 4 buffer sizing can overflow.
+ if (width > int.MaxValue / 4 || height > int.MaxValue)
+ {
+ ExrThrowHelper.ThrowInvalidImageContentException("EXR DataWindow dimensions exceed the maximum allowed size.");
+ }
+
+ this.Width = (int)width;
+ this.Height = (int)height;
this.Channels = this.HeaderAttributes.Channels;
this.Compression = this.HeaderAttributes.Compression;
+ uint rowsPerBlock = ExrUtils.RowsPerBlock(this.Compression);
+ long chunkCount = (this.Height + (long)rowsPerBlock - 1) / rowsPerBlock;
+ long offsetTableByteCount = chunkCount * sizeof(ulong);
+
+ // The scanline offset table sits between the header and pixel chunks; proving it
+ // fits in the stream keeps all later chunk offsets on the pixel-data side.
+ if (stream.Position > stream.Length || offsetTableByteCount > stream.Length - stream.Position)
+ {
+ ExrThrowHelper.ThrowInvalidImageContentException("EXR chunk offset table is outside the bounds of the stream.");
+ }
+
+ this.MinimumChunkOffset = stream.Position + offsetTableByteCount;
this.PixelType = this.ValidateChannels();
this.ImageDataType = this.DetermineImageDataType();
@@ -866,6 +920,19 @@ internal sealed class ExrDecoderCore : ImageDecoderCore
_ => false,
};
+ ///
+ /// Validates a scanline chunk offset read from the EXR offset table.
+ ///
+ /// The chunk offset to validate.
+ /// The stream containing the image data.
+ private void ValidateChunkOffset(ulong chunkOffset, BufferedReadStream stream)
+ {
+ if (chunkOffset < (ulong)this.MinimumChunkOffset || chunkOffset >= (ulong)stream.Length)
+ {
+ ExrThrowHelper.ThrowInvalidImageContentException("EXR chunk offset is outside the bounds of the stream.");
+ }
+ }
+
///
/// Determines whether this image has alpha channel.
///
diff --git a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs
index e2750d8972..81b7c4da11 100644
--- a/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs
+++ b/src/ImageSharp/Formats/Exr/ExrEncoderCore.cs
@@ -170,9 +170,13 @@ internal sealed class ExrEncoderCore
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
- uint bytesPerRow = ExrUtils.CalculateBytesPerRow(channels, (uint)width);
+ ulong bytesPerRow = ExrUtils.CalculateBytesPerRow(channels, (uint)width);
uint rowsPerBlock = ExrUtils.RowsPerBlock(compression);
- uint bytesPerBlock = bytesPerRow * rowsPerBlock;
+ ulong bytesPerBlock = bytesPerRow * rowsPerBlock;
+ if (bytesPerRow > uint.MaxValue || bytesPerBlock > int.MaxValue)
+ {
+ throw new ImageFormatException("Image is too large to encode in EXR format.");
+ }
using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 4, AllocationOptions.Clean);
using IMemoryOwner rowBlockBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock, AllocationOptions.Clean);
@@ -181,7 +185,7 @@ internal sealed class ExrEncoderCore
Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width);
Span alphaBuffer = rgbBuffer.GetSpan().Slice(width * 3, width);
- using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, bytesPerBlock, bytesPerRow, rowsPerBlock, width);
+ using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, (uint)bytesPerBlock, (uint)bytesPerRow, rowsPerBlock, width);
ulong[] rowOffsets = new ulong[height];
for (uint y = 0; y < height; y += rowsPerBlock)
@@ -262,9 +266,13 @@ internal sealed class ExrEncoderCore
CancellationToken cancellationToken)
where TPixel : unmanaged, IPixel
{
- uint bytesPerRow = ExrUtils.CalculateBytesPerRow(channels, (uint)width);
+ ulong bytesPerRow = ExrUtils.CalculateBytesPerRow(channels, (uint)width);
uint rowsPerBlock = ExrUtils.RowsPerBlock(compression);
- uint bytesPerBlock = bytesPerRow * rowsPerBlock;
+ ulong bytesPerBlock = bytesPerRow * rowsPerBlock;
+ if (bytesPerRow > uint.MaxValue || bytesPerBlock > int.MaxValue)
+ {
+ throw new ImageFormatException("Image is too large to encode in EXR format.");
+ }
using IMemoryOwner rgbBuffer = this.memoryAllocator.Allocate(width * 4, AllocationOptions.Clean);
using IMemoryOwner rowBlockBuffer = this.memoryAllocator.Allocate((int)bytesPerBlock, AllocationOptions.Clean);
@@ -273,7 +281,7 @@ internal sealed class ExrEncoderCore
Span blueBuffer = rgbBuffer.GetSpan().Slice(width * 2, width);
Span alphaBuffer = rgbBuffer.GetSpan().Slice(width * 3, width);
- using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, bytesPerBlock, bytesPerRow, rowsPerBlock, width);
+ using ExrBaseCompressor compressor = ExrCompressorFactory.Create(compression, this.memoryAllocator, stream, (uint)bytesPerBlock, (uint)bytesPerRow, rowsPerBlock, width);
Rgba128 rgb = default;
ulong[] rowOffsets = new ulong[height];
diff --git a/src/ImageSharp/Formats/Exr/ExrUtils.cs b/src/ImageSharp/Formats/Exr/ExrUtils.cs
index 386210b81d..532950daa9 100644
--- a/src/ImageSharp/Formats/Exr/ExrUtils.cs
+++ b/src/ImageSharp/Formats/Exr/ExrUtils.cs
@@ -13,9 +13,9 @@ internal static class ExrUtils
/// The image channels array.
/// The width in pixels of a row.
/// The number of bytes per row.
- public static uint CalculateBytesPerRow(IList channels, uint width)
+ public static ulong CalculateBytesPerRow(IList channels, uint width)
{
- uint bytesPerRow = 0;
+ ulong bytesPerRow = 0;
foreach (ExrChannelInfo channelInfo in channels)
{
if (channelInfo.ChannelName.Equals("A", StringComparison.Ordinal)
@@ -26,11 +26,11 @@ internal static class ExrUtils
{
if (channelInfo.PixelType == ExrPixelType.Half)
{
- bytesPerRow += 2 * width;
+ bytesPerRow += 2UL * width;
}
else
{
- bytesPerRow += 4 * width;
+ bytesPerRow += 4UL * width;
}
}
}
diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets
index 6b25509ed8..71f2bc333c 100644
--- a/tests/Directory.Build.targets
+++ b/tests/Directory.Build.targets
@@ -19,11 +19,6 @@
-
-
diff --git a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs
index 232d8b3e27..c69510bc4d 100644
--- a/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs
+++ b/tests/ImageSharp.Benchmarks/General/PixelConversion/PixelConversion_Rgba32_To_Bgra32.cs
@@ -7,7 +7,6 @@ using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.PixelFormats;
-using SixLabors.ImageSharp.Tuples;
namespace SixLabors.ImageSharp.Benchmarks.General.PixelConversion;
@@ -225,8 +224,8 @@ public class PixelConversion_Rgba32_To_Bgra32
// [Benchmark]
public void Bitops_Simd()
{
- ref Octet sBase = ref Unsafe.As>(ref this.source[0]);
- ref Octet dBase = ref Unsafe.As>(ref this.dest[0]);
+ ref InlineArray8 sBase = ref Unsafe.As>(ref this.source[0]);
+ ref InlineArray8 dBase = ref Unsafe.As>(ref this.dest[0]);
for (nuint i = 0; i < (uint)this.Count / 8; i++)
{
@@ -249,9 +248,9 @@ public class PixelConversion_Rgba32_To_Bgra32
#pragma warning restore SA1132 // Do not combine fields
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static void BitopsSimdImpl(ref Octet s, ref Octet d)
+ private static void BitopsSimdImpl(ref InlineArray8 s, ref InlineArray8 d)
{
- Vector sVec = Unsafe.As, Vector>(ref s);
+ Vector sVec = Unsafe.As, Vector>(ref s);
Vector aMask = new(0xFF00FF00);
Vector bMask = new(0x00FF00FF);
@@ -274,7 +273,7 @@ public class PixelConversion_Rgba32_To_Bgra32
Vector cc = Unsafe.As>(ref c);
Vector dd = aa + cc;
- d = Unsafe.As, Octet>(ref dd);
+ d = Unsafe.As, InlineArray8>(ref dd);
}
// [Benchmark]
diff --git a/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs b/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs
index da7ddae41e..fd17262dec 100644
--- a/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs
+++ b/tests/ImageSharp.Benchmarks/General/Vectorization/WidenBytesToUInt32.cs
@@ -5,8 +5,6 @@ using System.Numerics;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
-using SixLabors.ImageSharp.Tuples;
-
namespace SixLabors.ImageSharp.Benchmarks.General.Vectorization;
[Config(typeof(Config.Short))]
@@ -30,12 +28,22 @@ public class WidenBytesToUInt32
{
const int N = Count / 8;
- ref Octet sBase = ref Unsafe.As>(ref this.source[0]);
- ref Octet dBase = ref Unsafe.As>(ref this.dest[0]);
+ ref InlineArray8 sBase = ref Unsafe.As>(ref this.source[0]);
+ ref InlineArray8 dBase = ref Unsafe.As>(ref this.dest[0]);
for (nuint i = 0; i < N; i++)
{
- Unsafe.Add(ref dBase, i).LoadFrom(ref Unsafe.Add(ref sBase, i));
+ ref InlineArray8 source = ref Unsafe.Add(ref sBase, i);
+ ref InlineArray8 destination = ref Unsafe.Add(ref dBase, i);
+
+ destination[0] = source[0];
+ destination[1] = source[1];
+ destination[2] = source[2];
+ destination[3] = source[3];
+ destination[4] = source[4];
+ destination[5] = source[5];
+ destination[6] = source[6];
+ destination[7] = source[7];
}
}
diff --git a/tests/ImageSharp.Tests/ColorProfiles/RgbWorkingSpaceTests.cs b/tests/ImageSharp.Tests/ColorProfiles/RgbWorkingSpaceTests.cs
new file mode 100644
index 0000000000..8a38ea11fc
--- /dev/null
+++ b/tests/ImageSharp.Tests/ColorProfiles/RgbWorkingSpaceTests.cs
@@ -0,0 +1,122 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Numerics;
+using SixLabors.ImageSharp.ColorProfiles;
+using SixLabors.ImageSharp.ColorProfiles.WorkingSpaces;
+
+namespace SixLabors.ImageSharp.Tests.ColorProfiles;
+
+///
+/// Tests the class.
+///
+[Trait("Color", "Conversion")]
+public class RgbWorkingSpaceTests
+{
+ private static readonly ApproximateFloatComparer TolerantComparer = new(1e-5F);
+
+ public static readonly TheoryData WorkingSpaces = new()
+ {
+ KnownRgbWorkingSpaces.SRgb,
+ KnownRgbWorkingSpaces.SRgbSimplified,
+ KnownRgbWorkingSpaces.Rec709,
+ KnownRgbWorkingSpaces.Rec2020,
+ KnownRgbWorkingSpaces.ECIRgbv2,
+ KnownRgbWorkingSpaces.AdobeRgb1998,
+ KnownRgbWorkingSpaces.ApplesRgb,
+ KnownRgbWorkingSpaces.BestRgb,
+ KnownRgbWorkingSpaces.BetaRgb,
+ KnownRgbWorkingSpaces.BruceRgb,
+ KnownRgbWorkingSpaces.CIERgb,
+ KnownRgbWorkingSpaces.ColorMatchRgb,
+ KnownRgbWorkingSpaces.DonRgb4,
+ KnownRgbWorkingSpaces.EktaSpacePS5,
+ KnownRgbWorkingSpaces.NTSCRgb,
+ KnownRgbWorkingSpaces.PALSECAMRgb,
+ KnownRgbWorkingSpaces.ProPhotoRgb,
+ KnownRgbWorkingSpaces.SMPTECRgb,
+ KnownRgbWorkingSpaces.WideGamutRgb
+ };
+
+ [Fact]
+ public void RgbWorkingSpaceEqualityRequiresSameConcreteType()
+ {
+ RgbWorkingSpace sRgb = KnownRgbWorkingSpaces.SRgb;
+ RgbWorkingSpace sRgbSimplified = KnownRgbWorkingSpaces.SRgbSimplified;
+ RgbWorkingSpace rec709 = KnownRgbWorkingSpaces.Rec709;
+
+ Assert.False(sRgb.Equals(sRgbSimplified));
+ Assert.False(sRgbSimplified.Equals(sRgb));
+ Assert.False(sRgb.Equals(rec709));
+ Assert.False(rec709.Equals(sRgb));
+
+ Assert.NotEqual(sRgb.GetHashCode(), sRgbSimplified.GetHashCode());
+ Assert.NotEqual(sRgb.GetHashCode(), rec709.GetHashCode());
+ }
+
+ [Fact]
+ public void RgbWorkingSpaceEqualityMatchesSameConcreteTypeValues()
+ {
+ RgbWorkingSpace x = new SRgbWorkingSpace(
+ KnownRgbWorkingSpaces.SRgb.WhitePoint,
+ KnownRgbWorkingSpaces.SRgb.ChromaticityCoordinates);
+
+ RgbWorkingSpace y = new SRgbWorkingSpace(
+ KnownRgbWorkingSpaces.SRgb.WhitePoint,
+ KnownRgbWorkingSpaces.SRgb.ChromaticityCoordinates);
+
+ Assert.Equal(x, y);
+ Assert.Equal(x.GetHashCode(), y.GetHashCode());
+ }
+
+ [Fact]
+ public void GammaWorkingSpaceEqualityIncludesGamma()
+ {
+ GammaWorkingSpace x = new(
+ 2.2F,
+ KnownRgbWorkingSpaces.SRgbSimplified.WhitePoint,
+ KnownRgbWorkingSpaces.SRgbSimplified.ChromaticityCoordinates);
+
+ GammaWorkingSpace y = new(
+ 1.8F,
+ KnownRgbWorkingSpaces.SRgbSimplified.WhitePoint,
+ KnownRgbWorkingSpaces.SRgbSimplified.ChromaticityCoordinates);
+
+ Assert.NotEqual(x, y);
+ Assert.NotEqual(x.GetHashCode(), y.GetHashCode());
+ }
+
+ [Theory]
+ [MemberData(nameof(WorkingSpaces))]
+ public void CompressAndExpand_RoundTripsWithTolerance(RgbWorkingSpace workingSpace)
+ {
+ Vector4[] linear =
+ [
+ new(0F, .001F, .18F, 1F), // Endpoint, below the sRGB breakpoint, common middle gray, and opaque alpha.
+ new(.0031308F, .25F, .5F, .75F), // sRGB linear/gamma breakpoint with interior values and partial alpha.
+ new(.75F, .5F, .25F, .5F), // Reversed interior values ensure channel order does not hide errors.
+ new(1F, .8F, .2F, .25F) // Upper endpoint, high/low interior values, and low alpha.
+ ];
+
+ Vector4[] expectedCompressed = new Vector4[linear.Length];
+
+ for (int i = 0; i < linear.Length; i++)
+ {
+ Vector4 compressed = workingSpace.Compress(linear[i]);
+ Vector4 expanded = workingSpace.Expand(compressed);
+
+ expectedCompressed[i] = compressed;
+
+ Assert.Equal(linear[i], expanded, TolerantComparer);
+ }
+
+ Vector4[] actualCompressed = linear.ToArray();
+ workingSpace.Compress(actualCompressed);
+
+ Assert.Equal(expectedCompressed, actualCompressed, TolerantComparer);
+
+ workingSpace.Expand(actualCompressed);
+
+ Assert.Equal(linear, actualCompressed, TolerantComparer);
+ }
+}
diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs
index 81daac3e0b..12b4c34262 100644
--- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs
+++ b/tests/ImageSharp.Tests/Common/SimdUtilsTests.Shuffle.cs
@@ -292,7 +292,7 @@ public partial class SimdUtilsTests
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
count,
- HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
+ HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
}
[Theory]
@@ -478,7 +478,7 @@ public partial class SimdUtilsTests
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
count,
- HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
+ HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
}
private static void TestShuffleFloat4Channel(
diff --git a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs
index e39f9456f0..dfd0a6b307 100644
--- a/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs
+++ b/tests/ImageSharp.Tests/Common/SimdUtilsTests.cs
@@ -133,7 +133,7 @@ public partial class SimdUtilsTests
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
count,
- HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512 | HwIntrinsics.DisableAVX2);
+ HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2);
}
[Theory]
@@ -171,7 +171,7 @@ public partial class SimdUtilsTests
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
count,
- HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512 | HwIntrinsics.DisableAVX2);
+ HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2);
}
[Theory]
diff --git a/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderSecurityTests.cs b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderSecurityTests.cs
new file mode 100644
index 0000000000..2d59c02355
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Exr/ExrDecoderSecurityTests.cs
@@ -0,0 +1,274 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers.Binary;
+using SixLabors.ImageSharp.Formats;
+using SixLabors.ImageSharp.Formats.Exr;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace SixLabors.ImageSharp.Tests.Formats.Exr;
+
+///
+/// Security regression tests for the EXR decoder (Findings EXR-1, EXR-2, EXR-3).
+/// The EXR decoder was merged to main but not yet included in a tagged NuGet release.
+/// Each test demonstrates a crafted-input crash present in the unfixed code.
+///
+[Trait("Format", "Exr")]
+[ValidateDisposedMemoryAllocations]
+public class ExrDecoderSecurityTests
+{
+ ///
+ /// EXR-1 — EXR DataWindow Integer Overflow Produces Negative Image Dimensions (DoS)
+ ///
+ /// Width and Height are computed from attacker-controlled DataWindow attributes
+ /// using unchecked int subtraction:
+ /// this.Width = XMax - XMin + 1 // overflows to -2147483647
+ ///
+ /// The negative Width is then passed to the Image<TPixel> constructor, which calls
+ /// Guard.MustBeGreaterThan(width, 0) → ArgumentOutOfRangeException.
+ ///
+ /// After a fix this should throw InvalidImageContentException instead.
+ ///
+ /// Affected file:
+ /// src/ImageSharp/Formats/Exr/ExrDecoderCore.cs lines 600–601
+ ///
+ [Fact]
+ public void Decode_DataWindowOverflow_NegativeWidth_Throws()
+ {
+ // XMin = -1073741825, XMax = 1073741823
+ // Width = 1073741823 - (-1073741825) + 1 = 2^31 + 1 → wraps to -2147483647
+ byte[] data = BuildMinimalExr(xMin: -1073741825, yMin: 0, xMax: 1073741823, yMax: 0);
+
+ using var stream = new MemoryStream(data);
+ Assert.Throws(
+ () => ExrDecoder.Instance.Decode(DecoderOptions.Default, stream));
+ }
+
+ ///
+ /// EXR-2 — EXR Row Offset Table Unvalidated Seek (DoS)
+ ///
+ /// Row offsets are read from the file and used unconditionally to seek the stream:
+ /// ulong rowOffset = this.ReadUnsignedLong(stream);
+ /// stream.Position = (long)rowOffset; // no bounds check
+ ///
+ /// A crafted offset of 0xFFFFFFFFFFFFFFFF casts to −1 as long, causing
+ /// an ArgumentOutOfRangeException when setting stream.Position.
+ ///
+ /// After a fix this should throw InvalidImageContentException instead.
+ ///
+ /// Affected file:
+ /// src/ImageSharp/Formats/Exr/ExrDecoderCore.cs lines 170–175 (scanline)
+ /// lines 243–248 (tile)
+ ///
+ [Fact]
+ public void Decode_CraftedRowOffsets_OutOfBounds_Throws()
+ {
+ // Valid 2×2 image (XMin=0,YMin=0,XMax=1,YMax=1 → Width=2,Height=2).
+ // Row offset table immediately follows the header null byte:
+ // 2 rows × 8 bytes each, all set to 0xFFFFFFFFFFFFFFFF.
+ byte[] invalidOffsets = new byte[16];
+ BinaryPrimitives.WriteUInt64LittleEndian(invalidOffsets, 0xFFFFFFFFFFFFFFFF);
+ BinaryPrimitives.WriteUInt64LittleEndian(invalidOffsets.AsSpan(8), 0xFFFFFFFFFFFFFFFF);
+
+ byte[] data = BuildMinimalExr(
+ xMin: 0, yMin: 0, xMax: 1, yMax: 1,
+ rowOffsetTableAppend: invalidOffsets);
+
+ using var stream = new MemoryStream(data);
+ Assert.Throws(
+ () => ExrDecoder.Instance.Decode(DecoderOptions.Default, stream));
+ }
+
+ [Fact]
+ public void Decode_CraftedRowOffsets_IntoHeader_Throws()
+ {
+ // Offset 0 points back into the EXR file header and must be rejected
+ // before the decoder seeks to attacker-controlled non-pixel data.
+ byte[] headerOffsets = new byte[16];
+
+ byte[] data = BuildMinimalExr(
+ xMin: 0, yMin: 0, xMax: 1, yMax: 1,
+ rowOffsetTableAppend: headerOffsets);
+
+ using var stream = new MemoryStream(data);
+ Assert.Throws(
+ () => ExrDecoder.Instance.Decode(DecoderOptions.Default, stream));
+ }
+
+ [Fact]
+ public void Decode_CraftedRowOffsets_IntoOffsetTable_Throws()
+ {
+ byte[] data = BuildMinimalExr(
+ xMin: 0, yMin: 0, xMax: 1, yMax: 1,
+ rowOffsetTableAppend: new byte[16]);
+
+ // Point the first row offset at the second row offset entry.
+ BinaryPrimitives.WriteUInt64LittleEndian(data.AsSpan(data.Length - 16), (ulong)(data.Length - 8));
+
+ using var stream = new MemoryStream(data);
+ Assert.Throws(
+ () => ExrDecoder.Instance.Decode(DecoderOptions.Default, stream));
+ }
+
+ ///
+ /// EXR-3 — Oversized EXR RGBA row sizing is rejected as invalid image content.
+ ///
+ /// With 4 RGBA HALF channels and Width = 2^29, the decoded row staging and
+ /// bytes-per-row arithmetic both exceed the supported buffer sizing limits.
+ /// The decoder must reject this as InvalidImageContentException before any allocation.
+ ///
+ /// Affected file:
+ /// src/ImageSharp/Formats/Exr/ExrDecoderCore.cs lines 142–150, 215–223
+ /// src/ImageSharp/Formats/Exr/ExrUtils.cs CalculateBytesPerRow
+ ///
+ [Fact]
+ public void Decode_RgbaRowSizingExceedsBufferLimits_Throws()
+ {
+ // 4 RGBA HALF channels at this width cannot be represented by the decoder's
+ // int-sized row staging or block buffers.
+ byte[] data = BuildMinimalRgbaExr(xMin: 0, yMin: 0, xMax: 536870911, yMax: 0);
+
+ using var stream = new MemoryStream(data);
+ Assert.Throws(
+ () => ExrDecoder.Instance.Decode(DecoderOptions.Default, stream));
+ }
+
+ [Fact]
+ public void Decode_DataWindowWidthExceedsRowBufferLimit_Throws()
+ {
+ // A single HALF channel keeps bytesPerBlock below int.MaxValue, but the decoder
+ // still stages four color planes and must reject widths that overflow width × 4.
+ byte[] data = BuildMinimalExr(xMin: 0, yMin: 0, xMax: int.MaxValue / 4, yMax: 0);
+
+ using var stream = new MemoryStream(data);
+ Assert.Throws(
+ () => ExrDecoder.Instance.Decode(DecoderOptions.Default, stream));
+ }
+
+ [Fact]
+ public void Identify_RowOffsetTableExceedsStream_Throws()
+ {
+ // Identify parses the header only, so this verifies the offset table bound is
+ // validated before scanline decoding reads from the table.
+ byte[] data = BuildMinimalExr(xMin: 0, yMin: 0, xMax: 1, yMax: 1);
+
+ using var stream = new MemoryStream(data);
+ Assert.Throws(
+ () => ExrDecoder.Instance.Identify(DecoderOptions.Default, stream));
+ }
+
+ // -------------------------------------------------------------------------
+ // Helpers: construct minimal valid-enough EXR scanline files.
+ //
+ // Required attributes per ParseHeaderAttributes validation:
+ // channels, compression, dataWindow, displayWindow,
+ // lineOrder, pixelAspectRatio, screenWindowCenter, screenWindowWidth
+ // -------------------------------------------------------------------------
+
+ private static byte[] BuildMinimalExr(
+ int xMin, int yMin, int xMax, int yMax,
+ byte[] rowOffsetTableAppend = null)
+ {
+ // channels: single "R" HALF channel with xSampling=1, ySampling=1
+ // Layout per ReadChannelInfo: name\0 (2) + pixelType (4) + pLinear+reserved (4)
+ // + xSampling (4) + ySampling (4) = 18 bytes/channel
+ // + list-null (1) = 19 total
+ byte[] channelData =
+ [
+ 0x52, 0x00, // "R\0"
+ 0x01, 0x00, 0x00, 0x00, // pixelType = Half (1)
+ 0x00, 0x00, 0x00, 0x00, // pLinear + 3 reserved bytes
+ 0x01, 0x00, 0x00, 0x00, // xSampling = 1
+ 0x01, 0x00, 0x00, 0x00, // ySampling = 1
+ 0x00, // channel-list null terminator
+ ];
+
+ return BuildExrWithChannels(xMin, yMin, xMax, yMax, channelData, rowOffsetTableAppend);
+ }
+
+ private static byte[] BuildMinimalRgbaExr(int xMin, int yMin, int xMax, int yMax)
+ {
+ // 4 HALF channels in alphabetical order (A, B, G, R) per EXR spec.
+ // 18 bytes per channel × 4 channels + 1 list-null = 73 bytes total.
+ byte[] channelData =
+ [
+ 0x41, 0x00, // "A\0"
+ 0x01, 0x00, 0x00, 0x00, // pixelType = Half (1)
+ 0x00, 0x00, 0x00, 0x00, // pLinear + 3 reserved bytes
+ 0x01, 0x00, 0x00, 0x00, // xSampling = 1
+ 0x01, 0x00, 0x00, 0x00, // ySampling = 1
+ 0x42, 0x00, // "B\0"
+ 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00,
+ 0x47, 0x00, // "G\0"
+ 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00,
+ 0x52, 0x00, // "R\0"
+ 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00,
+ 0x01, 0x00, 0x00, 0x00,
+ 0x00, // channel-list null terminator
+ ];
+
+ return BuildExrWithChannels(xMin, yMin, xMax, yMax, channelData);
+ }
+
+ private static byte[] BuildExrWithChannels(
+ int xMin, int yMin, int xMax, int yMax,
+ byte[] channelData,
+ byte[] rowOffsetTableAppend = null)
+ {
+ using var ms = new MemoryStream();
+ using var bw = new BinaryWriter(ms, System.Text.Encoding.ASCII, leaveOpen: true);
+
+ // Magic (0x01312F76 LE) + version 2, scanline (flags = 0x00)
+ bw.Write(new byte[] { 0x76, 0x2F, 0x31, 0x01, 0x02, 0x00, 0x00, 0x00 });
+
+ void WriteAttr(string name, string type, byte[] payload)
+ {
+ foreach (char c in name) bw.Write((byte)c);
+ bw.Write((byte)0);
+ foreach (char c in type) bw.Write((byte)c);
+ bw.Write((byte)0);
+ bw.Write(payload.Length);
+ bw.Write(payload);
+ }
+
+ WriteAttr("channels", "chlist", channelData);
+
+ WriteAttr("compression", "compression", [0x00]); // None
+
+ byte[] dw = new byte[16];
+ BinaryPrimitives.WriteInt32LittleEndian(dw, xMin);
+ BinaryPrimitives.WriteInt32LittleEndian(dw.AsSpan(4), yMin);
+ BinaryPrimitives.WriteInt32LittleEndian(dw.AsSpan(8), xMax);
+ BinaryPrimitives.WriteInt32LittleEndian(dw.AsSpan(12), yMax);
+ WriteAttr("dataWindow", "box2i", dw);
+
+ WriteAttr("displayWindow", "box2i", new byte[16]); // all zeros (0,0,0,0)
+
+ WriteAttr("lineOrder", "lineOrder", [0x00]); // IncreasingY
+
+ byte[] aspect = new byte[4];
+ BinaryPrimitives.WriteSingleLittleEndian(aspect, 1.0f);
+ WriteAttr("pixelAspectRatio", "float", aspect);
+
+ WriteAttr("screenWindowCenter", "v2f", new byte[8]); // (0f, 0f)
+
+ byte[] sww = new byte[4];
+ BinaryPrimitives.WriteSingleLittleEndian(sww, 1.0f);
+ WriteAttr("screenWindowWidth", "float", sww);
+
+ bw.Write((byte)0x00); // end-of-header sentinel
+
+ if (rowOffsetTableAppend is not null)
+ bw.Write(rowOffsetTableAppend);
+
+ return ms.ToArray();
+ }
+}
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
index 544ac85a51..610693c7f4 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/Block8x8FTests.cs
@@ -219,7 +219,7 @@ public partial class Block8x8FTests : JpegFixture
RunTest,
srcSeed,
qtSeed,
- HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
+ HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
}
[Fact]
@@ -391,7 +391,7 @@ public partial class Block8x8FTests : JpegFixture
// 3. DisableAvx2 - call fallback code of float implementation
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
- HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
+ HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
}
[Theory]
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs
index 83c3a344da..b17a39f388 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/DCTTests.cs
@@ -152,7 +152,7 @@ public static class DCTTests
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
seed,
- HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
+ HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
}
[Theory]
@@ -359,7 +359,7 @@ public static class DCTTests
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
seed,
- HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
+ HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
}
}
}
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
index 8d94fb5cf4..ba0dce4c54 100644
--- a/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegColorConverterTests.cs
@@ -69,7 +69,7 @@ public class JpegColorConverterTests
{
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
- HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512 | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
+ HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
static void RunTest(string arg)
{
@@ -102,7 +102,7 @@ public class JpegColorConverterTests
{
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
- HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512 | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic);
+ HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic);
static void RunTest(string arg)
{
@@ -168,7 +168,7 @@ public class JpegColorConverterTests
{
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
- HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512 | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic);
+ HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic);
static void RunTest(string arg)
{
@@ -201,7 +201,7 @@ public class JpegColorConverterTests
{
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
- HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512 | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic);
+ HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX2 | HwIntrinsics.DisableHWIntrinsic);
static void RunTest(string arg)
{
diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs
index 924e94d929..ddd79bb756 100644
--- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs
+++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenderTests.cs
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.PixelFormats.PixelBlenders;
using SixLabors.ImageSharp.Tests.TestUtilities;
@@ -32,6 +33,172 @@ public class PixelBlenderTests
{ new TestPixel(), typeof(DefaultPixelBlenders.MultiplySrcOver), PixelColorBlendingMode.Multiply },
};
+ public static TheoryData BlenderModeMappings
+ {
+ get
+ {
+ TheoryData data = new();
+
+ AddBlenderModeMappings(
+ data,
+ PixelAlphaCompositionMode.Clear,
+ typeof(DefaultPixelBlenders.NormalClear),
+ typeof(DefaultPixelBlenders.MultiplyClear),
+ typeof(DefaultPixelBlenders.AddClear),
+ typeof(DefaultPixelBlenders.SubtractClear),
+ typeof(DefaultPixelBlenders.ScreenClear),
+ typeof(DefaultPixelBlenders.DarkenClear),
+ typeof(DefaultPixelBlenders.LightenClear),
+ typeof(DefaultPixelBlenders.OverlayClear),
+ typeof(DefaultPixelBlenders.HardLightClear));
+
+ AddBlenderModeMappings(
+ data,
+ PixelAlphaCompositionMode.Xor,
+ typeof(DefaultPixelBlenders.NormalXor),
+ typeof(DefaultPixelBlenders.MultiplyXor),
+ typeof(DefaultPixelBlenders.AddXor),
+ typeof(DefaultPixelBlenders.SubtractXor),
+ typeof(DefaultPixelBlenders.ScreenXor),
+ typeof(DefaultPixelBlenders.DarkenXor),
+ typeof(DefaultPixelBlenders.LightenXor),
+ typeof(DefaultPixelBlenders.OverlayXor),
+ typeof(DefaultPixelBlenders.HardLightXor));
+
+ AddBlenderModeMappings(
+ data,
+ PixelAlphaCompositionMode.Src,
+ typeof(DefaultPixelBlenders.NormalSrc),
+ typeof(DefaultPixelBlenders.MultiplySrc),
+ typeof(DefaultPixelBlenders.AddSrc),
+ typeof(DefaultPixelBlenders.SubtractSrc),
+ typeof(DefaultPixelBlenders.ScreenSrc),
+ typeof(DefaultPixelBlenders.DarkenSrc),
+ typeof(DefaultPixelBlenders.LightenSrc),
+ typeof(DefaultPixelBlenders.OverlaySrc),
+ typeof(DefaultPixelBlenders.HardLightSrc));
+
+ AddBlenderModeMappings(
+ data,
+ PixelAlphaCompositionMode.SrcAtop,
+ typeof(DefaultPixelBlenders.NormalSrcAtop),
+ typeof(DefaultPixelBlenders.MultiplySrcAtop),
+ typeof(DefaultPixelBlenders.AddSrcAtop),
+ typeof(DefaultPixelBlenders.SubtractSrcAtop),
+ typeof(DefaultPixelBlenders.ScreenSrcAtop),
+ typeof(DefaultPixelBlenders.DarkenSrcAtop),
+ typeof(DefaultPixelBlenders.LightenSrcAtop),
+ typeof(DefaultPixelBlenders.OverlaySrcAtop),
+ typeof(DefaultPixelBlenders.HardLightSrcAtop));
+
+ AddBlenderModeMappings(
+ data,
+ PixelAlphaCompositionMode.SrcIn,
+ typeof(DefaultPixelBlenders.NormalSrcIn),
+ typeof(DefaultPixelBlenders.MultiplySrcIn),
+ typeof(DefaultPixelBlenders.AddSrcIn),
+ typeof(DefaultPixelBlenders.SubtractSrcIn),
+ typeof(DefaultPixelBlenders.ScreenSrcIn),
+ typeof(DefaultPixelBlenders.DarkenSrcIn),
+ typeof(DefaultPixelBlenders.LightenSrcIn),
+ typeof(DefaultPixelBlenders.OverlaySrcIn),
+ typeof(DefaultPixelBlenders.HardLightSrcIn));
+
+ AddBlenderModeMappings(
+ data,
+ PixelAlphaCompositionMode.SrcOut,
+ typeof(DefaultPixelBlenders.NormalSrcOut),
+ typeof(DefaultPixelBlenders.MultiplySrcOut),
+ typeof(DefaultPixelBlenders.AddSrcOut),
+ typeof(DefaultPixelBlenders.SubtractSrcOut),
+ typeof(DefaultPixelBlenders.ScreenSrcOut),
+ typeof(DefaultPixelBlenders.DarkenSrcOut),
+ typeof(DefaultPixelBlenders.LightenSrcOut),
+ typeof(DefaultPixelBlenders.OverlaySrcOut),
+ typeof(DefaultPixelBlenders.HardLightSrcOut));
+
+ AddBlenderModeMappings(
+ data,
+ PixelAlphaCompositionMode.Dest,
+ typeof(DefaultPixelBlenders.NormalDest),
+ typeof(DefaultPixelBlenders.MultiplyDest),
+ typeof(DefaultPixelBlenders.AddDest),
+ typeof(DefaultPixelBlenders.SubtractDest),
+ typeof(DefaultPixelBlenders.ScreenDest),
+ typeof(DefaultPixelBlenders.DarkenDest),
+ typeof(DefaultPixelBlenders.LightenDest),
+ typeof(DefaultPixelBlenders.OverlayDest),
+ typeof(DefaultPixelBlenders.HardLightDest));
+
+ AddBlenderModeMappings(
+ data,
+ PixelAlphaCompositionMode.DestAtop,
+ typeof(DefaultPixelBlenders.NormalDestAtop),
+ typeof(DefaultPixelBlenders.MultiplyDestAtop),
+ typeof(DefaultPixelBlenders.AddDestAtop),
+ typeof(DefaultPixelBlenders.SubtractDestAtop),
+ typeof(DefaultPixelBlenders.ScreenDestAtop),
+ typeof(DefaultPixelBlenders.DarkenDestAtop),
+ typeof(DefaultPixelBlenders.LightenDestAtop),
+ typeof(DefaultPixelBlenders.OverlayDestAtop),
+ typeof(DefaultPixelBlenders.HardLightDestAtop));
+
+ AddBlenderModeMappings(
+ data,
+ PixelAlphaCompositionMode.DestIn,
+ typeof(DefaultPixelBlenders.NormalDestIn),
+ typeof(DefaultPixelBlenders.MultiplyDestIn),
+ typeof(DefaultPixelBlenders.AddDestIn),
+ typeof(DefaultPixelBlenders.SubtractDestIn),
+ typeof(DefaultPixelBlenders.ScreenDestIn),
+ typeof(DefaultPixelBlenders.DarkenDestIn),
+ typeof(DefaultPixelBlenders.LightenDestIn),
+ typeof(DefaultPixelBlenders.OverlayDestIn),
+ typeof(DefaultPixelBlenders.HardLightDestIn));
+
+ AddBlenderModeMappings(
+ data,
+ PixelAlphaCompositionMode.DestOut,
+ typeof(DefaultPixelBlenders.NormalDestOut),
+ typeof(DefaultPixelBlenders.MultiplyDestOut),
+ typeof(DefaultPixelBlenders.AddDestOut),
+ typeof(DefaultPixelBlenders.SubtractDestOut),
+ typeof(DefaultPixelBlenders.ScreenDestOut),
+ typeof(DefaultPixelBlenders.DarkenDestOut),
+ typeof(DefaultPixelBlenders.LightenDestOut),
+ typeof(DefaultPixelBlenders.OverlayDestOut),
+ typeof(DefaultPixelBlenders.HardLightDestOut));
+
+ AddBlenderModeMappings(
+ data,
+ PixelAlphaCompositionMode.DestOver,
+ typeof(DefaultPixelBlenders.NormalDestOver),
+ typeof(DefaultPixelBlenders.MultiplyDestOver),
+ typeof(DefaultPixelBlenders.AddDestOver),
+ typeof(DefaultPixelBlenders.SubtractDestOver),
+ typeof(DefaultPixelBlenders.ScreenDestOver),
+ typeof(DefaultPixelBlenders.DarkenDestOver),
+ typeof(DefaultPixelBlenders.LightenDestOver),
+ typeof(DefaultPixelBlenders.OverlayDestOver),
+ typeof(DefaultPixelBlenders.HardLightDestOver));
+
+ AddBlenderModeMappings(
+ data,
+ PixelAlphaCompositionMode.SrcOver,
+ typeof(DefaultPixelBlenders.NormalSrcOver),
+ typeof(DefaultPixelBlenders.MultiplySrcOver),
+ typeof(DefaultPixelBlenders.AddSrcOver),
+ typeof(DefaultPixelBlenders.SubtractSrcOver),
+ typeof(DefaultPixelBlenders.ScreenSrcOver),
+ typeof(DefaultPixelBlenders.DarkenSrcOver),
+ typeof(DefaultPixelBlenders.LightenSrcOver),
+ typeof(DefaultPixelBlenders.OverlaySrcOver),
+ typeof(DefaultPixelBlenders.HardLightSrcOver));
+
+ return data;
+ }
+ }
+
[Theory]
[MemberData(nameof(BlenderMappings))]
public void ReturnsCorrectBlender(TestPixel pixel, Type type, PixelColorBlendingMode mode)
@@ -41,6 +208,126 @@ public class PixelBlenderTests
Assert.IsType(type, blender);
}
+ [Theory]
+ [MemberData(nameof(BlenderModeMappings))]
+ public void ReturnsCorrectBlenderForAllModeCombinations(PixelColorBlendingMode colorMode, PixelAlphaCompositionMode alphaMode, Type type)
+ {
+ PixelBlender blender = PixelOperations.Instance.GetPixelBlender(colorMode, alphaMode);
+ Assert.IsType(type, blender);
+ }
+
+ [Fact]
+ public void BlendFunctionsAreCalledForAllModeCombinations() =>
+ FeatureTestRunner.RunWithHwIntrinsicsFeature(
+ ExerciseAllBlenderModeCombinations,
+ HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX | HwIntrinsics.DisableHWIntrinsic);
+
+ [Fact]
+ public void Blend_WithConstantSourceAndSingleAmount()
+ {
+ PixelBlender blender = new DefaultPixelBlenders.NormalSrcOver();
+ Rgba32[] destination = new Rgba32[2];
+ Rgba32[] background =
+ [
+ Color.Red.ToPixel(),
+ Color.Green.ToPixel()
+ ];
+
+ Rgba32 source = Color.Blue.ToPixel();
+
+ blender.Blend(Configuration.Default, destination, background, source, 1F);
+
+ Assert.Equal(source, destination[0]);
+ Assert.Equal(source, destination[1]);
+ }
+
+ [Fact]
+ public void Blend_WithConstantSourceSingleAmountAndWorkingBuffer()
+ {
+ PixelBlender blender = new DefaultPixelBlenders.NormalSrcOver();
+ Rgba32[] destination = new Rgba32[2];
+ Rgba32[] background =
+ [
+ Color.Red.ToPixel(),
+ Color.Green.ToPixel()
+ ];
+
+ Rgba32 source = Color.Blue.ToPixel();
+ Vector4[] workingBuffer = new Vector4[destination.Length * 2];
+
+ blender.Blend(Configuration.Default, destination, background, source, 1F, workingBuffer);
+
+ Assert.Equal(source, destination[0]);
+ Assert.Equal(source, destination[1]);
+ }
+
+ [Fact]
+ public void Blend_WithConstantSourceAndAmountSpan()
+ {
+ PixelBlender blender = new DefaultPixelBlenders.NormalSrcOver();
+ Rgba32[] destination = new Rgba32[2];
+ Rgba32[] background =
+ [
+ Color.Red.ToPixel(),
+ Color.Green.ToPixel()
+ ];
+
+ Rgba32 source = Color.Blue.ToPixel();
+ float[] amount = [1F, 1F];
+
+ blender.Blend(Configuration.Default, destination, background, source, amount);
+
+ Assert.Equal(source, destination[0]);
+ Assert.Equal(source, destination[1]);
+ }
+
+ [Fact]
+ public void Blend_WithConstantSourceAmountSpanAndWorkingBuffer()
+ {
+ PixelBlender blender = new DefaultPixelBlenders.NormalSrcOver();
+ Rgba32[] destination = new Rgba32[2];
+ Rgba32[] background =
+ [
+ Color.Red.ToPixel(),
+ Color.Green.ToPixel()
+ ];
+
+ Rgba32 source = Color.Blue.ToPixel();
+ float[] amount = [1F, 1F];
+ Vector4[] workingBuffer = new Vector4[destination.Length * 2];
+
+ blender.Blend(Configuration.Default, destination, background, source, amount, workingBuffer);
+
+ Assert.Equal(source, destination[0]);
+ Assert.Equal(source, destination[1]);
+ }
+
+ [Fact]
+ public void Blend_WithSourceSpanAmountSpanAndWorkingBuffer()
+ {
+ PixelBlender blender = new DefaultPixelBlenders.NormalSrcOver();
+ Rgba32[] destination = new Rgba32[2];
+ Rgba32[] background =
+ [
+ Color.Red.ToPixel(),
+ Color.Green.ToPixel()
+ ];
+
+ Rgba32[] source =
+ [
+ Color.Blue.ToPixel(),
+ Color.Yellow.ToPixel()
+ ];
+
+ float[] amount = [1F, 1F];
+ Vector4[] workingBuffer = new Vector4[destination.Length * 3];
+
+ blender.Blend(Configuration.Default, destination, background, source, amount, workingBuffer);
+
+ Assert.Equal(source[0], destination[0]);
+ Assert.Equal(source[1], destination[1]);
+ }
+
public static TheoryData ColorBlendingExpectedResults = new()
{
{ Color.MistyRose.ToPixel(), Color.MidnightBlue.ToPixel(), 1, PixelColorBlendingMode.Normal, Color.MidnightBlue.ToPixel() },
@@ -92,4 +379,67 @@ public class PixelBlenderTests
// var str = actualResult.Rgba.ToString("X8"); // used to extract expectedResults
Assert.Equal(actualResult.ToVector4(), expectedResult.ToVector4());
}
+
+ private static void AddBlenderModeMappings(
+ TheoryData data,
+ PixelAlphaCompositionMode alphaMode,
+ Type normal,
+ Type multiply,
+ Type add,
+ Type subtract,
+ Type screen,
+ Type darken,
+ Type lighten,
+ Type overlay,
+ Type hardLight)
+ {
+ data.Add(PixelColorBlendingMode.Normal, alphaMode, normal);
+ data.Add(PixelColorBlendingMode.Multiply, alphaMode, multiply);
+ data.Add(PixelColorBlendingMode.Add, alphaMode, add);
+ data.Add(PixelColorBlendingMode.Subtract, alphaMode, subtract);
+ data.Add(PixelColorBlendingMode.Screen, alphaMode, screen);
+ data.Add(PixelColorBlendingMode.Darken, alphaMode, darken);
+ data.Add(PixelColorBlendingMode.Lighten, alphaMode, lighten);
+ data.Add(PixelColorBlendingMode.Overlay, alphaMode, overlay);
+ data.Add(PixelColorBlendingMode.HardLight, alphaMode, hardLight);
+ }
+
+ private static void ExerciseAllBlenderModeCombinations()
+ {
+ foreach (PixelAlphaCompositionMode alphaMode in Enum.GetValues())
+ {
+ foreach (PixelColorBlendingMode colorMode in Enum.GetValues())
+ {
+ PixelBlender blender = PixelOperations.Instance.GetPixelBlender(colorMode, alphaMode);
+ ExerciseBlender(blender);
+ }
+ }
+ }
+
+ private static void ExerciseBlender(PixelBlender blender)
+ {
+ Rgba32 background = Color.MistyRose.ToPixel();
+ Rgba32 source = Color.MidnightBlue.ToPixel();
+ float[] amount = [1F, 1F, 1F, 1F];
+
+ Rgba32 expected = blender.Blend(background, source, 1F);
+
+ Rgba32[] destination = new Rgba32[4];
+ Rgba32[] backgroundSpan = [background, background, background, background];
+ Rgba32[] sourceSpan = [source, source, source, source];
+ Vector4[] sourceSpanBuffer = new Vector4[destination.Length * 3];
+ Vector4[] constantSourceBuffer = new Vector4[destination.Length * 2];
+
+ blender.Blend(Configuration.Default, destination, backgroundSpan, sourceSpan, 1F, sourceSpanBuffer);
+ Assert.All(destination, x => Assert.Equal(expected, x));
+
+ blender.Blend(Configuration.Default, destination, backgroundSpan, source, 1F, constantSourceBuffer);
+ Assert.All(destination, x => Assert.Equal(expected, x));
+
+ blender.Blend(Configuration.Default, destination, backgroundSpan, sourceSpan, amount, sourceSpanBuffer);
+ Assert.All(destination, x => Assert.Equal(expected, x));
+
+ blender.Blend(Configuration.Default, destination, backgroundSpan, source, amount, constantSourceBuffer);
+ Assert.All(destination, x => Assert.Equal(expected, x));
+ }
}
diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs
index 994b7d02ee..f8c296846b 100644
--- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs
+++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffCompositorTests.cs
@@ -59,7 +59,7 @@ public class PorterDuffCompositorTests
FeatureTestRunner.RunWithHwIntrinsicsFeature(
RunTest,
- HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512 | HwIntrinsics.DisableAVX,
+ HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX512F | HwIntrinsics.DisableAVX,
provider,
mode.ToString());
}
diff --git a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs
index be3e9ccd5d..4cd30fc571 100644
--- a/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/FeatureTesting/FeatureTestRunner.cs
@@ -439,7 +439,7 @@ public enum HwIntrinsics : long
DisableSSE42 = 1L << 1,
DisableAVX = 1L << 2,
DisableAVX2 = 1L << 3,
- DisableAVX512 = 1L << 4,
+ DisableAVX512F = 1L << 4,
DisableAVX512v2 = 1L << 5,
DisableAVX512v3 = 1L << 6,
DisableAVX10v1 = 1L << 7,
diff --git a/tests/coverlet.runsettings b/tests/coverlet.runsettings
index cffce3540b..f327b47a2e 100644
--- a/tests/coverlet.runsettings
+++ b/tests/coverlet.runsettings
@@ -9,7 +9,7 @@
lcov
- [SixLabors.*]*
+ [SixLabors.ImageSharp*]*
true