Implement gradient brush similar to PathGradientBrush (#969)#989
Implement gradient brush similar to PathGradientBrush (#969)#989antonfirsov merged 7 commits intoSixLabors:masterfrom mysticfall:master
Conversation
A gradient brush implementation that works similar to System.Drawing.Drawing2D.PathGradientBrush. This fixes #969, but only convex paths are supported for now.
|
To maintainers: I think I need some help about this PR. I wrote test cases for this feature, but I don't know what to do with the text fixture (reference output images). Should I fork SixLabors/Imagesharp.Tests.Images also and make a separate PR? Or maybe I should just attach those images here (which I'll do it just in case)? Sorry if it's something obvious, but it's the first time I've ever made a PR that spans across multiple Git submodules. As to the code itself, I think it works as intended for simple convex shapes but it probably won't work for conclave ones, and I haven't compared its output with that of the original There's an implicit restriction to the Still, I wanted to ensure that the |
|
@mysticfall If you fork the We're going to simplify all this for the future, it's far too complicated and disjointed a process currently. |
|
@JimBobSquarePants Sure, I can do that. Thank for the instruction :) |
|
|
||
| PointF end = point + (PointF)(direction * this.maxDistance); | ||
|
|
||
| (Edge edge, PointF intersection) = this.edges |
There was a problem hiding this comment.
Linq here will negatively affect performance.
There was a problem hiding this comment.
Does it involve extra allocations or something similar? I'm not very familiar with internal details about LINQ but I'll try to replace it with a for-iteration, if it's indeed the case.
There was a problem hiding this comment.
The performance impacts are coming from stuff like extra allocations, virtual calls, deeper stack etc.
There was a problem hiding this comment.
@antonfirsov Thanks for the clarification. I just want to know exactly how and when using LINQ would affect the performance. I've made this PR to be used for my game project, so the knowledge would benefit me in that aspect too.
I'll try to dig deeper into that LINQ chain to see if there's significant overhead before rewriting it to simple for loops.
There was a problem hiding this comment.
I'm still not sure if the original code would incur significant overhead, but I just replaced it with a plain foreach-loop just in case.
I'm not a seasoned C# developer, so if anyone could help me understand where the performance caveat lies, or how to find out such a problem, it would be much appreciated.
mysticfall
left a comment
There was a problem hiding this comment.
Submitted a pull request for test fixtures as SixLabors/Imagesharp.Tests.Images#11.
|
|
||
| PointF end = point + (PointF)(direction * this.maxDistance); | ||
|
|
||
| (Edge edge, PointF intersection) = this.edges |
There was a problem hiding this comment.
Does it involve extra allocations or something similar? I'm not very familiar with internal details about LINQ but I'll try to replace it with a for-iteration, if it's indeed the case.
Test fixtures for SixLabors/ImageSharp#989
|
@mysticfall merged your PR. If you push a submodule update, your tests should pass. |
| private static Color CalculateCenterColor(Color[] colors) => | ||
| new Color(colors.Select(c => c.ToVector4()).Aggregate((p1, p2) => p1 + p2) / colors.Length); | ||
|
|
||
| private static float DistanceBetween(PointF p1, PointF p2) => ((Vector2)(p2 - p1)).Length(); |
There was a problem hiding this comment.
Squared distance is enough for your purpose. (dx^2 + dy^2 instead of sqrt( dx^2 + dy^2 ))
There was a problem hiding this comment.
There was a problem hiding this comment.
Upon inspection, I found that DistanceBetween cannot be modified that way without affecting the output, since the brush relies on a ratio of actual distances.
I replaced Length() used in FindIntersection method as suggested, however, as it's only used to find the closest edge.
| return this.buffer | ||
| .Take(intersections) | ||
| .Select(p => (point: p, distance: ((Vector2)(p - start)).Length())) | ||
| .OrderBy(v => v.distance) |
There was a problem hiding this comment.
Another optimization hint:
Finding a closest intersection might be enough you don't need to order the whole ordered list.
There was a problem hiding this comment.
Removed unnecessary OrderBy invocations. Thanks!
Use LengthSquared() instead of Length() when it's possible. Avoid unnecessary ordering of elements.
Codecov Report
@@ Coverage Diff @@
## master #989 +/- ##
==========================================
+ Coverage 89.63% 89.66% +0.02%
==========================================
Files 1098 1100 +2
Lines 48690 48906 +216
Branches 3433 3447 +14
==========================================
+ Hits 43643 43851 +208
- Misses 4293 4297 +4
- Partials 754 758 +4
Continue to review full report at Codecov.
|
antonfirsov
left a comment
There was a problem hiding this comment.
@mysticfall thanks for the changes!
Just one final point: we need arg check in public API-s. If we do it quickly, you'll have a chance to have this feature in beta7 ;)
If you are AFK at the moment I can help you out pushing the change.
Regarding performance impacts of LINQ:
I haven't forgotten your question, just give me a little time to gather my thoughts :) Since you're a Unity dev, I think it is really important for you to be aware of this stuff :)
| /// <param name="colors">Array of colors that correspond to each point in the polygon.</param> | ||
| /// <param name="centerColor">Color at the center of the gradient area to which the other colors converge.</param> | ||
| public PathGradientBrush(ILineSegment[] lines, Color[] colors, Color centerColor) | ||
| { |
There was a problem hiding this comment.
We need arg. check here:
lines, colors: not null, and counts are matching
There was a problem hiding this comment.
Sure, I considered adding them but thought it wasn't a convention after looking at other brush implementations (see my first comment).
Would it be ok if I just throw ArgumentNullException / ArgumentOutOfRangeException as I see fit? And, should I also add associated tests?
P. S.: I'm a Godot developer by the way, so yeah I really should be aware of such things :)
There was a problem hiding this comment.
It's done. Please let me know if you want me to change anything else.
…SixLabors#989) * Implement gradient brush similar to PathGradientBrush (SixLabors#969) A gradient brush implementation that works similar to System.Drawing.Drawing2D.PathGradientBrush. This fixes SixLabors#969, but only convex paths are supported for now. * Update submodule to add test fixtures for SixLabors#989 * Performance optimization Use LengthSquared() instead of Length() when it's possible. Avoid unnecessary ordering of elements. * Avoid using LINQ in a hotspot * Validate arguments for the public constructor




Prerequisites
Description
A gradient brush implementation that works similar to System.Drawing.Drawing2D.PathGradientBrush.
This fixes #969, but only convex paths are supported for now.