diff --git a/src/ImageSharp/Processing/Extensions/Effects/PixelShaderExtensions.cs b/src/ImageSharp/Processing/Extensions/Effects/PixelShaderExtensions.cs
new file mode 100644
index 0000000000..b866e7fb14
--- /dev/null
+++ b/src/ImageSharp/Processing/Extensions/Effects/PixelShaderExtensions.cs
@@ -0,0 +1,103 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing.Processors.Effects;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing
+{
+ ///
+ /// Defines extension methods that allow the application of user defined pixel shaders to an .
+ ///
+ public static class PixelShaderExtensions
+ {
+ ///
+ /// Applies a user defined pixel shader to the image.
+ ///
+ /// The image this method extends.
+ /// The user defined pixel shader to use to modify images.
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext ApplyPixelShaderProcessor(this IImageProcessingContext source, PixelShader pixelShader)
+ => source.ApplyProcessor(new PixelShaderProcessor(pixelShader));
+
+ ///
+ /// Applies a user defined pixel shader to the image.
+ ///
+ /// The image this method extends.
+ /// The user defined pixel shader to use to modify images.
+ /// The to apply during the pixel conversions.
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext ApplyPixelShaderProcessor(this IImageProcessingContext source, PixelShader pixelShader, PixelConversionModifiers modifiers)
+ => source.ApplyProcessor(new PixelShaderProcessor(pixelShader, modifiers));
+
+ ///
+ /// Applies a user defined pixel shader to the image.
+ ///
+ /// The image this method extends.
+ /// The user defined pixel shader to use to modify images.
+ ///
+ /// The structure that specifies the portion of the image object to alter.
+ ///
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext ApplyPixelShaderProcessor(this IImageProcessingContext source, PixelShader pixelShader, Rectangle rectangle)
+ => source.ApplyProcessor(new PixelShaderProcessor(pixelShader), rectangle);
+
+ ///
+ /// Applies a user defined pixel shader to the image.
+ ///
+ /// The image this method extends.
+ /// The user defined pixel shader to use to modify images.
+ ///
+ /// The structure that specifies the portion of the image object to alter.
+ ///
+ /// The to apply during the pixel conversions.
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext ApplyPixelShaderProcessor(this IImageProcessingContext source, PixelShader pixelShader, Rectangle rectangle, PixelConversionModifiers modifiers)
+ => source.ApplyProcessor(new PixelShaderProcessor(pixelShader, modifiers), rectangle);
+
+ ///
+ /// Applies a user defined pixel shader to the image.
+ ///
+ /// The image this method extends.
+ /// The user defined pixel shader to use to modify images.
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext ApplyPixelShaderProcessor(this IImageProcessingContext source, PositionAwarePixelShader pixelShader)
+ => source.ApplyProcessor(new PositionAwarePixelShaderProcessor(pixelShader));
+
+ ///
+ /// Applies a user defined pixel shader to the image.
+ ///
+ /// The image this method extends.
+ /// The user defined pixel shader to use to modify images.
+ /// The to apply during the pixel conversions.
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext ApplyPixelShaderProcessor(this IImageProcessingContext source, PositionAwarePixelShader pixelShader, PixelConversionModifiers modifiers)
+ => source.ApplyProcessor(new PositionAwarePixelShaderProcessor(pixelShader, modifiers));
+
+ ///
+ /// Applies a user defined pixel shader to the image.
+ ///
+ /// The image this method extends.
+ /// The user defined pixel shader to use to modify images.
+ ///
+ /// The structure that specifies the portion of the image object to alter.
+ ///
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext ApplyPixelShaderProcessor(this IImageProcessingContext source, PositionAwarePixelShader pixelShader, Rectangle rectangle)
+ => source.ApplyProcessor(new PositionAwarePixelShaderProcessor(pixelShader), rectangle);
+
+ ///
+ /// Applies a user defined pixel shader to the image.
+ ///
+ /// The image this method extends.
+ /// The user defined pixel shader to use to modify images.
+ ///
+ /// The structure that specifies the portion of the image object to alter.
+ ///
+ /// The to apply during the pixel conversions.
+ /// The to allow chaining of operations.
+ public static IImageProcessingContext ApplyPixelShaderProcessor(this IImageProcessingContext source, PositionAwarePixelShader pixelShader, Rectangle rectangle, PixelConversionModifiers modifiers)
+ => source.ApplyProcessor(new PositionAwarePixelShaderProcessor(pixelShader, modifiers), rectangle);
+ }
+}
diff --git a/src/ImageSharp/Processing/PixelShader.cs b/src/ImageSharp/Processing/PixelShader.cs
new file mode 100644
index 0000000000..0245931fb5
--- /dev/null
+++ b/src/ImageSharp/Processing/PixelShader.cs
@@ -0,0 +1,15 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Numerics;
+
+namespace SixLabors.ImageSharp.Processing
+{
+ ///
+ /// A representing a user defined pixel shader.
+ ///
+ /// The target row of pixels to process.
+ /// The , , , and fields map the RGBA channels respectively.
+ public delegate void PixelShader(Span span);
+}
diff --git a/src/ImageSharp/Processing/PositionAwarePixelShader.cs b/src/ImageSharp/Processing/PositionAwarePixelShader.cs
new file mode 100644
index 0000000000..1ae3ba295a
--- /dev/null
+++ b/src/ImageSharp/Processing/PositionAwarePixelShader.cs
@@ -0,0 +1,17 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Numerics;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing
+{
+ ///
+ /// A representing a user defined pixel shader.
+ ///
+ /// The target row of pixels to process.
+ /// The initial horizontal and vertical offset for the input pixels to process.
+ /// The , , , and fields map the RGBA channels respectively.
+ public delegate void PositionAwarePixelShader(Span span, Point offset);
+}
diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelShaderProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/PixelShaderProcessor.cs
new file mode 100644
index 0000000000..2a271bef3c
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Effects/PixelShaderProcessor.cs
@@ -0,0 +1,61 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Effects
+{
+ ///
+ /// Applies a user defined pixel shader effect through a given delegate.
+ ///
+ public sealed class PixelShaderProcessor : IImageProcessor
+ {
+ ///
+ /// The default to apply during the pixel conversions.
+ ///
+ public const PixelConversionModifiers DefaultModifiers = PixelConversionModifiers.None;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The user defined pixel shader to use to modify images.
+ ///
+ public PixelShaderProcessor(PixelShader pixelShader)
+ {
+ this.PixelShader = pixelShader;
+ this.Modifiers = DefaultModifiers;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The user defined pixel shader to use to modify images.
+ ///
+ /// The to apply during the pixel conversions.
+ public PixelShaderProcessor(PixelShader pixelShader, PixelConversionModifiers modifiers)
+ {
+ this.PixelShader = pixelShader;
+ this.Modifiers = modifiers;
+ }
+
+ ///
+ /// Gets the user defined pixel shader.
+ ///
+ public PixelShader PixelShader { get; }
+
+ ///
+ /// Gets the to apply during the pixel conversions.
+ ///
+ public PixelConversionModifiers Modifiers { get; }
+
+ ///
+ public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle)
+ where TPixel : struct, IPixel
+ {
+ return new PixelShaderProcessor(this, source, sourceRectangle);
+ }
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelShaderProcessorBase.cs b/src/ImageSharp/Processing/Processors/Effects/PixelShaderProcessorBase.cs
new file mode 100644
index 0000000000..d46a4cf320
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Effects/PixelShaderProcessorBase.cs
@@ -0,0 +1,73 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Numerics;
+
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.Advanced.ParallelUtils;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Effects
+{
+ ///
+ /// Applies a user defined pixel shader effect through a given delegate.
+ ///
+ /// The pixel format.
+ internal abstract class PixelShaderProcessorBase : ImageProcessor
+ where TPixel : struct, IPixel
+ {
+ ///
+ /// The to apply during the pixel conversions.
+ ///
+ private readonly PixelConversionModifiers modifiers;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The to apply during the pixel conversions.
+ /// The source for the current processor instance.
+ /// The source area to process for the current processor instance.
+ protected PixelShaderProcessorBase(PixelConversionModifiers modifiers, Image source, Rectangle sourceRectangle)
+ : base(source, sourceRectangle)
+ {
+ this.modifiers = modifiers;
+ }
+
+ ///
+ protected override void OnFrameApply(ImageFrame source)
+ {
+ var interest = Rectangle.Intersect(this.SourceRectangle, source.Bounds());
+ int startX = interest.X;
+ Configuration configuration = this.Configuration;
+ PixelConversionModifiers modifiers = this.modifiers;
+
+ ParallelHelper.IterateRowsWithTempBuffer(
+ interest,
+ this.Configuration,
+ (rows, vectorBuffer) =>
+ {
+ for (int y = rows.Min; y < rows.Max; y++)
+ {
+ Span vectorSpan = vectorBuffer.Span;
+ int length = vectorSpan.Length;
+ Span rowSpan = source.GetPixelRowSpan(y).Slice(startX, length);
+ PixelOperations.Instance.ToVector4(configuration, rowSpan, vectorSpan, modifiers);
+
+ // Run the user defined pixel shader on the current row of pixels
+ this.ApplyPixelShader(vectorSpan, new Point(startX, y));
+
+ PixelOperations.Instance.FromVector4Destructive(configuration, vectorSpan, rowSpan, modifiers);
+ }
+ });
+ }
+
+ ///
+ /// Applies the current pixel shader effect on a target row of preprocessed pixels.
+ ///
+ /// The target row of pixels to process.
+ /// The initial horizontal and vertical offset for the input pixels to process.
+ protected abstract void ApplyPixelShader(Span span, Point offset);
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Effects/PixelShaderProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/PixelShaderProcessor{TPixel}.cs
new file mode 100644
index 0000000000..ce7b858a3e
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Effects/PixelShaderProcessor{TPixel}.cs
@@ -0,0 +1,39 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Numerics;
+
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Effects
+{
+ ///
+ /// Applies a user defined pixel shader effect through a given delegate.
+ ///
+ /// The pixel format.
+ internal sealed class PixelShaderProcessor : PixelShaderProcessorBase
+ where TPixel : struct, IPixel
+ {
+ ///
+ /// The user defined pixel shader.
+ ///
+ private readonly PixelShader pixelShader;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The defining the processor parameters.
+ /// The source for the current processor instance.
+ /// The source area to process for the current processor instance.
+ public PixelShaderProcessor(PixelShaderProcessor definition, Image source, Rectangle sourceRectangle)
+ : base(definition.Modifiers, source, sourceRectangle)
+ {
+ this.pixelShader = definition.PixelShader;
+ }
+
+ ///
+ protected override void ApplyPixelShader(Span span, Point offset) => this.pixelShader(span);
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelShaderProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelShaderProcessor.cs
new file mode 100644
index 0000000000..908f7472b7
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelShaderProcessor.cs
@@ -0,0 +1,61 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Effects
+{
+ ///
+ /// Applies a user defined pixel shader effect through a given delegate.
+ ///
+ public sealed class PositionAwarePixelShaderProcessor : IImageProcessor
+ {
+ ///
+ /// The default to apply during the pixel conversions.
+ ///
+ public const PixelConversionModifiers DefaultModifiers = PixelConversionModifiers.None;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The user defined pixel shader to use to modify images.
+ ///
+ public PositionAwarePixelShaderProcessor(PositionAwarePixelShader pixelShader)
+ {
+ this.PixelShader = pixelShader;
+ this.Modifiers = DefaultModifiers;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ ///
+ /// The user defined pixel shader to use to modify images.
+ ///
+ /// The to apply during the pixel conversions.
+ public PositionAwarePixelShaderProcessor(PositionAwarePixelShader pixelShader, PixelConversionModifiers modifiers)
+ {
+ this.PixelShader = pixelShader;
+ this.Modifiers = modifiers;
+ }
+
+ ///
+ /// Gets the user defined pixel shader.
+ ///
+ public PositionAwarePixelShader PixelShader { get; }
+
+ ///
+ /// Gets the to apply during the pixel conversions.
+ ///
+ public PixelConversionModifiers Modifiers { get; }
+
+ ///
+ public IImageProcessor CreatePixelSpecificProcessor(Image source, Rectangle sourceRectangle)
+ where TPixel : struct, IPixel
+ {
+ return new PositionAwarePixelShaderProcessor(this, source, sourceRectangle);
+ }
+ }
+}
diff --git a/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelShaderProcessor{TPixel}.cs b/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelShaderProcessor{TPixel}.cs
new file mode 100644
index 0000000000..549dedac15
--- /dev/null
+++ b/src/ImageSharp/Processing/Processors/Effects/PositionAwarePixelShaderProcessor{TPixel}.cs
@@ -0,0 +1,39 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Numerics;
+
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Processors.Effects
+{
+ ///
+ /// Applies a user defined pixel shader effect through a given delegate.
+ ///
+ /// The pixel format.
+ internal sealed class PositionAwarePixelShaderProcessor : PixelShaderProcessorBase
+ where TPixel : struct, IPixel
+ {
+ ///
+ /// The user defined pixel shader.
+ ///
+ private readonly PositionAwarePixelShader pixelShader;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The defining the processor parameters.
+ /// The source for the current processor instance.
+ /// The source area to process for the current processor instance.
+ public PositionAwarePixelShaderProcessor(PositionAwarePixelShaderProcessor definition, Image source, Rectangle sourceRectangle)
+ : base(definition.Modifiers, source, sourceRectangle)
+ {
+ this.pixelShader = definition.PixelShader;
+ }
+
+ ///
+ protected override void ApplyPixelShader(Span span, Point offset) => this.pixelShader(span, offset);
+ }
+}
diff --git a/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelShaderTest.cs b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelShaderTest.cs
new file mode 100644
index 0000000000..c18f043852
--- /dev/null
+++ b/tests/ImageSharp.Tests/Processing/Processors/Effects/PixelShaderTest.cs
@@ -0,0 +1,113 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Numerics;
+
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Processing.Processors.Effects
+{
+ [GroupOutput("Effects")]
+ public class PixelShaderTest
+ {
+ [Theory]
+ [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)]
+ public void FullImage(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ provider.RunValidatingProcessorTest(
+ x => x.ApplyPixelShaderProcessor(
+ span =>
+ {
+ for (int i = 0; i < span.Length; i++)
+ {
+ Vector4 v4 = span[i];
+ float avg = (v4.X + v4.Y + v4.Z) / 3f;
+ span[i] = new Vector4(avg);
+ }
+ }),
+ appendPixelTypeToFileName: false);
+ }
+
+ [Theory]
+ [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)]
+ public void InBox(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ provider.RunRectangleConstrainedValidatingProcessorTest(
+ (x, rect) => x.ApplyPixelShaderProcessor(
+ span =>
+ {
+ for (int i = 0; i < span.Length; i++)
+ {
+ Vector4 v4 = span[i];
+ float avg = (v4.X + v4.Y + v4.Z) / 3f;
+ span[i] = new Vector4(avg);
+ }
+ }, rect));
+ }
+
+ [Theory]
+ [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)]
+ public void PositionAwareFullImage(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ provider.RunValidatingProcessorTest(
+ c => c.ApplyPixelShaderProcessor(
+ (span, offset) =>
+ {
+ int y = offset.Y;
+ int x = offset.X;
+ for (int i = 0; i < span.Length; i++)
+ {
+ float
+ sine = MathF.Sin(y),
+ cosine = MathF.Cos(x + i),
+ sum = sine + cosine,
+ abs = MathF.Abs(sum),
+ a = 0.5f + (abs / 2); // Max value for sin(y) + cos(x) is 2
+
+ Vector4 v4 = span[i];
+ float avg = (v4.X + v4.Y + v4.Z) / 3f;
+ var gray = new Vector4(avg, avg, avg, a);
+
+ span[i] = Vector4.Clamp(gray, Vector4.Zero, Vector4.One);
+ }
+ }),
+ appendPixelTypeToFileName: false);
+ }
+
+ [Theory]
+ [WithFile(TestImages.Png.CalliphoraPartial, PixelTypes.Rgba32)]
+ public void PositionAwareInBox(TestImageProvider provider)
+ where TPixel : struct, IPixel
+ {
+ provider.RunRectangleConstrainedValidatingProcessorTest(
+ (c, rect) => c.ApplyPixelShaderProcessor(
+ (span, offset) =>
+ {
+ int y = offset.Y;
+ int x = offset.X;
+ for (int i = 0; i < span.Length; i++)
+ {
+ float
+ sine = MathF.Sin(y),
+ cosine = MathF.Cos(x + i),
+ sum = sine + cosine,
+ abs = MathF.Abs(sum),
+ a = 0.5f + (abs / 2);
+
+ Vector4 v4 = span[i];
+ float avg = (v4.X + v4.Y + v4.Z) / 3f;
+ var gray = new Vector4(avg, avg, avg, a);
+
+ span[i] = Vector4.Clamp(gray, Vector4.Zero, Vector4.One);
+ }
+ }, rect));
+ }
+ }
+}
diff --git a/tests/Images/External b/tests/Images/External
index d7c099cebd..d8ea82085a 160000
--- a/tests/Images/External
+++ b/tests/Images/External
@@ -1 +1 @@
-Subproject commit d7c099cebd58f1d3ff997383351d52d28a29df3d
+Subproject commit d8ea82085ac39a6aa6ca8e0806a9518d3a7d3337