diff --git a/src/ImageSharp.Drawing/Brushes/IBrush.cs b/src/ImageSharp.Drawing/Brushes/IBrush.cs index e16f22028..9534c7a88 100644 --- a/src/ImageSharp.Drawing/Brushes/IBrush.cs +++ b/src/ImageSharp.Drawing/Brushes/IBrush.cs @@ -24,6 +24,7 @@ namespace ImageSharp.Drawing /// /// The pixel source. /// The region the brush will be applied to. + /// The graphic options /// /// The brush applicator for this brush /// @@ -31,6 +32,6 @@ namespace ImageSharp.Drawing /// The when being applied to things like shapes would usually be the /// bounding box of the shape not necessarily the bounds of the whole image /// - BrushApplicator CreateApplicator(PixelAccessor pixelSource, RectangleF region); + BrushApplicator CreateApplicator(PixelAccessor pixelSource, RectangleF region, GraphicsOptions options); } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs index 3e2da040f..5038ea01b 100644 --- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs @@ -31,9 +31,9 @@ namespace ImageSharp.Drawing.Brushes } /// - public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region) + public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) { - return new ImageBrushApplicator(sourcePixels, this.image, region); + return new ImageBrushApplicator(sourcePixels, this.image, region, options); } /// @@ -57,9 +57,14 @@ namespace ImageSharp.Drawing.Brushes private readonly int xLength; /// - /// The offset. + /// The Y offset. /// - private readonly Vector2 offset; + private readonly int offsetY; + + /// + /// The X offset. + /// + private readonly int offsetX; /// /// Initializes a new instance of the class. @@ -70,16 +75,18 @@ namespace ImageSharp.Drawing.Brushes /// /// The region. /// + /// The options /// /// The sourcePixels. /// - public ImageBrushApplicator(PixelAccessor sourcePixels, IImageBase image, RectangleF region) - : base(sourcePixels) + public ImageBrushApplicator(PixelAccessor sourcePixels, IImageBase image, RectangleF region, GraphicsOptions options) + : base(sourcePixels, options) { this.source = image.Lock(); this.xLength = image.Width; this.yLength = image.Height; - this.offset = new Vector2(MathF.Max(MathF.Floor(region.Top), 0), MathF.Max(MathF.Floor(region.Left), 0)); + this.offsetY = (int)MathF.Max(MathF.Floor(region.Top), 0); + this.offsetX = (int)MathF.Max(MathF.Floor(region.Left), 0); } /// @@ -94,13 +101,8 @@ namespace ImageSharp.Drawing.Brushes { get { - Vector2 point = new Vector2(x, y); - - // Offset the requested pixel by the value in the rectangle (the shapes position) - point = point - this.offset; - int srcX = (int)point.X % this.xLength; - int srcY = (int)point.Y % this.yLength; - + int srcX = (x - this.offsetX) % this.xLength; + int srcY = (y - this.offsetY) % this.yLength; return this.source[srcX, srcY]; } } @@ -112,33 +114,27 @@ namespace ImageSharp.Drawing.Brushes } /// - internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) + internal override void Apply(BufferSpan scanline, int x, int y) { - Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); - - using (Buffer buffer = new Buffer(scanlineBuffer)) + // create a span for colors + using (Buffer amountBuffer = new Buffer(scanline.Length)) + using (Buffer overlay = new Buffer(scanline.Length)) { - BufferSpan slice = buffer.Slice(offset); + int sourceY = (y - this.offsetY) % this.yLength; + int offsetX = x - this.offsetX; + BufferSpan sourceRow = this.source.GetRowSpan(sourceY); - for (int xPos = 0; xPos < scanlineWidth; xPos++) + for (int i = 0; i < scanline.Length; i++) { - int targetX = xPos + x; - int targetY = y; - - float opacity = slice[xPos]; - if (opacity > Constants.Epsilon) - { - Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4(); - - Vector4 sourceVector = this[targetX, targetY].ToVector4(); + amountBuffer[i] = scanline[i] * this.Options.BlendPercentage; - Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); - - TPixel packed = default(TPixel); - packed.PackFromVector4(finalColor); - this.Target[targetX, targetY] = packed; - } + int sourceX = (i + offsetX) % this.xLength; + TPixel pixel = sourceRow[sourceX]; + overlay[i] = pixel; } + + BufferSpan destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length); + this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer); } } } diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs index ad37f4d28..dc8a4bc90 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs @@ -90,9 +90,9 @@ namespace ImageSharp.Drawing.Brushes } /// - public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region) + public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) { - return new PatternBrushApplicator(sourcePixels, this.pattern, this.patternVector); + return new PatternBrushApplicator(sourcePixels, this.pattern, this.patternVector, options); } /// @@ -112,8 +112,9 @@ namespace ImageSharp.Drawing.Brushes /// The sourcePixels. /// The pattern. /// The patternVector. - public PatternBrushApplicator(PixelAccessor sourcePixels, Fast2DArray pattern, Fast2DArray patternVector) - : base(sourcePixels) + /// The options + public PatternBrushApplicator(PixelAccessor sourcePixels, Fast2DArray pattern, Fast2DArray patternVector, GraphicsOptions options) + : base(sourcePixels, options) { this.pattern = pattern; this.patternVector = patternVector; @@ -146,34 +147,22 @@ namespace ImageSharp.Drawing.Brushes } /// - internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) + internal override void Apply(BufferSpan scanline, int x, int y) { - Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); - - using (Buffer buffer = new Buffer(scanlineBuffer)) + int patternY = y % this.pattern.Height; + using (Buffer amountBuffer = new Buffer(scanline.Length)) + using (Buffer overlay = new Buffer(scanline.Length)) { - BufferSpan slice = buffer.Slice(offset); - - for (int xPos = 0; xPos < scanlineWidth; xPos++) + for (int i = 0; i < scanline.Length; i++) { - int targetX = xPos + x; - int targetY = y; - - float opacity = slice[xPos]; - if (opacity > Constants.Epsilon) - { - Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4(); + amountBuffer[i] = (scanline[i] * this.Options.BlendPercentage).Clamp(0, 1); - // 2d array index at row/column - Vector4 sourceVector = this.patternVector[targetY % this.patternVector.Height, targetX % this.patternVector.Width]; - - Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); - - TPixel packed = default(TPixel); - packed.PackFromVector4(finalColor); - this.Target[targetX, targetY] = packed; - } + int patternX = (x + i) % this.pattern.Width; + overlay[i] = this.pattern[patternY, patternX]; } + + BufferSpan destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length); + this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer); } } } diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index 5dd6dad76..d7c70220c 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -21,16 +21,31 @@ namespace ImageSharp.Drawing.Processors /// Initializes a new instance of the class. /// /// The target. - internal BrushApplicator(PixelAccessor target) + /// The options. + internal BrushApplicator(PixelAccessor target, GraphicsOptions options) { this.Target = target; + + this.Options = options; + + this.Blender = PixelOperations.Instance.GetPixelBlender(options.BlenderMode); } + /// + /// Gets the blendder + /// + internal PixelBlender Blender { get; } + /// /// Gets the destinaion /// protected PixelAccessor Target { get; } + /// + /// Gets the blend percentage + /// + protected GraphicsOptions Options { get; private set; } + /// /// Gets the color for a single pixel. /// @@ -45,39 +60,27 @@ namespace ImageSharp.Drawing.Processors /// /// Applies the opactiy weighting for each pixel in a scanline to the target based on the pattern contained in the brush. /// - /// The a collection of opacity values between 0 and 1 to be merged with the brushed color value before being applied to the target. - /// The number of pixels effected by this scanline. - /// The offset fromthe begining of the opacity data starts. + /// The a collection of opacity values between 0 and 1 to be merged with the brushed color value before being applied to the target. /// The x position in the target pixel space that the start of the scanline data corresponds to. /// The y position in the target pixel space that whole scanline corresponds to. /// scanlineBuffer will be > scanlineWidth but provide and offset in case we want to share a larger buffer across runs. - internal virtual void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) + internal virtual void Apply(BufferSpan scanline, int x, int y) { - DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); - - using (Buffer buffer = new Buffer(scanlineBuffer)) + using (Buffer amountBuffer = new Buffer(scanline.Length)) + using (Buffer overlay = new Buffer(scanline.Length)) { - BufferSpan slice = buffer.Slice(offset); - - for (int xPos = 0; xPos < scanlineWidth; xPos++) + for (int i = 0; i < scanline.Length; i++) { - int targetX = xPos + x; - int targetY = y; - - float opacity = slice[xPos]; - if (opacity > Constants.Epsilon) + if (this.Options.BlendPercentage < 1) { - Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4(); - - Vector4 sourceVector = this[targetX, targetY].ToVector4(); - - Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); - - TPixel packed = default(TPixel); - packed.PackFromVector4(finalColor); - this.Target[targetX, targetY] = packed; + amountBuffer[i] = scanline[i] * this.Options.BlendPercentage; } + + overlay[i] = this[x + i, y]; } + + BufferSpan destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length); + this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer); } } } diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs index 7c192b2d3..a96202b7b 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs @@ -54,9 +54,9 @@ namespace ImageSharp.Drawing.Brushes public TPixel TargeTPixel { get; } /// - public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region) + public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) { - return new RecolorBrushApplicator(sourcePixels, this.SourceColor, this.TargeTPixel, this.Threshold); + return new RecolorBrushApplicator(sourcePixels, this.SourceColor, this.TargeTPixel, this.Threshold, options); } /// @@ -72,25 +72,29 @@ namespace ImageSharp.Drawing.Brushes /// /// The target color. /// - private readonly Vector4 targeTPixel; + private readonly Vector4 targetColor; /// /// The threshold. /// private readonly float threshold; + private readonly TPixel targetColorPixel; + /// /// Initializes a new instance of the class. /// /// The source pixels. /// Color of the source. - /// Color of the target. + /// Color of the target. /// The threshold . - public RecolorBrushApplicator(PixelAccessor sourcePixels, TPixel sourceColor, TPixel targeTPixel, float threshold) - : base(sourcePixels) + /// The options + public RecolorBrushApplicator(PixelAccessor sourcePixels, TPixel sourceColor, TPixel targetColor, float threshold, GraphicsOptions options) + : base(sourcePixels, options) { this.sourceColor = sourceColor.ToVector4(); - this.targeTPixel = targeTPixel.ToVector4(); + this.targetColor = targetColor.ToVector4(); + this.targetColorPixel = targetColor; // Lets hack a min max extreams for a color space by letteing the IPackedPixel clamp our values to something in the correct spaces :) TPixel maxColor = default(TPixel); @@ -119,11 +123,10 @@ namespace ImageSharp.Drawing.Brushes if (distance <= this.threshold) { float lerpAmount = (this.threshold - distance) / this.threshold; - Vector4 blended = Vector4BlendTransforms.PremultipliedLerp( - background, - this.targeTPixel, + return this.Blender.Blend( + result, + this.targetColorPixel, lerpAmount); - result.PackFromVector4(blended); } return result; @@ -136,42 +139,24 @@ namespace ImageSharp.Drawing.Brushes } /// - internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) + internal override void Apply(BufferSpan scanline, int x, int y) { - Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); - - using (Buffer buffer = new Buffer(scanlineBuffer)) + using (Buffer amountBuffer = new Buffer(scanline.Length)) + using (Buffer overlay = new Buffer(scanline.Length)) { - BufferSpan slice = buffer.Slice(offset); - - for (int xPos = 0; xPos < scanlineWidth; xPos++) + for (int i = 0; i < scanline.Length; i++) { - int targetX = xPos + x; - int targetY = y; - - float opacity = slice[xPos]; - if (opacity > Constants.Epsilon) - { - Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4(); - - Vector4 sourceVector = backgroundVector; - float distance = Vector4.DistanceSquared(sourceVector, this.sourceColor); - if (distance <= this.threshold) - { - float lerpAmount = (this.threshold - distance) / this.threshold; - sourceVector = Vector4BlendTransforms.PremultipliedLerp( - sourceVector, - this.targeTPixel, - lerpAmount); - - Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); - - TPixel packed = default(TPixel); - packed.PackFromVector4(finalColor); - this.Target[targetX, targetY] = packed; - } - } + amountBuffer[i] = scanline[i] * this.Options.BlendPercentage; + + int offsetX = x + i; + + // no doubt this one can be optermised further but I can't imagine its + // actually being used and can probably be removed/interalised for now + overlay[i] = this[offsetX, y]; } + + BufferSpan destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length); + this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer); } } } diff --git a/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs index 634a2b70d..71b802136 100644 --- a/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs @@ -39,9 +39,9 @@ namespace ImageSharp.Drawing.Brushes public TPixel Color => this.color; /// - public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region) + public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) { - return new SolidBrushApplicator(sourcePixels, this.color); + return new SolidBrushApplicator(sourcePixels, this.color, options); } /// @@ -49,24 +49,27 @@ namespace ImageSharp.Drawing.Brushes /// private class SolidBrushApplicator : BrushApplicator { - /// - /// The solid color. - /// - private readonly TPixel color; - private readonly Vector4 colorVector; - /// /// Initializes a new instance of the class. /// /// The color. + /// The options /// The sourcePixels. - public SolidBrushApplicator(PixelAccessor sourcePixels, TPixel color) - : base(sourcePixels) + public SolidBrushApplicator(PixelAccessor sourcePixels, TPixel color, GraphicsOptions options) + : base(sourcePixels, options) { - this.color = color; - this.colorVector = color.ToVector4(); + this.Colors = new Buffer(sourcePixels.Width); + for (int i = 0; i < this.Colors.Length; i++) + { + this.Colors[i] = color; + } } + /// + /// Gets the colors. + /// + protected Buffer Colors { get; } + /// /// Gets the color for a single pixel. /// @@ -75,41 +78,27 @@ namespace ImageSharp.Drawing.Brushes /// /// The color /// - internal override TPixel this[int x, int y] => this.color; + internal override TPixel this[int x, int y] => this.Colors[x]; /// public override void Dispose() { - // noop + this.Colors.Dispose(); } /// - internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) + internal override void Apply(BufferSpan scanline, int x, int y) { - Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); + BufferSpan destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length); - using (Buffer buffer = new Buffer(scanlineBuffer)) + using (Buffer amountBuffer = new Buffer(scanline.Length)) { - BufferSpan slice = buffer.Slice(offset); - - for (int xPos = 0; xPos < scanlineWidth; xPos++) + for (int i = 0; i < scanline.Length; i++) { - int targetX = xPos + x; - int targetY = y; - - float opacity = slice[xPos]; - if (opacity > Constants.Epsilon) - { - Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4(); - Vector4 sourceVector = this.colorVector; - - Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); - - TPixel packed = default(TPixel); - packed.PackFromVector4(finalColor); - this.Target[targetX, targetY] = packed; - } + amountBuffer[i] = scanline[i] * this.Options.BlendPercentage; } + + this.Blender.Blend(destinationRow, destinationRow, this.Colors, amountBuffer); } } } diff --git a/src/ImageSharp.Drawing/DrawImage.cs b/src/ImageSharp.Drawing/DrawImage.cs index 6a4f49337..acc821292 100644 --- a/src/ImageSharp.Drawing/DrawImage.cs +++ b/src/ImageSharp.Drawing/DrawImage.cs @@ -1,57 +1,131 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using Drawing.Processors; - using ImageSharp.PixelFormats; - - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The pixel format. - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The opacity of the image image to blend. Must be between 0 and 100. - /// The . - public static Image Blend(this Image source, Image image, int percent = 50) - where TPixel : struct, IPixel - { - return DrawImage(source, image, percent, default(Size), default(Point)); - } - - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The pixel format. - /// The opacity of the image image to blend. Must be between 0 and 100. - /// The size to draw the blended image. - /// The location to draw the blended image. - /// The . - public static Image DrawImage(this Image source, Image image, int percent, Size size, Point location) - where TPixel : struct, IPixel - { - if (size == default(Size)) - { - size = new Size(image.Width, image.Height); - } - - if (location == default(Point)) - { - location = Point.Empty; - } - - source.ApplyProcessor(new DrawImageProcessor(image, size, location, percent), source.Bounds); - return source; - } - } +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using Drawing.Processors; + using ImageSharp.Drawing; + using ImageSharp.PixelFormats; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The pixel format. + /// The size to draw the blended image. + /// The location to draw the blended image. + /// The options. + /// The . + public static Image DrawImage(this Image source, Image image, Size size, Point location, GraphicsOptions options) + where TPixel : struct, IPixel + { + if (size == default(Size)) + { + size = new Size(image.Width, image.Height); + } + + if (location == default(Point)) + { + location = Point.Empty; + } + + source.ApplyProcessor(new DrawImageProcessor(image, size, location, options), source.Bounds); + return source; + } + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The pixel format. + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The opacity of the image image to blend. Must be between 0 and 1. + /// The . + public static Image Blend(this Image source, Image image, float percent) + where TPixel : struct, IPixel + { + GraphicsOptions options = GraphicsOptions.Default; + options.BlendPercentage = percent; + return DrawImage(source, image, default(Size), default(Point), options); + } + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The pixel format. + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The blending mode. + /// The opacity of the image image to blend. Must be between 0 and 1. + /// The . + public static Image Blend(this Image source, Image image, PixelBlenderMode blender, float percent) + where TPixel : struct, IPixel + { + GraphicsOptions options = GraphicsOptions.Default; + options.BlendPercentage = percent; + options.BlenderMode = blender; + return DrawImage(source, image, default(Size), default(Point), options); + } + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The pixel format. + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The options, including the blending type and belnding amount. + /// The . + public static Image Blend(this Image source, Image image, GraphicsOptions options) + where TPixel : struct, IPixel + { + return DrawImage(source, image, default(Size), default(Point), options); + } + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The pixel format. + /// The opacity of the image image to blend. Must be between 0 and 1. + /// The size to draw the blended image. + /// The location to draw the blended image. + /// The . + public static Image DrawImage(this Image source, Image image, float percent, Size size, Point location) + where TPixel : struct, IPixel + { + GraphicsOptions options = GraphicsOptions.Default; + options.BlendPercentage = percent; + return source.DrawImage(image, size, location, options); + } + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The pixel format. + /// The type of bending to apply. + /// The opacity of the image image to blend. Must be between 0 and 1. + /// The size to draw the blended image. + /// The location to draw the blended image. + /// The . + public static Image DrawImage(this Image source, Image image, PixelBlenderMode blender, float percent, Size size, Point location) + where TPixel : struct, IPixel + { + GraphicsOptions options = GraphicsOptions.Default; + options.BlenderMode = blender; + options.BlendPercentage = percent; + return source.DrawImage(image, size, location, options); + } + } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/FillRegion.cs b/src/ImageSharp.Drawing/FillRegion.cs index f29c37a67..b3ee2ed99 100644 --- a/src/ImageSharp.Drawing/FillRegion.cs +++ b/src/ImageSharp.Drawing/FillRegion.cs @@ -15,6 +15,20 @@ namespace ImageSharp /// public static partial class ImageExtensions { + /// + /// Flood fills the image with the specified brush. + /// + /// The type of the color. + /// The image this method extends. + /// The details how to fill the region of interest. + /// The graphics options. + /// The . + public static Image Fill(this Image source, IBrush brush, GraphicsOptions options) + where TPixel : struct, IPixel + { + return source.Apply(new FillProcessor(brush, options)); + } + /// /// Flood fills the image with the specified brush. /// @@ -25,7 +39,7 @@ namespace ImageSharp public static Image Fill(this Image source, IBrush brush) where TPixel : struct, IPixel { - return source.Apply(new FillProcessor(brush)); + return source.Fill(brush, GraphicsOptions.Default); } /// diff --git a/src/ImageSharp.Drawing/GraphicsOptions.cs b/src/ImageSharp.Drawing/GraphicsOptions.cs deleted file mode 100644 index a21617ead..000000000 --- a/src/ImageSharp.Drawing/GraphicsOptions.cs +++ /dev/null @@ -1,38 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing -{ - /// - /// Options for influencing the drawing functions. - /// - public struct GraphicsOptions - { - /// - /// Represents the default . - /// - public static readonly GraphicsOptions Default = new GraphicsOptions(true); - - /// - /// Whether antialiasing should be applied. - /// - public bool Antialias; - - /// - /// The number of subpixels to use while rendering with antialiasing enabled. - /// - public int AntialiasSubpixelDepth; - - /// - /// Initializes a new instance of the struct. - /// - /// If set to true [enable antialiasing]. - public GraphicsOptions(bool enableAntialiasing) - { - this.Antialias = enableAntialiasing; - this.AntialiasSubpixelDepth = 16; - } - } -} \ No newline at end of file diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj index 7c483712d..9fb0e1e8d 100644 --- a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj +++ b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj @@ -2,7 +2,7 @@ An extension to ImageSharp that allows the drawing of images, paths, and text. ImageSharp.Drawing - 1.0.0-alpha7 + 1.0.0-alpha8 James Jackson-South and contributors netstandard1.1 true diff --git a/src/ImageSharp.Drawing/Pens/IPen.cs b/src/ImageSharp.Drawing/Pens/IPen.cs index 31a609c8e..d488dbfb0 100644 --- a/src/ImageSharp.Drawing/Pens/IPen.cs +++ b/src/ImageSharp.Drawing/Pens/IPen.cs @@ -20,12 +20,13 @@ namespace ImageSharp.Drawing.Pens /// /// The pixel source. /// The region the pen will be applied to. + /// The currently active graphic options. /// /// Returns a the applicator for the pen. /// /// /// The when being applied to things like shapes would usually be the bounding box of the shape not necessarily the shape of the whole image. /// - PenApplicator CreateApplicator(PixelAccessor pixelSource, RectangleF region); + PenApplicator CreateApplicator(PixelAccessor pixelSource, RectangleF region, GraphicsOptions options); } } diff --git a/src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs b/src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs index f49d03cbc..1da50e0d6 100644 --- a/src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs +++ b/src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs @@ -103,6 +103,7 @@ namespace ImageSharp.Drawing.Pens /// /// The source pixels. /// The region the pen will be applied to. + /// The Graphics options /// /// Returns a the applicator for the pen. /// @@ -110,16 +111,16 @@ namespace ImageSharp.Drawing.Pens /// The when being applied to things like shapes would ussually be the /// bounding box of the shape not necorserrally the shape of the whole image /// - public PenApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region) + public PenApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) { if (this.pattern == null || this.pattern.Length < 2) { // if there is only one item in the pattern then 100% of it will // be solid so use the quicker applicator - return new SolidPenApplicator(sourcePixels, this.Brush, region, this.Width); + return new SolidPenApplicator(sourcePixels, this.Brush, region, this.Width, options); } - return new PatternPenApplicator(sourcePixels, this.Brush, region, this.Width, this.pattern); + return new PatternPenApplicator(sourcePixels, this.Brush, region, this.Width, this.pattern, options); } private class SolidPenApplicator : PenApplicator @@ -127,9 +128,9 @@ namespace ImageSharp.Drawing.Pens private readonly BrushApplicator brush; private readonly float halfWidth; - public SolidPenApplicator(PixelAccessor sourcePixels, IBrush brush, RectangleF region, float width) + public SolidPenApplicator(PixelAccessor sourcePixels, IBrush brush, RectangleF region, float width, GraphicsOptions options) { - this.brush = brush.CreateApplicator(sourcePixels, region); + this.brush = brush.CreateApplicator(sourcePixels, region, options); this.halfWidth = width / 2; this.RequiredRegion = RectangleF.Outset(region, width); } @@ -170,9 +171,9 @@ namespace ImageSharp.Drawing.Pens private readonly float[] pattern; private readonly float totalLength; - public PatternPenApplicator(PixelAccessor sourcePixels, IBrush brush, RectangleF region, float width, float[] pattern) + public PatternPenApplicator(PixelAccessor sourcePixels, IBrush brush, RectangleF region, float width, float[] pattern, GraphicsOptions options) { - this.brush = brush.CreateApplicator(sourcePixels, region); + this.brush = brush.CreateApplicator(sourcePixels, region, options); this.halfWidth = width / 2; this.totalLength = 0; diff --git a/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs index e2a9ef024..c49631de8 100644 --- a/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs @@ -18,19 +18,22 @@ namespace ImageSharp.Drawing.Processors internal class DrawImageProcessor : ImageProcessor where TPixel : struct, IPixel { + private readonly PixelBlender blender; + /// /// Initializes a new instance of the class. /// /// The image to blend with the currently processing image. /// The size to draw the blended image. /// The location to draw the blended image. - /// The opacity of the image to blend. Between 0 and 100. - public DrawImageProcessor(Image image, Size size, Point location, int alpha = 100) + /// The opacity of the image to blend. Between 0 and 100. + public DrawImageProcessor(Image image, Size size, Point location, GraphicsOptions options) { - Guard.MustBeBetweenOrEqualTo(alpha, 0, 100, nameof(alpha)); + Guard.MustBeBetweenOrEqualTo(options.BlendPercentage, 0, 1, nameof(options.BlendPercentage)); this.Image = image; this.Size = size; - this.Alpha = alpha; + this.Alpha = options.BlendPercentage; + this.blender = PixelOperations.Instance.GetPixelBlender(options.BlenderMode); this.Location = location; } @@ -42,7 +45,7 @@ namespace ImageSharp.Drawing.Processors /// /// Gets the alpha percentage value. /// - public int Alpha { get; } + public float Alpha { get; } /// /// Gets the size to draw the blended image. @@ -57,43 +60,52 @@ namespace ImageSharp.Drawing.Processors /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { - if (this.Image.Bounds.Size != this.Size) + Image disposableImage = null; + Image targetImage = this.Image; + + try { - // should Resize be moved to core? - this.Image = this.Image.Resize(this.Size.Width, this.Size.Height); - } + if (targetImage.Bounds.Size != this.Size) + { + targetImage = disposableImage = new Image(this.Image).Resize(this.Size.Width, this.Size.Height); + } - // Align start/end positions. - Rectangle bounds = this.Image.Bounds; - int minX = Math.Max(this.Location.X, sourceRectangle.X); - int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Width); - int minY = Math.Max(this.Location.Y, sourceRectangle.Y); - int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); + // Align start/end positions. + Rectangle bounds = this.Image.Bounds; + int minX = Math.Max(this.Location.X, sourceRectangle.X); + int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Width); + maxX = Math.Min(this.Location.X + this.Size.Width, maxX); - float alpha = this.Alpha / 100F; + int minY = Math.Max(this.Location.Y, sourceRectangle.Y); + int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); - using (PixelAccessor toBlendPixels = this.Image.Lock()) - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - for (int x = minX; x < maxX; x++) - { - Vector4 backgroundVector = sourcePixels[x, y].ToVector4(); - Vector4 sourceVector = toBlendPixels[x - minX, y - minY].ToVector4(); + maxY = Math.Min(this.Location.Y + this.Size.Height, maxY); - // Lerping colors is dependent on the alpha of the blended color - backgroundVector = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, alpha); + int width = maxX - minX; + using (Buffer amount = new Buffer(width)) + using (PixelAccessor toBlendPixels = targetImage.Lock()) + using (PixelAccessor sourcePixels = source.Lock()) + { + for (int i = 0; i < width; i++) + { + amount[i] = this.Alpha; + } - TPixel packed = default(TPixel); - packed.PackFromVector4(backgroundVector); - sourcePixels[x, y] = packed; - } - }); + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + BufferSpan background = sourcePixels.GetRowSpan(y).Slice(minX, width); + BufferSpan foreground = toBlendPixels.GetRowSpan(y - this.Location.Y).Slice(0, width); + this.blender.Blend(background, background, foreground, amount); + }); + } + } + finally + { + disposableImage?.Dispose(); } } } diff --git a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs index 62e366d2a..3fd829549 100644 --- a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs @@ -55,7 +55,7 @@ namespace ImageSharp.Drawing.Processors protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { using (PixelAccessor sourcePixels = source.Lock()) - using (PenApplicator applicator = this.Pen.CreateApplicator(sourcePixels, this.Path.Bounds)) + using (PenApplicator applicator = this.Pen.CreateApplicator(sourcePixels, this.Path.Bounds, this.Options)) { Rectangle rect = RectangleF.Ceiling(applicator.RequiredRegion); @@ -86,37 +86,34 @@ namespace ImageSharp.Drawing.Processors polyStartY = 0; } + int width = maxX - minX; + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(this.Options.BlenderMode); + Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - int offsetY = y - polyStartY; + minY, + maxY, + this.ParallelOptions, + y => + { + int offsetY = y - polyStartY; - for (int x = minX; x < maxX; x++) + using (Buffer amount = new Buffer(width)) + using (Buffer colors = new Buffer(width)) + { + for (int i = 0; i < width; i++) { - // TODO add find intersections code to skip and scan large regions of this. + int x = i + minX; int offsetX = x - startX; PointInfo info = this.Path.GetPointInfo(offsetX, offsetY); - ColoredPointInfo color = applicator.GetColor(offsetX, offsetY, info); - - float opacity = this.Opacity(color.DistanceFromElement); - - if (opacity > Constants.Epsilon) - { - Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4(); - Vector4 sourceVector = color.Color.ToVector4(); - - Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); - - TPixel packed = default(TPixel); - packed.PackFromVector4(finalColor); - sourcePixels[offsetX, offsetY] = packed; - } + amount[i] = (this.Opacity(color.DistanceFromElement) * this.Options.BlendPercentage).Clamp(0, 1); + colors[i] = color.Color; } - }); + + BufferSpan destination = sourcePixels.GetRowSpan(offsetY).Slice(minX - startX, width); + blender.Blend(destination, destination, colors, amount); + } + }); } } diff --git a/src/ImageSharp.Drawing/Processors/FillProcessor.cs b/src/ImageSharp.Drawing/Processors/FillProcessor.cs index ca2dc9982..25eccd245 100644 --- a/src/ImageSharp.Drawing/Processors/FillProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillProcessor.cs @@ -24,14 +24,17 @@ namespace ImageSharp.Drawing.Processors /// The brush. /// private readonly IBrush brush; + private readonly GraphicsOptions options; /// /// Initializes a new instance of the class. /// /// The brush to source pixel colors from. - public FillProcessor(IBrush brush) + /// The options + public FillProcessor(IBrush brush, GraphicsOptions options) { this.brush = brush; + this.options = options; } /// @@ -59,32 +62,30 @@ namespace ImageSharp.Drawing.Processors startY = 0; } + int width = maxX - minX; + // we could possibly do some optermising by having knowledge about the individual brushes operate // for example If brush is SolidBrush then we could just get the color upfront // and skip using the IBrushApplicator?. using (PixelAccessor sourcePixels = source.Lock()) - using (BrushApplicator applicator = this.brush.CreateApplicator(sourcePixels, sourceRectangle)) + using (Buffer amount = new Buffer(width)) + using (BrushApplicator applicator = this.brush.CreateApplicator(sourcePixels, sourceRectangle, this.options)) { - Parallel.For( + for (int i = 0; i < width; i++) + { + amount[i] = this.options.BlendPercentage; + } + + Parallel.For( minY, maxY, this.ParallelOptions, y => { int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; - - Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4(); - Vector4 sourceVector = applicator[offsetX, offsetY].ToVector4(); - - Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, 1); + int offsetX = minX - startX; - TPixel packed = default(TPixel); - packed.PackFromVector4(finalColor); - sourcePixels[offsetX, offsetY] = packed; - } + applicator.Apply(amount, offsetX, offsetY); }); } } diff --git a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index af1e6fa89..323214fb8 100644 --- a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -88,104 +88,105 @@ namespace ImageSharp.Drawing.Processors } using (PixelAccessor sourcePixels = source.Lock()) - using (BrushApplicator applicator = this.Brush.CreateApplicator(sourcePixels, rect)) + using (BrushApplicator applicator = this.Brush.CreateApplicator(sourcePixels, rect, this.Options)) { float[] buffer = arrayPool.Rent(maxIntersections); int scanlineWidth = maxX - minX; - float[] scanline = ArrayPool.Shared.Rent(scanlineWidth); - try + using (Buffer scanline = new Buffer(scanlineWidth)) { - bool scanlineDirty = true; - for (int y = minY; y < maxY; y++) + try { - if (scanlineDirty) + bool scanlineDirty = true; + for (int y = minY; y < maxY; y++) { - // clear the buffer - for (int x = 0; x < scanlineWidth; x++) + if (scanlineDirty) { - scanline[x] = 0; - } - - scanlineDirty = false; - } + // clear the buffer + for (int x = 0; x < scanlineWidth; x++) + { + scanline[x] = 0; + } - float subpixelFraction = 1f / subpixelCount; - float subpixelFractionPoint = subpixelFraction / subpixelCount; - for (float subPixel = (float)y; subPixel < y + 1; subPixel += subpixelFraction) - { - int pointsFound = region.Scan(subPixel, buffer, maxIntersections, 0); - if (pointsFound == 0) - { - // nothing on this line skip - continue; + scanlineDirty = false; } - QuickSort(buffer, pointsFound); - - for (int point = 0; point < pointsFound; point += 2) + float subpixelFraction = 1f / subpixelCount; + float subpixelFractionPoint = subpixelFraction / subpixelCount; + for (float subPixel = (float)y; subPixel < y + 1; subPixel += subpixelFraction) { - // points will be paired up - float scanStart = buffer[point] - minX; - float scanEnd = buffer[point + 1] - minX; - int startX = (int)MathF.Floor(scanStart); - int endX = (int)MathF.Floor(scanEnd); + int pointsFound = region.Scan(subPixel, buffer, maxIntersections, 0); + if (pointsFound == 0) + { + // nothing on this line skip + continue; + } - if (startX >= 0 && startX < scanline.Length) + QuickSort(buffer, pointsFound); + + for (int point = 0; point < pointsFound; point += 2) { - for (float x = scanStart; x < startX + 1; x += subpixelFraction) + // points will be paired up + float scanStart = buffer[point] - minX; + float scanEnd = buffer[point + 1] - minX; + int startX = (int)MathF.Floor(scanStart); + int endX = (int)MathF.Floor(scanEnd); + + if (startX >= 0 && startX < scanline.Length) { - scanline[startX] += subpixelFractionPoint; - scanlineDirty = true; + for (float x = scanStart; x < startX + 1; x += subpixelFraction) + { + scanline[startX] += subpixelFractionPoint; + scanlineDirty = true; + } } - } - if (endX >= 0 && endX < scanline.Length) - { - for (float x = endX; x < scanEnd; x += subpixelFraction) + if (endX >= 0 && endX < scanline.Length) { - scanline[endX] += subpixelFractionPoint; - scanlineDirty = true; + for (float x = endX; x < scanEnd; x += subpixelFraction) + { + scanline[endX] += subpixelFractionPoint; + scanlineDirty = true; + } } - } - int nextX = startX + 1; - endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge - if (nextX >= 0) - { - for (int x = nextX; x < endX; x++) + int nextX = startX + 1; + endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge + if (nextX >= 0) { - scanline[x] += subpixelFraction; - scanlineDirty = true; + for (int x = nextX; x < endX; x++) + { + scanline[x] += subpixelFraction; + scanlineDirty = true; + } } } } - } - if (scanlineDirty) - { - if (!this.Options.Antialias) + if (scanlineDirty) { - for (int x = 0; x < scanlineWidth; x++) + if (!this.Options.Antialias) { - if (scanline[x] > 0.5) - { - scanline[x] = 1; - } - else + for (int x = 0; x < scanlineWidth; x++) { - scanline[x] = 0; + if (scanline[x] > 0.5) + { + scanline[x] = 1; + } + else + { + scanline[x] = 0; + } } } - } - applicator.Apply(scanline, scanlineWidth, 0, minX, y); + applicator.Apply(scanline, minX, y); + } } } - } - finally - { - arrayPool.Return(buffer); - ArrayPool.Shared.Return(scanline); + finally + { + arrayPool.Return(buffer); + } } } } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 05ba5ee60..904aa1ff6 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -623,13 +623,13 @@ namespace ImageSharp.Formats case PngColorType.Rgb: - BulkPixelOperations.Instance.PackFromXyzBytes(scanlineBuffer, pixelBuffer, this.header.Width); + PixelOperations.Instance.PackFromXyzBytes(scanlineBuffer, pixelBuffer, this.header.Width); break; case PngColorType.RgbWithAlpha: - BulkPixelOperations.Instance.PackFromXyzwBytes(scanlineBuffer, pixelBuffer, this.header.Width); + PixelOperations.Instance.PackFromXyzwBytes(scanlineBuffer, pixelBuffer, this.header.Width); break; } diff --git a/src/ImageSharp/GraphicsOptions.cs b/src/ImageSharp/GraphicsOptions.cs new file mode 100644 index 000000000..f45582e1e --- /dev/null +++ b/src/ImageSharp/GraphicsOptions.cs @@ -0,0 +1,80 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using ImageSharp.PixelFormats; + + /// + /// Options for influencing the drawing functions. + /// + public struct GraphicsOptions + { + /// + /// Represents the default . + /// + public static readonly GraphicsOptions Default = new GraphicsOptions(true); + + private float? blendPercentage; + + private int? antialiasSubpixelDepth; + + private bool? antialias; + + private PixelBlenderMode blenderMode; + + /// + /// Initializes a new instance of the struct. + /// + /// If set to true [enable antialiasing]. + public GraphicsOptions(bool enableAntialiasing) + { + this.blenderMode = PixelBlenderMode.Normal; + this.blendPercentage = 1; + this.antialiasSubpixelDepth = 16; + this.antialias = enableAntialiasing; + } + + /// + /// Gets or sets a value indicating whether antialiasing should be applied. + /// + public bool Antialias + { + get => this.antialias ?? true; + set => this.antialias = value; + } + + /// + /// Gets or sets a value indicating the number of subpixels to use while rendering with antialiasing enabled. + /// + public int AntialiasSubpixelDepth + { + get => this.antialiasSubpixelDepth ?? 16; + set => this.antialiasSubpixelDepth = value; + } + + /// + /// Gets or sets a value indicating the blending percentage to apply to the drawing operation + /// + public float BlendPercentage + { + get => (this.blendPercentage ?? 1).Clamp(0, 1); + set => this.blendPercentage = value; + } + + // In the future we could expose a PixelBlender directly on here + // or some forms of PixelBlender factory for each pixel type. Will need + // some API thought post V1. + + /// + /// Gets or sets a value indicating the blending percentage to apply to the drawing operation + /// + public PixelBlenderMode BlenderMode + { + get => this.blenderMode; + set => this.blenderMode = value; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Image/PixelAccessor{TPixel}.cs b/src/ImageSharp/Image/PixelAccessor{TPixel}.cs index 3c6763886..1e46a672e 100644 --- a/src/ImageSharp/Image/PixelAccessor{TPixel}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TPixel}.cs @@ -116,7 +116,7 @@ namespace ImageSharp /// BufferSpan IBuffer2D.Span => this.pixelBuffer; - private static BulkPixelOperations Operations => BulkPixelOperations.Instance; + private static PixelOperations Operations => PixelOperations.Instance; /// /// Gets or sets the pixel at the specified position. diff --git a/src/ImageSharp/ImageProcessor.cs b/src/ImageSharp/ImageProcessor.cs index 7b9f74547..745b25fb6 100644 --- a/src/ImageSharp/ImageProcessor.cs +++ b/src/ImageSharp/ImageProcessor.cs @@ -41,7 +41,11 @@ namespace ImageSharp.Processing } catch (Exception ex) { +#if DEBUG + throw; +#else throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); +#endif } } diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 64183d05c..16fff3212 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -2,7 +2,7 @@ A cross-platform library for the processing of image files; written in C# ImageSharp - 1.0.0-alpha7 + 1.0.0-alpha8 James Jackson-South and contributors netstandard1.3;netstandard1.1 true diff --git a/src/ImageSharp/PixelFormats/Alpha8.cs b/src/ImageSharp/PixelFormats/Alpha8.cs index 5e2fff5d0..5a6eebf40 100644 --- a/src/ImageSharp/PixelFormats/Alpha8.cs +++ b/src/ImageSharp/PixelFormats/Alpha8.cs @@ -60,7 +60,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/Argb32.cs b/src/ImageSharp/PixelFormats/Argb32.cs index a6db505bb..2798c4c5a 100644 --- a/src/ImageSharp/PixelFormats/Argb32.cs +++ b/src/ImageSharp/PixelFormats/Argb32.cs @@ -221,7 +221,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/Bgr565.cs b/src/ImageSharp/PixelFormats/Bgr565.cs index a3d8cbcf2..78442a3ce 100644 --- a/src/ImageSharp/PixelFormats/Bgr565.cs +++ b/src/ImageSharp/PixelFormats/Bgr565.cs @@ -69,7 +69,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// /// Expands the packed representation into a . diff --git a/src/ImageSharp/PixelFormats/Bgra4444.cs b/src/ImageSharp/PixelFormats/Bgra4444.cs index 8d4e9b4f4..9138e25ca 100644 --- a/src/ImageSharp/PixelFormats/Bgra4444.cs +++ b/src/ImageSharp/PixelFormats/Bgra4444.cs @@ -68,7 +68,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/Bgra5551.cs b/src/ImageSharp/PixelFormats/Bgra5551.cs index 8ae1cd430..9ff571243 100644 --- a/src/ImageSharp/PixelFormats/Bgra5551.cs +++ b/src/ImageSharp/PixelFormats/Bgra5551.cs @@ -69,7 +69,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/Byte4.cs b/src/ImageSharp/PixelFormats/Byte4.cs index acb73dd1c..b27952ce4 100644 --- a/src/ImageSharp/PixelFormats/Byte4.cs +++ b/src/ImageSharp/PixelFormats/Byte4.cs @@ -71,7 +71,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/HalfSingle.cs b/src/ImageSharp/PixelFormats/HalfSingle.cs index 893667fc7..2a686f535 100644 --- a/src/ImageSharp/PixelFormats/HalfSingle.cs +++ b/src/ImageSharp/PixelFormats/HalfSingle.cs @@ -73,7 +73,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// /// Expands the packed representation into a . diff --git a/src/ImageSharp/PixelFormats/HalfVector2.cs b/src/ImageSharp/PixelFormats/HalfVector2.cs index bd3cda55d..ef9676b56 100644 --- a/src/ImageSharp/PixelFormats/HalfVector2.cs +++ b/src/ImageSharp/PixelFormats/HalfVector2.cs @@ -83,7 +83,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// /// Expands the packed representation into a . diff --git a/src/ImageSharp/PixelFormats/HalfVector4.cs b/src/ImageSharp/PixelFormats/HalfVector4.cs index 03e4326b7..8b284509f 100644 --- a/src/ImageSharp/PixelFormats/HalfVector4.cs +++ b/src/ImageSharp/PixelFormats/HalfVector4.cs @@ -86,7 +86,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/IPixel.cs b/src/ImageSharp/PixelFormats/IPixel.cs index 43fe81e95..9a8d9730a 100644 --- a/src/ImageSharp/PixelFormats/IPixel.cs +++ b/src/ImageSharp/PixelFormats/IPixel.cs @@ -16,11 +16,11 @@ namespace ImageSharp.PixelFormats where TSelf : struct, IPixel { /// - /// Creates a instance for this pixel type. - /// This method is not intended to be consumed directly. Use instead. + /// Creates a instance for this pixel type. + /// This method is not intended to be consumed directly. Use instead. /// - /// The instance. - BulkPixelOperations CreateBulkOperations(); + /// The instance. + PixelOperations CreateBulkOperations(); } /// diff --git a/src/ImageSharp/PixelFormats/NormalizedByte2.cs b/src/ImageSharp/PixelFormats/NormalizedByte2.cs index dab113ae7..c5b6eff6b 100644 --- a/src/ImageSharp/PixelFormats/NormalizedByte2.cs +++ b/src/ImageSharp/PixelFormats/NormalizedByte2.cs @@ -89,7 +89,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// /// Expands the packed representation into a . diff --git a/src/ImageSharp/PixelFormats/NormalizedByte4.cs b/src/ImageSharp/PixelFormats/NormalizedByte4.cs index 0cb5c756b..9e3676660 100644 --- a/src/ImageSharp/PixelFormats/NormalizedByte4.cs +++ b/src/ImageSharp/PixelFormats/NormalizedByte4.cs @@ -91,7 +91,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/NormalizedShort2.cs b/src/ImageSharp/PixelFormats/NormalizedShort2.cs index 86d80cbad..01a2d9954 100644 --- a/src/ImageSharp/PixelFormats/NormalizedShort2.cs +++ b/src/ImageSharp/PixelFormats/NormalizedShort2.cs @@ -88,7 +88,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/NormalizedShort4.cs b/src/ImageSharp/PixelFormats/NormalizedShort4.cs index 8512d4131..3e4535fdf 100644 --- a/src/ImageSharp/PixelFormats/NormalizedShort4.cs +++ b/src/ImageSharp/PixelFormats/NormalizedShort4.cs @@ -90,7 +90,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/PixelBlenderMode.cs b/src/ImageSharp/PixelFormats/PixelBlenderMode.cs new file mode 100644 index 000000000..d8031fe6e --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenderMode.cs @@ -0,0 +1,62 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats +{ + using System; + using System.Collections.Generic; + using System.Text; + + /// + /// The various blending modes. + /// + public enum PixelBlenderMode + { + /// + /// Default blending mode, also known as "Normal" or "Alpha Blending" + /// + Normal = 0, + + /// + /// Blends the 2 values by multiplication. + /// + Multiply, + + /// + /// Blends the 2 values by addition. + /// + Add, + + /// + /// Blends the 2 values by subtraction. + /// + Substract, + + /// + /// Multiplies the complements of the backdrop and source values, then complements the result. + /// + Screen, + + /// + /// Selects the minimum of the backdrop and source values. + /// + Darken, + + /// + /// Selects the max of the backdrop and source values. + /// + Lighten, + + /// + /// Multiplies or screens the values, depending on the backdrop vector values. + /// + Overlay, + + /// + /// Multiplies or screens the colors, depending on the source value. + /// + HardLight + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs new file mode 100644 index 000000000..ab3aee041 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs @@ -0,0 +1,55 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System; + using System.Numerics; + using ImageSharp.PixelFormats; + + /// + /// Applies an "Add" blending to pixels. + /// + /// The type of the pixel + internal class DefaultAddPixelBlender : PixelBlender + where TPixel : struct, IPixel + { + /// + /// Gets the static instance of this blender. + /// + public static DefaultAddPixelBlender Instance { get; } = new DefaultAddPixelBlender(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.AddFunction(background, source, amount); + } + + /// + public override void Blend(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + { + Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); + + using (Buffer buffer = new Buffer(destination.Length * 3)) + { + BufferSpan destinationSpan = buffer.Slice(0, destination.Length); + BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length); + BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + + PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); + PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); + + for (int i = 0; i < destination.Length; i++) + { + destinationSpan[i] = PorterDuffFunctions.AddFunction(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs new file mode 100644 index 000000000..e0ff80b66 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs @@ -0,0 +1,55 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System; + using System.Numerics; + using ImageSharp.PixelFormats; + + /// + /// Applies an "Darken" blending to pixels. + /// + /// The type of the pixel + internal class DefaultDarkenPixelBlender : PixelBlender + where TPixel : struct, IPixel + { + /// + /// Gets the static instance of this blender. + /// + public static DefaultDarkenPixelBlender Instance { get; } = new DefaultDarkenPixelBlender(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.DarkenFunction(background, source, amount); + } + + /// + public override void Blend(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + { + Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); + + using (Buffer buffer = new Buffer(destination.Length * 3)) + { + BufferSpan destinationSpan = buffer.Slice(0, destination.Length); + BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length); + BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + + PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); + PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); + + for (int i = 0; i < destination.Length; i++) + { + destinationSpan[i] = PorterDuffFunctions.DarkenFunction(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs new file mode 100644 index 000000000..cec0dc0db --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs @@ -0,0 +1,55 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System; + using System.Numerics; + using ImageSharp.PixelFormats; + + /// + /// Applies an "Hard Light" blending to pixels. + /// + /// The type of the pixel + internal class DefaultHardLightPixelBlender : PixelBlender + where TPixel : struct, IPixel + { + /// + /// Gets the static instance of this blender. + /// + public static DefaultHardLightPixelBlender Instance { get; } = new DefaultHardLightPixelBlender(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.HardLightFunction(background, source, amount); + } + + /// + public override void Blend(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + { + Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); + + using (Buffer buffer = new Buffer(destination.Length * 3)) + { + BufferSpan destinationSpan = buffer.Slice(0, destination.Length); + BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length); + BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + + PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); + PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); + + for (int i = 0; i < destination.Length; i++) + { + destinationSpan[i] = PorterDuffFunctions.HardLightFunction(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs new file mode 100644 index 000000000..32cd20650 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs @@ -0,0 +1,55 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System; + using System.Numerics; + using ImageSharp.PixelFormats; + + /// + /// Applies an "Lighten" blending to pixels. + /// + /// The type of the pixel + internal class DefaultLightenPixelBlender : PixelBlender + where TPixel : struct, IPixel + { + /// + /// Gets the static instance of this blender. + /// + public static DefaultLightenPixelBlender Instance { get; } = new DefaultLightenPixelBlender(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.LightenFunction(background, source, amount); + } + + /// + public override void Blend(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + { + Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); + + using (Buffer buffer = new Buffer(destination.Length * 3)) + { + BufferSpan destinationSpan = buffer.Slice(0, destination.Length); + BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length); + BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + + PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); + PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); + + for (int i = 0; i < destination.Length; i++) + { + destinationSpan[i] = PorterDuffFunctions.LightenFunction(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs new file mode 100644 index 000000000..7e0137018 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs @@ -0,0 +1,55 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System; + using System.Numerics; + using ImageSharp.PixelFormats; + + /// + /// Applies an "Multiply" blending to pixels. + /// + /// The type of the pixel + internal class DefaultMultiplyPixelBlender : PixelBlender + where TPixel : struct, IPixel + { + /// + /// Gets the static instance of this blender. + /// + public static DefaultMultiplyPixelBlender Instance { get; } = new DefaultMultiplyPixelBlender(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.MultiplyFunction(background, source, amount); + } + + /// + public override void Blend(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + { + Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); + + using (Buffer buffer = new Buffer(destination.Length * 3)) + { + BufferSpan destinationSpan = buffer.Slice(0, destination.Length); + BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length); + BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + + PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); + PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); + + for (int i = 0; i < destination.Length; i++) + { + destinationSpan[i] = PorterDuffFunctions.MultiplyFunction(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs new file mode 100644 index 000000000..47bb084ca --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs @@ -0,0 +1,55 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System; + using System.Numerics; + using ImageSharp.PixelFormats; + + /// + /// Applies a "Normal" otherwise nown as "Alpha Blending" blending to pixels. + /// + /// The type of the pixel + internal class DefaultNormalPixelBlender : PixelBlender + where TPixel : struct, IPixel + { + /// + /// Gets the static instance of this blender. + /// + public static DefaultNormalPixelBlender Instance { get; } = new DefaultNormalPixelBlender(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.NormalBlendFunction(background, source, amount); + } + + /// + public override void Blend(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + { + Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); + + using (Buffer buffer = new Buffer(destination.Length * 3)) + { + BufferSpan destinationSpan = buffer.Slice(0, destination.Length); + BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length); + BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + + PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); + PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); + + for (int i = 0; i < destination.Length; i++) + { + destinationSpan[i] = PorterDuffFunctions.NormalBlendFunction(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs new file mode 100644 index 000000000..fcb56e3dc --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs @@ -0,0 +1,55 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System; + using System.Numerics; + using ImageSharp.PixelFormats; + + /// + /// Applies an "Overlay" blending to pixels. + /// + /// The type of the pixel + internal class DefaultOverlayPixelBlender : PixelBlender + where TPixel : struct, IPixel + { + /// + /// Gets the static instance of this blender. + /// + public static DefaultOverlayPixelBlender Instance { get; } = new DefaultOverlayPixelBlender(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.OverlayFunction(background, source, amount); + } + + /// + public override void Blend(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + { + Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); + + using (Buffer buffer = new Buffer(destination.Length * 3)) + { + BufferSpan destinationSpan = buffer.Slice(0, destination.Length); + BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length); + BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + + PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); + PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); + + for (int i = 0; i < destination.Length; i++) + { + destinationSpan[i] = PorterDuffFunctions.OverlayFunction(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs new file mode 100644 index 000000000..df0de293c --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs @@ -0,0 +1,55 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System; + using System.Numerics; + using ImageSharp.PixelFormats; + + /// + /// Applies an "Screen" blending to pixels. + /// + /// The type of the pixel + internal class DefaultScreenPixelBlender : PixelBlender + where TPixel : struct, IPixel + { + /// + /// Gets the static instance of this blender. + /// + public static DefaultScreenPixelBlender Instance { get; } = new DefaultScreenPixelBlender(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.ScreenFunction(background, source, amount); + } + + /// + public override void Blend(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + { + Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); + + using (Buffer buffer = new Buffer(destination.Length * 3)) + { + BufferSpan destinationSpan = buffer.Slice(0, destination.Length); + BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length); + BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + + PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); + PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); + + for (int i = 0; i < destination.Length; i++) + { + destinationSpan[i] = PorterDuffFunctions.ScreenFunction(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultSubstractPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultSubstractPixelBlender{TPixel}.cs new file mode 100644 index 000000000..415ac04b2 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultSubstractPixelBlender{TPixel}.cs @@ -0,0 +1,55 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System; + using System.Numerics; + using ImageSharp.PixelFormats; + + /// + /// Applies an "Subtract" blending to pixels. + /// + /// The type of the pixel + internal class DefaultSubstractPixelBlender : PixelBlender + where TPixel : struct, IPixel + { + /// + /// Gets the static instance of this blender. + /// + public static DefaultSubstractPixelBlender Instance { get; } = new DefaultSubstractPixelBlender(); + + /// + public override TPixel Blend(TPixel background, TPixel source, float amount) + { + return PorterDuffFunctions.SubstractFunction(background, source, amount); + } + + /// + public override void Blend(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + { + Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); + + using (Buffer buffer = new Buffer(destination.Length * 3)) + { + BufferSpan destinationSpan = buffer.Slice(0, destination.Length); + BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length); + BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + + PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); + PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); + + for (int i = 0; i < destination.Length; i++) + { + destinationSpan[i] = PorterDuffFunctions.SubstractFunction(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs new file mode 100644 index 000000000..25eb6a5c8 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs @@ -0,0 +1,242 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// Collection of Porter Duff alpha blending functions applying an the 'Over' composition model. + /// + /// + /// These functions are designed to be a general solution for all color cases, + /// that is, they take in account the alpha value of both the backdrop + /// and source, and there's no need to alpha-premultiply neither the backdrop + /// nor the source. + /// Note there are faster functions for when the backdrop color is known + /// to be opaque + /// + internal static partial class PorterDuffFunctions + { + /// + /// Source over backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalBlendFunction(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + if (source.W == 0) + { + return backdrop; + } + + return Compose(backdrop, source, source); + } + + /// + /// Source multiplied by backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyFunction(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + if (source.W == 0) + { + return backdrop; + } + + return Compose(backdrop, source, backdrop * source); + } + + /// + /// Source added to backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddFunction(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + if (source.W == 0) + { + return backdrop; + } + + return Compose(backdrop, source, Vector4.Min(Vector4.One, backdrop + source)); + } + + /// + /// Source substracted from backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubstractFunction(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + if (source.W == 0) + { + return backdrop; + } + + return Compose(backdrop, source, Vector4.Max(Vector4.Zero, backdrop - source)); + } + + /// + /// Complement of source multiplied by the complement of backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenFunction(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + if (source.W == 0) + { + return backdrop; + } + + return Compose(backdrop, source, Vector4.One - ((Vector4.One - backdrop) * (Vector4.One - source))); + } + + /// + /// Per element, chooses the smallest value of source and backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenFunction(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + if (source.W == 0) + { + return backdrop; + } + + return Compose(backdrop, source, Vector4.Min(backdrop, source)); + } + + /// + /// Per element, chooses the largest value of source and backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenFunction(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + if (source.W == 0) + { + return backdrop; + } + + return Compose(backdrop, source, Vector4.Max(backdrop, source)); + } + + /// + /// Overlays source over backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlayFunction(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + if (source.W == 0) + { + return backdrop; + } + + float cr = OverlayValueFunction(backdrop.X, source.X); + float cg = OverlayValueFunction(backdrop.Y, source.Y); + float cb = OverlayValueFunction(backdrop.Z, source.Z); + + return Compose(backdrop, source, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0))); + } + + /// + /// Hard light effect + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightFunction(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + if (source.W == 0) + { + return backdrop; + } + + float cr = OverlayValueFunction(source.X, backdrop.X); + float cg = OverlayValueFunction(source.Y, backdrop.Y); + float cb = OverlayValueFunction(source.Z, backdrop.Z); + + return Compose(backdrop, source, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0))); + } + + /// + /// Helper function for Overlay andHardLight modes + /// + /// Backdrop color element + /// Source color element + /// Overlay value + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float OverlayValueFunction(float backdrop, float source) + { + return backdrop <= 0.5f ? (2 * backdrop * source) : 1 - ((2 * (1 - source)) * (1 - backdrop)); + } + + /// + /// General composition function for all modes, with a general solution for alpha channel + /// + /// Original backgrop color + /// Original source color + /// Desired transformed color, without taking Alpha channel in account + /// The final color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 Compose(Vector4 backdrop, Vector4 source, Vector4 xform) + { + DebugGuard.MustBeGreaterThan(source.W, 0, nameof(source.W)); + + // calculate weights + float xw = backdrop.W * source.W; + float bw = backdrop.W - xw; + float sw = source.W - xw; + + // calculate final alpha + float a = xw + bw + sw; + + // calculate final value + xform = ((xform * xw) + (backdrop * bw) + (source * sw)) / a; + xform.W = a; + + return xform; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions{TPixel}.cs new file mode 100644 index 000000000..4e829212e --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions{TPixel}.cs @@ -0,0 +1,151 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// Collection of Porter Duff alpha blending functions + /// + /// Pixel Format + /// + /// These functions are designed to be a general solution for all color cases, + /// that is, they take in account the alpha value of both the backdrop + /// and source, and there's no need to alpha-premultiply neither the backdrop + /// nor the source. + /// Note there are faster functions for when the backdrop color is known + /// to be opaque + /// + internal static class PorterDuffFunctions + where TPixel : IPixel + { + /// + /// Source over backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalBlendFunction(TPixel backdrop, TPixel source, float opacity) + { + return ToPixel(PorterDuffFunctions.NormalBlendFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); + } + + /// + /// Source multiplied by backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplyFunction(TPixel backdrop, TPixel source, float opacity) + { + return ToPixel(PorterDuffFunctions.MultiplyFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); + } + + /// + /// Source added to backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddFunction(TPixel backdrop, TPixel source, float opacity) + { + return ToPixel(PorterDuffFunctions.AddFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); + } + + /// + /// Source substracted from backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubstractFunction(TPixel backdrop, TPixel source, float opacity) + { + return ToPixel(PorterDuffFunctions.SubstractFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); + } + + /// + /// Complement of source multiplied by the complement of backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenFunction(TPixel backdrop, TPixel source, float opacity) + { + return ToPixel(PorterDuffFunctions.ScreenFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); + } + + /// + /// Per element, chooses the smallest value of source and backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenFunction(TPixel backdrop, TPixel source, float opacity) + { + return ToPixel(PorterDuffFunctions.DarkenFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); + } + + /// + /// Per element, chooses the largest value of source and backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenFunction(TPixel backdrop, TPixel source, float opacity) + { + return ToPixel(PorterDuffFunctions.LightenFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); + } + + /// + /// Overlays source over backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlayFunction(TPixel backdrop, TPixel source, float opacity) + { + return ToPixel(PorterDuffFunctions.OverlayFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); + } + + /// + /// Hard light effect + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightFunction(TPixel backdrop, TPixel source, float opacity) + { + return ToPixel(PorterDuffFunctions.HardLightFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static TPixel ToPixel(Vector4 vector) + { + TPixel p = default(TPixel); + p.PackFromVector4(vector); + return p; + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs new file mode 100644 index 000000000..23340a60a --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs @@ -0,0 +1,39 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats +{ + /// + /// Abstract base class for calling pixel composition functions + /// + /// The type of the pixel + internal abstract class PixelBlender + where TPixel : struct, IPixel + { + /// + /// Blend 2 pixels together. + /// + /// The background color. + /// The source color. + /// + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "from" is returned, at amount = 1, "to" is returned. + /// + /// The final pixel value after composition + public abstract TPixel Blend(TPixel background, TPixel source, float amount); + + /// + /// Blend 2 pixels together. + /// + /// The destination span. + /// The background span. + /// The source span. + /// + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "from" is returned, at amount = 1, "to" is returned. + /// + public abstract void Blend(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount); + } +} diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs new file mode 100644 index 000000000..cab357c41 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs @@ -0,0 +1,47 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats +{ + using ImageSharp.PixelFormats.PixelBlenders; + + /// + /// Provides access to pixel blenders + /// + public partial class PixelOperations + where TPixel : struct, IPixel + { + /// + /// Find an instance of the pixel blender. + /// + /// The blending mode to apply + /// A . + internal virtual PixelBlender GetPixelBlender(PixelBlenderMode mode) + { + switch (mode) + { + case PixelBlenderMode.Multiply: + return DefaultMultiplyPixelBlender.Instance; + case PixelBlenderMode.Add: + return DefaultAddPixelBlender.Instance; + case PixelBlenderMode.Substract: + return DefaultSubstractPixelBlender.Instance; + case PixelBlenderMode.Screen: + return DefaultScreenPixelBlender.Instance; + case PixelBlenderMode.Darken: + return DefaultDarkenPixelBlender.Instance; + case PixelBlenderMode.Lighten: + return DefaultLightenPixelBlender.Instance; + case PixelBlenderMode.Overlay: + return DefaultOverlayPixelBlender.Instance; + case PixelBlenderMode.HardLight: + return DefaultHardLightPixelBlender.Instance; + case PixelBlenderMode.Normal: + default: + return DefaultNormalPixelBlender.Instance; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/BulkPixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs similarity index 96% rename from src/ImageSharp/PixelFormats/BulkPixelOperations{TPixel}.cs rename to src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs index 9a3d266c1..207040521 100644 --- a/src/ImageSharp/PixelFormats/BulkPixelOperations{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -13,13 +13,13 @@ namespace ImageSharp.PixelFormats /// for pixel buffers of type . /// /// The pixel format. - public class BulkPixelOperations + public partial class PixelOperations where TPixel : struct, IPixel { /// - /// Gets the global instance for the pixel type + /// Gets the global instance for the pixel type /// - public static BulkPixelOperations Instance { get; } = default(TPixel).CreateBulkOperations(); + public static PixelOperations Instance { get; } = default(TPixel).CreateBulkOperations(); /// /// Bulk version of diff --git a/src/ImageSharp/PixelFormats/Rg32.cs b/src/ImageSharp/PixelFormats/Rg32.cs index a4bfc5823..4d1f0fecf 100644 --- a/src/ImageSharp/PixelFormats/Rg32.cs +++ b/src/ImageSharp/PixelFormats/Rg32.cs @@ -74,7 +74,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// /// Expands the packed representation into a . diff --git a/src/ImageSharp/PixelFormats/Rgba1010102.cs b/src/ImageSharp/PixelFormats/Rgba1010102.cs index cfd60f410..96f7af773 100644 --- a/src/ImageSharp/PixelFormats/Rgba1010102.cs +++ b/src/ImageSharp/PixelFormats/Rgba1010102.cs @@ -77,7 +77,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/Rgba32.BulkOperations.cs b/src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs similarity index 97% rename from src/ImageSharp/PixelFormats/Rgba32.BulkOperations.cs rename to src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs index df21cdc70..ff284e625 100644 --- a/src/ImageSharp/PixelFormats/Rgba32.BulkOperations.cs +++ b/src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -16,9 +16,9 @@ namespace ImageSharp.PixelFormats public partial struct Rgba32 { /// - /// implementation optimized for . + /// implementation optimized for . /// - internal class BulkOperations : BulkPixelOperations + internal class PixelOperations : PixelOperations { /// /// SIMD optimized bulk implementation of diff --git a/src/ImageSharp/PixelFormats/Rgba32.Transforms.cs b/src/ImageSharp/PixelFormats/Rgba32.Transforms.cs deleted file mode 100644 index bd13a2de1..000000000 --- a/src/ImageSharp/PixelFormats/Rgba32.Transforms.cs +++ /dev/null @@ -1,254 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.PixelFormats -{ - using System.Numerics; - using System.Runtime.CompilerServices; - - /// - /// Provides operators and composition algorithms. - /// - public partial struct Rgba32 - { - /// - /// Adds the second color to the first. - /// - /// The first source color. - /// The second source color. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba32 operator +(Rgba32 left, Rgba32 right) - { - Vector4 add = left.ToVector4() + right.ToVector4(); - return PackNew(ref add); - } - - /// - /// Subtracts the second color from the first. - /// - /// The first source color. - /// The second source color. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba32 operator -(Rgba32 left, Rgba32 right) - { - Vector4 sub = left.ToVector4() - right.ToVector4(); - return PackNew(ref sub); - } - - /// - /// The blending formula simply selects the source color. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 Normal(Rgba32 backdrop, Rgba32 source) - { - Vector4 normal = Vector4BlendTransforms.Normal(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref normal); - } - - /// - /// Blends two colors by multiplication. - /// - /// The source color is multiplied by the destination color and replaces the destination. - /// The resultant color is always at least as dark as either the source or destination color. - /// Multiplying any color with black results in black. Multiplying any color with white preserves the - /// original color. - /// - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 Multiply(Rgba32 backdrop, Rgba32 source) - { - Vector4 multiply = Vector4BlendTransforms.Multiply(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref multiply); - } - - /// - /// Multiplies the complements of the backdrop and source color values, then complements the result. - /// - /// The result color is always at least as light as either of the two constituent colors. Screening any - /// color with white produces white; screening with black leaves the original color unchanged. - /// The effect is similar to projecting multiple photographic slides simultaneously onto a single screen. - /// - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 Screen(Rgba32 backdrop, Rgba32 source) - { - Vector4 subtract = Vector4BlendTransforms.Screen(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref subtract); - } - - /// - /// Multiplies or screens the colors, depending on the source color value. The effect is similar to - /// shining a harsh spotlight on the backdrop. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 HardLight(Rgba32 backdrop, Rgba32 source) - { - Vector4 hardlight = Vector4BlendTransforms.HardLight(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref hardlight); - } - - /// - /// Multiplies or screens the colors, depending on the backdrop color value. - /// - /// Source colors overlay the backdrop while preserving its highlights and shadows. - /// The backdrop color is not replaced but is mixed with the source color to reflect the lightness or darkness - /// of the backdrop. - /// - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 Overlay(Rgba32 backdrop, Rgba32 source) - { - Vector4 overlay = Vector4BlendTransforms.Overlay(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref overlay); - } - - /// - /// Selects the darker of the backdrop and source colors. - /// The backdrop is replaced with the source where the source is darker; otherwise, it is left unchanged. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 Darken(Rgba32 backdrop, Rgba32 source) - { - Vector4 darken = Vector4BlendTransforms.Darken(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref darken); - } - - /// - /// Selects the lighter of the backdrop and source colors. - /// The backdrop is replaced with the source where the source is lighter; otherwise, it is left unchanged. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 Lighten(Rgba32 backdrop, Rgba32 source) - { - Vector4 lighten = Vector4BlendTransforms.Lighten(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref lighten); - } - - /// - /// Darkens or lightens the colors, depending on the source color value. The effect is similar to shining - /// a diffused spotlight on the backdrop. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 SoftLight(Rgba32 backdrop, Rgba32 source) - { - Vector4 softlight = Vector4BlendTransforms.SoftLight(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref softlight); - } - - /// - /// Brightens the backdrop color to reflect the source color. Painting with black produces no changes. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 ColorDodge(Rgba32 backdrop, Rgba32 source) - { - Vector4 dodge = Vector4BlendTransforms.Dodge(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref dodge); - } - - /// - /// Darkens the backdrop color to reflect the source color. Painting with white produces no change. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 ColorBurn(Rgba32 backdrop, Rgba32 source) - { - Vector4 burn = Vector4BlendTransforms.Burn(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref burn); - } - - /// - /// Subtracts the darker of the two constituent colors from the lighter color. - /// Painting with white inverts the backdrop color; painting with black produces no change. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 Difference(Rgba32 backdrop, Rgba32 source) - { - Vector4 difference = Vector4BlendTransforms.Difference(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref difference); - } - - /// - /// Produces an effect similar to that of the mode but lower in contrast. Painting with white - /// inverts the backdrop color; painting with black produces no change - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 Exclusion(Rgba32 backdrop, Rgba32 source) - { - Vector4 exclusion = Vector4BlendTransforms.Exclusion(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref exclusion); - } - - /// - /// Linearly interpolates from one color to another based on the given weighting. - /// - /// The first color value. - /// The second color value. - /// - /// A value between 0 and 1 indicating the weight of the second source vector. - /// At amount = 0, "from" is returned, at amount = 1, "to" is returned. - /// - /// - /// The - /// - public static Rgba32 Lerp(Rgba32 from, Rgba32 to, float amount) - { - Vector4 lerp = Vector4.Lerp(from.ToVector4(), to.ToVector4(), amount); - return PackNew(ref lerp); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/Rgba32.cs b/src/ImageSharp/PixelFormats/Rgba32.cs index d668be76f..0d5926910 100644 --- a/src/ImageSharp/PixelFormats/Rgba32.cs +++ b/src/ImageSharp/PixelFormats/Rgba32.cs @@ -202,7 +202,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/Rgba64.cs b/src/ImageSharp/PixelFormats/Rgba64.cs index 20bba9f41..b7ff143a9 100644 --- a/src/ImageSharp/PixelFormats/Rgba64.cs +++ b/src/ImageSharp/PixelFormats/Rgba64.cs @@ -76,7 +76,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/RgbaVector.BulkOperations.cs b/src/ImageSharp/PixelFormats/RgbaVector.PixelOperations.cs similarity index 81% rename from src/ImageSharp/PixelFormats/RgbaVector.BulkOperations.cs rename to src/ImageSharp/PixelFormats/RgbaVector.PixelOperations.cs index 9dcfc9074..da0900c11 100644 --- a/src/ImageSharp/PixelFormats/RgbaVector.BulkOperations.cs +++ b/src/ImageSharp/PixelFormats/RgbaVector.PixelOperations.cs @@ -13,9 +13,9 @@ namespace ImageSharp.PixelFormats public partial struct RgbaVector { /// - /// implementation optimized for . + /// implementation optimized for . /// - internal class BulkOperations : BulkPixelOperations + internal class PixelOperations : PixelOperations { /// internal override unsafe void ToVector4(BufferSpan sourceColors, BufferSpan destVectors, int count) diff --git a/src/ImageSharp/PixelFormats/RgbaVector.Transforms.cs b/src/ImageSharp/PixelFormats/RgbaVector.Transforms.cs deleted file mode 100644 index fec1aa346..000000000 --- a/src/ImageSharp/PixelFormats/RgbaVector.Transforms.cs +++ /dev/null @@ -1,251 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.PixelFormats -{ - using System.Numerics; - using System.Runtime.CompilerServices; - - /// - /// Provides operators and composition algorithms. - /// - public partial struct RgbaVector - { - /// - /// Adds the second color to the first. - /// - /// The first source color. - /// The second source color. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaVector operator +(RgbaVector left, RgbaVector right) - { - return new RgbaVector(left.backingVector + right.backingVector); - } - - /// - /// Subtracts the second color from the first. - /// - /// The first source color. - /// The second source color. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaVector operator -(RgbaVector left, RgbaVector right) - { - return new RgbaVector(left.backingVector - right.backingVector); - } - - /// - /// The blending formula simply selects the source color. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector Normal(RgbaVector backdrop, RgbaVector source) - { - Vector4 normal = Vector4BlendTransforms.Normal(backdrop.backingVector, source.backingVector); - return new RgbaVector(normal); - } - - /// - /// Blends two colors by multiplication. - /// - /// The source color is multiplied by the destination color and replaces the destination. - /// The resultant color is always at least as dark as either the source or destination color. - /// Multiplying any color with black results in black. Multiplying any color with white preserves the - /// original color. - /// - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector Multiply(RgbaVector backdrop, RgbaVector source) - { - Vector4 multiply = Vector4BlendTransforms.Multiply(backdrop.backingVector, source.backingVector); - return new RgbaVector(multiply); - } - - /// - /// Multiplies the complements of the backdrop and source color values, then complements the result. - /// - /// The result color is always at least as light as either of the two constituent colors. Screening any - /// color with white produces white; screening with black leaves the original color unchanged. - /// The effect is similar to projecting multiple photographic slides simultaneously onto a single screen. - /// - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector Screen(RgbaVector backdrop, RgbaVector source) - { - Vector4 subtract = Vector4BlendTransforms.Screen(backdrop.backingVector, source.backingVector); - return new RgbaVector(subtract); - } - - /// - /// Multiplies or screens the colors, depending on the source color value. The effect is similar to - /// shining a harsh spotlight on the backdrop. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector HardLight(RgbaVector backdrop, RgbaVector source) - { - Vector4 hardlight = Vector4BlendTransforms.HardLight(backdrop.backingVector, source.backingVector); - return new RgbaVector(hardlight); - } - - /// - /// Multiplies or screens the colors, depending on the backdrop color value. - /// - /// Source colors overlay the backdrop while preserving its highlights and shadows. - /// The backdrop color is not replaced but is mixed with the source color to reflect the lightness or darkness - /// of the backdrop. - /// - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector Overlay(RgbaVector backdrop, RgbaVector source) - { - Vector4 overlay = Vector4BlendTransforms.Overlay(backdrop.backingVector, source.backingVector); - return new RgbaVector(overlay); - } - - /// - /// Selects the darker of the backdrop and source colors. - /// The backdrop is replaced with the source where the source is darker; otherwise, it is left unchanged. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector Darken(RgbaVector backdrop, RgbaVector source) - { - Vector4 darken = Vector4BlendTransforms.Darken(backdrop.backingVector, source.backingVector); - return new RgbaVector(darken); - } - - /// - /// Selects the lighter of the backdrop and source colors. - /// The backdrop is replaced with the source where the source is lighter; otherwise, it is left unchanged. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector Lighten(RgbaVector backdrop, RgbaVector source) - { - Vector4 lighten = Vector4BlendTransforms.Lighten(backdrop.backingVector, source.backingVector); - return new RgbaVector(lighten); - } - - /// - /// Darkens or lightens the colors, depending on the source color value. The effect is similar to shining - /// a diffused spotlight on the backdrop. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector SoftLight(RgbaVector backdrop, RgbaVector source) - { - Vector4 softlight = Vector4BlendTransforms.SoftLight(backdrop.backingVector, source.backingVector); - return new RgbaVector(softlight); - } - - /// - /// Brightens the backdrop color to reflect the source color. Painting with black produces no changes. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector ColorDodge(RgbaVector backdrop, RgbaVector source) - { - Vector4 dodge = Vector4BlendTransforms.Dodge(backdrop.backingVector, source.backingVector); - return new RgbaVector(dodge); - } - - /// - /// Darkens the backdrop color to reflect the source color. Painting with white produces no change. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector ColorBurn(RgbaVector backdrop, RgbaVector source) - { - Vector4 burn = Vector4BlendTransforms.Burn(backdrop.backingVector, source.backingVector); - return new RgbaVector(burn); - } - - /// - /// Subtracts the darker of the two constituent colors from the lighter color. - /// Painting with white inverts the backdrop color; painting with black produces no change. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector Difference(RgbaVector backdrop, RgbaVector source) - { - Vector4 difference = Vector4BlendTransforms.Difference(backdrop.backingVector, source.backingVector); - return new RgbaVector(difference); - } - - /// - /// Produces an effect similar to that of the mode but lower in contrast. Painting with white - /// inverts the backdrop color; painting with black produces no change - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector Exclusion(RgbaVector backdrop, RgbaVector source) - { - Vector4 exclusion = Vector4BlendTransforms.Exclusion(backdrop.backingVector, source.backingVector); - return new RgbaVector(exclusion); - } - - /// - /// Linearly interpolates from one color to another based on the given weighting. - /// - /// The first color value. - /// The second color value. - /// - /// A value between 0 and 1 indicating the weight of the second source vector. - /// At amount = 0, "from" is returned, at amount = 1, "to" is returned. - /// - /// - /// The - /// - public static RgbaVector Lerp(RgbaVector from, RgbaVector to, float amount) - { - return new RgbaVector(Vector4.Lerp(from.backingVector, to.backingVector, amount)); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/RgbaVector.cs b/src/ImageSharp/PixelFormats/RgbaVector.cs index c59e93259..25eaa9711 100644 --- a/src/ImageSharp/PixelFormats/RgbaVector.cs +++ b/src/ImageSharp/PixelFormats/RgbaVector.cs @@ -208,7 +208,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new RgbaVector.BulkOperations(); + public PixelOperations CreateBulkOperations() => new RgbaVector.PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/Short2.cs b/src/ImageSharp/PixelFormats/Short2.cs index 60fbb5b35..d681e27b7 100644 --- a/src/ImageSharp/PixelFormats/Short2.cs +++ b/src/ImageSharp/PixelFormats/Short2.cs @@ -89,7 +89,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/Short4.cs b/src/ImageSharp/PixelFormats/Short4.cs index 65ce51eb2..f58949a72 100644 --- a/src/ImageSharp/PixelFormats/Short4.cs +++ b/src/ImageSharp/PixelFormats/Short4.cs @@ -91,7 +91,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/Vector4BlendTransforms.cs b/src/ImageSharp/PixelFormats/Vector4BlendTransforms.cs deleted file mode 100644 index 87c7a289e..000000000 --- a/src/ImageSharp/PixelFormats/Vector4BlendTransforms.cs +++ /dev/null @@ -1,292 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.PixelFormats -{ - using System.Numerics; - - /// - /// Transform algorithms that match the equations defined in the W3C Compositing and Blending Level 1 specification. - /// - /// - public class Vector4BlendTransforms - { - /// - /// The blending formula simply selects the source vector. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Normal(Vector4 backdrop, Vector4 source) - { - return new Vector4(source.X, source.Y, source.Z, source.W); - } - - /// - /// Blends two vectors by multiplication. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Multiply(Vector4 backdrop, Vector4 source) - { - Vector4 multiply = backdrop * source; - multiply.W = backdrop.W; - return multiply; - } - - /// - /// Multiplies the complements of the backdrop and source vector values, then complements the result. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Screen(Vector4 backdrop, Vector4 source) - { - Vector4 subtract = backdrop + source - (backdrop * source); - subtract.W = backdrop.W; - return subtract; - } - - /// - /// Multiplies or screens the colors, depending on the source vector value. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 HardLight(Vector4 backdrop, Vector4 source) - { - return new Vector4(BlendOverlay(source.X, backdrop.X), BlendOverlay(source.Y, backdrop.Y), BlendOverlay(source.Z, backdrop.Z), backdrop.W); - } - - /// - /// Multiplies or screens the vectors, depending on the backdrop vector value. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Overlay(Vector4 backdrop, Vector4 source) - { - return new Vector4(BlendOverlay(backdrop.X, source.X), BlendOverlay(backdrop.Y, source.Y), BlendOverlay(backdrop.Z, source.Z), backdrop.W); - } - - /// - /// Selects the minimum of the backdrop and source vectors. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Darken(Vector4 backdrop, Vector4 source) - { - Vector4 result = Vector4.Min(backdrop, source); - result.W = backdrop.W; - return result; - } - - /// - /// Selects the max of the backdrop and source vector. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Lighten(Vector4 backdrop, Vector4 source) - { - Vector4 result = Vector4.Max(backdrop, source); - result.W = backdrop.W; - return result; - } - - /// - /// Selects the maximum or minimum of the vectors, depending on the source vector value. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 SoftLight(Vector4 backdrop, Vector4 source) - { - return new Vector4(BlendSoftLight(backdrop.X, source.X), BlendSoftLight(backdrop.Y, source.Y), BlendSoftLight(backdrop.Z, source.Z), backdrop.W); - } - - /// - /// Increases the backdrop vector to reflect the source vector. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Dodge(Vector4 backdrop, Vector4 source) - { - return new Vector4(BlendDodge(backdrop.X, source.X), BlendDodge(backdrop.Y, source.Y), BlendDodge(backdrop.Z, source.Z), backdrop.W); - } - - /// - /// Decreases the backdrop vector to reflect the source vector. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Burn(Vector4 backdrop, Vector4 source) - { - return new Vector4(BlendBurn(backdrop.X, source.X), BlendBurn(backdrop.Y, source.Y), BlendBurn(backdrop.Z, source.Z), backdrop.W); - } - - /// - /// Subtracts the minimum of the two constituent vectors from the maximum vector. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Difference(Vector4 backdrop, Vector4 source) - { - Vector4 result = Vector4.Abs(backdrop - source); - result.W = backdrop.W; - return result; - } - - /// - /// Produces an effect similar to that of the mode but lower in magnitude. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Exclusion(Vector4 backdrop, Vector4 source) - { - return new Vector4(BlendExclusion(backdrop.X, source.X), BlendExclusion(backdrop.Y, source.Y), BlendExclusion(backdrop.Z, source.Z), backdrop.W); - } - - /// - /// Linearly interpolates from one vector to another based on the given weighting. - /// The two vectors are premultiplied before operating. - /// - /// The backdrop vector. - /// The source vector. - /// - /// A value between 0 and 1 indicating the weight of the second source vector. - /// At amount = 0, "from" is returned, at amount = 1, "to" is returned. - /// - /// - /// The - /// - public static Vector4 PremultipliedLerp(Vector4 backdrop, Vector4 source, float amount) - { - amount = amount.Clamp(0, 1); - - // Santize on zero alpha - if (MathF.Abs(backdrop.W) < Constants.Epsilon) - { - source.W *= amount; - return source; - } - - if (MathF.Abs(source.W) < Constants.Epsilon) - { - return backdrop; - } - - // Premultiply the source vector. - // Oddly premultiplying the background vector creates dark outlines when pixels - // Have low alpha values. - source = new Vector4(source.X, source.Y, source.Z, 1) * (source.W * amount); - - // This should be implementing the following formula - // https://en.wikipedia.org/wiki/Alpha_compositing - // Vout = Vs + Vb (1 - Vsa) - // Aout = Vsa + Vsb (1 - Vsa) - Vector3 inverseW = new Vector3(1 - source.W); - Vector3 xyzB = new Vector3(backdrop.X, backdrop.Y, backdrop.Z); - Vector3 xyzS = new Vector3(source.X, source.Y, source.Z); - - return new Vector4(xyzS + (xyzB * inverseW), source.W + (backdrop.W * (1 - source.W))); - } - - /// - /// Multiplies or screens the backdrop component, depending on the component value. - /// - /// The backdrop component. - /// The source component. - /// - /// The . - /// - private static float BlendOverlay(float b, float s) - { - return b <= .5F ? (2F * b * s) : (1F - (2F * (1F - b) * (1F - s))); - } - - /// - /// Darkens or lightens the backdrop component, depending on the source component value. - /// - /// The backdrop component. - /// The source component. - /// - /// The . - /// - private static float BlendSoftLight(float b, float s) - { - return s <= .5F ? ((2F * b * s) + (b * b * (1F - (2F * s)))) : (MathF.Sqrt(b) * ((2F * s) - 1F)) + (2F * b * (1F - s)); - } - - /// - /// Brightens the backdrop component to reflect the source component. - /// - /// The backdrop component. - /// The source component. - /// - /// The . - /// - private static float BlendDodge(float b, float s) - { - return MathF.Abs(s - 1F) < Constants.Epsilon ? s : MathF.Min(b / (1F - s), 1F); - } - - /// - /// Darkens the backdrop component to reflect the source component. - /// - /// The backdrop component. - /// The source component. - /// - /// The . - /// - private static float BlendBurn(float b, float s) - { - return MathF.Abs(s) < Constants.Epsilon ? s : MathF.Max(1F - ((1F - b) / s), 0F); - } - - /// - /// Darkens the backdrop component to reflect the source component. - /// - /// The backdrop component. - /// The source component. - /// - /// The . - /// - private static float BlendExclusion(float b, float s) - { - return b + s - (2F * b * s); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/ColorMatrix/Lomograph.cs b/src/ImageSharp/Processing/ColorMatrix/Lomograph.cs index 3299add7f..937dca9ba 100644 --- a/src/ImageSharp/Processing/ColorMatrix/Lomograph.cs +++ b/src/ImageSharp/Processing/ColorMatrix/Lomograph.cs @@ -26,7 +26,7 @@ namespace ImageSharp public static Image Lomograph(this Image source) where TPixel : struct, IPixel { - return Lomograph(source, source.Bounds); + return Lomograph(source, source.Bounds, GraphicsOptions.Default); } /// @@ -41,7 +41,36 @@ namespace ImageSharp public static Image Lomograph(this Image source, Rectangle rectangle) where TPixel : struct, IPixel { - source.ApplyProcessor(new LomographProcessor(), rectangle); + return Lomograph(source, rectangle, GraphicsOptions.Default); + } + + /// + /// Alters the colors of the image recreating an old Lomograph camera effect. + /// + /// The pixel format. + /// The image this method extends. + /// The options effecting pixel blending. + /// The . + public static Image Lomograph(this Image source, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Lomograph(source, source.Bounds, options); + } + + /// + /// Alters the colors of the image recreating an old Lomograph camera effect. + /// + /// The pixel format. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The options effecting pixel blending. + /// The . + public static Image Lomograph(this Image source, Rectangle rectangle, GraphicsOptions options) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new LomographProcessor(options), rectangle); return source; } } diff --git a/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs b/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs index 194800ec7..f1a573c05 100644 --- a/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs +++ b/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs @@ -26,7 +26,7 @@ namespace ImageSharp public static Image Polaroid(this Image source) where TPixel : struct, IPixel { - return Polaroid(source, source.Bounds); + return Polaroid(source, GraphicsOptions.Default); } /// @@ -41,7 +41,36 @@ namespace ImageSharp public static Image Polaroid(this Image source, Rectangle rectangle) where TPixel : struct, IPixel { - source.ApplyProcessor(new PolaroidProcessor(), rectangle); + return Polaroid(source, rectangle, GraphicsOptions.Default); + } + + /// + /// Alters the colors of the image recreating an old Polaroid camera effect. + /// + /// The pixel format. + /// The image this method extends. + /// The options effecting pixel blending. + /// The . + public static Image Polaroid(this Image source, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Polaroid(source, source.Bounds, options); + } + + /// + /// Alters the colors of the image recreating an old Polaroid camera effect. + /// + /// The pixel format. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The options effecting pixel blending. + /// The . + public static Image Polaroid(this Image source, Rectangle rectangle, GraphicsOptions options) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new PolaroidProcessor(options), rectangle); return source; } } diff --git a/src/ImageSharp/Processing/Effects/Alpha.cs b/src/ImageSharp/Processing/Effects/Alpha.cs index a54bde675..a2f721cfa 100644 --- a/src/ImageSharp/Processing/Effects/Alpha.cs +++ b/src/ImageSharp/Processing/Effects/Alpha.cs @@ -21,9 +21,9 @@ namespace ImageSharp /// /// The pixel format. /// The image this method extends. - /// The new opacity of the image. Must be between 0 and 100. + /// The new opacity of the image. Must be between 0 and 1. /// The . - public static Image Alpha(this Image source, int percent) + public static Image Alpha(this Image source, float percent) where TPixel : struct, IPixel { return Alpha(source, percent, source.Bounds); @@ -34,12 +34,12 @@ namespace ImageSharp /// /// The pixel format. /// The image this method extends. - /// The new opacity of the image. Must be between 0 and 100. + /// The new opacity of the image. Must be between 0 and 1. /// /// The structure that specifies the portion of the image object to alter. /// /// The . - public static Image Alpha(this Image source, int percent, Rectangle rectangle) + public static Image Alpha(this Image source, float percent, Rectangle rectangle) where TPixel : struct, IPixel { source.ApplyProcessor(new AlphaProcessor(percent), rectangle); diff --git a/src/ImageSharp/Processing/Effects/BackgroundColor.cs b/src/ImageSharp/Processing/Effects/BackgroundColor.cs index a1914fee3..f52cf1cb2 100644 --- a/src/ImageSharp/Processing/Effects/BackgroundColor.cs +++ b/src/ImageSharp/Processing/Effects/BackgroundColor.cs @@ -16,6 +16,38 @@ namespace ImageSharp /// public static partial class ImageExtensions { + /// + /// Replaces the background color of image with the given one. + /// + /// The pixel format. + /// The image this method extends. + /// The color to set as the background. + /// The options effecting pixel blending. + /// The . + public static Image BackgroundColor(this Image source, TPixel color, GraphicsOptions options) + where TPixel : struct, IPixel + { + return BackgroundColor(source, color, source.Bounds, options); + } + + /// + /// Replaces the background color of image with the given one. + /// + /// The pixel format. + /// The image this method extends. + /// The color to set as the background. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The options effecting pixel blending. + /// The . + public static Image BackgroundColor(this Image source, TPixel color, Rectangle rectangle, GraphicsOptions options) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new BackgroundColorProcessor(color, options), rectangle); + return source; + } + /// /// Replaces the background color of image with the given one. /// @@ -26,7 +58,7 @@ namespace ImageSharp public static Image BackgroundColor(this Image source, TPixel color) where TPixel : struct, IPixel { - return BackgroundColor(source, color, source.Bounds); + return BackgroundColor(source, color, GraphicsOptions.Default); } /// @@ -42,8 +74,7 @@ namespace ImageSharp public static Image BackgroundColor(this Image source, TPixel color, Rectangle rectangle) where TPixel : struct, IPixel { - source.ApplyProcessor(new BackgroundColorProcessor(color), rectangle); - return source; + return BackgroundColor(source, color, rectangle, GraphicsOptions.Default); } } } diff --git a/src/ImageSharp/Processing/Overlays/Glow.cs b/src/ImageSharp/Processing/Overlays/Glow.cs index 1be15ad65..587bbe610 100644 --- a/src/ImageSharp/Processing/Overlays/Glow.cs +++ b/src/ImageSharp/Processing/Overlays/Glow.cs @@ -25,7 +25,7 @@ namespace ImageSharp public static Image Glow(this Image source) where TPixel : struct, IPixel { - return Glow(source, NamedColors.Black, source.Bounds.Width * .5F, source.Bounds); + return Glow(source, GraphicsOptions.Default); } /// @@ -38,7 +38,7 @@ namespace ImageSharp public static Image Glow(this Image source, TPixel color) where TPixel : struct, IPixel { - return Glow(source, color, source.Bounds.Width * .5F, source.Bounds); + return Glow(source, color, GraphicsOptions.Default); } /// @@ -51,7 +51,7 @@ namespace ImageSharp public static Image Glow(this Image source, float radius) where TPixel : struct, IPixel { - return Glow(source, NamedColors.Black, radius, source.Bounds); + return Glow(source, radius, GraphicsOptions.Default); } /// @@ -66,7 +66,7 @@ namespace ImageSharp public static Image Glow(this Image source, Rectangle rectangle) where TPixel : struct, IPixel { - return Glow(source, NamedColors.Black, 0, rectangle); + return Glow(source, rectangle, GraphicsOptions.Default); } /// @@ -83,7 +83,82 @@ namespace ImageSharp public static Image Glow(this Image source, TPixel color, float radius, Rectangle rectangle) where TPixel : struct, IPixel { - GlowProcessor processor = new GlowProcessor(color) { Radius = radius, }; + return Glow(source, color, radius, rectangle, GraphicsOptions.Default); + } + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The options effecting things like blending. + /// The . + public static Image Glow(this Image source, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Glow(source, NamedColors.Black, source.Bounds.Width * .5F, source.Bounds, options); + } + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The color to set as the glow. + /// The options effecting things like blending. + /// The . + public static Image Glow(this Image source, TPixel color, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Glow(source, color, source.Bounds.Width * .5F, source.Bounds, options); + } + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The the radius. + /// The options effecting things like blending. + /// The . + public static Image Glow(this Image source, float radius, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Glow(source, NamedColors.Black, radius, source.Bounds, options); + } + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The options effecting things like blending. + /// The . + public static Image Glow(this Image source, Rectangle rectangle, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Glow(source, NamedColors.Black, 0, rectangle, options); + } + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The color to set as the glow. + /// The the radius. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The options effecting things like blending. + /// The . + public static Image Glow(this Image source, TPixel color, float radius, Rectangle rectangle, GraphicsOptions options) + where TPixel : struct, IPixel + { + GlowProcessor processor = new GlowProcessor(color, options) { Radius = radius, }; source.ApplyProcessor(processor, rectangle); return source; } diff --git a/src/ImageSharp/Processing/Overlays/Vignette.cs b/src/ImageSharp/Processing/Overlays/Vignette.cs index f805dd07a..2eaf2e1ef 100644 --- a/src/ImageSharp/Processing/Overlays/Vignette.cs +++ b/src/ImageSharp/Processing/Overlays/Vignette.cs @@ -25,7 +25,7 @@ namespace ImageSharp public static Image Vignette(this Image source) where TPixel : struct, IPixel { - return Vignette(source, NamedColors.Black, source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds); + return Vignette(source, GraphicsOptions.Default); } /// @@ -38,7 +38,7 @@ namespace ImageSharp public static Image Vignette(this Image source, TPixel color) where TPixel : struct, IPixel { - return Vignette(source, color, source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds); + return Vignette(source, color, GraphicsOptions.Default); } /// @@ -52,7 +52,7 @@ namespace ImageSharp public static Image Vignette(this Image source, float radiusX, float radiusY) where TPixel : struct, IPixel { - return Vignette(source, NamedColors.Black, radiusX, radiusY, source.Bounds); + return Vignette(source, radiusX, radiusY, GraphicsOptions.Default); } /// @@ -67,7 +67,7 @@ namespace ImageSharp public static Image Vignette(this Image source, Rectangle rectangle) where TPixel : struct, IPixel { - return Vignette(source, NamedColors.Black, 0, 0, rectangle); + return Vignette(source, rectangle, GraphicsOptions.Default); } /// @@ -85,7 +85,84 @@ namespace ImageSharp public static Image Vignette(this Image source, TPixel color, float radiusX, float radiusY, Rectangle rectangle) where TPixel : struct, IPixel { - VignetteProcessor processor = new VignetteProcessor(color) { RadiusX = radiusX, RadiusY = radiusY }; + return Vignette(source, color, radiusX, radiusY, rectangle, GraphicsOptions.Default); + } + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The options effecting pixel blending. + /// The . + public static Image Vignette(this Image source, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Vignette(source, NamedColors.Black, source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds, options); + } + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The color to set as the vignette. + /// The options effecting pixel blending. + /// The . + public static Image Vignette(this Image source, TPixel color, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Vignette(source, color, source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds, options); + } + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The the x-radius. + /// The the y-radius. + /// The options effecting pixel blending. + /// The . + public static Image Vignette(this Image source, float radiusX, float radiusY, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Vignette(source, NamedColors.Black, radiusX, radiusY, source.Bounds, options); + } + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The options effecting pixel blending. + /// The . + public static Image Vignette(this Image source, Rectangle rectangle, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Vignette(source, NamedColors.Black, 0, 0, rectangle, options); + } + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The color to set as the vignette. + /// The the x-radius. + /// The the y-radius. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The options effecting pixel blending. + /// The . + public static Image Vignette(this Image source, TPixel color, float radiusX, float radiusY, Rectangle rectangle, GraphicsOptions options) + where TPixel : struct, IPixel + { + VignetteProcessor processor = new VignetteProcessor(color, options) { RadiusX = radiusX, RadiusY = radiusY }; source.ApplyProcessor(processor, rectangle); return source; } diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/LomographProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/LomographProcessor.cs index 70b997997..f6480c183 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/LomographProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/LomographProcessor.cs @@ -18,6 +18,16 @@ namespace ImageSharp.Processing.Processors where TPixel : struct, IPixel { private static readonly TPixel VeryDarkGreen = ColorBuilder.FromRGBA(0, 10, 0, 255); + private GraphicsOptions options; + + /// + /// Initializes a new instance of the class. + /// + /// The options effecting blending and composition. + public LomographProcessor(GraphicsOptions options) + { + this.options = options; + } /// public override Matrix4x4 Matrix => new Matrix4x4() @@ -34,7 +44,7 @@ namespace ImageSharp.Processing.Processors /// protected override void AfterApply(ImageBase source, Rectangle sourceRectangle) { - new VignetteProcessor(VeryDarkGreen).Apply(source, sourceRectangle); + new VignetteProcessor(VeryDarkGreen, this.options).Apply(source, sourceRectangle); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs index ccc3c0060..5df034add 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs @@ -19,6 +19,16 @@ namespace ImageSharp.Processing.Processors { private static TPixel veryDarkOrange = ColorBuilder.FromRGB(102, 34, 0); private static TPixel lightOrange = ColorBuilder.FromRGBA(255, 153, 102, 178); + private GraphicsOptions options; + + /// + /// Initializes a new instance of the class. + /// + /// The options effecting blending and composition. + public PolaroidProcessor(GraphicsOptions options) + { + this.options = options; + } /// public override Matrix4x4 Matrix => new Matrix4x4() @@ -41,8 +51,8 @@ namespace ImageSharp.Processing.Processors /// protected override void AfterApply(ImageBase source, Rectangle sourceRectangle) { - new VignetteProcessor(veryDarkOrange).Apply(source, sourceRectangle); - new GlowProcessor(lightOrange) { Radius = source.Width / 4F }.Apply(source, sourceRectangle); + new VignetteProcessor(veryDarkOrange, this.options).Apply(source, sourceRectangle); + new GlowProcessor(lightOrange, this.options) { Radius = source.Width / 4F }.Apply(source, sourceRectangle); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs index a60106546..5e7310e32 100644 --- a/src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs @@ -21,26 +21,24 @@ namespace ImageSharp.Processing.Processors /// /// Initializes a new instance of the class. /// - /// The percentage to adjust the opacity of the image. Must be between 0 and 100. + /// The percentage to adjust the opacity of the image. Must be between 0 and 1. /// - /// is less than 0 or is greater than 100. + /// is less than 0 or is greater than 1. /// - public AlphaProcessor(int percent) + public AlphaProcessor(float percent) { - Guard.MustBeBetweenOrEqualTo(percent, 0, 100, nameof(percent)); + Guard.MustBeBetweenOrEqualTo(percent, 0, 1, nameof(percent)); this.Value = percent; } /// /// Gets the alpha value. /// - public int Value { get; } + public float Value { get; } /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { - float alpha = this.Value / 100F; - int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; @@ -63,7 +61,7 @@ namespace ImageSharp.Processing.Processors startY = 0; } - Vector4 alphaVector = new Vector4(1, 1, 1, alpha); + Vector4 alphaVector = new Vector4(1, 1, 1, this.Value); using (PixelAccessor sourcePixels = source.Lock()) { diff --git a/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs index 21973de3e..511a810b2 100644 --- a/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs @@ -18,13 +18,17 @@ namespace ImageSharp.Processing.Processors internal class BackgroundColorProcessor : ImageProcessor where TPixel : struct, IPixel { + private readonly GraphicsOptions options; + /// /// Initializes a new instance of the class. /// /// The to set the background color to. - public BackgroundColorProcessor(TPixel color) + /// The options defining blending algorithum and amount. + public BackgroundColorProcessor(TPixel color, GraphicsOptions options) { this.Value = color; + this.options = options; } /// @@ -57,10 +61,19 @@ namespace ImageSharp.Processing.Processors startY = 0; } - Vector4 backgroundColor = this.Value.ToVector4(); + int width = maxX - minX; + using (Buffer colors = new Buffer(width)) + using (Buffer amount = new Buffer(width)) using (PixelAccessor sourcePixels = source.Lock()) { + for (int i = 0; i < width; i++) + { + colors[i] = this.Value; + amount[i] = this.options.BlendPercentage; + } + + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(this.options.BlenderMode); Parallel.For( minY, maxY, @@ -68,26 +81,11 @@ namespace ImageSharp.Processing.Processors y => { int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; - Vector4 color = sourcePixels[offsetX, offsetY].ToVector4(); - float a = color.W; - - if (a < 1 && a > 0) - { - color = Vector4BlendTransforms.PremultipliedLerp(backgroundColor, color, .5F); - } - if (MathF.Abs(a) < Constants.Epsilon) - { - color = backgroundColor; - } + BufferSpan destination = sourcePixels.GetRowSpan(offsetY).Slice(minX - startX, width); - TPixel packed = default(TPixel); - packed.PackFromVector4(color); - sourcePixels[offsetX, offsetY] = packed; - } + // this switched color & destination in the 2nd and 3rd places because we are applying the target colour under the current one + blender.Blend(destination, colors, destination, amount); }); } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs index 049378256..5b5d64a9c 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs @@ -18,13 +18,19 @@ namespace ImageSharp.Processing.Processors internal class GlowProcessor : ImageProcessor where TPixel : struct, IPixel { + private readonly GraphicsOptions options; + private readonly PixelBlender blender; + /// /// Initializes a new instance of the class. /// /// The color or the glow. - public GlowProcessor(TPixel color) + /// The options effecting blending and composition. + public GlowProcessor(TPixel color, GraphicsOptions options) { + this.options = options; this.GlowColor = color; + this.blender = PixelOperations.Instance.GetPixelBlender(this.options.BlenderMode); } /// @@ -65,25 +71,36 @@ namespace ImageSharp.Processing.Processors startY = 0; } + int width = maxX - minX; + using (Buffer rowColors = new Buffer(width)) using (PixelAccessor sourcePixels = source.Lock()) { + for (int i = 0; i < width; i++) + { + rowColors[i] = glowColor; + } + Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; - float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY)); - Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4(); - TPixel packed = default(TPixel); - packed.PackFromVector4(Vector4BlendTransforms.PremultipliedLerp(sourceColor, glowColor.ToVector4(), 1 - (.95F * (distance / maxDistance)))); - sourcePixels[offsetX, offsetY] = packed; - } - }); + minY, + maxY, + this.ParallelOptions, + y => + { + using (Buffer amounts = new Buffer(width)) + { + int offsetY = y - startY; + int offsetX = minX - startX; + for (int i = 0; i < width; i++) + { + float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); + amounts[i] = (this.options.BlendPercentage * (1 - (.95F * (distance / maxDistance)))).Clamp(0, 1); + } + + BufferSpan destination = sourcePixels.GetRowSpan(offsetY).Slice(offsetX, width); + + this.blender.Blend(destination, destination, rowColors, amounts); + } + }); } } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs index 31e813564..d698b543c 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs @@ -18,13 +18,20 @@ namespace ImageSharp.Processing.Processors internal class VignetteProcessor : ImageProcessor where TPixel : struct, IPixel { + private readonly GraphicsOptions options; + private readonly PixelBlender blender; + /// /// Initializes a new instance of the class. /// /// The color of the vignette. - public VignetteProcessor(TPixel color) + /// The options effecting blending and composition. + public VignetteProcessor(TPixel color, GraphicsOptions options) { this.VignetteColor = color; + + this.options = options; + this.blender = PixelOperations.Instance.GetPixelBlender(this.options.BlenderMode); } /// @@ -72,23 +79,34 @@ namespace ImageSharp.Processing.Processors startY = 0; } + int width = maxX - minX; + using (Buffer rowColors = new Buffer(width)) using (PixelAccessor sourcePixels = source.Lock()) { + for (int i = 0; i < width; i++) + { + rowColors[i] = vignetteColor; + } + Parallel.For( minY, maxY, this.ParallelOptions, y => { - int offsetY = y - startY; - for (int x = minX; x < maxX; x++) + using (Buffer amounts = new Buffer(width)) { - int offsetX = x - startX; - float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY)); - Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4(); - TPixel packed = default(TPixel); - packed.PackFromVector4(Vector4BlendTransforms.PremultipliedLerp(sourceColor, vignetteColor.ToVector4(), .9F * (distance / maxDistance))); - sourcePixels[offsetX, offsetY] = packed; + int offsetY = y - startY; + int offsetX = minX - startX; + for (int i = 0; i < width; i++) + { + float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); + amounts[i] = (this.options.BlendPercentage * (.9F * (distance / maxDistance))).Clamp(0, 1); + } + + BufferSpan destination = sourcePixels.GetRowSpan(offsetY).Slice(offsetX, width); + + this.blender.Blend(destination, destination, rowColors, amounts); } }); } diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs index 31328d8b7..dde79a7e4 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -123,7 +123,7 @@ namespace ImageSharp.Processing.Processors { BufferSpan sourceRow = sourcePixels.GetRowSpan(y); - BulkPixelOperations.Instance.ToVector4( + PixelOperations.Instance.ToVector4( sourceRow, tempRowBuffer, sourceRow.Length); diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs index 2a370bc00..efec90c99 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs @@ -47,13 +47,13 @@ namespace ImageSharp.Benchmarks.Color.Bulk [Benchmark] public void CommonBulk() { - new BulkPixelOperations().PackFromXyzwBytes(this.source, this.destination, this.Count); + new PixelOperations().PackFromXyzwBytes(this.source, this.destination, this.Count); } [Benchmark] public void OptimizedBulk() { - BulkPixelOperations.Instance.PackFromXyzwBytes(this.source, this.destination, this.Count); + PixelOperations.Instance.PackFromXyzwBytes(this.source, this.destination, this.Count); } } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs index 1234a9946..e2c1ac726 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs @@ -47,13 +47,13 @@ namespace ImageSharp.Benchmarks.Color.Bulk [Benchmark] public void CommonBulk() { - new BulkPixelOperations().ToVector4(this.source, this.destination, this.Count); + new PixelOperations().ToVector4(this.source, this.destination, this.Count); } [Benchmark] public void OptimizedBulk() { - BulkPixelOperations.Instance.ToVector4(this.source, this.destination, this.Count); + PixelOperations.Instance.ToVector4(this.source, this.destination, this.Count); } } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs index fe201549b..88dac21cd 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs @@ -45,13 +45,13 @@ namespace ImageSharp.Benchmarks.Color.Bulk [Benchmark] public void CommonBulk() { - new BulkPixelOperations().ToXyzBytes(this.source, this.destination, this.Count); + new PixelOperations().ToXyzBytes(this.source, this.destination, this.Count); } [Benchmark] public void OptimizedBulk() { - BulkPixelOperations.Instance.ToXyzBytes(this.source, this.destination, this.Count); + PixelOperations.Instance.ToXyzBytes(this.source, this.destination, this.Count); } } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs index f7406d0f6..11545d3d9 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs @@ -49,13 +49,13 @@ namespace ImageSharp.Benchmarks.Color.Bulk [Benchmark] public void CommonBulk() { - new BulkPixelOperations().ToXyzwBytes(this.source, this.destination, this.Count); + new PixelOperations().ToXyzwBytes(this.source, this.destination, this.Count); } [Benchmark] public void OptimizedBulk() { - BulkPixelOperations.Instance.ToXyzwBytes(this.source, this.destination, this.Count); + PixelOperations.Instance.ToXyzwBytes(this.source, this.destination, this.Count); } } diff --git a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs new file mode 100644 index 000000000..a616733b5 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs @@ -0,0 +1,103 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Benchmarks +{ + + using BenchmarkDotNet.Attributes; + using ImageSharp.PixelFormats; + using ImageSharp.Drawing; + using ImageSharp.Processing.Processors; + using CoreImage = ImageSharp.Image; + using CoreSize = ImageSharp.Size; + using System.Numerics; + using ImageSharp.PixelFormats.PixelBlenders; + + public class PorterDuffBulkVsPixel : BenchmarkBase + { + private void BulkVectorConvert(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + where TPixel : struct, IPixel + { + Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length)); + + using (Buffer buffer = new Buffer(destination.Length * 3)) + { + BufferSpan destinationSpan = buffer.Slice(0, destination.Length); + BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length); + BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + + PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); + PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); + + for (int i = 0; i < destination.Length; i++) + { + destinationSpan[i] = PorterDuffFunctions.NormalBlendFunction(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + private void BulkPixelConvert(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + where TPixel : struct, IPixel + { + Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalBlendFunction(destination[i], source[i], amount[i]); + } + } + + [Benchmark(Description = "ImageSharp BulkVectorConvert")] + public CoreSize BulkVectorConvert() + { + using (CoreImage image = new CoreImage(800, 800)) + { + Buffer amounts = new Buffer(image.Width); + + for (int x = 0; x < image.Width; x++) + { + amounts[x] = 1; + } + using (PixelAccessor pixels = image.Lock()) + { + for (int y = 0; y < image.Height; y++) + { + BufferSpan span = pixels.GetRowSpan(y); + BulkVectorConvert(span, span, span, amounts); + } + } + return new CoreSize(image.Width, image.Height); + } + } + + [Benchmark(Description = "ImageSharp BulkPixelConvert")] + public CoreSize BulkPixelConvert() + { + using (CoreImage image = new CoreImage(800, 800)) + { + Buffer amounts = new Buffer(image.Width); + + for (int x = 0; x < image.Width; x++) + { + amounts[x] = 1; + } + using (PixelAccessor pixels = image.Lock()) + { + for (int y = 0; y < image.Height; y++) + { + BufferSpan span = pixels.GetRowSpan(y); + BulkPixelConvert(span, span, span, amounts); + } + } + return new CoreSize(image.Width, image.Height); + } + } + } +} diff --git a/tests/ImageSharp.Benchmarks/Samplers/Glow.cs b/tests/ImageSharp.Benchmarks/Samplers/Glow.cs new file mode 100644 index 000000000..6daf120fa --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Samplers/Glow.cs @@ -0,0 +1,164 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Benchmarks +{ + + using BenchmarkDotNet.Attributes; + using ImageSharp.PixelFormats; + using ImageSharp.Drawing; + using ImageSharp.Processing.Processors; + using CoreImage = ImageSharp.Image; + using CoreSize = ImageSharp.Size; + using ImageSharp.Processing; + using System.Numerics; + using System; + using System.Threading.Tasks; + + public class Glow : BenchmarkBase + { + private GlowProcessor bulk; + private GlowProcessorParallel parallel; + + [Setup] + public void Setup() + { + this.bulk = new GlowProcessor(NamedColors.Beige, GraphicsOptions.Default) { Radius = 800 * .5f, }; + this.parallel = new GlowProcessorParallel(NamedColors.Beige) { Radius = 800 * .5f, }; + + } + [Benchmark(Description = "ImageSharp Glow - Bulk")] + public CoreSize GlowBulk() + { + using (CoreImage image = new CoreImage(800, 800)) + { + image.ApplyProcessor(bulk, image.Bounds); + return new CoreSize(image.Width, image.Height); + } + } + + [Benchmark(Description = "ImageSharp Glow - Parallel")] + public CoreSize GLowSimple() + { + using (CoreImage image = new CoreImage(800, 800)) + { + image.ApplyProcessor(parallel, image.Bounds); + return new CoreSize(image.Width, image.Height); + } + } + + internal class GlowProcessorParallel : ImageProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The color or the glow. + public GlowProcessorParallel(TPixel color) + { + this.GlowColor = color; + } + + /// + /// Gets or sets the glow color to apply. + /// + public TPixel GlowColor { get; set; } + + /// + /// Gets or sets the the radius. + /// + public float Radius { get; set; } + + /// + protected override void OnApply(ImageBase source, Rectangle sourceRectangle) + { + int startY = sourceRectangle.Y; + int endY = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + TPixel glowColor = this.GlowColor; + Vector2 centre = Rectangle.Center(sourceRectangle).ToVector2(); + float maxDistance = this.Radius > 0 ? MathF.Min(this.Radius, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; + + // Align start/end positions. + int minX = Math.Max(0, startX); + int maxX = Math.Min(source.Width, endX); + int minY = Math.Max(0, startY); + int maxY = Math.Min(source.Height, endY); + + // Reset offset if necessary. + if (minX > 0) + { + startX = 0; + } + + if (minY > 0) + { + startY = 0; + } + + int width = maxX - minX; + using (Buffer rowColors = new Buffer(width)) + using (PixelAccessor sourcePixels = source.Lock()) + { + for (int i = 0; i < width; i++) + { + rowColors[i] = glowColor; + } + + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + int offsetY = y - startY; + + for (int x = minX; x < maxX; x++) + { + int offsetX = x - startX; + float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY)); + Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4(); + TPixel packed = default(TPixel); + packed.PackFromVector4(PremultipliedLerp(sourceColor, glowColor.ToVector4(), 1 - (.95F * (distance / maxDistance)))); + sourcePixels[offsetX, offsetY] = packed; + } + }); + } + } + public static Vector4 PremultipliedLerp(Vector4 backdrop, Vector4 source, float amount) + { + amount = amount.Clamp(0, 1); + + // Santize on zero alpha + if (MathF.Abs(backdrop.W) < Constants.Epsilon) + { + source.W *= amount; + return source; + } + + if (MathF.Abs(source.W) < Constants.Epsilon) + { + return backdrop; + } + + // Premultiply the source vector. + // Oddly premultiplying the background vector creates dark outlines when pixels + // Have low alpha values. + source = new Vector4(source.X, source.Y, source.Z, 1) * (source.W * amount); + + // This should be implementing the following formula + // https://en.wikipedia.org/wiki/Alpha_compositing + // Vout = Vs + Vb (1 - Vsa) + // Aout = Vsa + Vsb (1 - Vsa) + Vector3 inverseW = new Vector3(1 - source.W); + Vector3 xyzB = new Vector3(backdrop.X, backdrop.Y, backdrop.Z); + Vector3 xyzS = new Vector3(source.X, source.Y, source.Z); + + return new Vector4(xyzS + (xyzB * inverseW), source.W + (backdrop.W * (1 - source.W))); + } + } + } +} diff --git a/tests/ImageSharp.Sandbox46/Program.cs b/tests/ImageSharp.Sandbox46/Program.cs index 1bd51d8f3..7ea459aae 100644 --- a/tests/ImageSharp.Sandbox46/Program.cs +++ b/tests/ImageSharp.Sandbox46/Program.cs @@ -53,7 +53,7 @@ namespace ImageSharp.Sandbox46 private static void RunToVector4ProfilingTest() { - BulkPixelOperationsTests.Color32 tests = new BulkPixelOperationsTests.Color32(new ConsoleOutput()); + PixelOperationsTests.Color32 tests = new PixelOperationsTests.Color32(new ConsoleOutput()); tests.Benchmark_ToVector4(); } diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/PixelOperationsTests.cs similarity index 96% rename from tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs rename to tests/ImageSharp.Tests/Colors/PixelOperationsTests.cs index b498c93ac..3d0392753 100644 --- a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/Colors/PixelOperationsTests.cs @@ -10,7 +10,7 @@ namespace ImageSharp.Tests.Colors using Xunit; using Xunit.Abstractions; - public class BulkPixelOperationsTests + public class PixelOperationsTests { public class Color32 : BulkPixelOperationsTests { @@ -25,7 +25,7 @@ namespace ImageSharp.Tests.Colors [Fact] public void IsSpecialImplementation() { - Assert.IsType(BulkPixelOperations.Instance); + Assert.IsType(PixelOperations.Instance); } [Fact] @@ -37,7 +37,7 @@ namespace ImageSharp.Tests.Colors TestOperation( source, expected, - (s, d) => Rgba32.BulkOperations.ToVector4SimdAligned(s, d, 64) + (s, d) => Rgba32.PixelOperations.ToVector4SimdAligned(s, d, 64) ); } @@ -54,7 +54,7 @@ namespace ImageSharp.Tests.Colors times, () => { - BulkPixelOperations.Instance.ToVector4(source, dest, count); + PixelOperations.Instance.ToVector4(source, dest, count); }); } } @@ -76,7 +76,7 @@ namespace ImageSharp.Tests.Colors public void GetGlobalInstance(TestImageProvider dummy) where TPixel : struct, IPixel { - Assert.NotNull(BulkPixelOperations.Instance); + Assert.NotNull(PixelOperations.Instance); } } @@ -90,7 +90,7 @@ namespace ImageSharp.Tests.Colors public static TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; - private static BulkPixelOperations Operations => BulkPixelOperations.Instance; + private static PixelOperations Operations => PixelOperations.Instance; internal static TPixel[] CreateExpectedPixelData(Vector4[] source) { diff --git a/tests/ImageSharp.Tests/Colors/Rgba32TransformTests.cs b/tests/ImageSharp.Tests/Colors/Rgba32TransformTests.cs deleted file mode 100644 index 8d5e973b1..000000000 --- a/tests/ImageSharp.Tests/Colors/Rgba32TransformTests.cs +++ /dev/null @@ -1,118 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests.Colors -{ - using ImageSharp.PixelFormats; - - using Xunit; - - /// - /// Tests the color transform algorithms. Test results match the output of CSS equivalents. - /// - /// - public class ColorTransformTests - { - /// - /// Orange backdrop - /// - private static readonly Rgba32 Backdrop = new Rgba32(204, 102, 0); - - /// - /// Blue source - /// - private static readonly Rgba32 Source = new Rgba32(0, 102, 153); - - [Fact] - public void Normal() - { - Rgba32 normal = Rgba32.Normal(Backdrop, Source); - Assert.True(normal == Source); - } - - [Fact] - public void Multiply() - { - Assert.True(Rgba32.Multiply(Backdrop, Rgba32.Black) == Rgba32.Black); - Assert.True(Rgba32.Multiply(Backdrop, Rgba32.White) == Backdrop); - - Rgba32 multiply = Rgba32.Multiply(Backdrop, Source); - Assert.True(multiply == new Rgba32(0, 41, 0)); - } - - [Fact] - public void Screen() - { - Assert.True(Rgba32.Screen(Backdrop, Rgba32.Black) == Backdrop); - Assert.True(Rgba32.Screen(Backdrop, Rgba32.White) == Rgba32.White); - - Rgba32 screen = Rgba32.Screen(Backdrop, Source); - Assert.True(screen == new Rgba32(204, 163, 153)); - } - - [Fact] - public void HardLight() - { - Rgba32 hardLight = Rgba32.HardLight(Backdrop, Source); - Assert.True(hardLight == new Rgba32(0, 82, 51)); - } - - [Fact] - public void Overlay() - { - Rgba32 overlay = Rgba32.Overlay(Backdrop, Source); - Assert.True(overlay == new Rgba32(153, 82, 0)); - } - - [Fact] - public void Darken() - { - Rgba32 darken = Rgba32.Darken(Backdrop, Source); - Assert.True(darken == new Rgba32(0, 102, 0)); - } - - [Fact] - public void Lighten() - { - Rgba32 lighten = Rgba32.Lighten(Backdrop, Source); - Assert.True(lighten == new Rgba32(204, 102, 153)); - } - - [Fact] - public void SoftLight() - { - Rgba32 softLight = Rgba32.SoftLight(Backdrop, Source); - Assert.True(softLight == new Rgba32(163, 90, 0)); - } - - [Fact] - public void ColorDodge() - { - Rgba32 colorDodge = Rgba32.ColorDodge(Backdrop, Source); - Assert.True(colorDodge == new Rgba32(204, 170, 0)); - } - - [Fact] - public void ColorBurn() - { - Rgba32 colorBurn = Rgba32.ColorBurn(Backdrop, Source); - Assert.True(colorBurn == new Rgba32(0, 0, 0)); - } - - [Fact] - public void Difference() - { - Rgba32 difference = Rgba32.Difference(Backdrop, Source); - Assert.True(difference == new Rgba32(204, 0, 153)); - } - - [Fact] - public void Exclusion() - { - Rgba32 exclusion = Rgba32.Exclusion(Backdrop, Source); - Assert.True(exclusion == new Rgba32(204, 122, 153)); - } - } -} diff --git a/tests/ImageSharp.Tests/Colors/RgbaVectorTransformTests.cs b/tests/ImageSharp.Tests/Colors/RgbaVectorTransformTests.cs deleted file mode 100644 index 81cbb63c4..000000000 --- a/tests/ImageSharp.Tests/Colors/RgbaVectorTransformTests.cs +++ /dev/null @@ -1,120 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests.Colors -{ - using ImageSharp.PixelFormats; - using Xunit; - - /// - /// Tests the color transform algorithms. Test results match the output of CSS equivalents. - /// - /// - public class RgbaVectorTransformTests - { - private static readonly ApproximateFloatComparer FloatComparer = new ApproximateFloatComparer(0.01F); - - /// - /// Orange backdrop - /// - private static readonly RgbaVector Backdrop = new RgbaVector(204, 102, 0); - - /// - /// Blue source - /// - private static readonly RgbaVector Source = new RgbaVector(0, 102, 153); - - [Fact] - public void Normal() - { - RgbaVector normal = RgbaVector.Normal(Backdrop, Source); - Assert.True(normal == Source); - } - - // TODO: These tests keep sporadically breaking our builds. Fins out why they work locally but not on the CI. - // [Fact] - // public void Multiply() - // { - // Assert.Equal(RgbaVector.Multiply(Backdrop, RgbaVector.Black).ToVector4(), RgbaVector.Black.ToVector4(), FloatComparer); - // Assert.Equal(RgbaVector.Multiply(Backdrop, RgbaVector.White).ToVector4(), Backdrop.ToVector4(), FloatComparer); - - // RgbaVector multiply = RgbaVector.Multiply(Backdrop, Source); - // Assert.Equal(multiply.ToVector4(), new RgbaVector(0, 41, 0).ToVector4(), FloatComparer); - // } - - // [Fact] - // public void Screen() - // { - // Assert.Equal(RgbaVector.Screen(Backdrop, RgbaVector.Black).ToVector4(), Backdrop.ToVector4(), FloatComparer); - // Assert.Equal(RgbaVector.Screen(Backdrop, RgbaVector.White).ToVector4(), RgbaVector.White.ToVector4(), FloatComparer); - - // RgbaVector screen = RgbaVector.Screen(Backdrop, Source); - // Assert.Equal(screen.ToVector4(), new RgbaVector(204, 163, 153).ToVector4(), FloatComparer); - // } - - [Fact] - public void HardLight() - { - RgbaVector hardLight = RgbaVector.HardLight(Backdrop, Source); - Assert.Equal(hardLight.ToVector4(), new RgbaVector(0, 82, 51).ToVector4(), FloatComparer); - } - - [Fact] - public void Overlay() - { - RgbaVector overlay = RgbaVector.Overlay(Backdrop, Source); - Assert.Equal(overlay.ToVector4(), new RgbaVector(153, 82, 0).ToVector4(), FloatComparer); - } - - [Fact] - public void Darken() - { - RgbaVector darken = RgbaVector.Darken(Backdrop, Source); - Assert.Equal(darken.ToVector4(), new RgbaVector(0, 102, 0).ToVector4(), FloatComparer); - } - - [Fact] - public void Lighten() - { - RgbaVector lighten = RgbaVector.Lighten(Backdrop, Source); - Assert.Equal(lighten.ToVector4(), new RgbaVector(204, 102, 153).ToVector4(), FloatComparer); - } - - [Fact] - public void SoftLight() - { - RgbaVector softLight = RgbaVector.SoftLight(Backdrop, Source); - Assert.Equal(softLight.ToVector4(), new RgbaVector(163, 90, 0).ToVector4(), FloatComparer); - } - - [Fact] - public void ColorDodge() - { - RgbaVector colorDodge = RgbaVector.ColorDodge(Backdrop, Source); - Assert.Equal(colorDodge.ToVector4(), new RgbaVector(204, 170, 0).ToVector4(), FloatComparer); - } - - [Fact] - public void ColorBurn() - { - RgbaVector colorBurn = RgbaVector.ColorBurn(Backdrop, Source); - Assert.Equal(colorBurn.ToVector4(), new RgbaVector(0, 0, 0).ToVector4(), FloatComparer); - } - - [Fact] - public void Difference() - { - RgbaVector difference = RgbaVector.Difference(Backdrop, Source); - Assert.Equal(difference.ToVector4(), new RgbaVector(204, 0, 153).ToVector4(), FloatComparer); - } - - [Fact] - public void Exclusion() - { - RgbaVector exclusion = RgbaVector.Exclusion(Backdrop, Source); - Assert.Equal(exclusion.ToVector4(), new RgbaVector(204, 122, 153).ToVector4(), FloatComparer); - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/BlendedShapes.cs b/tests/ImageSharp.Tests/Drawing/BlendedShapes.cs new file mode 100644 index 000000000..6c742c2e0 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/BlendedShapes.cs @@ -0,0 +1,56 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Drawing +{ + using System; + using System.Linq; + using System.Collections.Generic; + using System.Text; + using ImageSharp.PixelFormats; + using Xunit; + + public class BlendedShapes + { + public static IEnumerable modes = ((PixelBlenderMode[])Enum.GetValues(typeof(PixelBlenderMode))) + .Select(x=> new object[] { x }); + + [Theory] + [WithBlankImages(nameof(modes), 100, 100, PixelTypes.StandardImageClass)] + public void DrawBlendedValues(TestImageProvider provider, PixelBlenderMode mode) + where TPixel : struct, IPixel + { + using (var img = provider.GetImage()) + { + img.Fill(NamedColors.DarkBlue, new Rectangle(0, 40, 100, 20)); + img.Fill(NamedColors.HotPink, new Rectangle(40, 0, 20, 100), new ImageSharp.GraphicsOptions(true) + { + BlenderMode = mode + }); + img.DebugSave(provider, new { mode }); + } + } + + [Theory] + [WithBlankImages(nameof(modes), 100, 100, PixelTypes.StandardImageClass)] + public void DrawBlendedValues_transparent(TestImageProvider provider, PixelBlenderMode mode) + where TPixel : struct, IPixel + { + using (var img = provider.GetImage()) + { + img.Fill(NamedColors.DarkBlue, new Rectangle(0, 40, 100, 20)); + img.Fill(NamedColors.HotPink, new Rectangle(20, 0, 40, 100), new ImageSharp.GraphicsOptions(true) + { + BlenderMode = mode + }); + img.Fill(NamedColors.Transparent, new Rectangle(40, 0, 20, 100), new ImageSharp.GraphicsOptions(true) + { + BlenderMode = mode + }); + img.DebugSave(provider, new { mode }); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs index 3a59de624..030034a8f 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs @@ -6,30 +6,42 @@ namespace ImageSharp.Tests { using System.IO; - + using System.Linq; + using ImageSharp.PixelFormats; using Xunit; public class DrawImageTest : FileTestBase { - [Fact] - public void ImageShouldApplyDrawImageFilter() - { - string path = this.CreateOutputDirectory("Drawing", "DrawImage"); + private const PixelTypes PixelTypes = Tests.PixelTypes.StandardImageClass; + + public static readonly string[] TestFiles = { + TestImages.Jpeg.Baseline.Calliphora, + TestImages.Bmp.Car, + TestImages.Png.Splash, + TestImages.Gif.Rings + }; - using (Image blend = TestFile.Create(TestImages.Bmp.Car).CreateImage()) + object[][] Modes = System.Enum.GetValues(typeof(PixelBlenderMode)).Cast().Select(x => new object[] { x }).ToArray(); + + [Theory] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Normal)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Multiply)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Add)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Substract)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Screen)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Darken)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Lighten)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Overlay)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.HardLight)] + public void ImageShouldApplyDrawImage(TestImageProvider provider, PixelBlenderMode mode) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + using (Image blend = Image.Load(TestFile.Create(TestImages.Bmp.Car).Bytes)) { - foreach (TestFile file in Files) - { - using (Image image = file.CreateImage()) - { - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.DrawImage(blend, 75, new Size(image.Width / 2, image.Height / 2), new Point(image.Width / 4, image.Height / 4)) - .Save(output); - } - } - } + image.DrawImage(blend, mode, .75f, new Size(image.Width / 2, image.Height / 2), new Point(image.Width / 4, image.Height / 4)) + .DebugSave(provider, new { mode }); } } } -} \ No newline at end of file +} diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index ff5eccc78..55b3c80e3 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -6,6 +6,9 @@ portable True + + + diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs new file mode 100644 index 000000000..45962c589 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs @@ -0,0 +1,183 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.PixelFormats.PixelBlenders +{ + using System; + using System.Collections.Generic; + using System.Numerics; + using System.Text; + using ImageSharp.PixelFormats.PixelBlenders; + using ImageSharp.Tests.TestUtilities; + using Xunit; + + public class PorterDuffFunctionsTests + { + public static TheoryData NormalBlendFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1) }, + }; + + [Theory] + [MemberData(nameof(NormalBlendFunctionData))] + public void NormalBlendFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.NormalBlendFunction(back, source, amount); + Assert.Equal(expected, actual); + } + + public static TheoryData MultiplyFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1) }, + { + new TestVector4(0.9f,0.9f,0.9f,0.9f), + new TestVector4(0.4f,0.4f,0.4f,0.4f), + .5f, + new TestVector4(0.7834783f, 0.7834783f, 0.7834783f, 0.92f) + }, + }; + + [Theory] + [MemberData(nameof(MultiplyFunctionData))] + public void MultiplyFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.MultiplyFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 5); + } + + public static TheoryData AddFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(.6f, .6f, .6f, 1f) }, + { + new TestVector4(0.2f,0.2f,0.2f,0.3f), + new TestVector4(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestVector4(.2075676f, .2075676f, .2075676f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(AddFunctionData))] + public void AddFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.MultiplyFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 5); + } + + public static TheoryData SubstractFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(0,0,0,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(1,1,1, 1f) }, + { + new TestVector4(0.2f,0.2f,0.2f,0.3f), + new TestVector4(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestVector4(.2027027f, .2027027f, .2027027f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(SubstractFunctionData))] + public void SubstractFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.SubstractFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 5); + } + + public static TheoryData ScreenFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(1,1,1, 1f) }, + { + new TestVector4(0.2f,0.2f,0.2f,0.3f), + new TestVector4(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestVector4(.2383784f, .2383784f, .2383784f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(ScreenFunctionData))] + public void ScreenFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.ScreenFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 5); + } + + public static TheoryData DarkenFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(.6f,.6f,.6f, 1f) }, + { + new TestVector4(0.2f,0.2f,0.2f,0.3f), + new TestVector4(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestVector4(.2189189f, .2189189f, .2189189f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(DarkenFunctionData))] + public void DarkenFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.DarkenFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 5); + } + + public static TheoryData LightenFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(1,1,1,1f) }, + { + new TestVector4(0.2f,0.2f,0.2f,0.3f), + new TestVector4(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestVector4(.227027f, .227027f, .227027f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(LightenFunctionData))] + public void LightenFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.LightenFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 5); + } + + public static TheoryData OverlayFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(1,1,1,1f) }, + { + new TestVector4(0.2f,0.2f,0.2f,0.3f), + new TestVector4(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestVector4(.2124324f, .2124324f, .2124324f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(OverlayFunctionData))] + public void OverlayFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.OverlayFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 5); + } + + public static TheoryData HardLightFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(0.6f,0.6f,0.6f,1f) }, + { + new TestVector4(0.2f,0.2f,0.2f,0.3f), + new TestVector4(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestVector4(.2124324f, .2124324f, .2124324f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(HardLightFunctionData))] + public void HardLightFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.HardLightFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 5); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs new file mode 100644 index 000000000..5fa1fccbc --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs @@ -0,0 +1,370 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.PixelFormats.PixelBlenders +{ + using System; + using System.Collections.Generic; + using System.Numerics; + using System.Text; + using ImageSharp.PixelFormats; + using ImageSharp.PixelFormats.PixelBlenders; + using ImageSharp.Tests.TestUtilities; + using Xunit; + + public class PorterDuffFunctionsTests_TPixel + { + private static BufferSpan AsSpan(T value) + where T : struct + { + return new BufferSpan(new[] { value }); + } + + public static TheoryData NormalBlendFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(0.6f, 0.6f, 0.6f, 1) }, + }; + + [Theory] + [MemberData(nameof(NormalBlendFunctionData))] + public void NormalBlendFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.NormalBlendFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(NormalBlendFunctionData))] + public void NormalBlendFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultNormalPixelBlender().Blend(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(NormalBlendFunctionData))] + public void NormalBlendFunction_Blender_Bulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + BufferSpan dest = new BufferSpan(new TPixel[1]); + new DefaultNormalPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } + + public static TheoryData MultiplyFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(0.6f, 0.6f, 0.6f, 1) }, + { + new TestPixel(0.9f,0.9f,0.9f,0.9f), + new TestPixel(0.4f,0.4f,0.4f,0.4f), + .5f, + new TestPixel(0.7834783f, 0.7834783f, 0.7834783f, 0.92f) + }, + }; + + [Theory] + [MemberData(nameof(MultiplyFunctionData))] + public void MultiplyFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.MultiplyFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(MultiplyFunctionData))] + public void MultiplyFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultMultiplyPixelBlender().Blend(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(MultiplyFunctionData))] + public void MultiplyFunction_Blender_Bulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + BufferSpan dest = new BufferSpan(new TPixel[1]); + new DefaultMultiplyPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } + + public static TheoryData AddFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1f, 1f, 1f, 1f) }, + { + new TestPixel(0.2f,0.2f,0.2f,0.3f), + new TestPixel(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestPixel(.2431373f, .2431373f, .2431373f, .372549f) + }, + }; + + [Theory] + [MemberData(nameof(AddFunctionData))] + public void AddFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.AddFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(AddFunctionData))] + public void AddFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultAddPixelBlender().Blend(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(AddFunctionData))] + public void AddFunction_Blender_Bulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + BufferSpan dest = new BufferSpan(new TPixel[1]); + new DefaultAddPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } + + public static TheoryData SubstractFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(0,0,0,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1,1,1, 1f) }, + { + new TestPixel(0.2f,0.2f,0.2f,0.3f), + new TestPixel(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestPixel(.2027027f, .2027027f, .2027027f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(SubstractFunctionData))] + public void SubstractFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.SubstractFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(SubstractFunctionData))] + public void SubstractFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultSubstractPixelBlender().Blend(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(SubstractFunctionData))] + public void SubstractFunction_Blender_Bulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + BufferSpan dest = new BufferSpan(new TPixel[1]); + new DefaultSubstractPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } + + public static TheoryData ScreenFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1,1,1, 1f) }, + { + new TestPixel(0.2f,0.2f,0.2f,0.3f), + new TestPixel(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestPixel(.2383784f, .2383784f, .2383784f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(ScreenFunctionData))] + public void ScreenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.ScreenFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(ScreenFunctionData))] + public void ScreenFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultScreenPixelBlender().Blend(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(ScreenFunctionData))] + public void ScreenFunction_Blender_Bulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + BufferSpan dest = new BufferSpan(new TPixel[1]); + new DefaultScreenPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } + + public static TheoryData DarkenFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(.6f,.6f,.6f, 1f) }, + { + new TestPixel(0.2f,0.2f,0.2f,0.3f), + new TestPixel(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestPixel(.2189189f, .2189189f, .2189189f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(DarkenFunctionData))] + public void DarkenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.DarkenFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(DarkenFunctionData))] + public void DarkenFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultDarkenPixelBlender().Blend(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(DarkenFunctionData))] + public void DarkenFunction_Blender_Bulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + BufferSpan dest = new BufferSpan(new TPixel[1]); + new DefaultDarkenPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } + + public static TheoryData LightenFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1,1,1,1f) }, + { + new TestPixel(0.2f,0.2f,0.2f,0.3f), + new TestPixel(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestPixel(.227027f, .227027f, .227027f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(LightenFunctionData))] + public void LightenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.LightenFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(LightenFunctionData))] + public void LightenFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultLightenPixelBlender().Blend(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(LightenFunctionData))] + public void LightenFunction_Blender_Bulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + BufferSpan dest = new BufferSpan(new TPixel[1]); + new DefaultLightenPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } + + public static TheoryData OverlayFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1,1,1,1f) }, + { + new TestPixel(0.2f,0.2f,0.2f,0.3f), + new TestPixel(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestPixel(.2124324f, .2124324f, .2124324f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(OverlayFunctionData))] + public void OverlayFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.OverlayFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(OverlayFunctionData))] + public void OverlayFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultOverlayPixelBlender().Blend(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(OverlayFunctionData))] + public void OverlayFunction_Blender_Bulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + BufferSpan dest = new BufferSpan(new TPixel[1]); + new DefaultOverlayPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } + + public static TheoryData HardLightFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(0.6f,0.6f,0.6f,1f) }, + { + new TestPixel(0.2f,0.2f,0.2f,0.3f), + new TestPixel(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestPixel(.2124324f, .2124324f, .2124324f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(HardLightFunctionData))] + public void HardLightFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.HardLightFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(HardLightFunctionData))] + public void HardLightFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultHardLightPixelBlender().Blend(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(HardLightFunctionData))] + public void HardLightFunction_Blender_Bulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + BufferSpan dest = new BufferSpan(new TPixel[1]); + new DefaultHardLightPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations.cs new file mode 100644 index 000000000..a9108692e --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.PixelFormats +{ + using System; + using System.Collections.Generic; + using System.Text; + using ImageSharp.PixelFormats; + using ImageSharp.PixelFormats.PixelBlenders; + using ImageSharp.Tests.TestUtilities; + using Xunit; + + public class PixelOperations + { + public static TheoryData blenderMappings = new TheoryData() + { + { new TestPixel(), typeof(DefaultNormalPixelBlender), PixelBlenderMode.Normal }, + { new TestPixel(), typeof(DefaultScreenPixelBlender), PixelBlenderMode.Screen }, + { new TestPixel(), typeof(DefaultHardLightPixelBlender), PixelBlenderMode.HardLight }, + { new TestPixel(), typeof(DefaultOverlayPixelBlender), PixelBlenderMode.Overlay }, + { new TestPixel(), typeof(DefaultDarkenPixelBlender), PixelBlenderMode.Darken }, + { new TestPixel(), typeof(DefaultLightenPixelBlender), PixelBlenderMode.Lighten }, + { new TestPixel(), typeof(DefaultAddPixelBlender), PixelBlenderMode.Add }, + { new TestPixel(), typeof(DefaultSubstractPixelBlender), PixelBlenderMode.Substract }, + { new TestPixel(), typeof(DefaultMultiplyPixelBlender), PixelBlenderMode.Multiply }, + + { new TestPixel(), typeof(DefaultNormalPixelBlender), PixelBlenderMode.Normal }, + { new TestPixel(), typeof(DefaultScreenPixelBlender), PixelBlenderMode.Screen }, + { new TestPixel(), typeof(DefaultHardLightPixelBlender), PixelBlenderMode.HardLight }, + { new TestPixel(), typeof(DefaultOverlayPixelBlender), PixelBlenderMode.Overlay }, + { new TestPixel(), typeof(DefaultDarkenPixelBlender), PixelBlenderMode.Darken }, + { new TestPixel(), typeof(DefaultLightenPixelBlender), PixelBlenderMode.Lighten }, + { new TestPixel(), typeof(DefaultAddPixelBlender), PixelBlenderMode.Add }, + { new TestPixel(), typeof(DefaultSubstractPixelBlender), PixelBlenderMode.Substract }, + { new TestPixel(), typeof(DefaultMultiplyPixelBlender), PixelBlenderMode.Multiply }, + }; + + [Theory] + [MemberData(nameof(blenderMappings))] + public void ReturnsCorrectBlender(TestPixel pixel, Type type, PixelBlenderMode mode) + where TPixel : struct, IPixel + { + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(mode); + Assert.IsType(type, blender); + } + } +} diff --git a/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs b/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs index 2b39086e3..e1557abca 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs @@ -11,11 +11,11 @@ namespace ImageSharp.Tests public class AlphaTest : FileTestBase { - public static readonly TheoryData AlphaValues - = new TheoryData + public static readonly TheoryData AlphaValues + = new TheoryData { - 20 , - 80 + 20/100f , + 80/100f }; [Theory] diff --git a/tests/ImageSharp.Tests/Processors/Filters/GlowTest.cs b/tests/ImageSharp.Tests/Processors/Filters/GlowTest.cs index ad1048845..ebbfdd5f0 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/GlowTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/GlowTest.cs @@ -71,7 +71,7 @@ namespace ImageSharp.Tests using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Glow(new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2)) + image.Glow(new Rectangle(image.Width / 8, image.Height / 8, image.Width / 2, image.Height / 2)) .Save(output); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs b/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs new file mode 100644 index 000000000..7e3d318c0 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Text; +using ImageSharp.PixelFormats; +using Xunit.Abstractions; + +namespace ImageSharp.Tests.TestUtilities +{ + public class TestPixel : IXunitSerializable + where TPixel : struct, IPixel + { + public TestPixel() + { + } + + public TestPixel(float red, float green, float blue, float alpha) + { + this.Red = red; + this.Green = green; + this.Blue = blue; + this.Alpha = alpha; + } + + public float Red { get; set; } + public float Green { get; set; } + public float Blue { get; set; } + public float Alpha { get; set; } + + public static implicit operator TPixel(TestPixel d) + { + return d?.AsPixel() ?? default(TPixel); + } + + public TPixel AsPixel() + { + TPixel pix = default(TPixel); + pix.PackFromVector4(new System.Numerics.Vector4(this.Red, this.Green, this.Blue, this.Alpha)); + return pix; + } + + internal BufferSpan AsSpan() + { + return new BufferSpan(new[] { AsPixel() }); + } + + public void Deserialize(IXunitSerializationInfo info) + { + this.Red = info.GetValue("red"); + this.Green = info.GetValue("green"); + this.Blue = info.GetValue("blue"); + this.Alpha = info.GetValue("alpha"); + } + + public void Serialize(IXunitSerializationInfo info) + { + info.AddValue("red", this.Red); + info.AddValue("green", this.Green); + info.AddValue("blue", this.Blue); + info.AddValue("alpha", this.Alpha); + } + + public override string ToString() + { + return $"{typeof(TPixel).Name}{this.AsPixel().ToString()}"; + } + } +} diff --git a/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs b/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs new file mode 100644 index 000000000..beb3fcd97 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Text; +using ImageSharp.PixelFormats; +using Xunit.Abstractions; + +namespace ImageSharp.Tests.TestUtilities +{ + public class TestVector4 : IXunitSerializable + { + public TestVector4() + { + } + + public TestVector4(float x, float y, float z, float w) + { + this.X = x; + this.Y = y; + this.Z = x; + this.W = w; + } + + public float X { get; set; } + public float Y { get; set; } + public float Z { get; set; } + public float W { get; set; } + + public static implicit operator Vector4(TestVector4 d) + { + return d?.AsVector() ?? default(Vector4); + } + + public Vector4 AsVector() + { + return new Vector4(this.X, this.Y, this.Z, this.W); + } + + public void Deserialize(IXunitSerializationInfo info) + { + this.X = info.GetValue("x"); + this.Y = info.GetValue("y"); + this.Z= info.GetValue("z"); + this.W= info.GetValue("w"); + } + + public void Serialize(IXunitSerializationInfo info) + { + info.AddValue("x", this.X); + info.AddValue("y", this.Y); + info.AddValue("z", this.Z); + info.AddValue("w", this.W); + } + + public override string ToString() + { + return $"{this.AsVector().ToString()}"; + } + } +} diff --git a/tests/ImageSharp.Tests/VectorAssert.cs b/tests/ImageSharp.Tests/VectorAssert.cs new file mode 100644 index 000000000..ad26963d4 --- /dev/null +++ b/tests/ImageSharp.Tests/VectorAssert.cs @@ -0,0 +1,98 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +// ReSharper disable MemberHidesStaticFromOuterClass +namespace ImageSharp.Tests +{ + using System; + using System.Collections.Generic; + using System.Numerics; + using ImageSharp; + using ImageSharp.PixelFormats; + using Xunit; + + /// + /// Class to perform simple image comparisons. + /// + public static class VectorAssert + { + public static void Equal(TPixel expected, TPixel actual, int precision = int.MaxValue) + where TPixel : struct, IPixel + { + Equal(expected.ToVector4(), actual.ToVector4(), precision); + } + + public static void Equal(Vector4 expected, Vector4 actual, int precision = int.MaxValue) + { + Assert.Equal(expected, actual, new PrecisionEqualityComparer(precision)); + } + + public static void Equal(Vector3 expected, Vector3 actual, int precision = int.MaxValue) + { + Assert.Equal(expected, actual, new PrecisionEqualityComparer(precision)); + } + + public static void Equal(Vector2 expected, Vector2 actual, int precision = int.MaxValue) + { + Assert.Equal(expected, actual, new PrecisionEqualityComparer(precision)); + } + + private struct PrecisionEqualityComparer : IEqualityComparer, IEqualityComparer, IEqualityComparer, IEqualityComparer + { + private readonly int precision; + + public PrecisionEqualityComparer(int precision) + { + this.precision = precision; + } + + public bool Equals(Vector2 x, Vector2 y) + { + return Equals(x.X, y.X) && + Equals(x.Y, y.Y); + + } + public bool Equals(Vector3 x, Vector3 y) + { + return Equals(x.X, y.X) && + Equals(x.Y, y.Y) && + Equals(x.Z, y.Z); + + } + + public bool Equals(Vector4 x, Vector4 y) + { + return Equals(x.W, y.W) && + Equals(x.X, y.X) && + Equals(x.Y, y.Y) && + Equals(x.Z, y.Z); + + } + + public bool Equals(float x, float y) + { + return Math.Round(x, this.precision) == Math.Round(y, this.precision); + } + + public int GetHashCode(Vector4 obj) + { + return obj.GetHashCode(); + } + public int GetHashCode(Vector3 obj) + { + return obj.GetHashCode(); + } + public int GetHashCode(Vector2 obj) + { + return obj.GetHashCode(); + } + + public int GetHashCode(float obj) + { + return obj.GetHashCode(); + } + } + } +}