Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
c02f9d8
#86: started to work on Gradient Brushes: Linear gradient brush,
jongleur1983 Apr 19, 2018
4975eae
fix some typos in documentation.
jongleur1983 Apr 19, 2018
e1ee9b0
FIX bug in BrushApplicator when applying BlendPercentage
jongleur1983 Apr 19, 2018
6d87844
#542: fix test: indices are 0-based, so bottom left pixel is one smaller
jongleur1983 Apr 19, 2018
1e587bc
#542: use struct for ColorStops
jongleur1983 Apr 19, 2018
6dd544d
fix code styling issues
jongleur1983 Apr 20, 2018
ce72683
#542: reduce test output image sizes
jongleur1983 Apr 21, 2018
90293cc
#542: add test for vertical gradient
jongleur1983 Apr 21, 2018
94dbb74
#542: fix implementation of non-axial gradients and add tests
jongleur1983 Apr 21, 2018
7a3ba8a
#542: add tests for multi-color gradients
jongleur1983 Apr 21, 2018
28457c8
cleanup whitespace
jongleur1983 Apr 21, 2018
35d0f56
#542: add test creating black-white patterns by gradients
jongleur1983 Apr 21, 2018
0a642ff
#542: define debuggerDisplay of ColorStop,
jongleur1983 Apr 21, 2018
eafdfea
fix whitespacing
jongleur1983 Apr 21, 2018
1cd8656
#542: use bigger files for the tests to "hide" rounding issues:
jongleur1983 Apr 21, 2018
393444c
#542: fix rounding issues in tests
jongleur1983 Apr 21, 2018
d977ecf
#542: rename file to match other files in the solution (include generic)
jongleur1983 Apr 21, 2018
d43c70a
fix documentation typo
jongleur1983 Apr 22, 2018
72ae5fa
#542: refactor to prepare for other gradients
jongleur1983 Apr 22, 2018
94f1698
implement radial gradient brush.
jongleur1983 Apr 22, 2018
e837349
first implementation of an elliptical gradient brush
jongleur1983 Apr 26, 2018
a6e0402
optimization of EllipticGradientBrush
jongleur1983 Apr 26, 2018
75dab52
improve performance on distance calculation
jongleur1983 Apr 26, 2018
d29b601
implement GradientRepetitionModes
jongleur1983 Apr 26, 2018
c8d0256
remove not implemented Polygon Brush from documentation for now.
jongleur1983 Apr 26, 2018
1cac740
Merge branch 'master' into GradientBrush
jongleur1983 Apr 26, 2018
2032ec2
Merge branch 'master' into GradientBrush
JimBobSquarePants Apr 28, 2018
4f2d9d3
Merge remote-tracking branch 'origin/master' into GradientBrush
jongleur1983 May 1, 2018
9e98752
#542: apply change requests made by @tocsoft in code review.
jongleur1983 May 1, 2018
c8b8037
Merge branch 'master' into GradientBrush
jongleur1983 May 2, 2018
7b4ec33
Merge branch 'master' into GradientBrush
JimBobSquarePants May 4, 2018
b397622
#542: refactor tests to follow the recommended pattern for drawing te…
jongleur1983 May 6, 2018
2af95be
#542: reduce test image sizes to save submudule size
jongleur1983 May 6, 2018
92b0130
rename files to add {TPixel} generic parameter.
jongleur1983 May 6, 2018
09f6a4f
#542: remove pixel checks and base class
jongleur1983 May 7, 2018
3052fe0
#542: improve tests for elliptic gradients
jongleur1983 May 7, 2018
f290896
#542: remove redundant Asserts, cleanup code
jongleur1983 May 7, 2018
e8bb3d0
Merge branch 'master' into GradientBrush
JimBobSquarePants May 8, 2018
98ff041
Merge branch 'master' into GradientBrush
JimBobSquarePants May 9, 2018
46c8bd9
#542: shorten test names, improve test image filenames
jongleur1983 May 11, 2018
ea7cb83
#542: code cleanup
jongleur1983 May 11, 2018
68b5e61
Merge remote-tracking branch 'SixLabors/master'
jongleur1983 May 11, 2018
11fb7d3
Merge branch 'master' into GradientBrush
jongleur1983 May 11, 2018
8f5cb5f
#542: cleanup test file names, fix naming for RadialGradientBrush tes…
jongleur1983 May 11, 2018
e3889ab
introduce TestImageExtensions.VerifyOperation(), simplify FillRadialG…
antonfirsov May 11, 2018
ce8ca97
FillLinearGradientBrushTests #1
antonfirsov May 11, 2018
e818a54
VerticalReturnsUnicolorColumns -> VerticalBrushReturnsUnicolorRows (+…
antonfirsov May 11, 2018
d53e015
finish refactoring FillLinearGradientBrushTests
antonfirsov May 11, 2018
a217e42
FillEllipticGradientBrushTests
antonfirsov May 13, 2018
5158f0b
update submodule
antonfirsov May 13, 2018
152725c
Merge branch 'master' into GradientBrush
antonfirsov May 13, 2018
9fa1e3e
add tolerance to comparison in tests
antonfirsov May 13, 2018
195a453
Merge remote-tracking branch 'origin/master' into GradientBrush
antonfirsov May 13, 2018
5d3daaa
#542: apply naming scheme for abstract classes
jongleur1983 May 14, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ internal virtual void Apply(Span<float> scanline, int x, int y)
{
amountSpan[i] = scanline[i] * this.Options.BlendPercentage;
}
else
{
amountSpan[i] = scanline[i];
}

overlaySpan[i] = this[x + i, y];
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Diagnostics;

using SixLabors.ImageSharp.PixelFormats;

namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
{
/// <summary>
/// A struct that defines a single color stop.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
[DebuggerDisplay("ColorStop({Ratio} -> {Color}")]
public struct ColorStop<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// Initializes a new instance of the <see cref="ColorStop{TPixel}" /> struct.
/// </summary>
/// <param name="ratio">Where should it be? 0 is at the start, 1 at the end of the Gradient.</param>
/// <param name="color">What color should be used at that point?</param>
public ColorStop(float ratio, TPixel color)
{
this.Ratio = ratio;
this.Color = color;
}

/// <summary>
/// Gets the point along the defined gradient axis.
/// </summary>
public float Ratio { get; }

/// <summary>
/// Gets the color to be used.
/// </summary>
public TPixel Color { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
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 sealed class EllipticGradientBrush<TPixel> : GradientBrushBase<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly Point center;

private readonly Point referenceAxisEnd;

private readonly float axisRatio;

/// <inheritdoc cref="GradientBrushBase{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="repetitionMode">Defines how the colors of the gradients are repeated.</param>
/// <param name="colorStops">the color stops as defined in base class.</param>
public EllipticGradientBrush(
Point center,
Point referenceAxisEnd,
float axisRatio,
GradientRepetitionMode repetitionMode,
params ColorStop<TPixel>[] colorStops)
: base(repetitionMode, 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,
this.RepetitionMode);

/// <inheritdoc />
private sealed class RadialGradientBrushApplicator : GradientBrushApplicatorBase
{
private readonly Point center;

private readonly Point referenceAxisEnd;

private readonly float axisRatio;

private readonly double rotation;

private readonly float referenceRadius;

private readonly float secondRadius;

private readonly float cosRotation;

private readonly float sinRotation;

private readonly float secondRadiusSquared;

private readonly float referenceRadiusSquared;

/// <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="repetitionMode">Defines how the gradient colors are repeated.</param>
public RadialGradientBrushApplicator(
ImageFrame<TPixel> target,
GraphicsOptions options,
Point center,
Point referenceAxisEnd,
float axisRatio,
ColorStop<TPixel>[] colorStops,
GradientRepetitionMode repetitionMode)
: base(target, options, colorStops, repetitionMode)
{
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;

this.referenceRadiusSquared = this.referenceRadius * this.referenceRadius;
this.secondRadiusSquared = this.secondRadius * this.secondRadius;

this.sinRotation = (float)Math.Sin(this.rotation);
this.cosRotation = (float)Math.Cos(this.rotation);
}

/// <inheritdoc />
public override void Dispose()
{
}

/// <inheritdoc />
protected override float PositionOnGradient(int xt, int yt)
{
float x0 = xt - this.center.X;
float y0 = yt - this.center.Y;

float x = (x0 * this.cosRotation) - (y0 * this.sinRotation);
float y = (x0 * this.sinRotation) + (y0 * this.cosRotation);

float xSquared = x * x;
float ySquared = y * y;

var inBoundaryChecker = (xSquared / this.referenceRadiusSquared)
+ (ySquared / this.secondRadiusSquared);

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)
{
float dX = p1.X - p2.X;
float dXsquared = dX * dX;

float dY = p1.Y - p2.Y;
float dYsquared = dY * dY;
return (float)Math.Sqrt(dXsquared + dYsquared);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
using System;
using System.Numerics;

using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.PixelFormats.PixelBlenders;
using SixLabors.Primitives;

namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
{
/// <summary>
/// Base class for Gradient brushes
/// </summary>
/// <typeparam name="TPixel">The pixel format</typeparam>
public abstract class GradientBrushBase<TPixel> : IBrush<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <inheritdoc cref="IBrush{TPixel}"/>
/// <param name="repetitionMode">Defines how the colors are repeated beyond the interval [0..1]</param>
/// <param name="colorStops">The gradient colors.</param>
protected GradientBrushBase(
GradientRepetitionMode repetitionMode,
params ColorStop<TPixel>[] colorStops)
{
this.RepetitionMode = repetitionMode;
this.ColorStops = colorStops;
}

/// <summary>
/// Gets how the colors are repeated beyond the interval [0..1].
/// </summary>
protected GradientRepetitionMode RepetitionMode { get; }

/// <summary>
/// Gets the list of color stops for this gradient.
/// </summary>
protected ColorStop<TPixel>[] ColorStops { get; }

/// <inheritdoc cref="IBrush{TPixel}" />
public abstract BrushApplicator<TPixel> CreateApplicator(
ImageFrame<TPixel> source,
RectangleF region,
GraphicsOptions options);

/// <summary>
/// Base class for gradient brush applicators
/// </summary>
protected abstract class GradientBrushApplicatorBase : BrushApplicator<TPixel>
{
private readonly ColorStop<TPixel>[] colorStops;

private readonly GradientRepetitionMode repetitionMode;

/// <summary>
/// Initializes a new instance of the <see cref="GradientBrushApplicatorBase"/> class.
/// </summary>
/// <param name="target">The target.</param>
/// <param name="options">The options.</param>
/// <param name="colorStops">An array of color stops sorted by their position.</param>
/// <param name="repetitionMode">Defines if and how the gradient should be repeated.</param>
protected GradientBrushApplicatorBase(
ImageFrame<TPixel> target,
GraphicsOptions options,
ColorStop<TPixel>[] colorStops,
GradientRepetitionMode repetitionMode)
: base(target, options)
{
this.colorStops = colorStops; // TODO: requires colorStops to be sorted by position - should that be checked?
this.repetitionMode = repetitionMode;
}

/// <summary>
/// Base implementation of the indexer for gradients
/// (follows the facade pattern, using abstract methods)
/// </summary>
/// <param name="x">X coordinate of the Pixel.</param>
/// <param name="y">Y coordinate of the Pixel.</param>
internal override TPixel this[int x, int y]
{
get
{
float positionOnCompleteGradient = this.PositionOnGradient(x, y);

switch (this.repetitionMode)
{
case GradientRepetitionMode.None:
// do nothing. The following could be done, but is not necessary:
// onLocalGradient = Math.Min(0, Math.Max(1, onLocalGradient));
break;
case GradientRepetitionMode.Repeat:
positionOnCompleteGradient = positionOnCompleteGradient % 1;
break;
case GradientRepetitionMode.Reflect:
positionOnCompleteGradient = positionOnCompleteGradient % 2;
if (positionOnCompleteGradient > 1)
{
positionOnCompleteGradient = 2 - positionOnCompleteGradient;
}

break;
case GradientRepetitionMode.DontFill:
if (positionOnCompleteGradient > 1 || positionOnCompleteGradient < 0)
{
return NamedColors<TPixel>.Transparent;
}

break;
default:
throw new ArgumentOutOfRangeException();
}

var (from, to) = this.GetGradientSegment(positionOnCompleteGradient);

if (from.Color.Equals(to.Color))
{
return from.Color;
}
else
{
var fromAsVector = from.Color.ToVector4();
var toAsVector = to.Color.ToVector4();
float onLocalGradient = (positionOnCompleteGradient - from.Ratio) / to.Ratio;

// TODO: this should be changeble for different gradienting functions
Vector4 result = PorterDuffFunctions.Normal(
fromAsVector,
toAsVector,
onLocalGradient);

TPixel resultColor = default;
resultColor.PackFromVector4(result);
return resultColor;
}
}
}

/// <summary>
/// calculates the position on the gradient for a given pixel.
/// This method is abstract as it's content depends on the shape of the gradient.
/// </summary>
/// <param name="x">The x coordinate of the pixel</param>
/// <param name="y">The y coordinate of the pixel</param>
/// <returns>
/// The position the given pixel has on the gradient.
/// The position is not bound to the [0..1] interval.
/// Values outside of that interval may be treated differently,
/// e.g. for the <see cref="GradientRepetitionMode" /> enum.
/// </returns>
protected abstract float PositionOnGradient(int x, int y);

private (ColorStop<TPixel> from, ColorStop<TPixel> to) GetGradientSegment(
float positionOnCompleteGradient)
{
var localGradientFrom = this.colorStops[0];
ColorStop<TPixel> localGradientTo = default;

// TODO: ensure colorStops has at least 2 items (technically 1 would be okay, but that's no gradient)
foreach (var colorStop in this.colorStops)
{
localGradientTo = colorStop;

if (colorStop.Ratio > positionOnCompleteGradient)
{
// we're done here, so break it!
break;
}

localGradientFrom = localGradientTo;
}

return (localGradientFrom, localGradientTo);
}
}
}
}
Loading