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