Convert colours to gray
(1) Simplest way to convert RGB to Gray: use MAX
rgb(R,G,B) ==> rgb(max(R,G,B),max(R,G,B),max(R,G,B))
(2) better value: use YIQ conversion (monochrome TV method)
rgb(R,G,B) ==> calculate Y=0.299R+0.587G+0.114B
Here is something that might give an even better result, garnered from comp.graphics.algorithms
from Charles Poynton's color FAQ
"Contemporary CRT phosphors are standardized in Rec. 709 , to be described in section 17. The weights to compute true CIE luminance from linear red, green and blue (indicated without prime symbols), for the Rec. 709, are these: Y = 0.212671 * R + 0.715160 * G + 0.072169 * B;
This computation assumes that the luminance spectral weighting can be formed as a linear combination of the scanner curves, and assumes that the component signals represent linear-light. Either or both of these conditions can be relaxed to some extent depending on the application.
Some computer systems have computed brightness using (R+G+B)/3. This is at odds with the properties of human vision, as will be discussed under What are HSB and HLS? in section 36.
The coefficients 0.299, 0.587 and 0.114 properly computed luminance for monitors having phosphors that were contemporary at the introduction of NTSC television in 1953. They are still appropriate for computing video luma to be discussed below in section 11. However, these coefficients do not accurately compute luminance for contemporary monitors."
Robert, Kiyoyuki and others (Robert, sorry for the double send)
The 1953 NTSC seems to work best in all color combinations.
Below is some test code to display the results.
Switch your monitor to grayscale.
Changing gamma in the control panel made little difference in the subjective comparison of the results.
In theory, what should be the mathematical gray equivalent of color?
My "real" need is to measure the opacity (or translucency) of a specimen.
It seems that an average would be appropriate, even though it appears not to be the best match on the monitor.
WINDOW 1,"Color to gray",(0,0)-(600,300) LOCAL FN testGrays(blue&,green&,red&) CLS PRINT TEXT,,1, LONG COLOR blue&,green&,red& BOX FILL 1,1 TO 510,120 PRINT "rgb" '- average&=((red&+green&+blue&)/3) LONG COLOR average&,average&,average& BOX FILL 110,10 TO 200,100 PRINT "average" '- Y& =0.212671*red&+0.715160*green&+0.072169*blue& LONG COLOR Y&,Y&,Y& BOX FILL 210,10 TO 300,100 PRINT "true CIE luminance" '- Y2& =0.299*red&+0.587*green&+0.114*blue& LONG COLOR Y2&,Y2&,Y2& BOX FILL 310,10 TO 400,100 PRINT "NTSC television 1953" '- max&=0 IF red&>green& THEN max&=red& ELSE max&=green& IF blue&>max& THEN max&=blue& LONG COLOR max&,max&,max& BOX FILL 410,10 TO 500,100 PRINT "max(rgb)" max& END FN red&=8200 green&=55535 blue&=55535 FN testGrays(blue&,green&,red&) DELAY 250 DO UNTIL FN BUTTON red&=55535 green&=55535 blue&=8200 FN testGrays(blue&,green&,red&) DELAY 250 DO UNTIL FN BUTTON red&=55535 green&=8200 blue&=55535 FN testGrays(blue&,green&,red&) DELAY 250 DO UNTIL FN BUTTON
Pierre A. Zippi
You cant simply average the color values that way. Try...
red1& = VAL(UNS$(red1%)) : red2& = VAL(UNS$(red2%)) red& = (red1& + red2&) / 2&
There is a bug in this method of averaging, which explains why the resulting gray is often completely wrong. An RGB component is an unsigned word, with values 0-65535. FB does not support this data type. So when myRGB.red, for instance, is in the range 32768-65535 FB tends to treat it as a negative number, and you get a wrong average. Although you can force the correct value with VAL(UNS$(myRGB.red)) the conversion is extremely slow. Assuming that you need to convert millions of pixels colours, use this:-
LOCAL FN WeightAverageRGB(srcRGBPtr&,redWt,greenWt,blueWt) DIM srceRGB;0, srcRed,srcGreen,srcBlue srceRGB;6=srcRGBPtr& REGISTER(d2)=redWt REGISTER(d1)=greenWt REGISTER(d0)=blueWt ` move.w d0,d3 ` add.w d1,d3 ` add.w d2,d3 ; sum of weights (must be <65536) ` mulu.w ^srcRed,d2 ; unsigned multiply ` mulu.w ^srcGreen,d1 ` mulu.w ^srcBlue,d0 ` add.l d2,d1 ` add.l d1,d0 ; weighted sum ` divu.w d3,d0 ; normalise to sum of weights ` ext.l d0 END FN DIM myRGB.6, avColor myRGB.red=60000: myRGB.green=1000: myRGB.blue=20000 WINDOW 1 PRINT "Colors" PRINT myRGB.red; UNS$(myRGB.red) PRINT myRGB.green; UNS$(myRGB.green) PRINT myRGB.blue; UNS$(myRGB.blue) avColor=FN WeightAverageRGB(@myRGB,333,333,333) PRINT "Weighted average" PRINT avColor;UNS$(avColor) DO: UNTIL FN BUTTON
This really does yield the correct number!
total&=((red&+green&+blue&)/3)The problem is introduced once you use FB's (toolbox's?) data structure for color.
myRGB.red is not a long integer.
red&=65535 is a long int.
total&=((red&+green&+blue&)/3) is also ok.
oldAve&=(60000+40000+20000)/3 avColor=FN WeightAverageRGB(@myRGB,333,333,333) PRINT "Weighted average" PRINT avColor; UNS$(avColor); oldAve&oldAve& and UNS$(avColor) are both = 40000
Anyway, thanks I can use the code Robert sent to calc weighted average. (because I will be using the color data structure later).
Any ideas on what the "mathematically" correct gray equivalent of color should be?
Pierre A. Zippi
I don't know about detecting color vs. grayscale other than testing pixels with GetCPixel and comparing channel info, but to convert to grayscale use the below. These factors are tailored to today's monitor phosphors.
Y = 0.212671 * R + 0.715160 * G + 0.072169 * BIn other words, multiply the respective RGB channels for the test pixel by the above factors, then set each RGB channel to the result (brightness).
That should give you a grayscale reprentation of your color picture.
For a quickie conversion, just make brightness=(R+B+G)/3 and set the channels to that value.
If you want monochrome as in black/white (and I have misunderstood) then testing for black/white pixels alone would be easy.
To convert to such you could first convert to grayscale using the above then split the channel spec (65535?) into 2, 32767, and any pixel brightnesses that come above that become white, any below or equal become black. This works well, I just tested it. If using direct pixmap access in a 32 bit gWorld, then the range per channel will be 0-255, 16 bit gWorld,then 0-31 per channel.
There are a lot of image processing lurkers on this list I think. Your input is welcome of course if I am in error.
Some sample direct pixmap source is below. I just grabbed some of it from my main pixel grabbing routine for my displacement mapper, it lacks some DIM's and may not work as is. It is based on the PixelManipulation-offscreen.bas FB example,and I use it in the copyoffscreentowind FN. So should be easy enough to adapt. There may be faster methods to go about all this using some aspects of Quickdraw/copybits, like copying to a port of a different bit depth I would think.
targetPixMap& = FN GETGWORLDPIXMAP(offPort&) locked = FN LOCKPIXELS(targetPixMap&) LONG IF locked% pixArrayBase& = targetPixMap&..baseAddr& pixRowWidth% = targetPixMap&..rowBytes% AND &3FFF DIM curPixelPtr& DIM Avg% FOR y%=0 TO (rect.bottom-1) FOR x%=0 TO (rect.right-1) curPixelPtr& = pixArrayBase& + (y% * pixRowWidth%) + (x% * 4) redbits% = PEEK(curPixelPtr& + 1) greenbits% = PEEK(curPixelPtr& + 2) bluebits% = PEEK(curPixelPtr& + 3) 'Avg%=(redbits%+greenbits%+bluebits%)/3 'cheater method Avg%=((redbits%*0.212671)+(greenbits%*0.715160)+(bluebits%*0.072169)) 'Brightness = 0.212671 * R + 0.715160 * G + 0.072169 * B IF Avg%<=127 THEN Avg=0 ELSE Avg=255 'comment out for grayscale POKE curPixelPtr& + 1,Avg% POKE curPixelPtr& + 2,Avg% POKE curPixelPtr& + 3,Avg% NEXT x% NEXT y% CALL UNLOCKPIXELS(FN GETGWORLDPIXMAP(offPort&)) CALL COPYBITS(#offPort&+2,#wndPort&+2,rect,rect,_srcCopy,0) END IF
It may help to set up a simple example. You can do this in your head. Say you've got one thing that's bright blue, and another thing that's bright green. If you're "averaging" the colors, you'll come out with something that's bright cyan, right?
But your computer will look at the numbers:
bright blue: 0% red,0% green, 100% blue
bright green: 0% red, 100% green, 0% blue
so, taking an average:
result color: 0% red, 50% green, 50% blue
= not bright cyan but a much darker shade.
... and that's not even considering the fact that your computer's pure green appears much "brighter" than its red, let alone its blue. (For all I know, it really _is_ brighter. See Bodanis, _The Secret House_, on "dirt from Sweden.")
The moral behind all this is that the meeting between mathematics and visual art is not nearly as simple & elegant as, say, that of mathematics and music.
Which is why to digress but slightly your table of 216 "web-safe" colors contains all those shades of babypuke and shadowy mauve that nobody will ever use in real life. Easy to set up arithmetically, pretty much useless if you're trying to draw pictures.