Browse Source

first implementation of an elliptical gradient brush

af/merge-core
Unknown 8 years ago
parent
commit
d7414294ab
  1. 143
      src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush.cs
  2. 124
      tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs

143
src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush.cs

@ -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));
}
}
}
}

124
tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs

@ -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…
Cancel
Save