Browse Source

Begin Vignette.

Former-commit-id: c5e1903acb354e047d73223867a04ae29f301354
Former-commit-id: a86e30bf71e3eab130a4cc8d9879aa2d55db440b
Former-commit-id: 272754ac9ccd2c97cfeb320d3e25ff431ac43de4
pull/17/head
James Jackson-South 10 years ago
parent
commit
e0208e114e
  1. 20
      appveyor.yml
  2. 9
      src/ImageProcessor/Filters/Blend.cs
  3. 77
      src/ImageProcessor/Filters/Vignette.cs
  4. 188
      src/ImageProcessor/Numerics/Ellipse.cs
  5. 2
      src/ImageProcessor/Numerics/Rectangle.cs
  6. 20
      src/ImageProcessor/ParallelImageProcessor.cs
  7. 65
      tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs

20
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
#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

9
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;

77
src/ImageProcessor/Filters/Vignette.cs

@ -0,0 +1,77 @@
namespace ImageProcessor.Filters
{
using System.Numerics;
using System.Threading.Tasks;
/// <summary>
/// Creates a vignette
/// </summary>
public class Vignette : ParallelImageProcessor
{
/// <summary>
/// Used to hold a copy of the target image.
/// </summary>
private readonly Image targetCopy = new Image();
/// <summary>
/// Gets or sets the vignette color to apply.
/// </summary>
public Color Color { get; set; } = Color.Black;
/// <summary>
/// Gets or sets the the x-radius.
/// </summary>
public float RadiusX { get; set; }
/// <summary>
/// Gets or sets the the y-radius.
/// </summary>
public float RadiusY { get; set; }
/// <inheritdoc/>
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]);
}
/// <inheritdoc/>
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;
}
}
});
}
/// <inheritdoc/>
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);
}
}
}

188
src/ImageProcessor/Numerics/Ellipse.cs

@ -0,0 +1,188 @@
// <copyright file="Ellipse.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessor
{
using System;
using System.ComponentModel;
using System.Numerics;
public struct Ellipse : IEquatable<Ellipse>
{
/// <summary>
/// The center point.
/// </summary>
private Point center;
/// <summary>
/// The epsilon for comparing floating point numbers.
/// </summary>
private const float Epsilon = 0.0001f;
/// <summary>
/// Represents a <see cref="Ellipse"/> that has X and Y values set to zero.
/// </summary>
public static readonly Ellipse Empty = default(Ellipse);
public Ellipse(Point center, float radiusX, float radiusY)
{
this.center = center;
this.RadiusX = radiusX;
this.RadiusY = radiusY;
}
/// <summary>
/// Gets the x-radius of this <see cref="Ellipse"/>.
/// </summary>
public float RadiusX { get; }
/// <summary>
/// Gets the y-radius of this <see cref="Ellipse"/>.
/// </summary>
public float RadiusY { get; }
/// <summary>
/// Gets a value indicating whether this <see cref="Ellipse"/> is empty.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public bool IsEmpty => this.center.IsEmpty
&& Math.Abs(this.RadiusX) < Epsilon
&& Math.Abs(this.RadiusY) < Epsilon;
/// <summary>
/// Compares two <see cref="Ellipse"/> objects for equality.
/// </summary>
/// <param name="left">
/// The <see cref="Ellipse"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Ellipse"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is equal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator ==(Ellipse left, Ellipse right)
{
return left.Equals(right);
}
/// <summary>
/// Compares two <see cref="Ellipse"/> objects for inequality.
/// </summary>
/// <param name="left">
/// The <see cref="Ellipse"/> on the left side of the operand.
/// </param>
/// <param name="right">
/// The <see cref="Ellipse"/> on the right side of the operand.
/// </param>
/// <returns>
/// True if the current left is unequal to the <paramref name="right"/> parameter; otherwise, false.
/// </returns>
public static bool operator !=(Ellipse left, Ellipse right)
{
return !left.Equals(right);
}
/// <summary>
/// Returns the center point of the given <see cref="Ellipse"/>
/// </summary>
/// <param name="ellipse">The ellipse</param>
/// <returns><see cref="Vector2"/></returns>
public static Vector2 Center(Ellipse ellipse)
{
return new Vector2(ellipse.center.X, ellipse.center.Y);
}
/// <summary>
/// Determines if the specfied point is contained within the rectangular region defined by
/// this <see cref="Rectangle"/>.
/// </summary>
/// <param name="x">The x-coordinate of the given point.</param>
/// <param name="y">The y-coordinate of the given point.</param>
/// <returns>The <see cref="bool"/></returns>
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;
}
/// <inheritdoc/>
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;
}
/// <inheritdoc/>
public override int GetHashCode()
{
return this.GetHashCode(this);
}
/// <inheritdoc/>
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} ]";
}
/// <inheritdoc/>
public bool Equals(Ellipse other)
{
return this.center.Equals(other.center)
&& this.RadiusX.Equals(other.RadiusX)
&& this.RadiusX.Equals(other.RadiusY);
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <param name="point">
/// The instance of <see cref="Point"/> to return the hash code for.
/// </param>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
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;
}
}
}
}

2
src/ImageProcessor/Numerics/Rectangle.cs

@ -160,7 +160,7 @@ namespace ImageProcessor
/// Returns the center point of the given <see cref="Rectangle"/>
/// </summary>
/// <param name="rectangle">The rectangle</param>
/// <returns><see cref="Point"/></returns>
/// <returns><see cref="Vector2"/></returns>
public static Vector2 Center(Rectangle rectangle)
{
return new Vector2(rectangle.Left + rectangle.Width / 2, rectangle.Top + rectangle.Height / 2);

20
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);
}
/// <inheritdoc/>
@ -96,6 +98,8 @@ namespace ImageProcessor
{
this.Apply(target, temp, targetRectangle, sourceRectangle, targetRectangle.Y, targetRectangle.Bottom);
}
this.AfterApply(temp, target, target.Bounds, sourceRectangle);
}
/// <summary>
@ -134,5 +138,21 @@ namespace ImageProcessor
/// the result of image process as new image.
/// </remarks>
protected abstract void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY);
/// <summary>
/// This method is called after the process is applied to prepare the processor.
/// </summary>
/// <param name="source">The source image. Cannot be null.</param>
/// <param name="target">Target image to apply the process to.</param>
/// <param name="targetRectangle">
/// The <see cref="Rectangle"/> structure that specifies the location and size of the drawn image.
/// The image is scaled to fit the rectangle.
/// </param>
/// <param name="sourceRectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to draw.
/// </param>
protected virtual void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
{
}
}
}

65
tests/ImageProcessor.Tests/Processors/Filters/FilterTests.cs

@ -13,38 +13,39 @@ namespace ImageProcessor.Tests
{
public static readonly TheoryData<string, IImageProcessor> Filters = new TheoryData<string, IImageProcessor>
{
{ "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]

Loading…
Cancel
Save