views:

164

answers:

2

I want to make an application that can generate point to point gradient (like Photoshop does). I'm familiar with how to generate an up to down gradient but not point to point. How is this conceptually done.

Thanks

+3  A: 

I can't say if this is exactly how Photoshop does it, or that it's the most optimal way of doing it, but this should be the basic principle.

Think of the two points as defining a vector. You can find a normal vector for this, which will be perpendicular to the original vector (since that's the definition of a normal vector).

For each discrete point (pixel) on the line, calculate the gradient color as you would for an up-down (or left-right) gradient of the same length as your vector. Then draw a line of the selected color, such that it passes through the currently chosen point and is parallel with the normal vector.

It's actually very similar to the approach you'd use for an up-down gradient, except it's rotated.

Michael Madsen
I think you are right that this is not exactly how Photoshop does it, and it is not the most optimal way, but you are close. What you really want to do is to scan the filled image left to right, top to bottom. Since a cut through a linear gradient is linear, for each scan line you can compute the start and end color, and then interpolate between them quickly (preferably using a Bresenham algorithm). You are doing same amount of calculation as if you were drawing slanted lines, but you are accessing the memory in cache-friendly way.
Roman Zenka
+1  A: 

Building on Michael Madsen's answer-

For every point in the region you're filling, compute the closest point on the line segment. (You'll have to google that, but there are plenty of examples.) At some point in the computation of that closest point, there's a value computed that ranges from 0 at the start point to 1 at the end point. Plug that into whatever your gradient function is.

The overview of the algorithm is basically...

pc = # the point you are coloring now
p0 = # start point
p1 = # end point
v = p1 - p0
d = Length(v)
v = Normalize(v) # or Scale(v, 1/d)

v0 = pc - p0

t = Dot(v0, v)
t = Clamp(t/d, 0, 1)

color = (start_color * t) + (end_color * (1 - t))

You can actually lose the make t/d just t if you scale v by 1/d^2 instead of 1/d. But anyway... I think this'll get you there. Perhaps obviously, the first half is "static" so you only need to loop over the last four lines.

dash-tom-bang
I'm sure that drawing the perpendicular lines will be way faster, fwiw. :)
dash-tom-bang
Drawing perpendicular lines *will* most likely be faster, but is also likely to introduce a lot of aliasing if the gradient is not axis-aligned. Working point by point will most likely (in theory) give a better looking result in such cases.
Mac
Too slow, you are not taking advantage of the "linear" in the linear gradient. Compute start and end color for each scan line, interpolate.
Roman Zenka
Hi, I'm not sure if you (dash-tom-bang) will see this but I was wondering if you knew a similar algorithm to this but for gradients running around a radius (radial gradient) Thanks!
Milo
Sure- just use distance to point. At d=0 use the "start" color (set t=0), at d>=endDist use the "end" (t=1) color, and in between use d/endDist for your t value in the last line above.
dash-tom-bang