Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
@@ -0,0 +1,211 @@
using System;
using System.Numerics;

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

namespace SixLabors.ImageSharp.Processing.Drawing.Brushes
{
/// <summary>
/// Provides an implementation of a brush for painting gradients within areas.
/// Supported right now:
/// - a set of colors in relative distances to each other.
/// - two points to gradient along.
/// </summary>
/// <typeparam name="TPixel">The pixel format</typeparam>
public class LinearGradientBrush<TPixel> : IBrush<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly Point p1;

private readonly Point p2;

private readonly Tuple<float, TPixel>[] keyColors;

/// <summary>
/// Initializes a new instance of the <see cref="LinearGradientBrush{TPixel}"/> class.
/// </summary>
/// <param name="p1">Start point</param>
/// <param name="p2">End point</param>
/// <param name="keyColors">a set of color keys and where they are. The double must be in range [0..1] and is relative between p1 and p2.</param>
public LinearGradientBrush(Point p1, Point p2, params Tuple<float, TPixel>[] keyColors)
{
this.p1 = p1;
this.p2 = p2;
this.keyColors = keyColors;
}

/// <inheritdoc />
public BrushApplicator<TPixel> CreateApplicator(ImageFrame<TPixel> source, RectangleF region, GraphicsOptions options)
=> new LinearGradientBrushApplicator(source, this.p1, this.p2, this.keyColors, region, options);

/// <summary>
/// The linear gradient brush applicator.
/// </summary>
private class LinearGradientBrushApplicator : BrushApplicator<TPixel>
{
private readonly Point start;

private readonly Point end;

private readonly Tuple<float, TPixel>[] colorStops;

@antonfirsov antonfirsov Apr 19, 2018

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to define a small private struct instead of Tuple<float, TPixel>. It provides:

  • Better readability
  • Better memory locality (Tuple<>-s are classes on the heap)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point. Thanks. Except that the struct can't be private as it's used as a parameter in the Brush constructor. Therefore I'll use a public nested struct instead.


/// <summary>
/// the vector along the gradient, x component
/// </summary>
private readonly float alongX;

/// <summary>
/// the vector along the gradient, y component
/// </summary>
private readonly float alongY;

/// <summary>
/// the vector perpendicular to the gradient, y component
/// </summary>
private readonly float acrossY;

/// <summary>
/// the vector perpendicular to the gradient, x component
/// </summary>
private readonly float acrossX;

/// <summary>
/// helper to speed up calculation as these dont't change
/// </summary>
private readonly float aYcX;

/// <summary>
/// helper to speed up calculation as these dont't change
/// </summary>
private readonly float aXcY;

/// <summary>
/// helper to speed up calculation as these dont't change
/// </summary>
private readonly float aXcX;

/// <summary>
/// Initializes a new instance of the <see cref="LinearGradientBrushApplicator" /> class.
/// </summary>
/// <param name="source">The source</param>
/// <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="options">the graphics options</param>
public LinearGradientBrushApplicator(
ImageFrame<TPixel> source,
Point start,
Point end,
Tuple<float, TPixel>[] colorStops,
RectangleF region, // TODO: use region, compare with other Brushes for reference.
GraphicsOptions options)
: base(source, options)
{
this.start = start;
this.end = end;
this.colorStops = colorStops; // TODO: requires colorStops to be sorted by Item1!

// the along vector:
this.alongX = this.start.X - this.end.X;
this.alongY = this.start.Y - this.end.Y;

// the cross vector:
this.acrossX = this.alongY;
this.acrossY = -this.alongX;

// some helpers:
this.aYcX = this.alongY * this.acrossX;
this.aXcY = this.alongX * this.acrossY;
this.aXcX = this.alongX * this.acrossX;
}

/// <summary>
/// Gets the color for a single pixel
/// </summary>
/// <param name="x">The x.</param>
/// <param name="y">The y.</param>
internal override TPixel this[int x, int y]
{
get
{
// the following formula is the result of the linear equation system that forms the vector.
// TODO: this formula should be abstracted as it's the only difference between linear and radial gradient!
float onCompleteGradient = this.RatioOnGradient(x, y);

var localGradientFrom = this.colorStops[0];
Tuple<float, TPixel> localGradientTo = null;

// 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.Item1 >= onCompleteGradient)
{
// we're done here, so break it!
break;
}

localGradientFrom = localGradientTo;
}

TPixel resultColor = default;
if (localGradientFrom.Item2.Equals(localGradientTo.Item2))
{
resultColor = localGradientFrom.Item2;
}
else
{
var fromAsVector = localGradientFrom.Item2.ToVector4();
var toAsVector = localGradientTo.Item2.ToVector4();
float onLocalGradient = (onCompleteGradient - localGradientFrom.Item1) / localGradientTo.Item1; // TODO:

Vector4 result = PorterDuffFunctions.Normal(
fromAsVector,
toAsVector,
onLocalGradient);

// TODO: when resultColor is a struct, what does PackFromVector4 do here?
resultColor.PackFromVector4(result);
}

return resultColor;
}
}

private float RatioOnGradient(int x, int y)
{
return ((x / this.acrossX) - (this.alongX * y / this.aYcX))
/ (1 - (this.aXcY / this.aXcX));
}

internal override void Apply(Span<float> scanline, int x, int y)
{
base.Apply(scanline, x, y);

// Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
// MemoryManager memoryManager = this.Target.MemoryManager;
// using (IBuffer<float> amountBuffer = memoryManager.Allocate<float>(scanline.Length))
// {
// Span<float> amountSpan = amountBuffer.Span;
//
// for (int i = 0; i < scanline.Length; i++)
// {
// amountSpan[i] = scanline[i] * this.Options.BlendPercentage;
// }
//
// this.Blender.Blend(memoryManager, destinationRow, destinationRow, this.Colors.Span, amountSpan);
// }
}

/// <inheritdoc />
public override void Dispose()
{
}
}
}
}
47 changes: 47 additions & 0 deletions tests/ImageSharp.Tests/Drawing/FillLinearGradientBrushTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.

using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Drawing;
using Xunit;

namespace SixLabors.ImageSharp.Tests.Drawing
{
using System;

using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Drawing.Brushes;
using SixLabors.ImageSharp.Processing.Overlays;

using Point = SixLabors.Primitives.Point;

public class FillLinearGradientBrushTests : FileTestBase
{
[Fact]
public void LinearGradientBrushWithEqualColorsReturnsUnicolorImage()
{
string path = TestEnvironment.CreateOutputDirectory("Fill", "LinearGradientBrush");
using (var image = new Image<Rgba32>(500, 500))
{
LinearGradientBrush<Rgba32> unicolorLinearGradientBrush =
new LinearGradientBrush<Rgba32>(
new Point(0, 0),
new Point(500, 0),
new Tuple<float, Rgba32>(0, Rgba32.Red),
new Tuple<float, Rgba32>(1, Rgba32.Red));

image.Mutate(x => x.Fill(unicolorLinearGradientBrush));
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[199, 149]);
Assert.Equal(Rgba32.Red, sourcePixels[500, 500]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this need to be 499 not 500 as its one pixel wider than the buffer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, you're right. fixed.

}
}
}
}
}