Or in this case linear gradients. If you want linear gradients to work well you really need to calculate the correct start and end points.
This is especially true if you dont want a gradient that simply runs from left to right or top to bottom (or their reverse directions)
While figuring out exactly what I needed to do I had several different suggestions from different people that I found dont work quite right.
If you simply compute the points using sin() and cos() of whatever angle you wont get the right answer. Depending on what radius you use you’ll either get clipping of the gradient inside the rectangle you want to fill or you’ll position the start & end points far enough outside that rectangle that if you have lots of gradient steps some wont show.
For example, if we set the start and end points so they are slightly angled across a square canvas with code like
Var linearBrush As New LinearGradientBrush linearBrush.StartPoint = New Point(0, g.Height/2-20) linearBrush.EndPoint = New Point(g.Width, g.Height/2+20) linearBrush.GradientStops.Add(New Pair(0, Color.Red)) linearBrush.GradientStops.Add(New Pair(0.4, Color.Yellow)) linearBrush.GradientStops.Add(New Pair(0.7, Color.Magenta)) linearBrush.GradientStops.Add(New Pair(1.0, Color.Blue)) g.Brush = linearBrush g.FillRect(0, 0, g.Width, g.Height)
we get a clipped gradient fill in the upper left corner
because the gradient “rectangle”, when rotated, doesnt overlap the entire rectangle we filled. But notice how the gradient starts at blue & ends at red very nicely. It shows all our steps quite completely.
When you start the gradient too far outside the rectangle you get some steps in the gradient not being as visible as they should be (this one IS much harder to see but notice how little blue there is)
I set the rotation to be 22.5 degree and then set the start & end points using sin & cos using a radius that is the hypotenuse of the right trangle formed from the rectangle center point to the lower right corner (any corner would do since this is a square – it get worse if you use a non-square rectangle) The red circle around the filled rectangle outlines where the start and end points might be place for any rotation. Note how far outside the rectangle they are and the more color steps there are the more get clipped out. The code is as follows
Const pi As Double = 3.1415926535897932384626433 Const degreesToRadians As Double = pi / 180 Const degrees As Double = 22.5 Var linearBrush As New LinearGradientBrush Var hypot As Double = sqrt( (g.width - g.width/2)^2 + (g.height - g.height/2)^2) Dim startx As Double = cos(degrees * degreesToRadians) * hypot Dim starty As Double = sin(degrees * degreesToRadians) * hypot linearBrush.StartPoint = New Point(g.width/2+startX, g.height/2+startY) linearBrush.EndPoint = New Point(g.Width/2-startx, g.Height/2-startY) linearBrush.GradientStops.Add(New Pair(0, Color.Red)) linearBrush.GradientStops.Add(New Pair(0.4, Color.Yellow)) linearBrush.GradientStops.Add(New Pair(0.7, Color.Magenta)) linearBrush.GradientStops.Add(New Pair(1.0, Color.Blue)) g.Brush = linearBrush g.FillRect(0, 0, g.Width, g.Height)
You can see that in the two images about. Nove how much red & blue the first has compared to the second,. Yet the gradient is set up identically between them to trabsition between the same number of colors using the same number of steps.
The problem is that circle is much too large. Of course we could use a much smaller circle but if we set it so its inside the rectangle then we get the clipping problem back.
So whats the “right answer” ? So far it seems to me that the correct answer is to find a minimal bounding rectangle that is rotated however we want. And to make sure our gradient starts on the edges of that bounding box.
OK so what the kec does that mean ? 😛
Well – in action it looks like this
The white square is the rectangle we’re going to fill with the linear gradient. The light purple circle is the same circile that encloses the rectangle computed much like the one just above here. And the light blue line is where our gradient will run either left to right or right to left (we can invert those two trivially)
Where the gradient runs exactly left to right or top to bottom we can put the start & end points right on the white square. But as we start to rotate the gradient you’ll see some yellow and red lines show. Where the blue line crosses the yellow line is where we need to put the start & points of the gradient. And note those points are NOT on that circle drawn outside the rectangle. But those red & tyellow lines for the minimum enclosing rectangle that, if the gradient starts on its edges, then the entire rectangle in the white square will be filled. The result is this
I expect this mechanism can be extended to apply to any shape using a convex hull approach.
I’ll see about whether I can release this code or not.
Either way you have an explanation of how to compute the right start and end points for linear gradients and apply then to rectangular areas. And you’ll know why things might get clipped off or not fill quite right if you do something else.
Now I’m sure some of you will get to the end of this and go “Well Duh Norm”
Take this post purely as evidence my trig sucks 😛
I freely admit that. It does suck.