mirror of https://github.com/SixLabors/ImageSharp
2 changed files with 187 additions and 0 deletions
@ -0,0 +1,100 @@ |
|||||
|
using System; |
||||
|
|
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A Circular Gradient Brush, defined by center point and radius.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
public class RadialGradientBrush<TPixel> : AbstractGradientBrush<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
private readonly Point center; |
||||
|
|
||||
|
private readonly float radius; |
||||
|
|
||||
|
/// <inheritdoc cref="AbstractGradientBrush{TPixel}" />
|
||||
|
/// <param name="center">The center of the circular gradient and 0 for the color stops.</param>
|
||||
|
/// <param name="radius">The radius of the circular gradient and 1 for the color stops.</param>
|
||||
|
/// <param name="colorStops">the color stops as defined in base class.</param>
|
||||
|
public RadialGradientBrush( |
||||
|
Point center, |
||||
|
float radius, |
||||
|
params ColorStop<TPixel>[] colorStops) |
||||
|
: base(colorStops) |
||||
|
{ |
||||
|
this.center = center; |
||||
|
this.radius = radius; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc cref="CreateApplicator" />
|
||||
|
public override BrushApplicator<TPixel> CreateApplicator( |
||||
|
ImageFrame<TPixel> source, |
||||
|
RectangleF region, |
||||
|
GraphicsOptions options) => |
||||
|
new RadialGradientBrushApplicator( |
||||
|
source, |
||||
|
options, |
||||
|
this.center, |
||||
|
this.radius, |
||||
|
this.ColorStops, |
||||
|
region); |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
protected class RadialGradientBrushApplicator : AbstractGradientBrushApplicator |
||||
|
{ |
||||
|
private readonly Point center; |
||||
|
|
||||
|
private readonly float radius; |
||||
|
|
||||
|
/// <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 point of the gradient.</param>
|
||||
|
/// <param name="radius">Radius of the gradient.</param>
|
||||
|
/// <param name="colorStops">Definition of colors.</param>
|
||||
|
/// <param name="region">TODO !</param>
|
||||
|
public RadialGradientBrushApplicator( |
||||
|
ImageFrame<TPixel> target, |
||||
|
GraphicsOptions options, |
||||
|
Point center, |
||||
|
float radius, |
||||
|
ColorStop<TPixel>[] colorStops, |
||||
|
RectangleF region) |
||||
|
: base(target, options, colorStops, region) |
||||
|
{ |
||||
|
this.center = center; |
||||
|
this.radius = radius; |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc cref="Dispose" />
|
||||
|
public override void Dispose() |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// As this is a circular gradient, the position on the gradient is based on
|
||||
|
/// the distance of the point to the center.
|
||||
|
/// </summary>
|
||||
|
/// <param name="x">The X coordinate of the target pixel.</param>
|
||||
|
/// <param name="y">The Y coordinate of the target pixel.</param>
|
||||
|
/// <returns>the position on the color gradient.</returns>
|
||||
|
protected override float PositionOnGradient(int x, int y) |
||||
|
{ |
||||
|
float distance = (float)Math.Sqrt(Math.Pow(this.center.X - x, 2) + Math.Pow(this.center.Y - y, 2)); |
||||
|
return distance / this.radius; |
||||
|
} |
||||
|
|
||||
|
internal override void Apply(Span<float> scanline, int x, int y) |
||||
|
{ |
||||
|
// TODO: each row is symmetric across center, so we can calculate half of it and mirror it to improve performance.
|
||||
|
base.Apply(scanline, x, y); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,87 @@ |
|||||
|
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 FillRadialGradientBrushTests : FileTestBase |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void RadialGradientBrushWithEqualColorsReturnsUnicolorImage() |
||||
|
{ |
||||
|
string path = TestEnvironment.CreateOutputDirectory("Fill", "RadialGradientBrush"); |
||||
|
using (var image = new Image<Rgba32>(200, 200)) |
||||
|
{ |
||||
|
RadialGradientBrush<Rgba32> unicolorRadialGradientBrush = |
||||
|
new RadialGradientBrush<Rgba32>( |
||||
|
new SixLabors.Primitives.Point(0, 0), |
||||
|
100, |
||||
|
new ColorStop<Rgba32>(0, Rgba32.Red), |
||||
|
new ColorStop<Rgba32>(1, Rgba32.Red)); |
||||
|
|
||||
|
image.Mutate(x => x.Fill(unicolorRadialGradientBrush)); |
||||
|
image.Save($"{path}/UnicolorGradient.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(250, 250)] |
||||
|
[InlineData(0, 0)] |
||||
|
[InlineData(250, 0)] |
||||
|
[InlineData(0, 250)] |
||||
|
[InlineData(-100, 250)] |
||||
|
public void RadialGradientBrushWithDifferentCentersReturnsImage( |
||||
|
int centerX, |
||||
|
int centerY) |
||||
|
{ |
||||
|
int width = 500; |
||||
|
|
||||
|
string path = TestEnvironment.CreateOutputDirectory("Fill", "RadialGradientBrush"); |
||||
|
using (var image = new Image<Rgba32>(width, width)) |
||||
|
{ |
||||
|
RadialGradientBrush<Rgba32> brush = |
||||
|
new RadialGradientBrush<Rgba32>( |
||||
|
new SixLabors.Primitives.Point(centerX, centerY), |
||||
|
width / 2f, |
||||
|
new ColorStop<Rgba32>(0, Rgba32.Red), |
||||
|
new ColorStop<Rgba32>(1, Rgba32.Yellow)); |
||||
|
|
||||
|
image.Mutate(x => x.Fill(brush)); |
||||
|
image.Save($"{path}/CenterAt{centerX}_{centerY}.png"); |
||||
|
|
||||
|
// using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
|
||||
|
// {
|
||||
|
// Rgba32 columnColor0 = sourcePixels[0, 0];
|
||||
|
// Rgba32 columnColor23 = sourcePixels[23, 0];
|
||||
|
// Rgba32 columnColor42 = sourcePixels[42, 0];
|
||||
|
// Rgba32 columnColor333 = sourcePixels[333, 0];
|
||||
|
//
|
||||
|
// Rgba32 lastColumnColor = sourcePixels[lastColumnIndex, 0];
|
||||
|
//
|
||||
|
// for (int i = 0; i < width; i++)
|
||||
|
// {
|
||||
|
// // check first and last column:
|
||||
|
// Assert.Equal(columnColor0, sourcePixels[0, i]);
|
||||
|
// Assert.Equal(lastColumnColor, sourcePixels[lastColumnIndex, i]);
|
||||
|
//
|
||||
|
// // check the random colors:
|
||||
|
// Assert.True(columnColor23 == sourcePixels[23, i], $"at {i}");
|
||||
|
// Assert.Equal(columnColor42, sourcePixels[42, i]);
|
||||
|
// Assert.Equal(columnColor333, sourcePixels[333, i]);
|
||||
|
// }
|
||||
|
// }
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue