diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index 46f659c451..ace6a4306e 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -27,7 +27,7 @@ jobs:
git config --global core.longpaths true
- name: Git Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
with:
fetch-depth: 0
submodules: recursive
@@ -137,7 +137,7 @@ jobs:
git config --global core.longpaths true
- name: Git Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
with:
fetch-depth: 0
submodules: recursive
@@ -209,7 +209,7 @@ jobs:
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit
- name: Export Failed Output
- uses: actions/upload-artifact@v6
+ uses: actions/upload-artifact@v7
if: failure()
with:
name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip
@@ -227,7 +227,7 @@ jobs:
git config --global core.longpaths true
- name: Git Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
with:
fetch-depth: 0
submodules: recursive
diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml
index a095d1abeb..16ae0317dc 100644
--- a/.github/workflows/code-coverage.yml
+++ b/.github/workflows/code-coverage.yml
@@ -31,7 +31,7 @@ jobs:
git config --global core.longpaths true
- name: Git Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@v6
with:
fetch-depth: 0
submodules: recursive
@@ -86,7 +86,7 @@ jobs:
XUNIT_PATH: .\tests\ImageSharp.Tests # Required for xunit
- name: Export Failed Output
- uses: actions/upload-artifact@v6
+ uses: actions/upload-artifact@v7
if: failure()
with:
name: actual_output_${{ runner.os }}_${{ matrix.options.framework }}${{ matrix.options.runtime }}.zip
diff --git a/.gitignore b/.gitignore
index fadf36964c..a8d2917be7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -227,3 +227,5 @@ artifacts/
#lfs
hooks/**
lfs/**
+
+.dotnet
diff --git a/README.md b/README.md
index dc30734792..4d4375dbf5 100644
--- a/README.md
+++ b/README.md
@@ -10,16 +10,14 @@ SixLabors.ImageSharp
[](https://github.com/SixLabors/ImageSharp/actions)
[](https://codecov.io/gh/SixLabors/ImageSharp)
[](https://github.com/SixLabors/ImageSharp/blob/main/LICENSE)
-[](https://twitter.com/intent/tweet?hashtags=imagesharp,dotnet,oss&text=ImageSharp.+A+new+cross-platform+2D+graphics+API+in+C%23&url=https%3a%2f%2fgithub.com%2fSixLabors%2fImageSharp&via=sixlabors)
-### **ImageSharp** is a new, fully featured, fully managed, cross-platform, 2D graphics API.
+### **ImageSharp** is a high-performance, fully managed, cross-platform 2D graphics API.
-ImageSharp is a new, fully featured, fully managed, cross-platform, 2D graphics library.
-Designed to simplify image processing, ImageSharp brings you an incredibly powerful yet beautifully simple API.
+ImageSharp is a mature, fully featured, high-performance image processing and graphics library for .NET, built for workloads across device, cloud, and embedded/IoT scenarios.
-ImageSharp is designed from the ground up to be flexible and extensible. The library provides API endpoints for common image processing operations and the building blocks to allow for the development of additional operations.
+Designed from the ground up to balance performance, portability, and ease of use, ImageSharp provides a powerful yet approachable API for common image processing tasks, along with the low-level building blocks needed to extend the library for specialized workflows.
Built against [.NET 8](https://docs.microsoft.com/en-us/dotnet/standard/net-standard), ImageSharp can be used in device, cloud, and embedded/IoT scenarios.
diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
index a1de790c3d..230526fd4a 100644
--- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
+++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs
@@ -1548,6 +1548,12 @@ internal sealed class BmpDecoderCore : ImageDecoderCore
case BmpFileMarkerType.Bitmap:
if (this.fileHeader.HasValue)
{
+ if (this.fileHeader.Value.Offset > stream.Length)
+ {
+ BmpThrowHelper.ThrowInvalidImageContentException(
+ $"Pixel data offset {this.fileHeader.Value.Offset} exceeds file size {stream.Length}.");
+ }
+
colorMapSizeBytes = this.fileHeader.Value.Offset - BmpFileHeader.Size - this.infoHeader.HeaderSize;
}
else
diff --git a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
index 7825955e7a..d4517e9f19 100644
--- a/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
+++ b/src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
@@ -519,6 +519,11 @@ internal sealed class JpegDecoderCore : ImageDecoderCore, IRawJpegData
fileMarker = FindNextFileMarker(stream);
}
+ if (!metadataOnly && this.Frame is null)
+ {
+ JpegThrowHelper.ThrowInvalidImageContentException("No readable SOFn (Start Of Frame) marker found.");
+ }
+
this.Metadata.GetJpegMetadata().Interleaved = this.Frame.Interleaved;
}
diff --git a/src/ImageSharp/Formats/Png/Chunks/PngPhysical.cs b/src/ImageSharp/Formats/Png/Chunks/PngPhysical.cs
index 8af0ac8ca7..cce4d16b84 100644
--- a/src/ImageSharp/Formats/Png/Chunks/PngPhysical.cs
+++ b/src/ImageSharp/Formats/Png/Chunks/PngPhysical.cs
@@ -46,6 +46,11 @@ internal readonly struct PngPhysical
/// The parsed PhysicalChunkData.
public static PngPhysical Parse(ReadOnlySpan data)
{
+ if (data.Length < 9)
+ {
+ PngThrowHelper.ThrowInvalidImageContentException("pHYs chunk is too short");
+ }
+
uint hResolution = BinaryPrimitives.ReadUInt32BigEndian(data[..4]);
uint vResolution = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(4, 4));
byte unit = data[8];
diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
index 271474a7e5..8962182679 100644
--- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs
@@ -1253,6 +1253,12 @@ internal sealed class PngDecoderCore : ImageDecoderCore
ReadOnlySpan rgbTable = MemoryMarshal.Cast(palette);
Color.FromPixel(rgbTable, colorTable);
+ // The tRNS chunk must not contain more alpha values than there are palette entries.
+ if (alpha.Length > colorTable.Length)
+ {
+ alpha = alpha.Slice(0, colorTable.Length);
+ }
+
if (alpha.Length > 0)
{
// The alpha chunk may contain as many transparency entries as there are palette entries
@@ -1402,26 +1408,31 @@ internal sealed class PngDecoderCore : ImageDecoderCore
return;
}
- int zeroIndex = data.IndexOf((byte)0);
- if (zeroIndex is < PngConstants.MinTextKeywordLength or > PngConstants.MaxTextKeywordLength)
+ int keywordEnd = data.IndexOf((byte)0);
+ if (keywordEnd is < PngConstants.MinTextKeywordLength or > PngConstants.MaxTextKeywordLength)
{
return;
}
- byte compressionMethod = data[zeroIndex + 1];
+ if (keywordEnd < 0 || keywordEnd + 2 > data.Length)
+ {
+ return; // Not enough data for keyword + null + compression method.
+ }
+
+ byte compressionMethod = data[keywordEnd + 1];
if (compressionMethod != 0)
{
// Only compression method 0 is supported (zlib datastream with deflate compression).
return;
}
- ReadOnlySpan keywordBytes = data[..zeroIndex];
+ ReadOnlySpan keywordBytes = data[..keywordEnd];
if (!TryReadTextKeyword(keywordBytes, out string name))
{
return;
}
- ReadOnlySpan compressedData = data[(zeroIndex + 2)..];
+ ReadOnlySpan compressedData = data[(keywordEnd + 2)..];
if (this.TryDecompressTextData(compressedData, PngConstants.Encoding, out string? uncompressed)
&& !TryReadTextChunkMetadata(baseMetadata, name, uncompressed))
@@ -1932,6 +1943,11 @@ internal sealed class PngDecoderCore : ImageDecoderCore
return;
}
+ if (zeroIndexKeyword < 0 || zeroIndexKeyword + 4 > data.Length)
+ {
+ return; // Not enough data for keyword + null + flag + method + language.
+ }
+
byte compressionFlag = data[zeroIndexKeyword + 1];
if (compressionFlag is not (0 or 1))
{
@@ -1956,6 +1972,11 @@ internal sealed class PngDecoderCore : ImageDecoderCore
int translatedKeywordStartIdx = langStartIdx + languageLength + 1;
int translatedKeywordLength = data[translatedKeywordStartIdx..].IndexOf((byte)0);
+ if (translatedKeywordLength < 0)
+ {
+ return;
+ }
+
string translatedKeyword = PngConstants.TranslatedEncoding.GetString(data.Slice(translatedKeywordStartIdx, translatedKeywordLength));
ReadOnlySpan keywordBytes = data[..zeroIndexKeyword];
diff --git a/src/ImageSharp/Formats/Png/PngThrowHelper.cs b/src/ImageSharp/Formats/Png/PngThrowHelper.cs
index 8dc70e1d9a..80c51ef6e5 100644
--- a/src/ImageSharp/Formats/Png/PngThrowHelper.cs
+++ b/src/ImageSharp/Formats/Png/PngThrowHelper.cs
@@ -9,8 +9,7 @@ namespace SixLabors.ImageSharp.Formats.Png;
internal static class PngThrowHelper
{
[DoesNotReturn]
- public static void ThrowInvalidImageContentException(string errorMessage, Exception innerException)
- => throw new InvalidImageContentException(errorMessage, innerException);
+ public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage);
[DoesNotReturn]
public static void ThrowInvalidHeader() => throw new InvalidImageContentException("PNG Image must contain a header chunk and it must be located before any other chunks.");
diff --git a/src/ImageSharp/GraphicsOptions.cs b/src/ImageSharp/GraphicsOptions.cs
index dc3d179027..e056596512 100644
--- a/src/ImageSharp/GraphicsOptions.cs
+++ b/src/ImageSharp/GraphicsOptions.cs
@@ -6,11 +6,12 @@ using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp;
///
-/// Options for influencing the drawing functions.
+/// Provides configuration for controlling how graphics operations are rendered,
+/// including antialiasing, pixel blending, alpha composition, and coverage thresholding.
///
public class GraphicsOptions : IDeepCloneable
{
- private int antialiasSubpixelDepth = 16;
+ private float antialiasThreshold = .5F;
private float blendPercentage = 1F;
///
@@ -24,61 +25,62 @@ public class GraphicsOptions : IDeepCloneable
{
this.AlphaCompositionMode = source.AlphaCompositionMode;
this.Antialias = source.Antialias;
- this.AntialiasSubpixelDepth = source.AntialiasSubpixelDepth;
+ this.AntialiasThreshold = source.AntialiasThreshold;
this.BlendPercentage = source.BlendPercentage;
this.ColorBlendingMode = source.ColorBlendingMode;
}
///
/// Gets or sets a value indicating whether antialiasing should be applied.
- /// Defaults to true.
+ /// When , edges are rendered with smooth sub-pixel coverage.
+ /// When , coverage is snapped to binary (fully opaque or fully transparent)
+ /// using as the cutoff.
+ /// Defaults to .
///
public bool Antialias { get; set; } = true;
///
- /// Gets or sets a value indicating the number of subpixels to use while rendering with antialiasing enabled.
- /// Defaults to 16.
+ /// Gets or sets the coverage threshold used when is .
+ /// Pixels with antialiased coverage above this value are rendered as fully opaque;
+ /// pixels below are discarded. Valid range is 0 to 1. Lower values preserve more
+ /// thin features at small sizes. Defaults to 0.5F.
///
- public int AntialiasSubpixelDepth
+ public float AntialiasThreshold
{
- get
- {
- return this.antialiasSubpixelDepth;
- }
+ get => this.antialiasThreshold;
set
{
- Guard.MustBeGreaterThanOrEqualTo(value, 0, nameof(this.AntialiasSubpixelDepth));
- this.antialiasSubpixelDepth = value;
+ Guard.MustBeBetweenOrEqualTo(value, 0F, 1F, nameof(this.AntialiasThreshold));
+ this.antialiasThreshold = value;
}
}
///
- /// Gets or sets a value between indicating the blending percentage to apply to the drawing operation.
- /// Range 0..1; Defaults to 1.
+ /// Gets or sets the blending percentage applied to the drawing operation.
+ /// A value of 1.0 applies the operation at full strength; 0.0 makes it invisible.
+ /// Valid range is 0 to 1. Defaults to 1.0F.
///
public float BlendPercentage
{
- get
- {
- return this.blendPercentage;
- }
+ get => this.blendPercentage;
set
{
- Guard.MustBeBetweenOrEqualTo(value, 0, 1F, nameof(this.BlendPercentage));
+ Guard.MustBeBetweenOrEqualTo(value, 0F, 1F, nameof(this.BlendPercentage));
this.blendPercentage = value;
}
}
///
- /// Gets or sets a value indicating the color blending mode to apply to the drawing operation.
+ /// Gets or sets the color blending mode used to combine source and destination pixel colors.
/// Defaults to .
///
public PixelColorBlendingMode ColorBlendingMode { get; set; } = PixelColorBlendingMode.Normal;
///
- /// Gets or sets a value indicating the alpha composition mode to apply to the drawing operation
+ /// Gets or sets the alpha composition mode that determines how source and destination alpha
+ /// channels are combined using Porter-Duff operators.
/// Defaults to .
///
public PixelAlphaCompositionMode AlphaCompositionMode { get; set; } = PixelAlphaCompositionMode.SrcOver;
diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs
index f62d3c6761..7cd9cc57ad 100644
--- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs
+++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs
@@ -81,8 +81,10 @@ internal static class DefaultPixelBlenders
}
///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
+ protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount)
{
+ amount = Numerics.Clamp(amount, 0, 1);
+
if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256
@@ -90,65 +92,35 @@ internal static class DefaultPixelBlenders
ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background));
- ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
- ref float amountBase = ref MemoryMarshal.GetReference(amount);
-
- Vector256 vOne = Vector256.Create(1F);
+ Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
+ Vector256 opacity = Vector256.Create(amount);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
- // We need to create a Vector256 containing the current and next amount values
- // taking up each half of the Vector256 and then clamp them.
- Vector256 opacity = Vector256.Create(
- Vector128.Create(amountBase),
- Vector128.Create(Unsafe.Add(ref amountBase, 1)));
- opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne);
-
destinationBase = PorterDuffFunctions.NormalSrc(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
- sourceBase = ref Unsafe.Add(ref sourceBase, 1);
- amountBase = ref Unsafe.Add(ref amountBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
- destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
+ destination[i] = PorterDuffFunctions.NormalSrc(background[i], source, amount);
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
- destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
+ destination[i] = PorterDuffFunctions.NormalSrc(background[i], source, amount);
}
}
}
- }
-
- ///
- /// A pixel blender that implements the "MultiplySrc" composition equation.
- ///
- public class MultiplySrc : PixelBlender
- {
- ///
- /// Gets the static instance of this blender.
- ///
- public static MultiplySrc Instance { get; } = new MultiplySrc();
-
- ///
- public override TPixel Blend(TPixel background, TPixel source, float amount)
- {
- return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplySrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1)));
- }
///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount)
+ protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
{
- amount = Numerics.Clamp(amount, 0, 1);
-
if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256
@@ -157,34 +129,44 @@ internal static class DefaultPixelBlenders
ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background));
ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
- Vector256 opacity = Vector256.Create(amount);
+ ref float amountBase = ref MemoryMarshal.GetReference(amount);
+
+ Vector256 vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
- destinationBase = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity);
+ // We need to create a Vector256 containing the current and next amount values
+ // taking up each half of the Vector256 and then clamp them.
+ Vector256 opacity = Vector256.Create(
+ Vector128.Create(amountBase),
+ Vector128.Create(Unsafe.Add(ref amountBase, 1)));
+ opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne);
+
+ destinationBase = PorterDuffFunctions.NormalSrc(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
+ amountBase = ref Unsafe.Add(ref amountBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
- destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount);
+ destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
- destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount);
+ destination[i] = PorterDuffFunctions.NormalSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
}
}
}
///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
+ protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount)
{
if (Avx2.IsSupported && destination.Length >= 2)
{
@@ -193,9 +175,9 @@ internal static class DefaultPixelBlenders
ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background));
- ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
+ Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256 vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
@@ -207,10 +189,9 @@ internal static class DefaultPixelBlenders
Vector128.Create(Unsafe.Add(ref amountBase, 1)));
opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne);
- destinationBase = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity);
+ destinationBase = PorterDuffFunctions.NormalSrc(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
- sourceBase = ref Unsafe.Add(ref sourceBase, 1);
amountBase = ref Unsafe.Add(ref amountBase, 2);
}
@@ -218,33 +199,33 @@ internal static class DefaultPixelBlenders
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
- destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
+ destination[i] = PorterDuffFunctions.NormalSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
- destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
+ destination[i] = PorterDuffFunctions.NormalSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
}
}
}
}
///
- /// A pixel blender that implements the "AddSrc" composition equation.
+ /// A pixel blender that implements the "MultiplySrc" composition equation.
///
- public class AddSrc : PixelBlender
+ public class MultiplySrc : PixelBlender
{
///
/// Gets the static instance of this blender.
///
- public static AddSrc Instance { get; } = new AddSrc();
+ public static MultiplySrc Instance { get; } = new MultiplySrc();
///
public override TPixel Blend(TPixel background, TPixel source, float amount)
{
- return TPixel.FromScaledVector4(PorterDuffFunctions.AddSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1)));
+ return TPixel.FromScaledVector4(PorterDuffFunctions.MultiplySrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1)));
}
///
@@ -264,7 +245,7 @@ internal static class DefaultPixelBlenders
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
- destinationBase = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity);
+ destinationBase = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
@@ -274,21 +255,23 @@ internal static class DefaultPixelBlenders
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
- destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount);
+ destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount);
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
- destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount);
+ destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], amount);
}
}
}
///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
+ protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount)
{
+ amount = Numerics.Clamp(amount, 0, 1);
+
if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256
@@ -296,65 +279,35 @@ internal static class DefaultPixelBlenders
ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background));
- ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
- ref float amountBase = ref MemoryMarshal.GetReference(amount);
-
- Vector256 vOne = Vector256.Create(1F);
+ Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
+ Vector256 opacity = Vector256.Create(amount);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
- // We need to create a Vector256 containing the current and next amount values
- // taking up each half of the Vector256 and then clamp them.
- Vector256 opacity = Vector256.Create(
- Vector128.Create(amountBase),
- Vector128.Create(Unsafe.Add(ref amountBase, 1)));
- opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne);
-
- destinationBase = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity);
+ destinationBase = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
- sourceBase = ref Unsafe.Add(ref sourceBase, 1);
- amountBase = ref Unsafe.Add(ref amountBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
- destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
+ destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source, amount);
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
- destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
+ destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source, amount);
}
}
}
- }
-
- ///
- /// A pixel blender that implements the "SubtractSrc" composition equation.
- ///
- public class SubtractSrc : PixelBlender
- {
- ///
- /// Gets the static instance of this blender.
- ///
- public static SubtractSrc Instance { get; } = new SubtractSrc();
-
- ///
- public override TPixel Blend(TPixel background, TPixel source, float amount)
- {
- return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1)));
- }
///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount)
+ protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
{
- amount = Numerics.Clamp(amount, 0, 1);
-
if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256
@@ -363,34 +316,44 @@ internal static class DefaultPixelBlenders
ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background));
ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
- Vector256 opacity = Vector256.Create(amount);
+ ref float amountBase = ref MemoryMarshal.GetReference(amount);
+
+ Vector256 vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
- destinationBase = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity);
+ // We need to create a Vector256 containing the current and next amount values
+ // taking up each half of the Vector256 and then clamp them.
+ Vector256 opacity = Vector256.Create(
+ Vector128.Create(amountBase),
+ Vector128.Create(Unsafe.Add(ref amountBase, 1)));
+ opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne);
+
+ destinationBase = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
+ amountBase = ref Unsafe.Add(ref amountBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
- destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount);
+ destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
- destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount);
+ destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
}
}
}
///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
+ protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount)
{
if (Avx2.IsSupported && destination.Length >= 2)
{
@@ -399,9 +362,9 @@ internal static class DefaultPixelBlenders
ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background));
- ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
+ Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256 vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
@@ -413,10 +376,9 @@ internal static class DefaultPixelBlenders
Vector128.Create(Unsafe.Add(ref amountBase, 1)));
opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne);
- destinationBase = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity);
+ destinationBase = PorterDuffFunctions.MultiplySrc(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
- sourceBase = ref Unsafe.Add(ref sourceBase, 1);
amountBase = ref Unsafe.Add(ref amountBase, 2);
}
@@ -424,33 +386,33 @@ internal static class DefaultPixelBlenders
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
- destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
+ destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
- destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
+ destination[i] = PorterDuffFunctions.MultiplySrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
}
}
}
}
///
- /// A pixel blender that implements the "ScreenSrc" composition equation.
+ /// A pixel blender that implements the "AddSrc" composition equation.
///
- public class ScreenSrc : PixelBlender
+ public class AddSrc : PixelBlender
{
///
/// Gets the static instance of this blender.
///
- public static ScreenSrc Instance { get; } = new ScreenSrc();
+ public static AddSrc Instance { get; } = new AddSrc();
///
public override TPixel Blend(TPixel background, TPixel source, float amount)
{
- return TPixel.FromScaledVector4(PorterDuffFunctions.ScreenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1)));
+ return TPixel.FromScaledVector4(PorterDuffFunctions.AddSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1)));
}
///
@@ -470,7 +432,7 @@ internal static class DefaultPixelBlenders
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
- destinationBase = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity);
+ destinationBase = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
@@ -480,21 +442,23 @@ internal static class DefaultPixelBlenders
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
- destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount);
+ destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount);
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
- destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], amount);
+ destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], amount);
}
}
}
///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
+ protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount)
{
+ amount = Numerics.Clamp(amount, 0, 1);
+
if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256
@@ -502,65 +466,35 @@ internal static class DefaultPixelBlenders
ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background));
- ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
- ref float amountBase = ref MemoryMarshal.GetReference(amount);
-
- Vector256 vOne = Vector256.Create(1F);
+ Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
+ Vector256 opacity = Vector256.Create(amount);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
- // We need to create a Vector256 containing the current and next amount values
- // taking up each half of the Vector256 and then clamp them.
- Vector256 opacity = Vector256.Create(
- Vector128.Create(amountBase),
- Vector128.Create(Unsafe.Add(ref amountBase, 1)));
- opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne);
-
- destinationBase = PorterDuffFunctions.ScreenSrc(backgroundBase, sourceBase, opacity);
+ destinationBase = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
- sourceBase = ref Unsafe.Add(ref sourceBase, 1);
- amountBase = ref Unsafe.Add(ref amountBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
- destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
+ destination[i] = PorterDuffFunctions.AddSrc(background[i], source, amount);
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
- destination[i] = PorterDuffFunctions.ScreenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
+ destination[i] = PorterDuffFunctions.AddSrc(background[i], source, amount);
}
}
}
- }
-
- ///
- /// A pixel blender that implements the "DarkenSrc" composition equation.
- ///
- public class DarkenSrc : PixelBlender
- {
- ///
- /// Gets the static instance of this blender.
- ///
- public static DarkenSrc Instance { get; } = new DarkenSrc();
-
- ///
- public override TPixel Blend(TPixel background, TPixel source, float amount)
- {
- return TPixel.FromScaledVector4(PorterDuffFunctions.DarkenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1)));
- }
///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount)
+ protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
{
- amount = Numerics.Clamp(amount, 0, 1);
-
if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256
@@ -569,34 +503,44 @@ internal static class DefaultPixelBlenders
ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background));
ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
- Vector256 opacity = Vector256.Create(amount);
+ ref float amountBase = ref MemoryMarshal.GetReference(amount);
+
+ Vector256 vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
- destinationBase = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity);
+ // We need to create a Vector256 containing the current and next amount values
+ // taking up each half of the Vector256 and then clamp them.
+ Vector256 opacity = Vector256.Create(
+ Vector128.Create(amountBase),
+ Vector128.Create(Unsafe.Add(ref amountBase, 1)));
+ opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne);
+
+ destinationBase = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
+ amountBase = ref Unsafe.Add(ref amountBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
- destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount);
+ destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
- destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], amount);
+ destination[i] = PorterDuffFunctions.AddSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
}
}
}
///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
+ protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount)
{
if (Avx2.IsSupported && destination.Length >= 2)
{
@@ -605,9 +549,9 @@ internal static class DefaultPixelBlenders
ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background));
- ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
+ Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256 vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
@@ -619,10 +563,9 @@ internal static class DefaultPixelBlenders
Vector128.Create(Unsafe.Add(ref amountBase, 1)));
opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne);
- destinationBase = PorterDuffFunctions.DarkenSrc(backgroundBase, sourceBase, opacity);
+ destinationBase = PorterDuffFunctions.AddSrc(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
- sourceBase = ref Unsafe.Add(ref sourceBase, 1);
amountBase = ref Unsafe.Add(ref amountBase, 2);
}
@@ -630,33 +573,33 @@ internal static class DefaultPixelBlenders
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
- destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
+ destination[i] = PorterDuffFunctions.AddSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
- destination[i] = PorterDuffFunctions.DarkenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
+ destination[i] = PorterDuffFunctions.AddSrc(background[i], source, Numerics.Clamp(amount[i], 0, 1F));
}
}
}
}
///
- /// A pixel blender that implements the "LightenSrc" composition equation.
+ /// A pixel blender that implements the "SubtractSrc" composition equation.
///
- public class LightenSrc : PixelBlender
+ public class SubtractSrc : PixelBlender
{
///
/// Gets the static instance of this blender.
///
- public static LightenSrc Instance { get; } = new LightenSrc();
+ public static SubtractSrc Instance { get; } = new SubtractSrc();
///
public override TPixel Blend(TPixel background, TPixel source, float amount)
{
- return TPixel.FromScaledVector4(PorterDuffFunctions.LightenSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1)));
+ return TPixel.FromScaledVector4(PorterDuffFunctions.SubtractSrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1)));
}
///
@@ -676,7 +619,7 @@ internal static class DefaultPixelBlenders
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
- destinationBase = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity);
+ destinationBase = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
@@ -686,21 +629,23 @@ internal static class DefaultPixelBlenders
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
- destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], amount);
+ destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount);
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
- destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], amount);
+ destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], amount);
}
}
}
///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
+ protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, float amount)
{
+ amount = Numerics.Clamp(amount, 0, 1);
+
if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256
@@ -708,65 +653,35 @@ internal static class DefaultPixelBlenders
ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background));
- ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
- ref float amountBase = ref MemoryMarshal.GetReference(amount);
-
- Vector256 vOne = Vector256.Create(1F);
+ Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
+ Vector256 opacity = Vector256.Create(amount);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
- // We need to create a Vector256 containing the current and next amount values
- // taking up each half of the Vector256 and then clamp them.
- Vector256 opacity = Vector256.Create(
- Vector128.Create(amountBase),
- Vector128.Create(Unsafe.Add(ref amountBase, 1)));
- opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne);
-
- destinationBase = PorterDuffFunctions.LightenSrc(backgroundBase, sourceBase, opacity);
+ destinationBase = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
- sourceBase = ref Unsafe.Add(ref sourceBase, 1);
- amountBase = ref Unsafe.Add(ref amountBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
- destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
+ destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source, amount);
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
- destination[i] = PorterDuffFunctions.LightenSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
+ destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source, amount);
}
}
}
- }
-
- ///
- /// A pixel blender that implements the "OverlaySrc" composition equation.
- ///
- public class OverlaySrc : PixelBlender
- {
- ///
- /// Gets the static instance of this blender.
- ///
- public static OverlaySrc Instance { get; } = new OverlaySrc();
-
- ///
- public override TPixel Blend(TPixel background, TPixel source, float amount)
- {
- return TPixel.FromScaledVector4(PorterDuffFunctions.OverlaySrc(background.ToScaledVector4(), source.ToScaledVector4(), Numerics.Clamp(amount, 0, 1)));
- }
///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, float amount)
+ protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
{
- amount = Numerics.Clamp(amount, 0, 1);
-
if (Avx2.IsSupported && destination.Length >= 2)
{
// Divide by 2 as 4 elements per Vector4 and 8 per Vector256
@@ -775,34 +690,44 @@ internal static class DefaultPixelBlenders
ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background));
ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
- Vector256 opacity = Vector256.Create(amount);
+ ref float amountBase = ref MemoryMarshal.GetReference(amount);
+
+ Vector256 vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
{
- destinationBase = PorterDuffFunctions.OverlaySrc(backgroundBase, sourceBase, opacity);
+ // We need to create a Vector256 containing the current and next amount values
+ // taking up each half of the Vector256 and then clamp them.
+ Vector256 opacity = Vector256.Create(
+ Vector128.Create(amountBase),
+ Vector128.Create(Unsafe.Add(ref amountBase, 1)));
+ opacity = Avx.Min(Avx.Max(Vector256.Zero, opacity), vOne);
+
+ destinationBase = PorterDuffFunctions.SubtractSrc(backgroundBase, sourceBase, opacity);
destinationBase = ref Unsafe.Add(ref destinationBase, 1);
backgroundBase = ref Unsafe.Add(ref backgroundBase, 1);
sourceBase = ref Unsafe.Add(ref sourceBase, 1);
+ amountBase = ref Unsafe.Add(ref amountBase, 2);
}
if (Numerics.Modulo2(destination.Length) != 0)
{
// Vector4 fits neatly in pairs. Any overlap has to be equal to 1.
int i = destination.Length - 1;
- destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount);
+ destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
}
}
else
{
for (int i = 0; i < destination.Length; i++)
{
- destination[i] = PorterDuffFunctions.OverlaySrc(background[i], source[i], amount);
+ destination[i] = PorterDuffFunctions.SubtractSrc(background[i], source[i], Numerics.Clamp(amount[i], 0, 1F));
}
}
}
///
- protected override void BlendFunction(Span destination, ReadOnlySpan background, ReadOnlySpan source, ReadOnlySpan amount)
+ protected override void BlendFunction(Span destination, ReadOnlySpan background, Vector4 source, ReadOnlySpan amount)
{
if (Avx2.IsSupported && destination.Length >= 2)
{
@@ -811,9 +736,9 @@ internal static class DefaultPixelBlenders
ref Vector256 destinationLast = ref Unsafe.Add(ref destinationBase, (uint)destination.Length / 2u);
ref Vector256 backgroundBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(background));
- ref Vector256 sourceBase = ref Unsafe.As>(ref MemoryMarshal.GetReference(source));
ref float amountBase = ref MemoryMarshal.GetReference(amount);
+ Vector256 sourceBase = Vector256.Create(source.X, source.Y, source.Z, source.W, source.X, source.Y, source.Z, source.W);
Vector256 vOne = Vector256.Create(1F);
while (Unsafe.IsAddressLessThan(ref destinationBase, ref destinationLast))
@@ -825,10 +750,9 @@ internal static class DefaultPixelBlenders
Vector128.Create(Unsafe.Add(ref amountBase, 1)));
opacity = Avx.Min(Avx.Max(Vector256