mirror of https://github.com/SixLabors/ImageSharp
2 changed files with 267 additions and 0 deletions
@ -0,0 +1,143 @@ |
|||
using System; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes |
|||
{ |
|||
/// <summary>
|
|||
/// Gradient Brush with elliptic shape.
|
|||
/// The ellipse is defined by a center point,
|
|||
/// a point on the longest extension of the ellipse and
|
|||
/// the ratio between longest and shortest extension.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixel">The Pixel format that is used.</typeparam>
|
|||
public class EllipticGradientBrush<TPixel> : AbstractGradientBrush<TPixel> |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
private readonly Point center; |
|||
|
|||
private readonly Point referenceAxisEnd; |
|||
|
|||
private readonly float axisRatio; |
|||
|
|||
/// <inheritdoc cref="AbstractGradientBrush{TPixel}" />
|
|||
/// <param name="center">The center of the elliptical gradient and 0 for the color stops.</param>
|
|||
/// <param name="referenceAxisEnd">The end point of the reference axis of the ellipse.</param>
|
|||
/// <param name="axisRatio">
|
|||
/// The ratio of the axis widths.
|
|||
/// The second axis' is perpendicular to the reference axis and
|
|||
/// it's length is the reference axis' length multiplied by this factor.
|
|||
/// </param>
|
|||
/// <param name="colorStops">the color stops as defined in base class.</param>
|
|||
public EllipticGradientBrush( |
|||
Point center, |
|||
Point referenceAxisEnd, |
|||
float axisRatio, |
|||
params ColorStop<TPixel>[] colorStops) |
|||
: base(colorStops) |
|||
{ |
|||
this.center = center; |
|||
this.referenceAxisEnd = referenceAxisEnd; |
|||
this.axisRatio = axisRatio; |
|||
} |
|||
|
|||
/// <inheritdoc cref="CreateApplicator" />
|
|||
public override BrushApplicator<TPixel> CreateApplicator( |
|||
ImageFrame<TPixel> source, |
|||
RectangleF region, |
|||
GraphicsOptions options) => |
|||
new RadialGradientBrushApplicator( |
|||
source, |
|||
options, |
|||
this.center, |
|||
this.referenceAxisEnd, |
|||
this.axisRatio, |
|||
this.ColorStops, |
|||
region); |
|||
|
|||
/// <inheritdoc />
|
|||
protected class RadialGradientBrushApplicator : AbstractGradientBrushApplicator |
|||
{ |
|||
private readonly Point center; |
|||
|
|||
private readonly Point referenceAxisEnd; |
|||
|
|||
private readonly float axisRatio; |
|||
|
|||
private readonly double rotation; |
|||
|
|||
private readonly float referenceRadius; |
|||
|
|||
private readonly float secondRadius; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="RadialGradientBrushApplicator" /> class.
|
|||
/// </summary>
|
|||
/// <param name="target">The target image</param>
|
|||
/// <param name="options">The options</param>
|
|||
/// <param name="center">Center of the ellipse</param>
|
|||
/// <param name="referenceAxisEnd">Point on one angular points of the ellipse.</param>
|
|||
/// <param name="axisRatio">
|
|||
/// Ratio of the axis length's. Used to determine the length of the second axis,
|
|||
/// the first is defined by <see cref="center"/> and <see cref="referenceAxisEnd"/>.</param>
|
|||
/// <param name="colorStops">Definition of colors</param>
|
|||
/// <param name="region">TODO !</param>
|
|||
public RadialGradientBrushApplicator( |
|||
ImageFrame<TPixel> target, |
|||
GraphicsOptions options, |
|||
Point center, |
|||
Point referenceAxisEnd, |
|||
float axisRatio, |
|||
ColorStop<TPixel>[] colorStops, |
|||
RectangleF region) |
|||
: base(target, options, colorStops, region) |
|||
{ |
|||
this.center = center; |
|||
this.referenceAxisEnd = referenceAxisEnd; |
|||
this.axisRatio = axisRatio; |
|||
this.rotation = this.AngleBetween( |
|||
this.center, |
|||
new PointF(this.center.X + 1, this.center.Y), |
|||
this.referenceAxisEnd); |
|||
this.referenceRadius = this.DistanceBetween(this.center, this.referenceAxisEnd); |
|||
this.secondRadius = this.referenceRadius * this.axisRatio; |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
public override void Dispose() |
|||
{ |
|||
} |
|||
|
|||
/// <inheritdoc />
|
|||
protected override float PositionOnGradient(int xt, int yt) |
|||
{ |
|||
float x0 = xt - this.center.X; // TODO: rotate this point after translation
|
|||
float y0 = yt - this.center.Y; |
|||
|
|||
float x = (float)((x0 * Math.Cos(this.rotation)) - (y0 * Math.Sin(this.rotation))); // TODO: store sin and cos of rotation as constant!
|
|||
float y = (float)((x0 * Math.Sin(this.rotation)) + (y0 * Math.Cos(this.rotation))); |
|||
|
|||
var inBoundaryChecker = ((x * x) / (this.referenceRadius * this.referenceRadius)) |
|||
+ ((y * y) / (this.secondRadius * this.secondRadius)); |
|||
|
|||
return inBoundaryChecker; |
|||
} |
|||
|
|||
private float AngleBetween(PointF junction, PointF a, PointF b) |
|||
{ |
|||
var vA = a - junction; |
|||
var vB = b - junction; |
|||
return (float)(Math.Atan2(vB.Y, vB.X) |
|||
- Math.Atan2(vA.Y, vA.X)); |
|||
} |
|||
|
|||
private float DistanceBetween( |
|||
PointF p1, |
|||
PointF p2) |
|||
{ |
|||
return (float)Math.Sqrt(Math.Pow(p1.X - p2.X, 2) + Math.Pow(p1.Y - p2.Y, 2)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,124 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Linq; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.ImageSharp.Processing.Drawing; |
|||
using SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing |
|||
{ |
|||
public class FillEllipticGradientBrushTests : FileTestBase |
|||
{ |
|||
[Fact] |
|||
public void EllipticGradientBrushWithEqualColorsAndReturnsUnicolorImage() |
|||
{ |
|||
string path = TestEnvironment.CreateOutputDirectory("Fill", "EllipticGradientBrush"); |
|||
using (var image = new Image<Rgba32>(10, 10)) |
|||
{ |
|||
EllipticGradientBrush<Rgba32> unicolorLinearGradientBrush = |
|||
new EllipticGradientBrush<Rgba32>( |
|||
new SixLabors.Primitives.Point(0, 0), |
|||
new SixLabors.Primitives.Point(10, 0), |
|||
1.0f, |
|||
new ColorStop<Rgba32>(0, Rgba32.Red), |
|||
new ColorStop<Rgba32>(1, Rgba32.Red)); |
|||
|
|||
image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); |
|||
image.Save($"{path}/UnicolorCircleGradient.png"); |
|||
|
|||
using (PixelAccessor<Rgba32> sourcePixels = image.Lock()) |
|||
{ |
|||
Assert.Equal(Rgba32.Red, sourcePixels[0, 0]); |
|||
Assert.Equal(Rgba32.Red, sourcePixels[9, 9]); |
|||
Assert.Equal(Rgba32.Red, sourcePixels[5, 5]); |
|||
Assert.Equal(Rgba32.Red, sourcePixels[3, 8]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0.1)] |
|||
[InlineData(0.4)] |
|||
[InlineData(0.8)] |
|||
[InlineData(1.0)] |
|||
[InlineData(1.2)] |
|||
[InlineData(1.6)] |
|||
[InlineData(2.0)] |
|||
public void EllipticGradientBrushProducesAxisParallelEllipsesWithDifferentRatio( |
|||
float ratio) |
|||
{ |
|||
string path = TestEnvironment.CreateOutputDirectory("Fill", "EllipticGradientBrush"); |
|||
using (var image = new Image<Rgba32>(1000, 1000)) |
|||
{ |
|||
EllipticGradientBrush<Rgba32> unicolorLinearGradientBrush = |
|||
new EllipticGradientBrush<Rgba32>( |
|||
new SixLabors.Primitives.Point(500, 500), |
|||
new SixLabors.Primitives.Point(500, 750), |
|||
ratio, |
|||
new ColorStop<Rgba32>(0, Rgba32.Yellow), |
|||
new ColorStop<Rgba32>(1, Rgba32.Red), |
|||
new ColorStop<Rgba32>(1, Rgba32.Black)); |
|||
|
|||
image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); |
|||
image.Save($"{path}/Ellipsis{ratio}.png"); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(0.1, 0)] |
|||
[InlineData(0.4, 0)] |
|||
[InlineData(0.8, 0)] |
|||
[InlineData(1.0, 0)] |
|||
|
|||
[InlineData(0.1, 45)] |
|||
[InlineData(0.4, 45)] |
|||
[InlineData(0.8, 45)] |
|||
[InlineData(1.0, 45)] |
|||
|
|||
[InlineData(0.1, 90)] |
|||
[InlineData(0.4, 90)] |
|||
[InlineData(0.8, 90)] |
|||
[InlineData(1.0, 90)] |
|||
|
|||
[InlineData(0.1, 30)] |
|||
[InlineData(0.4, 30)] |
|||
[InlineData(0.8, 30)] |
|||
[InlineData(1.0, 30)] |
|||
public void EllipticGradientBrushProducesRotatedEllipsesWithDifferentRatio( |
|||
float ratio, |
|||
float rotationInDegree) |
|||
{ |
|||
var center = new SixLabors.Primitives.Point(500, 500); |
|||
|
|||
var rotation = (Math.PI * rotationInDegree) / 180.0; |
|||
var cos = Math.Cos(rotation); |
|||
var sin = Math.Sin(rotation); |
|||
|
|||
int axisX = (int)((center.X * cos) - (center.Y * sin)); |
|||
int axisY = (int)((center.X * sin) + (center.Y * cos)); |
|||
|
|||
string path = TestEnvironment.CreateOutputDirectory("Fill", "EllipticGradientBrush"); |
|||
using (var image = new Image<Rgba32>(1000, 1000)) |
|||
{ |
|||
EllipticGradientBrush<Rgba32> unicolorLinearGradientBrush = |
|||
new EllipticGradientBrush<Rgba32>( |
|||
center, |
|||
new SixLabors.Primitives.Point(axisX, axisY), |
|||
ratio, |
|||
new ColorStop<Rgba32>(0, Rgba32.Yellow), |
|||
new ColorStop<Rgba32>(1, Rgba32.Red), |
|||
new ColorStop<Rgba32>(1, Rgba32.Black)); |
|||
|
|||
image.Mutate(x => x.Fill(unicolorLinearGradientBrush)); |
|||
image.Save($"{path}/Ellipsis{ratio}_rot{rotationInDegree}°.png"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue