diff --git a/src/ImageProcessorCore/Filters/Glow.cs b/src/ImageProcessorCore/Filters/Glow.cs new file mode 100644 index 000000000..3a7f487d3 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Glow.cs @@ -0,0 +1,118 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image this method extends. + /// A delegate which is called as progress is made processing the image. + /// The . + public static Image Glow(this Image source, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct + { + return Glow(source, default(T), source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds, progressHandler); + } + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image this method extends. + /// The color to set as the glow. + /// A delegate which is called as progress is made processing the image. + /// The . + public static Image Glow(this Image source, T color, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct + { + return Glow(source, color, source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds, progressHandler); + } + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image this method extends. + /// The the x-radius. + /// The the y-radius. + /// A delegate which is called as progress is made processing the image. + /// The . + public static Image Glow(this Image source, float radiusX, float radiusY, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct + { + return Glow(source, default(T), radiusX, radiusY, source.Bounds, progressHandler); + } + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// A delegate which is called as progress is made processing the image. + /// The . + public static Image Glow(this Image source, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct + { + return Glow(source, default(T), 0, 0, rectangle, progressHandler); + } + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image this method extends. + /// The color to set as the glow. + /// The the x-radius. + /// The the y-radius. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// A delegate which is called as progress is made processing the image. + /// The . + public static Image Glow(this Image source, T color, float radiusX, float radiusY, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct + { + GlowProcessor processor = new GlowProcessor { RadiusX = radiusX, RadiusY = radiusY }; + + if (!color.Equals(default(T))) + { + processor.GlowColor = color; + } + + processor.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs b/src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs index fa090fc74..19a3fb518 100644 --- a/src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/AlphaProcessor.cs @@ -5,11 +5,12 @@ namespace ImageProcessorCore.Processors { + using System; using System.Numerics; using System.Threading.Tasks; /// - /// An to change the Alpha of an . + /// An to change the alpha component of an . /// /// The pixel format. /// The packed format. long, float. @@ -38,38 +39,49 @@ namespace ImageProcessorCore.Processors /// protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { - float alpha = this.Value / 100f; - int sourceY = sourceRectangle.Y; - int sourceBottom = sourceRectangle.Bottom; + float alpha = this.Value / 100F; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; + + // 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; + } + Vector4 alphaVector = new Vector4(1, 1, 1, alpha); using (IPixelAccessor sourcePixels = source.Lock()) using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( - startY, - endY, + minY, + maxY, this.ParallelOptions, y => { - if (y >= sourceY && y < sourceBottom) + int offsetY = y - startY; + for (int x = minX; x < maxX; x++) { - for (int x = startX; x < endX; x++) - { - Vector4 color = sourcePixels[x, y].ToVector4(); - color *= alphaVector; - - T packed = default(T); - packed.PackFromVector4(color); - targetPixels[x, y] = packed; - } - - this.OnRowProcessed(); + int offsetX = x - startX; + T packed = default(T); + packed.PackFromVector4(sourcePixels[offsetX, offsetY].ToVector4() * alphaVector); + targetPixels[offsetX, offsetY] = packed; } - }); + this.OnRowProcessed(); + }); } } } diff --git a/src/ImageProcessorCore/Filters/Processors/BackgroundColorProcessor.cs b/src/ImageProcessorCore/Filters/Processors/BackgroundColorProcessor.cs index 7e275a1f5..b45cc95ab 100644 --- a/src/ImageProcessorCore/Filters/Processors/BackgroundColorProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/BackgroundColorProcessor.cs @@ -38,45 +38,60 @@ namespace ImageProcessorCore.Processors /// protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { - int sourceY = sourceRectangle.Y; - int sourceBottom = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; + + // 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; + } + Vector4 backgroundColor = this.Value.ToVector4(); using (IPixelAccessor sourcePixels = source.Lock()) using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( - startY, - endY, + minY, + maxY, this.ParallelOptions, y => { - if (y >= sourceY && y < sourceBottom) + int offsetY = y - startY; + for (int x = minX; x < maxX; x++) { - for (int x = startX; x < endX; x++) - { - Vector4 color = sourcePixels[x, y].ToVector4(); - float a = color.W; + int offsetX = x - startX; + Vector4 color = sourcePixels[offsetX, offsetY].ToVector4(); + float a = color.W; - if (a < 1 && a > 0) - { - color = Vector4.Lerp(color, backgroundColor, .5f); - } - - if (Math.Abs(a) < Epsilon) - { - color = backgroundColor; - } + if (a < 1 && a > 0) + { + color = Vector4.Lerp(color, backgroundColor, .5F); + } - T packed = default(T); - packed.PackFromVector4(color); - targetPixels[x, y] = packed; + if (Math.Abs(a) < Epsilon) + { + color = backgroundColor; } - this.OnRowProcessed(); + T packed = default(T); + packed.PackFromVector4(color); + targetPixels[offsetX, offsetY] = packed; } + + this.OnRowProcessed(); }); } } diff --git a/src/ImageProcessorCore/Filters/Processors/Binarization/BinaryThresholdProcessor.cs b/src/ImageProcessorCore/Filters/Processors/Binarization/BinaryThresholdProcessor.cs index 9d42fce49..029f9c3b3 100644 --- a/src/ImageProcessorCore/Filters/Processors/Binarization/BinaryThresholdProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/Binarization/BinaryThresholdProcessor.cs @@ -9,8 +9,7 @@ namespace ImageProcessorCore.Processors /// /// An to perform binary threshold filtering against an - /// . The image will be converted to Grayscale before thresholding - /// occurs. + /// . The image will be converted to Grayscale before thresholding occurs. /// /// The pixel format. /// The packed format. long, float. diff --git a/src/ImageProcessorCore/Filters/Processors/BlendProcessor.cs b/src/ImageProcessorCore/Filters/Processors/BlendProcessor.cs index 3d74010ee..37ad39852 100644 --- a/src/ImageProcessorCore/Filters/Processors/BlendProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/BlendProcessor.cs @@ -5,6 +5,7 @@ namespace ImageProcessorCore.Processors { + using System; using System.Numerics; using System.Threading.Tasks; @@ -23,7 +24,7 @@ namespace ImageProcessorCore.Processors private readonly ImageBase blend; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// /// The image to blend with the currently processing image. @@ -45,48 +46,62 @@ namespace ImageProcessorCore.Processors /// protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { - int sourceY = sourceRectangle.Y; - int sourceBottom = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; Rectangle bounds = this.blend.Bounds; - float alpha = this.Value / 100f; + + // 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; + } + + float alpha = this.Value / 100F; using (IPixelAccessor toBlendPixels = this.blend.Lock()) using (IPixelAccessor sourcePixels = source.Lock()) using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( - startY, - endY, + minY, + maxY, this.ParallelOptions, y => { - if (y >= sourceY && y < sourceBottom) + int offsetY = y - startY; + for (int x = minX; x < maxX; x++) { - for (int x = startX; x < endX; x++) + int offsetX = x - startX; + Vector4 color = sourcePixels[offsetX, offsetY].ToVector4(); + + if (bounds.Contains(offsetX, offsetY)) { - Vector4 color = sourcePixels[x, y].ToVector4(); + Vector4 blendedColor = toBlendPixels[offsetX, offsetY].ToVector4(); - if (bounds.Contains(x, y)) + if (blendedColor.W > 0) { - Vector4 blendedColor = toBlendPixels[x, y].ToVector4(); - - if (blendedColor.W > 0) - { - // Lerping colors is dependent on the alpha of the blended color - float alphaFactor = alpha > 0 ? alpha : blendedColor.W; - color = Vector4.Lerp(color, blendedColor, alphaFactor); - } + // Lerping colors is dependent on the alpha of the blended color + color = Vector4.Lerp(color, blendedColor, alpha > 0 ? alpha : blendedColor.W); } - - T packed = default(T); - packed.PackFromVector4(color); - targetPixels[x, y] = packed; } - this.OnRowProcessed(); + T packed = default(T); + packed.PackFromVector4(color); + targetPixels[offsetX, offsetY] = packed; } + + this.OnRowProcessed(); }); } } diff --git a/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs b/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs index b8c7bc441..0be59d92c 100644 --- a/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/BrightnessProcessor.cs @@ -5,6 +5,7 @@ namespace ImageProcessorCore.Processors { + using System; using System.Numerics; using System.Threading.Tasks; @@ -18,7 +19,7 @@ namespace ImageProcessorCore.Processors where TP : struct { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The new brightness of the image. Must be between -100 and 100. /// @@ -38,39 +39,53 @@ namespace ImageProcessorCore.Processors /// protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { - float brightness = this.Value / 100f; - int sourceY = sourceRectangle.Y; - int sourceBottom = sourceRectangle.Bottom; + float brightness = this.Value / 100F; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; + // 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; + } + using (IPixelAccessor sourcePixels = source.Lock()) using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( - startY, - endY, + minY, + maxY, this.ParallelOptions, y => { - if (y >= sourceY && y < sourceBottom) + int offsetY = y - startY; + for (int x = minX; x < maxX; x++) { - for (int x = startX; x < endX; x++) - { - // TODO: Check this with other formats. - Vector4 vector = sourcePixels[x, y].ToVector4().Expand(); - Vector3 transformed = new Vector3(vector.X, vector.Y, vector.Z); - transformed += new Vector3(brightness); - vector = new Vector4(transformed, vector.W); + int offsetX = x - startX; - T packed = default(T); - packed.PackFromVector4(vector.Compress()); + // TODO: Check this with other formats. + Vector4 vector = sourcePixels[offsetX, offsetY].ToVector4().Expand(); + Vector3 transformed = new Vector3(vector.X, vector.Y, vector.Z) + new Vector3(brightness); + vector = new Vector4(transformed, vector.W); - targetPixels[x, y] = packed; - } + T packed = default(T); + packed.PackFromVector4(vector.Compress()); - this.OnRowProcessed(); + targetPixels[offsetX, offsetY] = packed; } + + this.OnRowProcessed(); }); } } diff --git a/src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs b/src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs index 4f791caad..0c5d7e225 100644 --- a/src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/ContrastProcessor.cs @@ -5,11 +5,12 @@ namespace ImageProcessorCore.Processors { + using System; using System.Numerics; using System.Threading.Tasks; /// - /// An to change the contrast of an . + /// An to change the contrast of an . /// /// The pixel format. /// The packed format. long, float. @@ -38,37 +39,53 @@ namespace ImageProcessorCore.Processors /// protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { - float contrast = (100f + this.Value) / 100f; - int sourceY = sourceRectangle.Y; - int sourceBottom = sourceRectangle.Bottom; + float contrast = (100F + this.Value) / 100F; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; Vector4 contrastVector = new Vector4(contrast, contrast, contrast, 1); - Vector4 shiftVector = new Vector4(.5f, .5f, .5f, 1); + Vector4 shiftVector = new Vector4(.5F, .5F, .5F, 1); + + // 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; + } using (IPixelAccessor sourcePixels = source.Lock()) using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( - startY, - endY, + minY, + maxY, this.ParallelOptions, y => { - if (y >= sourceY && y < sourceBottom) + int offsetY = y - startY; + for (int x = minX; x < maxX; x++) { - for (int x = startX; x < endX; x++) - { - Vector4 vector = (sourcePixels[x, y]).ToVector4().Expand(); - vector -= shiftVector; - vector *= contrastVector; - vector += shiftVector; - T packed = default(T); - packed.PackFromVector4(vector.Compress()); - targetPixels[x, y] = packed; - } - this.OnRowProcessed(); + int offsetX = x - startX; + + Vector4 vector = sourcePixels[offsetX, offsetY].ToVector4().Expand(); + vector -= shiftVector; + vector *= contrastVector; + vector += shiftVector; + T packed = default(T); + packed.PackFromVector4(vector.Compress()); + targetPixels[offsetX, offsetY] = packed; } + + this.OnRowProcessed(); }); } } diff --git a/src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs b/src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs index 18eab81f5..69c2b05f7 100644 --- a/src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/GlowProcessor.cs @@ -10,7 +10,7 @@ namespace ImageProcessorCore.Processors using System.Threading.Tasks; /// - /// Creates a glow effect on the image + /// An that applies a radial glow effect an . /// /// The pixel format. /// The packed format. long, float. @@ -19,11 +19,13 @@ namespace ImageProcessorCore.Processors where TP : struct { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public GlowProcessor() { - this.GlowColor.PackFromVector4(Color.White.ToVector4()); + T color = default(T); + color.PackFromVector4(Color.White.ToVector4()); + this.GlowColor = color; } /// @@ -47,29 +49,48 @@ namespace ImageProcessorCore.Processors int startX = sourceRectangle.X; int endX = sourceRectangle.Right; T glowColor = this.GlowColor; - Vector2 centre = Rectangle.Center(targetRectangle).ToVector2(); - float rX = this.RadiusX > 0 ? this.RadiusX : targetRectangle.Width / 2f; - float rY = this.RadiusY > 0 ? this.RadiusY : targetRectangle.Height / 2f; - float maxDistance = (float)Math.Sqrt(rX * rX + rY * rY); + Vector2 centre = Rectangle.Center(sourceRectangle).ToVector2(); + float rX = this.RadiusX > 0 ? Math.Min(this.RadiusX, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; + float rY = this.RadiusY > 0 ? Math.Min(this.RadiusY, sourceRectangle.Height * .5F) : sourceRectangle.Height * .5F; + float maxDistance = (float)Math.Sqrt((rX * rX) + (rY * rY)); + + // 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; + } using (IPixelAccessor sourcePixels = source.Lock()) using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( - startY, - endY, + minY, + maxY, this.ParallelOptions, y => { - for (int x = startX; x < endX; x++) + int offsetY = y - startY; + for (int x = minX; x < maxX; x++) { + int offsetX = x - startX; + // TODO: Premultiply? - float distance = Vector2.Distance(centre, new Vector2(x, y)); - Vector4 sourceColor = sourcePixels[x, y].ToVector4(); - Vector4 result = Vector4.Lerp(glowColor.ToVector4(), sourceColor, .5f * (distance / maxDistance)); + float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY)); + Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4(); T packed = default(T); - packed.PackFromVector4(result); - targetPixels[x, y] = packed; + packed.PackFromVector4(Vector4.Lerp(glowColor.ToVector4(), sourceColor, .5F * (distance / maxDistance))); + targetPixels[offsetX, offsetY] = packed; } this.OnRowProcessed(); diff --git a/src/ImageProcessorCore/Filters/Processors/InvertProcessor.cs b/src/ImageProcessorCore/Filters/Processors/InvertProcessor.cs index 381257e99..54b85540e 100644 --- a/src/ImageProcessorCore/Filters/Processors/InvertProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/InvertProcessor.cs @@ -5,12 +5,15 @@ namespace ImageProcessorCore.Processors { + using System; using System.Numerics; using System.Threading.Tasks; /// - /// An to invert the colors of an . + /// An to invert the colors of an . /// + /// The pixel format. + /// The packed format. long, float. public class InvertProcessor : ImageProcessor where T : IPackedVector where TP : struct @@ -18,35 +21,49 @@ namespace ImageProcessorCore.Processors /// protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { - int sourceY = sourceRectangle.Y; - int sourceBottom = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; Vector3 inverseVector = Vector3.One; + // 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; + } + using (IPixelAccessor sourcePixels = source.Lock()) using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( - startY, - endY, + minY, + maxY, this.ParallelOptions, y => { - if (y >= sourceY && y < sourceBottom) + int offsetY = y - startY; + for (int x = minX; x < maxX; x++) { - for (int x = startX; x < endX; x++) - { - Vector4 color = sourcePixels[x, y].ToVector4(); - Vector3 vector = inverseVector - new Vector3(color.X, color.Y, color.Z); + int offsetX = x - startX; + Vector4 color = sourcePixels[offsetX, offsetY].ToVector4(); + Vector3 vector = inverseVector - new Vector3(color.X, color.Y, color.Z); - T packed = default(T); - packed.PackFromVector4(new Vector4(vector, color.W)); - targetPixels[x, y] = packed; - } - - this.OnRowProcessed(); + T packed = default(T); + packed.PackFromVector4(new Vector4(vector, color.W)); + targetPixels[offsetX, offsetY] = packed; } + + this.OnRowProcessed(); }); } } diff --git a/src/ImageProcessorCore/Filters/Processors/PixelateProcessor.cs b/src/ImageProcessorCore/Filters/Processors/PixelateProcessor.cs index e1e833f35..b0348fb2b 100644 --- a/src/ImageProcessorCore/Filters/Processors/PixelateProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/PixelateProcessor.cs @@ -5,11 +5,12 @@ namespace ImageProcessorCore.Processors { + using System; using System.Collections.Generic; using System.Threading.Tasks; /// - /// An to invert the colors of an . + /// An to pixelate the colors of an . /// /// The pixel format. /// The packed format. long, float. @@ -38,15 +39,30 @@ namespace ImageProcessorCore.Processors /// protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY) { - int sourceY = sourceRectangle.Y; - int sourceBottom = sourceRectangle.Bottom; int startX = sourceRectangle.X; int endX = sourceRectangle.Right; int size = this.Value; int offset = this.Value / 2; + // 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; + } + // Get the range on the y-plane to choose from. - IEnumerable range = EnumerableExtensions.SteppedRange(startY, i => i < endY, size); + IEnumerable range = EnumerableExtensions.SteppedRange(minY, i => i < maxY, size); using (IPixelAccessor sourcePixels = source.Lock()) using (IPixelAccessor targetPixels = target.Lock()) @@ -56,41 +72,40 @@ namespace ImageProcessorCore.Processors this.ParallelOptions, y => { - if (y >= sourceY && y < sourceBottom) + int offsetY = y - startY; + int offsetPy = offset; + + for (int x = minX; x < maxX; x += size) { - for (int x = startX; x < endX; x += size) - { - int offsetX = offset; - int offsetY = offset; + int offsetX = x - startX; + int offsetPx = offset; - // Make sure that the offset is within the boundary of the - // image. - while (y + offsetY >= sourceBottom) - { - offsetY--; - } + // Make sure that the offset is within the boundary of the image. + while (offsetY + offsetPy >= maxY) + { + offsetPy--; + } - while (x + offsetX >= endX) - { - offsetX--; - } + while (x + offsetPx >= maxX) + { + offsetPx--; + } - // Get the pixel color in the centre of the soon to be pixelated area. - // ReSharper disable AccessToDisposedClosure - T pixel = sourcePixels[x + offsetX, y + offsetY]; + // Get the pixel color in the centre of the soon to be pixelated area. + // ReSharper disable AccessToDisposedClosure + T pixel = sourcePixels[offsetX + offsetPx, offsetY + offsetPy]; - // For each pixel in the pixelate size, set it to the centre color. - for (int l = y; l < y + size && l < sourceBottom; l++) + // For each pixel in the pixelate size, set it to the centre color. + for (int l = offsetY; l < offsetY + size && l < maxY; l++) + { + for (int k = offsetX; k < offsetX + size && k < maxX; k++) { - for (int k = x; k < x + size && k < endX; k++) - { - targetPixels[k, l] = pixel; - } + targetPixels[k, l] = pixel; } } - - this.OnRowProcessed(); } + + this.OnRowProcessed(); }); } } diff --git a/src/ImageProcessorCore/Filters/Processors/VignetteProcessor.cs b/src/ImageProcessorCore/Filters/Processors/VignetteProcessor.cs index 6c67270a9..a7d466c89 100644 --- a/src/ImageProcessorCore/Filters/Processors/VignetteProcessor.cs +++ b/src/ImageProcessorCore/Filters/Processors/VignetteProcessor.cs @@ -10,7 +10,7 @@ namespace ImageProcessorCore.Processors using System.Threading.Tasks; /// - /// Creates a vignette effect on the image + /// An that applies a radial vignette effect to an . /// /// The pixel format. /// The packed format. long, float. @@ -19,11 +19,13 @@ namespace ImageProcessorCore.Processors where TP : struct { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// public VignetteProcessor() { - this.VignetteColor.PackFromVector4(Color.Black.ToVector4()); + T color = default(T); + color.PackFromVector4(Color.Black.ToVector4()); + this.VignetteColor = color; } /// @@ -47,30 +49,48 @@ namespace ImageProcessorCore.Processors int startX = sourceRectangle.X; int endX = sourceRectangle.Right; T vignetteColor = this.VignetteColor; - Vector2 centre = Rectangle.Center(targetRectangle).ToVector2(); - float rX = this.RadiusX > 0 ? this.RadiusX : targetRectangle.Width / 2f; - float rY = this.RadiusY > 0 ? this.RadiusY : targetRectangle.Height / 2f; - float maxDistance = (float)Math.Sqrt(rX * rX + rY * rY); + Vector2 centre = Rectangle.Center(sourceRectangle).ToVector2(); + float rX = this.RadiusX > 0 ? Math.Min(this.RadiusX, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; + float rY = this.RadiusY > 0 ? Math.Min(this.RadiusY, sourceRectangle.Height * .5F) : sourceRectangle.Height * .5F; + float maxDistance = (float)Math.Sqrt((rX * rX) + (rY * rY)); + + // 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; + } using (IPixelAccessor sourcePixels = source.Lock()) using (IPixelAccessor targetPixels = target.Lock()) { Parallel.For( - startY, - endY, + minY, + maxY, this.ParallelOptions, y => { - for (int x = startX; x < endX; x++) + int offsetY = y - startY; + for (int x = minX; x < maxX; x++) { - float distance = Vector2.Distance(centre, new Vector2(x, y)); - Vector4 sourceColor = sourcePixels[x, y].ToVector4(); - Vector4 result = Vector4.Lerp(vignetteColor.ToVector4(), sourceColor, 1 - .9f * (distance / maxDistance)); + int offsetX = x - startX; + float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY)); + Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4(); T packed = default(T); - packed.PackFromVector4(result); - targetPixels[x, y] = packed; - + packed.PackFromVector4(Vector4.Lerp(vignetteColor.ToVector4(), sourceColor, 1 - (.9F * (distance / maxDistance)))); + targetPixels[offsetX, offsetY] = packed; } + this.OnRowProcessed(); }); } diff --git a/src/ImageProcessorCore/Filters/Vignette.cs b/src/ImageProcessorCore/Filters/Vignette.cs new file mode 100644 index 000000000..f439297c3 --- /dev/null +++ b/src/ImageProcessorCore/Filters/Vignette.cs @@ -0,0 +1,118 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore +{ + using Processors; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image this method extends. + /// A delegate which is called as progress is made processing the image. + /// The . + public static Image Vignette(this Image source, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct + { + return Vignette(source, default(T), source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds, progressHandler); + } + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image this method extends. + /// The color to set as the vignette. + /// A delegate which is called as progress is made processing the image. + /// The . + public static Image Vignette(this Image source, T color, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct + { + return Vignette(source, color, source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds, progressHandler); + } + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image this method extends. + /// The the x-radius. + /// The the y-radius. + /// A delegate which is called as progress is made processing the image. + /// The . + public static Image Vignette(this Image source, float radiusX, float radiusY, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct + { + return Vignette(source, default(T), radiusX, radiusY, source.Bounds, progressHandler); + } + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The packed format. long, float. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// A delegate which is called as progress is made processing the image. + /// The . + public static Image Vignette(this Image source, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct + { + return Vignette(source, default(T), 0, 0, rectangle, progressHandler); + } + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The packed format. long, float. + /// 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. + /// + /// A delegate which is called as progress is made processing the image. + /// The . + public static Image Vignette(this Image source, T color, float radiusX, float radiusY, Rectangle rectangle, ProgressEventHandler progressHandler = null) + where T : IPackedVector + where TP : struct + { + VignetteProcessor processor = new VignetteProcessor { RadiusX = radiusX, RadiusY = radiusY }; + + if (!color.Equals(default(T))) + { + processor.VignetteColor = color; + } + + processor.OnProgress += progressHandler; + + try + { + return source.Process(rectangle, processor); + } + finally + { + processor.OnProgress -= progressHandler; + } + } + } +} diff --git a/tests/ImageProcessorCore.Tests/FileTestBase.cs b/tests/ImageProcessorCore.Tests/FileTestBase.cs index 3eb5559ed..aff63e8d2 100644 --- a/tests/ImageProcessorCore.Tests/FileTestBase.cs +++ b/tests/ImageProcessorCore.Tests/FileTestBase.cs @@ -31,7 +31,7 @@ namespace ImageProcessorCore.Tests // "TestImages/Formats/Bmp/neg_height.bmp", // Perf: Enable for local testing only //"TestImages/Formats/Png/blur.png", // Perf: Enable for local testing only //"TestImages/Formats/Png/indexed.png", // Perf: Enable for local testing only - //TestImages/Formats/Png/splash.png", + "TestImages/Formats/Png/splash.png", "TestImages/Formats/Gif/rings.gif", //"TestImages/Formats/Gif/giphy.gif" // Perf: Enable for local testing only }; diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/AlphaTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/AlphaTest.cs index 6176cbb71..4389c4986 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Filters/AlphaTest.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/AlphaTest.cs @@ -15,7 +15,7 @@ namespace ImageProcessorCore.Tests = new TheoryData { 20 , - 80 , + 80 }; [Theory] @@ -43,5 +43,31 @@ namespace ImageProcessorCore.Tests } } } + + [Theory] + [MemberData("AlphaValues")] + public void ImageShouldApplyAlphaFilterInBox(int value) + { + const string path = "TestOutput/Alpha"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-" + value + "-InBox" + Path.GetExtension(file); + + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Alpha(value, new Rectangle(10, 10, image.Width / 2, image.Height / 2)) + .Save(output); + } + } + } + } } } \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/GlowTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/GlowTest.cs new file mode 100644 index 000000000..9666c31c2 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/GlowTest.cs @@ -0,0 +1,110 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class GlowTest : FileTestBase + { + [Fact] + public void ImageShouldApplyGlowFilter() + { + const string path = "TestOutput/Glow"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileName(file); + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Glow() + .Save(output); + } + } + } + } + + [Fact] + public void ImageShouldApplyGlowFilterColor() + { + const string path = "TestOutput/Glow"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-Color" + Path.GetExtension(file); + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Glow(Color.HotPink) + .Save(output); + } + } + } + } + + [Fact] + public void ImageShouldApplyGlowFilterRadius() + { + const string path = "TestOutput/Glow"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-Radius" + Path.GetExtension(file); + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Glow(image.Width / 4, image.Height / 4) + .Save(output); + } + } + } + } + + [Fact] + public void ImageShouldApplyGlowFilterInBox() + { + const string path = "TestOutput/Glow"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-InBox" + Path.GetExtension(file); + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Glow(new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2)) + .Save(output); + } + } + } + } + } +} \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/InvertTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/InvertTest.cs index 14b589b3f..f2dc58603 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Filters/InvertTest.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/InvertTest.cs @@ -34,5 +34,29 @@ namespace ImageProcessorCore.Tests } } } + + [Fact] + public void ImageShouldApplyInvertFilterInBox() + { + const string path = "TestOutput/Invert"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file); + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}-InBox{Path.GetExtension(file)}")) + { + image.Invert(new Rectangle(10, 10, image.Width / 2, image.Height / 2)) + .Save(output); + } + } + } + } } } \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/PixelateTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/PixelateTest.cs index b2ef2fb38..a2614324d 100644 --- a/tests/ImageProcessorCore.Tests/Processors/Filters/PixelateTest.cs +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/PixelateTest.cs @@ -15,7 +15,7 @@ namespace ImageProcessorCore.Tests = new TheoryData { 4 , - 8 , + 8 }; [Theory] @@ -43,5 +43,31 @@ namespace ImageProcessorCore.Tests } } } + + [Theory] + [MemberData("PixelateValues")] + public void ImageShouldApplyPixelateFilterInBox(int value) + { + const string path = "TestOutput/Pixelate"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-" + value + "-InBox" + Path.GetExtension(file); + + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Pixelate(value, new Rectangle(10, 10, image.Width / 2, image.Height / 2)) + .Save(output); + } + } + } + } } } \ No newline at end of file diff --git a/tests/ImageProcessorCore.Tests/Processors/Filters/VignetteTest.cs b/tests/ImageProcessorCore.Tests/Processors/Filters/VignetteTest.cs new file mode 100644 index 000000000..11e2f32f0 --- /dev/null +++ b/tests/ImageProcessorCore.Tests/Processors/Filters/VignetteTest.cs @@ -0,0 +1,110 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageProcessorCore.Tests +{ + using System.IO; + + using Xunit; + + public class VignetteTest : FileTestBase + { + [Fact] + public void ImageShouldApplyVignetteFilter() + { + const string path = "TestOutput/Vignette"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileName(file); + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Vignette() + .Save(output); + } + } + } + } + + [Fact] + public void ImageShouldApplyVignetteFilterColor() + { + const string path = "TestOutput/Vignette"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-Color" + Path.GetExtension(file); + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Vignette(Color.HotPink) + .Save(output); + } + } + } + } + + [Fact] + public void ImageShouldApplyVignetteFilterRadius() + { + const string path = "TestOutput/Vignette"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-Radius" + Path.GetExtension(file); + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Vignette(image.Width / 4, image.Height / 4) + .Save(output); + } + } + } + } + + [Fact] + public void ImageShouldApplyVignetteFilterInBox() + { + const string path = "TestOutput/Vignette"; + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + foreach (string file in Files) + { + using (FileStream stream = File.OpenRead(file)) + { + string filename = Path.GetFileNameWithoutExtension(file) + "-InBox" + Path.GetExtension(file); + Image image = new Image(stream); + using (FileStream output = File.OpenWrite($"{path}/{filename}")) + { + image.Vignette(new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2)) + .Save(output); + } + } + } + } + } +} \ No newline at end of file