diff --git a/appveyor.yml b/appveyor.yml
index d8223ad37..fd3b29259 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -40,13 +40,13 @@ test_script:
artifacts:
- path: artifacts\bin\ImageProcessor\**\*.nupkg
-deploy:
- # MyGet Deployment for builds & releases
- - provider: NuGet
- server: https://www.myget.org/F/imageprocessor/api/v2/package
- symbol_server: https://nuget.symbolsource.org/MyGet/imageprocessor
- api_key:
- secure: fz0rUrt3B1HczUC1ZehwVsrFSWX9WZGDQoueDztLte9/+yQG+BBU7UrO+coE8lUf
- artifact: /.*\.nupkg/
- on:
- branch: V3
\ No newline at end of file
+#deploy:
+# # MyGet Deployment for builds & releases
+# - provider: NuGet
+# server: https://www.myget.org/F/imageprocessor/api/v2/package
+# symbol_server: https://nuget.symbolsource.org/MyGet/imageprocessor
+# api_key:
+# secure: fz0rUrt3B1HczUC1ZehwVsrFSWX9WZGDQoueDztLte9/+yQG+BBU7UrO+coE8lUf
+# artifact: /.*\.nupkg/
+# on:
+# branch: V3
\ No newline at end of file
diff --git a/src/ImageProcessor/Filters/Blend.cs b/src/ImageProcessor/Filters/Blend.cs
index 23697b092..9447c93b3 100644
--- a/src/ImageProcessor/Filters/Blend.cs
+++ b/src/ImageProcessor/Filters/Blend.cs
@@ -59,9 +59,12 @@ namespace ImageProcessor.Filters
{
Color blendedColor = this.toBlend[x, y];
- // Lerping colors is dependent on the alpha of the blended color
- float alphaFactor = alpha > 0 ? alpha : blendedColor.A;
- color = Color.Lerp(color, blendedColor, alphaFactor);
+ if (blendedColor.A > 0)
+ {
+ // Lerping colors is dependent on the alpha of the blended color
+ float alphaFactor = alpha > 0 ? alpha : blendedColor.A;
+ color = Color.Lerp(color, blendedColor, alphaFactor);
+ }
}
target[x, y] = color;
diff --git a/src/ImageProcessor/Filters/Vignette.cs b/src/ImageProcessor/Filters/Vignette.cs
new file mode 100644
index 000000000..547308cc9
--- /dev/null
+++ b/src/ImageProcessor/Filters/Vignette.cs
@@ -0,0 +1,77 @@
+namespace ImageProcessor.Filters
+{
+ using System.Numerics;
+ using System.Threading.Tasks;
+
+ ///
+ /// Creates a vignette
+ ///
+ public class Vignette : ParallelImageProcessor
+ {
+ ///
+ /// Used to hold a copy of the target image.
+ ///
+ private readonly Image targetCopy = new Image();
+
+ ///
+ /// Gets or sets the vignette color to apply.
+ ///
+ public Color Color { get; set; } = Color.Black;
+
+ ///
+ /// Gets or sets the the x-radius.
+ ///
+ public float RadiusX { get; set; }
+
+ ///
+ /// Gets or sets the the y-radius.
+ ///
+ public float RadiusY { get; set; }
+
+ ///
+ protected override void OnApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
+ {
+ this.targetCopy.SetPixels(target.Width, target.Height, target.Pixels);
+ target.SetPixels(target.Width, target.Height, new float[target.Pixels.Length]);
+ }
+
+ ///
+ protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
+ {
+ int startX = sourceRectangle.X;
+ int endX = sourceRectangle.Right;
+
+ Vector2 centre = Rectangle.Center(targetRectangle);
+ int centerX = (int)centre.X;
+ int centerY = (int)centre.Y;
+ float rX = this.RadiusX > 0 ? this.RadiusX : targetRectangle.Width / 2f;
+ float rY = this.RadiusY > 0 ? this.RadiusY : targetRectangle.Height / 2f;
+
+ Ellipse ellipse = new Ellipse(new Point(centerX, centerY), rX - 1, rY - 1);
+
+ Parallel.For(
+ startY,
+ endY,
+ y =>
+ {
+ for (int x = startX; x < endX; x++)
+ {
+ if (!ellipse.Contains(x, y))
+ {
+ target[x, y] = Color.Black;
+ }
+ }
+ });
+ }
+
+ ///
+ protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
+ {
+ new GuassianBlur(30).Apply(target, target, targetRectangle);
+ Image temp = new Image(this.targetCopy);
+ new Blend(target, 40).Apply(this.targetCopy, temp, targetRectangle);
+ target.SetPixels(temp.Width, temp.Height, this.targetCopy.Pixels);
+ }
+ }
+}
+
diff --git a/src/ImageProcessor/Numerics/Ellipse.cs b/src/ImageProcessor/Numerics/Ellipse.cs
new file mode 100644
index 000000000..9bd340920
--- /dev/null
+++ b/src/ImageProcessor/Numerics/Ellipse.cs
@@ -0,0 +1,188 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageProcessor
+{
+ using System;
+ using System.ComponentModel;
+ using System.Numerics;
+
+ public struct Ellipse : IEquatable
+ {
+ ///
+ /// The center point.
+ ///
+ private Point center;
+
+ ///
+ /// The epsilon for comparing floating point numbers.
+ ///
+ private const float Epsilon = 0.0001f;
+
+ ///
+ /// Represents a that has X and Y values set to zero.
+ ///
+ public static readonly Ellipse Empty = default(Ellipse);
+
+ public Ellipse(Point center, float radiusX, float radiusY)
+ {
+ this.center = center;
+ this.RadiusX = radiusX;
+ this.RadiusY = radiusY;
+ }
+
+ ///
+ /// Gets the x-radius of this .
+ ///
+ public float RadiusX { get; }
+
+ ///
+ /// Gets the y-radius of this .
+ ///
+ public float RadiusY { get; }
+
+ ///
+ /// Gets a value indicating whether this is empty.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool IsEmpty => this.center.IsEmpty
+ && Math.Abs(this.RadiusX) < Epsilon
+ && Math.Abs(this.RadiusY) < Epsilon;
+
+ ///
+ /// Compares two objects for equality.
+ ///
+ ///
+ /// The on the left side of the operand.
+ ///
+ ///
+ /// The on the right side of the operand.
+ ///
+ ///
+ /// True if the current left is equal to the parameter; otherwise, false.
+ ///
+ public static bool operator ==(Ellipse left, Ellipse right)
+ {
+ return left.Equals(right);
+ }
+
+ ///
+ /// Compares two objects for inequality.
+ ///
+ ///
+ /// The on the left side of the operand.
+ ///
+ ///
+ /// The on the right side of the operand.
+ ///
+ ///
+ /// True if the current left is unequal to the parameter; otherwise, false.
+ ///
+ public static bool operator !=(Ellipse left, Ellipse right)
+ {
+ return !left.Equals(right);
+ }
+
+ ///
+ /// Returns the center point of the given
+ ///
+ /// The ellipse
+ ///
+ public static Vector2 Center(Ellipse ellipse)
+ {
+ return new Vector2(ellipse.center.X, ellipse.center.Y);
+ }
+
+ ///
+ /// Determines if the specfied point is contained within the rectangular region defined by
+ /// this .
+ ///
+ /// The x-coordinate of the given point.
+ /// The y-coordinate of the given point.
+ /// The
+ public bool Contains(int x, int y)
+ {
+ if (this.RadiusX <= 0 || this.RadiusY <= 0)
+ {
+ return false;
+ }
+
+ //This is a more general form of the circle equation
+ // X^2/a^2 + Y^2/b^2 <= 1
+ Point normalized = new Point(x - this.center.X, y - this.center.Y);
+ int nX = normalized.X;
+ int nY = normalized.Y;
+
+ //return (double)(nX * nX) / (this.RadiusX * this.RadiusX)
+ // + (double)(nY * nY) / (this.RadiusY * this.RadiusY)
+ // <= 1.0;
+
+ return ((double)(nX * nX) / (this.RadiusX * this.RadiusX))
+ + ((double)(nY * nY) / (this.RadiusY * this.RadiusY))
+ <= 1.0;
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (!(obj is Ellipse))
+ {
+ return false;
+ }
+
+ Ellipse other = (Ellipse)obj;
+
+ return other.center == this.center
+ && Math.Abs(other.RadiusX - this.RadiusX) < Epsilon
+ && Math.Abs(other.RadiusY - this.RadiusY) < Epsilon;
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return this.GetHashCode(this);
+ }
+
+ ///
+ public override string ToString()
+ {
+ if (this.IsEmpty)
+ {
+ return "Ellipse [ Empty ]";
+ }
+
+ return
+ $"Ellipse [ RadiusX={this.RadiusX}, RadiusY={this.RadiusX}, Centre={this.center.X},{this.center.Y} ]";
+ }
+
+ ///
+ public bool Equals(Ellipse other)
+ {
+ return this.center.Equals(other.center)
+ && this.RadiusX.Equals(other.RadiusX)
+ && this.RadiusX.Equals(other.RadiusY);
+ }
+
+ ///
+ /// Returns the hash code for this instance.
+ ///
+ ///
+ /// The instance of to return the hash code for.
+ ///
+ ///
+ /// A 32-bit signed integer that is the hash code for this instance.
+ ///
+ private int GetHashCode(Ellipse point)
+ {
+ unchecked
+ {
+ int hashCode = point.center.GetHashCode();
+ hashCode = (hashCode * 397) ^ point.RadiusX.GetHashCode();
+ hashCode = (hashCode * 397) ^ point.RadiusY.GetHashCode();
+ return hashCode;
+ }
+ }
+ }
+}
diff --git a/src/ImageProcessor/Numerics/Rectangle.cs b/src/ImageProcessor/Numerics/Rectangle.cs
index c7e97b956..3cd603d96 100644
--- a/src/ImageProcessor/Numerics/Rectangle.cs
+++ b/src/ImageProcessor/Numerics/Rectangle.cs
@@ -160,7 +160,7 @@ namespace ImageProcessor
/// Returns the center point of the given
///
/// The rectangle
- ///
+ ///
public static Vector2 Center(Rectangle rectangle)
{
return new Vector2(rectangle.Left + rectangle.Width / 2, rectangle.Top + rectangle.Height / 2);
diff --git a/src/ImageProcessor/ParallelImageProcessor.cs b/src/ImageProcessor/ParallelImageProcessor.cs
index 610d60c0c..cb5c1a36a 100644
--- a/src/ImageProcessor/ParallelImageProcessor.cs
+++ b/src/ImageProcessor/ParallelImageProcessor.cs
@@ -51,6 +51,8 @@ namespace ImageProcessor
{
this.Apply(target, temp, target.Bounds, sourceRectangle, sourceRectangle.Y, sourceRectangle.Bottom);
}
+
+ this.AfterApply(temp, target, target.Bounds, sourceRectangle);
}
///
@@ -96,6 +98,8 @@ namespace ImageProcessor
{
this.Apply(target, temp, targetRectangle, sourceRectangle, targetRectangle.Y, targetRectangle.Bottom);
}
+
+ this.AfterApply(temp, target, target.Bounds, sourceRectangle);
}
///
@@ -134,5 +138,21 @@ namespace ImageProcessor
/// the result of image process as new image.
///
protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY);
+
+ ///
+ /// This method is called after the process is applied to prepare the processor.
+ ///
+ /// The source image. Cannot be null.
+ /// Target image to apply the process to.
+ ///
+ /// The structure that specifies the location and size of the drawn image.
+ /// The image is scaled to fit the rectangle.
+ ///
+ ///
+ /// The structure that specifies the portion of the image object to draw.
+ ///
+ protected virtual void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
+ {
+ }
}
}
diff --git a/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs b/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs
index d4e0d610e..ae854d8e8 100644
--- a/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs
+++ b/tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs
@@ -13,38 +13,39 @@ namespace ImageProcessor.Tests
{
public static readonly TheoryData Filters = new TheoryData
{
- { "Brightness-50", new Brightness(50) },
- { "Brightness--50", new Brightness(-50) },
- { "Contrast-50", new Contrast(50) },
- { "Contrast--50", new Contrast(-50) },
- { "BackgroundColor", new BackgroundColor(new Color(243 / 255f, 87 / 255f, 161 / 255f))},
- { "Blend", new Blend(new Image(File.OpenRead("TestImages/Formats/Bmp/Car.bmp")),50)},
- { "Saturation-50", new Saturation(50) },
- { "Saturation--50", new Saturation(-50) },
- { "Alpha--50", new Alpha(50) },
- { "Invert", new Invert() },
- { "Sepia", new Sepia() },
- { "BlackWhite", new BlackWhite() },
- { "Lomograph", new Lomograph() },
- { "Polaroid", new Polaroid() },
- { "Kodachrome", new Kodachrome() },
- { "GreyscaleBt709", new GreyscaleBt709() },
- { "GreyscaleBt601", new GreyscaleBt601() },
- { "Kayyali", new Kayyali() },
- { "Kirsch", new Kirsch() },
- { "Laplacian3X3", new Laplacian3X3() },
- { "Laplacian5X5", new Laplacian5X5() },
- { "LaplacianOfGaussian", new LaplacianOfGaussian() },
- { "Prewitt", new Prewitt() },
- { "RobertsCross", new RobertsCross() },
- { "Scharr", new Scharr() },
- { "Sobel", new Sobel {Greyscale = true} },
- { "Pixelate", new Pixelate(8) },
- { "GuassianBlur", new GuassianBlur(10) },
- { "GuassianSharpen", new GuassianSharpen(10) },
- { "Hue-180", new Hue(180) },
- { "Hue--180", new Hue(-180) },
- { "BoxBlur", new BoxBlur(10) },
+ //{ "Brightness-50", new Brightness(50) },
+ //{ "Brightness--50", new Brightness(-50) },
+ //{ "Contrast-50", new Contrast(50) },
+ //{ "Contrast--50", new Contrast(-50) },
+ //{ "BackgroundColor", new BackgroundColor(new Color(243 / 255f, 87 / 255f, 161 / 255f))},
+ //{ "Blend", new Blend(new Image(File.OpenRead("TestImages/Formats/Bmp/Car.bmp")),50)},
+ //{ "Saturation-50", new Saturation(50) },
+ //{ "Saturation--50", new Saturation(-50) },
+ //{ "Alpha--50", new Alpha(50) },
+ //{ "Invert", new Invert() },
+ //{ "Sepia", new Sepia() },
+ //{ "BlackWhite", new BlackWhite() },
+ //{ "Lomograph", new Lomograph() },
+ //{ "Polaroid", new Polaroid() },
+ //{ "Kodachrome", new Kodachrome() },
+ //{ "GreyscaleBt709", new GreyscaleBt709() },
+ //{ "GreyscaleBt601", new GreyscaleBt601() },
+ //{ "Kayyali", new Kayyali() },
+ //{ "Kirsch", new Kirsch() },
+ //{ "Laplacian3X3", new Laplacian3X3() },
+ //{ "Laplacian5X5", new Laplacian5X5() },
+ //{ "LaplacianOfGaussian", new LaplacianOfGaussian() },
+ //{ "Prewitt", new Prewitt() },
+ //{ "RobertsCross", new RobertsCross() },
+ //{ "Scharr", new Scharr() },
+ //{ "Sobel", new Sobel {Greyscale = true} },
+ //{ "Pixelate", new Pixelate(8) },
+ //{ "GuassianBlur", new GuassianBlur(10) },
+ //{ "GuassianSharpen", new GuassianSharpen(10) },
+ //{ "Hue-180", new Hue(180) },
+ //{ "Hue--180", new Hue(-180) },
+ //{ "BoxBlur", new BoxBlur(10) },
+ {"Vignette", new Vignette()}
};
[Theory]