Transmogrifying colours (or, gradients the hard way)
I found myself needing a way to convert a colour some of the way toward another colour. Possible use cases include
- Lightening or darkening a shade
- Recolouring a palette to match a colour scheme
- Gradients and blends
Now, this is probably a specialised sort of function. With GDI+ (or the .NET System.Drawing) there are better ways to do gradient fills, and transparency may be a more understandable way of doing blending or merging. Still, it was a short function that took a deceptively long time to write, so you might find it useful.
In my case I had a multi-select grid, and wanted to change the colors to distinguish the current row (which using default windows colours is dark blue, but on my pc currently is dark purple) from other, selected but not current, rows. A selected row would be coloured similarly to the current row, but less so. Something like this:

I could have hardcoded the colours, but felt that users should have a choice (not to mention, purple isn’t for everyone). So my problem was how to generate the intermediate colour.
With simple numbers, finding an average is simple: average = (number1 + number2) / 2, but this does not work for colours at all. If I take the average of the purple rgb(128,0,128) and white rgb(255,255,255) the result is not what you might expect:

So the average of purple and white would appear to be .. blue?
The problem is that each of the red, green and blue components need to be averaged as separate numbers, and then recombined. Doing this efficiently involves some horribly dense code. For instance, the average of the green components is
nGreenBit = bitand( 0xFF00, (bitand(0xFF00,m.nFromRGB) + bitand(0xFF00,m.nToRGB) )/2 )
I’m also going to make it worse. I’d like to specify any point on a continuum between the two colours. For example, I’d like to shift from purple 75% of the way to white, to get a very light purple. This isn’t difficult, it just makes the code truly terrifying:
* Shift RGB value toward another color * * RGBShift( Red, White, 50% ) = pink sort of thing * Proportion may range from 0 to 1.0, default is 0.5 * function RGBShift( nFromRGB, nToRGB, nProportion ) * * For each of the ARGB components * newcomponent = from + (to-from)*scale * assert vartype(m.nFromRGB)+vartype(m.nToRGB)=='NN' assert (vartype(m.nProportion)=='L' and !m.nProportion) ; or (vartype(m.nProportion)=='N' and between(m.nProportion,0.0,1.0)) if vartype(m.nProportion)='L' nProportion = 0.5 && Default endif return ; bitand( 0xFF, bitand(0xFF,m.nFromRGB) ; + m.nProportion * ( bitand(0xFF,m.nToRGB) - bitand(0xFF,m.nFromRGB) ) ) ; +bitand( 0xFF00, bitand(0xFF00,m.nFromRGB) ; + m.nProportion * ( bitand(0xFF00,m.nToRGB) - bitand(0xFF00,m.nFromRGB) ) ) ; +bitand( 0xFF0000, bitand(0xFF0000,m.nFromRGB) ; + m.nProportion * ( bitand(0xFF0000,m.nToRGB) - bitand(0xFF0000,m.nFromRGB) ) ) ; +bitand( 0xFF000000, bitand(0xFF000000,m.nFromRGB) ; + m.nProportion * ( bitand(0xFF000000,m.nToRGB) - bitand(0xFF000000,m.nFromRGB) ) ) endfunc
However, now I can do gradients. Here’s an example:
* Draw gradient fill #define ORANGE rgb(255,128,0) #define CYAN rgb(33,200,200) activate screen clear LinearGradient( ORANGE , CYAN, 50, 0, 0, 300, 50 ) function LinearGradient( nFromRGB, nToRGB, nSteps, nX, nY, nWidth, nHeight ) local nCurrentX, nCurrentY, nSliceWidth nSliceWidth = m.nWidth/ nSteps nCurrentX = m.nX nCurrentY = m.nY _screen.FillStyle = 0 for ii = 0 to nSteps store CfcRGBShift( m.nFromRGB, m.nToRGB, m.ii /m.nSteps) ; to _screen.ForeColor, _screen.FillColor _screen.Box( m.nCurrentX, m.nCurrentY, m.nCurrentX + m.nSliceWidth, m.nCurrentY + m.nHeight ) m.nCurrentX = m.nCurrentX + m.nSliceWidth endfor endfunc
This program drew this:
(Incidentally, the LinearGradient function above has more than one bug in it. Since it isn’t my main point, I’m not going to fix them).
I am now in a position to use that function to do my original grid colouring. Assuming the cursor underneath the grid has a logical field marked which is .T. for selected rows, .F. otherwise; I get my intermediate colour like this:
with Thisform.Grid1 local nSelectionColour nSelectionColour = RgbShift( .HighlightBackColor, .BackColor ) .Column1.DynamicBackColor = "iif(marked,"+transform(m.nSelectedColor)+","+transform(.BackColor)+")" endwith
I hope you find this useful.