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