## Thursday, August 20, 2009

I've seen many examples over the years regarding gradients. Start with one color, then progressively blend it into another. What I haven't seen yet is how to create complex gradients with 3 or more colors. If you can create a two color gradient, a multi-color gradient is conceptually similar.

Pairs are handy for this, because we can prepare a list of percentages and colors and pass that to our method. The left value will contain the percentage, the right value will contain the color. Our gradient will be a simple red-yellow-green gradient:

`dim colors(2) as paircolors(0) = 0.0 : &cFF0000colors(1) = 0.5 : &cFFFF00colors(2) = 1.0 : &c00FF00`

This basically says that at 0%, it should be red. At 50%, it should be yellow. At 100%, it should be green. We'll pass this array a method which defines the width, height, and direction of the gradient:

`Function CreateGradient(Width As Integer, Height As Integer, Points() As Pair, Vertical As Boolean = false) As Picture  dim p as new picture(width,height,32)    dim i,x,w,nextstart as integer  dim pointOne,pointTwo as pair  dim colorOne,colorTwo,blendedColor as color  dim pct as double    for i = 0 to ubound(points) - 1   pointone = points(i)   pointtwo = points(i + 1)   colorone = pointone.right   colortwo = pointtwo.right   if vertical then     w = (pointtwo.left.doublevalue * height) - nextstart   else     w = (pointtwo.left.doublevalue * width) - nextstart   end     for x = 0 to w     pct = x / w     blendedcolor = rgb((colorone.red * (1 - pct)) + (colortwo.red * pct),(colorone.green * (1 - pct)) + (colortwo.green * pct),(colorone.blue * (1 - pct)) + (colortwo.blue * pct))     p.graphics.forecolor = blendedcolor     if vertical then       p.graphics.drawline(-1,x + nextstart,p.width + 1,x + nextstart)     else       p.graphics.drawline(x + nextstart,-1,x + nextstart,p.height + 1)     end   next     nextstart = nextstart + w  next    return pEnd Function`

And that's all you need to create better gradients on-the-fly. Just for fun, here's the colors you need to draw a gradient similar to Apple Mail's toolbar buttons:

`dim colors(3) as paircolors(0) = 0.00 : &cDADBDDcolors(1) = 0.50 : &cD6D7DAcolors(2) = 0.51 : &cC8CBCEcolors(3) = 1.00 : &cDDE0E3` Anonymous said...

That sounds really good but what if you needed to draw the gradient at an angle? Or even harder - radial gradients. How might one tackle that?

Thanks much.

Thom McGrath said...

Angled gradients are probably the more difficult. You basically need to recall some high school math and draw lines. Radials are simple, draw circles. Anonymous said...

How about providing a couple of screenshots of the effect. Nothing large; doesn't even need to be the entire window -- just a snapshot of what the code does.

Steve Garman said...

@Anonymous: It took me four minutes to paste Thom's code into a project and test it.
I've put a very amateurish screenshot at Anonymous said...

Ahhhh but your missing the point of a screenshot request -- it's for users who DO NOT HAVE REALbasic installed.
For example, I'm posting this from a BlackBerry.

Thom McGrath said...

I've decided to take part of the challenge. Here's the code to create radial gradients:

Function CreateRadialGradient(Width As Integer, Height As Integer, Points() As Pair) As Picture
// Change this constant to modify the quality. The higher the number, the more
// passes will be made to refine the gradient, at the cost of rendering speed.
const quality = 50

dim p as new picture(width,height,32)
dim i,x,y,v,w,h,s,e as integer
dim pointOne,pointTwo as pair
dim colorOne,colorTwo,blendedColor as color
dim pct,amt as double
dim curLeft,curTop,curWidth,curHeight,destLeft,destTop,destWidth,destHeight as integer

p.graphics.forecolor = points(0).right.colorvalue
p.graphics.fillrect(0,0,p.width,p.height)

for i = 0 to ubound(points) - 1
pointone = points(i)
pointtwo = points(i + 1)
colorone = pointone.right
colortwo = pointtwo.right

destwidth = ((1 - pointtwo.left.doublevalue) * width)
destheight = ((1 - pointtwo.left.doublevalue) * height)
destleft = curleft + (destwidth / 2)
desttop = curtop + (destheight / 2)
curwidth = (1 - pointone.left.doublevalue) * width
curheight = (1 - pointone.left.doublevalue) * height

s = pointone.left.doublevalue * quality
e = pointtwo.left.doublevalue * quality
for v = s to e
amt = v / quality
x = amt * (width / 2)
y = amt * (height / 2)
w = width * (1 - (v / quality))
h = height * (1 - (v / quality))
pct = (v - s) / (e - s)
blendedcolor = rgb((colorone.red * (1 - pct)) + (colortwo.red * pct),(colorone.green * (1 - pct)) + (colortwo.green * pct),(colorone.blue * (1 - pct)) + (colortwo.blue * pct))
p.graphics.forecolor = blendedcolor
p.graphics.filloval(x,y,w,h)
next

curleft = curleft + destleft
curtop = curtop + desttop
next

return p
End Function

Not sure how well that code will appear in the comments though.