Browse Source

implement GradientRepetitionModes

af/merge-core
Unknown 8 years ago
parent
commit
6bca0b2a6c
  1. 52
      src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/AbstractGradientBrush{TPixel}.cs
  2. 13
      src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/EllipticGradientBrush.cs
  3. 35
      src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/GradientRepetitionMode.cs
  4. 17
      src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/LinearGradientBrush{TPixel}.cs
  5. 12
      src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush.cs
  6. 5
      tests/ImageSharp.Tests/Drawing/FillEllipticGradientBrushTest.cs
  7. 56
      tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs
  8. 2
      tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs

52
src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/AbstractGradientBrush{TPixel}.cs

@ -1,4 +1,5 @@
using System.Numerics;
using System;
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.PixelFormats.PixelBlenders;
@ -14,12 +15,21 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
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 AbstractGradientBrush(params ColorStop<TPixel>[] colorStops)
protected AbstractGradientBrush(
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>
@ -38,21 +48,24 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
{
private readonly ColorStop<TPixel>[] colorStops;
private readonly GradientRepetitionMode repetitionMode;
/// <summary>
/// Initializes a new instance of the <see cref="AbstractGradientBrushApplicator"/> 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="region">TODO: use region, compare with other Brushes for reference</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 AbstractGradientBrushApplicator(
ImageFrame<TPixel> target,
GraphicsOptions options,
ColorStop<TPixel>[] colorStops,
RectangleF region)
GradientRepetitionMode repetitionMode)
: base(target, options)
{
this.colorStops = colorStops; // TODO: requires colorStops to be sorted by position - should that be checked?
this.repetitionMode = repetitionMode;
}
/// <summary>
@ -66,6 +79,35 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
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))

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

@ -29,13 +29,15 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
/// 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(colorStops)
: base(repetitionMode, colorStops)
{
this.center = center;
this.referenceAxisEnd = referenceAxisEnd;
@ -54,7 +56,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
this.referenceAxisEnd,
this.axisRatio,
this.ColorStops,
region);
this.RepetitionMode);
/// <inheritdoc />
protected class RadialGradientBrushApplicator : AbstractGradientBrushApplicator
@ -90,7 +92,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
/// 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>
/// <param name="repetitionMode">Defines how the gradient colors are repeated.</param>
public RadialGradientBrushApplicator(
ImageFrame<TPixel> target,
GraphicsOptions options,
@ -98,8 +100,8 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
Point referenceAxisEnd,
float axisRatio,
ColorStop<TPixel>[] colorStops,
RectangleF region)
: base(target, options, colorStops, region)
GradientRepetitionMode repetitionMode)
: base(target, options, colorStops, repetitionMode)
{
this.center = center;
this.referenceAxisEnd = referenceAxisEnd;
@ -116,7 +118,6 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
this.sinRotation = (float)Math.Sin(this.rotation);
this.cosRotation = (float)Math.Cos(this.rotation);
}
/// <inheritdoc />

35
src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/GradientRepetitionMode.cs

@ -0,0 +1,35 @@
namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
{
/// <summary>
/// Modes to repeat a gradient.
/// </summary>
public enum GradientRepetitionMode
{
/// <summary>
/// don't repeat, keep the color of start and end beyond those points stable.
/// </summary>
None,
/// <summary>
/// Repeat the gradient.
/// If it's a black-white gradient, with Repeat it will be Black->{gray}->White|Black->{gray}->White|...
/// </summary>
Repeat,
/// <summary>
/// Reflect the gradient.
/// Similar to <see cref="Repeat"/>, but each other repetition uses inverse order of <see cref="ColorStop{TPixel}"/>s.
/// Used on a Black-White gradient, Reflect leads to Black->{gray}->White->{gray}->White...
/// </summary>
Reflect,
/// <summary>
/// With DontFill a gradient does not touch any pixel beyond it's borders.
/// For the <see cref="LinearGradientBrush{TPixel}" /> this is beyond the orthogonal through start and end,
/// For the <see cref="PolygonalGradientBrush" /> it's outside the polygon,
/// TODO For see cref="RadialGradientBrush{TPixel}"/> and <see cref="EllipticGradientBrush{TPixel}"/> it's beyond 1.0.
/// TODO: check documentation consistency according to 1.0
/// </summary>
DontFill
}
}

17
src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/LinearGradientBrush{TPixel}.cs

@ -23,9 +23,14 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
/// </summary>
/// <param name="p1">Start point</param>
/// <param name="p2">End point</param>
/// <param name="repetitionMode">defines how colors are repeated.</param>
/// <param name="colorStops"><inheritdoc /></param>
public LinearGradientBrush(Point p1, Point p2, params ColorStop<TPixel>[] colorStops)
: base(colorStops)
public LinearGradientBrush(
Point p1,
Point p2,
GradientRepetitionMode repetitionMode,
params ColorStop<TPixel>[] colorStops)
: base(repetitionMode, colorStops)
{
this.p1 = p1;
this.p2 = p2;
@ -33,7 +38,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
/// <inheritdoc />
public override BrushApplicator<TPixel> CreateApplicator(ImageFrame<TPixel> source, RectangleF region, GraphicsOptions options)
=> new LinearGradientBrushApplicator(source, this.p1, this.p2, this.ColorStops, region, options);
=> new LinearGradientBrushApplicator(source, this.p1, this.p2, this.ColorStops, this.RepetitionMode, options);
/// <summary>
/// The linear gradient brush applicator.
@ -81,16 +86,16 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
/// <param name="start">start point of the gradient</param>
/// <param name="end">end point of the gradient</param>
/// <param name="colorStops">tuple list of colors and their respective position between 0 and 1 on the line</param>
/// <param name="region">the region, copied from SolidColorBrush, not sure if necessary! TODO</param>
/// <param name="repetitionMode">defines how the gradient colors are repeated.</param>
/// <param name="options">the graphics options</param>
public LinearGradientBrushApplicator(
ImageFrame<TPixel> source,
Point start,
Point end,
ColorStop<TPixel>[] colorStops,
RectangleF region,
GradientRepetitionMode repetitionMode,
GraphicsOptions options)
: base(source, options, colorStops, region)
: base(source, options, colorStops, repetitionMode)
{
this.start = start;
this.end = end;

12
src/ImageSharp.Drawing/Processing/Drawing/Brushes/GradientBrushes/RadialGradientBrush.cs

@ -19,12 +19,14 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
/// <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="repetitionMode">Defines how the colors in the gradient are repeated.</param>
/// <param name="colorStops">the color stops as defined in base class.</param>
public RadialGradientBrush(
Point center,
float radius,
GradientRepetitionMode repetitionMode,
params ColorStop<TPixel>[] colorStops)
: base(colorStops)
: base(repetitionMode, colorStops)
{
this.center = center;
this.radius = radius;
@ -41,7 +43,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
this.center,
this.radius,
this.ColorStops,
region);
this.RepetitionMode);
/// <inheritdoc />
protected class RadialGradientBrushApplicator : AbstractGradientBrushApplicator
@ -58,15 +60,15 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Brushes.GradientBrushes
/// <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>
/// <param name="repetitionMode">How the colors are repeated beyond the first gradient.</param>
public RadialGradientBrushApplicator(
ImageFrame<TPixel> target,
GraphicsOptions options,
Point center,
float radius,
ColorStop<TPixel>[] colorStops,
RectangleF region)
: base(target, options, colorStops, region)
GradientRepetitionMode repetitionMode)
: base(target, options, colorStops, repetitionMode)
{
this.center = center;
this.radius = radius;

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

@ -2,8 +2,6 @@
// 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;
@ -27,6 +25,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
new SixLabors.Primitives.Point(0, 0),
new SixLabors.Primitives.Point(10, 0),
1.0f,
GradientRepetitionMode.None,
new ColorStop<Rgba32>(0, Rgba32.Red),
new ColorStop<Rgba32>(1, Rgba32.Red));
@ -62,6 +61,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
new SixLabors.Primitives.Point(500, 500),
new SixLabors.Primitives.Point(500, 750),
ratio,
GradientRepetitionMode.None,
new ColorStop<Rgba32>(0, Rgba32.Yellow),
new ColorStop<Rgba32>(1, Rgba32.Red),
new ColorStop<Rgba32>(1, Rgba32.Black));
@ -112,6 +112,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
center,
new SixLabors.Primitives.Point(axisX, axisY),
ratio,
GradientRepetitionMode.None,
new ColorStop<Rgba32>(0, Rgba32.Yellow),
new ColorStop<Rgba32>(1, Rgba32.Red),
new ColorStop<Rgba32>(1, Rgba32.Black));

56
tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs

@ -24,6 +24,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
new LinearGradientBrush<Rgba32>(
new SixLabors.Primitives.Point(0, 0),
new SixLabors.Primitives.Point(10, 0),
GradientRepetitionMode.None,
new ColorStop<Rgba32>(0, Rgba32.Red),
new ColorStop<Rgba32>(1, Rgba32.Red));
@ -54,6 +55,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
new LinearGradientBrush<Rgba32>(
new SixLabors.Primitives.Point(0, 0),
new SixLabors.Primitives.Point(500, 0),
GradientRepetitionMode.None,
new ColorStop<Rgba32>(0, Rgba32.Red),
new ColorStop<Rgba32>(1, Rgba32.Yellow));
@ -84,6 +86,56 @@ namespace SixLabors.ImageSharp.Tests.Drawing
}
}
[Theory]
[InlineData(GradientRepetitionMode.DontFill)]
[InlineData(GradientRepetitionMode.None)]
[InlineData(GradientRepetitionMode.Repeat)]
[InlineData(GradientRepetitionMode.Reflect)]
public void HorizontalLinearGradientBrushWithDifferentRepetitionModesCreatesCorrectImages(
GradientRepetitionMode repetitionMode)
{
int width = 500;
int height = 10;
int lastColumnIndex = width - 1;
string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush");
using (var image = new Image<Rgba32>(width, height))
{
LinearGradientBrush<Rgba32> unicolorLinearGradientBrush =
new LinearGradientBrush<Rgba32>(
new SixLabors.Primitives.Point(0, 0),
new SixLabors.Primitives.Point(50, 0),
repetitionMode,
new ColorStop<Rgba32>(0, Rgba32.Red),
new ColorStop<Rgba32>(1, Rgba32.Yellow));
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
image.Save($"{path}/horizontalRedToYellow_{repetitionMode}.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 < height; 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]);
}
}
}
}
[Theory]
[InlineData(new[] { 0.5f })]
[InlineData(new[] { 0.2f, 0.4f, 0.6f, 0.8f })]
@ -117,6 +169,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
new LinearGradientBrush<Rgba32>(
new SixLabors.Primitives.Point(0, 0),
new SixLabors.Primitives.Point(width, 0),
GradientRepetitionMode.None,
colorStops);
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
@ -146,6 +199,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
new LinearGradientBrush<Rgba32>(
new SixLabors.Primitives.Point(0, 0),
new SixLabors.Primitives.Point(0, 500),
GradientRepetitionMode.None,
new ColorStop<Rgba32>(0, Rgba32.Red),
new ColorStop<Rgba32>(1, Rgba32.Yellow));
@ -194,6 +248,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
new LinearGradientBrush<Rgba32>(
new SixLabors.Primitives.Point(startX, startY),
new SixLabors.Primitives.Point(endX, endY),
GradientRepetitionMode.None,
new ColorStop<Rgba32>(0, Rgba32.Red),
new ColorStop<Rgba32>(1, Rgba32.Yellow));
@ -251,6 +306,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
new LinearGradientBrush<Rgba32>(
new SixLabors.Primitives.Point(startX, startY),
new SixLabors.Primitives.Point(endX, endY),
GradientRepetitionMode.None,
colorStops);
image.Mutate(x => x.Fill(unicolorLinearGradientBrush));

2
tests/ImageSharp.Tests/Drawing/FillRadialGradientBrushTests.cs

@ -19,6 +19,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
new RadialGradientBrush<Rgba32>(
new SixLabors.Primitives.Point(0, 0),
100,
GradientRepetitionMode.None,
new ColorStop<Rgba32>(0, Rgba32.Red),
new ColorStop<Rgba32>(1, Rgba32.Red));
@ -54,6 +55,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
new RadialGradientBrush<Rgba32>(
new SixLabors.Primitives.Point(centerX, centerY),
width / 2f,
GradientRepetitionMode.None,
new ColorStop<Rgba32>(0, Rgba32.Red),
new ColorStop<Rgba32>(1, Rgba32.Yellow));

Loading…
Cancel
Save