View Full Version : FilterMeister - Color Theory and Conversions Stroker 02-10-2006, 08:40 AM (byRo: This thread has been split - so some posts may seem out of context)
I'm sure most of us are familiar with the various colour spaces. The ones that come to mind are RGB, HSL, HSB, Lab, and CMYK. Oy, but Photoshop uses a priority space when you are not looking. This space is very similiar to HSL and HSB with a hint of xyz/Lab tossed in.
I used to call this colour space HS/Lum, but have taken to calling it HsY. I got the name, HsY, from a guy in Germany (I think Germany). I just like his name better.
Whenever you use the blending modes Hue, Saturation, Colour, or Luminosity, you are using this hidden space.
Here is a simple experiment to try:
- open photograph in RGB mode, preferably with lots of blue and yellow
- Adjustment Layer > Hue/Sat
-- bring Sat all the way down to -100
When you do this, you are looking at Lightness of HSL.
Now change the blending mode of the H/S Ad-Layer to Hue, Colour, or Sat. Different, eh? This is because you are now using HsY and seeing Y or Luminosity.
Here is the basic code for turning RGB into HsY:
%ffp
ForEveryPixel:{
int hue,sat,lum;
hue=rgb2hsl(r,g,b,0);
sat=max(r, max(g,b) ) - min(r, min(g,b) );
lum= r*0.30 + g*0.59 + b*0.11;
R=hue;
G=sat;
B=lum;
}
After running this, use the Channels palette to inspect the channels separately. One thing I want you to note is that the resulting Lum 'channel' will usually have higher fidelity than the Hue and Sat 'channels'.
You see, the two colour channels are usually compressed more than the luminosity channel. This is why there are tutorials out there about smoothifying when you increase saturation (Deke). If you amplify a compressed channel, then you will bring out the compressedness of the channel.
This one little thing can mean an awlful lot. Cameraken 02-10-2006, 11:01 AM Thank you Stroker.
It’s amazing what you can achieve with a few simple lines of code.
I am still learning. And find C+ syntax quite confusing at times. I learnt VB instead.
I usually look through the FilterMeister code library for a similar type filter and then amend it.
It’s Interesting the way you split HsY onto the channels. That was a good idea as I don’t think FilterMeister will let you create new layers.
Now if I could only put this back together again.
Ken Cameraken 02-10-2006, 07:20 PM Tips
If you wish to try Stroker’s code you will find that you can’t use the mouse to paste code straight into FilterMeister.
It is possible to paste with the keyboard.
• Ctrl+C - Copy (OR You Can use the mouse)
• Ctrl+V - Paste (place the cursor where you want to paste first)
Or, of course, the code can be saved as a *.ffp file using notepad.
Ken Stroker 02-11-2006, 03:41 AM What Ken said about copy, cut, and paste.
In my previous example, used rgb2hsl function to get a value of hue in the range of 0 to 255. You know, 1 dimensional greyscale or something. But what about full colour hue?
One way of doing this is by using rgb2hsl and hsl2rgb. Check it out:
%ffp
ForEveryPixel:{
int hue;
hue = rgb2hsl(r,g,b,0);
R=hsl2rgb(hue,255,128,0);
G=hsl2rgb(hue,255,128,1);
B=hsl2rgb(hue,255,128,2);
}
First we get the hue in the range of 0 to 255. Now, in order to get hue in full colour, we have to use sat=255 and lightness=128 in the hsl2rgb function. Why? Because in the HSL double-cone that is where the full range of RGB lies. That is, smack dab in the center with full saturation.
As fun as that is, there is another way that uses the scale function (scl). Find the min, find the max, then scale everything to full 0 to 255 range.
%ffp
ForEveryPixel:{
int min,max;
min = min(r, min(g,b) );
max = max(r, max(g,b) );
R=scl(r,min,max,0,255);
G=scl(g,min,max,0,255);
B=scl(b,min,max,0,255);
}
I prefer the latter. Later I'll tell you why. Stroker 02-11-2006, 06:36 AM Easy Sat:
sat=max(r, max(g,b) ) - min(r, min(g,b) );
Pieces of easy sat:
min = min(r, min(g,b) );
max = max(r, max(g,b) );
Scale for full colour hue:
R=scl(r,min,max,0,255);
G=scl(g,min,max,0,255);
B=scl(b,min,max,0,255);
Now we are going to take all of those pieces and put them together.
%ffp
ForEveryPixel:{
int min,max,sat;
min = min(r, min(g,b) );
max = max(r, max(g,b) );
sat = max - min;
R=scl(r,min,max,0,sat);
G=scl(g,min,max,0,sat);
B=scl(b,min,max,0,sat);
}
Notice that the scl functions use the different pieces of the saturation code. That is, we are using mix, max, and sat to scale the RGB values.
What did we do? Rather than scale RGB to full colour hue, we scaled RGB to saturation while retaining hue. That is, the final output will have the hue and sat information of the original input image. All we did is rearrange the hue and sat a little bit.
- copy photograph
- run that code
- change blending mode to Hue, Sat, or Colour
See any difference? You shouldn't because we are using HsY.
Let's try it again but with simple subtraction:
%ffp
ForEveryPixel:{
int min;
min = min(r, min(g,b) );
R=r-min;
G=g-min;
B=b-min;
}
Now let's try addition using max:
%ffp
ForEveryPixel:{
int max;
max = max(r, max(g,b) );
R = r + ( 255 - max );
G = g + ( 255 - max );
B = b + ( 255 - max );
}
The output should look way different, but it will still have the same hue and sat information present in the original. Try Hue, Sat, and/or Colour on the output.
What's the point of all of this? I'm not sure. I think it has something to do with different ways of getting the same data and moving it around different ways to get the same things. I guess your plan of attack will depend on what you are trying to achieve. Sound good? Probably not because I am going rather fast.
At the very least, you should have an idea about hue in RGB and sat. Maybe.
edit:
Okay, I'm gonna see if I can re-focus for where I want to go. Cameraken 02-11-2006, 08:13 PM Stroker. Thank You.
This is great. Not only are you teaching us FilterMeister but you are teaching us about color spaces as well. Wonderful stuff.
And they thought this was just for geeks. They don’t know what they are missing.
Keep it coming.
FilterMeister can do allsorts of other great things. Here is an example of a bit of code for making borders,
%ffp
ctl[0]: "Border thickness"
ctl[2]: "Red coloring"
ctl[3]: "Green coloring"
ctl[4]: "Blue coloring
R: ( x < ctl(0) || x > X-ctl(0)-1 || y < ctl(0) || y > Y-ctl(0)-1) ? ctl(2) : r
G: ( x < ctl(0) || x > X-ctl(0)-1 || y < ctl(0) || y > Y-ctl(0)-1) ? ctl(3) : g
B: ( x < ctl(0) || x > X-ctl(0)-1 || y < ctl(0) || y > Y-ctl(0)-1) ? ctl(4) : b
A:a
Ken Stroker 02-12-2006, 04:19 PM I'm just too ADD sometimes. Pretty sad when I've got to put myself on a leash. Okay, re-focus in effect. Gonna be headin' in Ken's direction.
Now if I could only put this back together again.
That is where we are headed. If I start to stray too much, give me the beat-down.
New code to work with:
%ffp
SupportedModes: RGBMode
ForEveryTile:{
int x,y,r,g,b;
int min,max;
int hue,sat,lum;
for (y=y_start; y<y_end; y++){
//if(updateProgress(y,y_end)) abort();
for (x=x_start; x<x_end; x++){
r=src(x,y,0);
g=src(x,y,1);
b=src(x,y,2);
hue=rgb2hsl(r,g,b,0);
min=min(r,min(g,b));
max=max(r,max(g,b));
sat=max-min;
lum=(max+min)/2;
pset(x,y,0,hue);
pset(x,y,1,sat);
pset(x,y,2,lum);
} // end x preview
} // end y preview
return true;
} // end for every tile
There are quite a few differences in there. The biggest thing I want you to note is this chunk:
ForEveryTile:{
int x,y,r,g,b;
int min,max;
int hue,sat,lum;
Notice that the structure is ForEveryTile (FET) instead of ForEveryPixel (FEP). While this isn't quite as easy as FEP, it is way more efficient. For images, it is usually better to work with chunks at a time, and those chunks are commonly called tiles. Usually you won't see a speed difference until you start working with way huge images, but let's get started using FET right now.
Using also FET means
- have to make a few extra declarations
- you have to cycle through the image one pixel at a time using for loops and src()
In the code, I have updateProgress commented out. I rarely use this, but I keep it in there just in case I feel the need.
Also made use of SupportedModes. While this won't affect testing within FM, it will make a difference when it comes time to make a stand-alone. RGBMode = RGB using 8-bit per channel.
Another difference, probably more major than minor, is this:
lum=(max+min)/2;
Even though the variable is lum, this is actually the formula for Lightness in HSL. Going to be using this for edification reasons. Once we get past a certain hump or two, we'll change this back to Luminosity that Photoshop uses. By going this route, hopefully save ya'll a lot of frustration.
What exactly are we doing right now? We are mixing-n-matching for our own colour space. Basically taking the easy parts from HSL and HsY. Cameraken 02-12-2006, 07:04 PM Hee Hee
Don’t try this at home folks
%ffp
SupportedModes: ADD ADHD
ctl[0]: "Attention Deficit Disorder"
ctl[1]: "Attention Deficit Hyperactivity Disorder"
/*R,G,B:
Stroker
I can’t get you last post code to work. It does not even want to compile. (But it does change the picture) I Can’t understand this. Should it work on a RGB picture or is it designed to work with HsY in the channels?
How on Earth did you find that you needed a gradient to put saturation back?
Ken Stroker 02-13-2006, 07:16 AM If it's not compiling, it should give you an error. What is the error?
The last code that I posted is for RGB mode with 8-bits per channel. What it does is take the RGB values and convert them over to our own blend of HSL/HsY. It's pretty much the same as the first code I posted in this thread.
How on Earth did you find that you needed a gradient to put saturation back?
It's just a matter of knowing what you've got, what you want, and the tools available. If you really understand saturation as Photoshop uses it, you should be able to put the saturation back in using Curves, Levels, or Channel Mixer. You can even do it right in the Channels palette if you really want to. I chose Gradient Map because I thought it would make more sense to other people. Cameraken 02-13-2006, 03:36 PM Stroker.
Thanks. Everything is working fine. Your code is putting HSL into the channels.
It seems like using the code more than once is the problem. The second time it is run on the same picture it seems to run without the need to compile it.
Open Picture
Open FilterMeister
Paste your code
Press Compile
Press OK
At this point everything is OK and HSL are in the channels
Now
Open FilterMeister again
Now the picture changes without pressing compile
I think I’ve seen this happen before.
Anyway the main thing is that it is working with FET.
Ken Stroker 02-13-2006, 04:10 PM Now the picture changes without pressing compile
This is because the last compile is cached.
Glad it's working. I'll get to work on the next bit. Actually make a graphic or two. Egads. Stroker 02-14-2006, 12:08 AM In previous examples, we saw that we could take hue and sat and move them around. In one example, 0 was used as the reference or starting point of sorts. In another example, 255 was used as the reference. You have to pick where to start and then go from there.
In our custom space, the reference or starting point is going to be lum (lum is the name of the variable, but Lightness by definition). Then it's just a matter of scaling. Scale what? Scale hue in full colour, of course.
Plan of attack
- get hue, sat, and lum
- get hue in full RGB colour using temporary values
- scale it using saturation to set the range and lum as the starting point
See attachment for a graphic.
While we are at it, lets add some controls to manipulate hue, sat, and lum.
New code:
%ffp
SupportedModes: RGBMode
ctl(0):standard,"Hue",range=(-128,128),val=0,track
ctl(1):standard,"Sat",range=(-255,255),val=0,track
ctl(2):standard,"Lum",range=(-255,255),val=0,track
ForEveryTile:{
int x,y,r,g,b;
int min,max;
int hue,sat,lum;
int rt,gt,bt;
for (y=y_start; y<y_end; y++){
//if(updateProgress(y,y_end)) abort();
for (x=x_start; x<x_end; x++){
r=src(x,y,0);
g=src(x,y,1);
b=src(x,y,2);
hue=rgb2hsl(r,g,b,0);
min=min(r,min(g,b));
max=max(r,max(g,b));
sat=max-min;
lum=(max+min)/2;
// tweak
hue+=ctl(0);
sat+=ctl(1);
lum+=ctl(2);
// get full colour hue
rt=hsl2rgb(hue,255,128,0);
gt=hsl2rgb(hue,255,128,1);
bt=hsl2rgb(hue,255,128,2);
// scale full colour hue down
// to sat range using lum as reference
r=scl(rt,0,255,lum-sat/2,lum+sat/2);
g=scl(gt,0,255,lum-sat/2,lum+sat/2);
b=scl(bt,0,255,lum-sat/2,lum+sat/2);
pset(x,y,0,r);
pset(x,y,1,g);
pset(x,y,2,b);
} // end x preview
} // end y preview
return true;
} // end for every tile
Notice that the controls make use of track. This will make the filter go as you scrub the sliders. Fine for simple filters, but may bog down as things get more intensive.
Also notice that the range of the hue slider is -128 to +128. Cookie if you know why. (Bonus cookie if you know why the range should really be -128 to +127.)
As you get to playing with it, you may notice some funky things. One funky thing in particular is when Sat is set to -255. What this does is allow for illegal values of saturation and that's not always good. To fix, you have to clamp it.
Add something like this:
if (sat>255){sat=255;}
if (sat<0)}{sat=0;}
Once you have that, you can desaturate to Lightness in HSL. This is *exactly* the same as hitting ctrl + shift + u. Feel free to do the detective work on that.
Feel free to clamp lum while you are at it. (Bonus cookie for why hue doesn't have to be clamped.)
There is still a lot of funkiness in there. The next funkiness is really funky. Stroker 02-14-2006, 07:59 AM I would like to thank the RetouchPRO Powers that Be for attachments. Normally I would use my own server for my own junk. Mucho appreciated.
In the last code that I posted, there is some funkiness. The major, glaring funk we are looking at is hue. What happens to hue when you get crazy with the Lum slider? It gets funkified. Why? Absolute clipping.
One of the interesting things about hue is that the RGB values stay relative to each other regardless of sat and lum. Do you know the RGB <> hue pattern for yellow? How about the RGB <> hue pattern for green? I'm sure most of you know this, but I don't know if any of you have ever looked at it this way.
The problem with the last code is that RGB <> hue is not being kept relative. If one value is out of bounds, it will get clipped, and that will inadvertantly affect hue. Hopefully the attachment illustrates this.
Welcome to my world. Cameraken 02-14-2006, 06:28 PM Stroker.
I have never seen ‘Track’ before. I can’t even find it in the manual. But I do understand what it is doing
I’ve spent a lot of time trying to win the cookies but I admit defeat. I could not find the answer at Tech Slop or at the Asylum. I did find an interesting article here.
http://en.wikipedia.org/wiki/Talk:HSV_color_space
I hope I did not miss the answers in this thread.
if (sat<0)}{sat=0;}
I took the extra bracket out
Should I be clamping Sat before or after the calculation of scale full colour hue down
I can’t see any difference on the pictures I tried if the code was before or after or not there at all
Stroker, at
http://tech-slop.serveit.org/wiki/index.php?title=Ra_frequencies1
You are breaking down the picture into frequencies. In another thread (another forum) I read where you tore the luminosity into low, medium and high to correct a face.
I know this can be done with Filtermeister as you must have used it in TS_LumFrequency. Would it be straying too far off subject to show us how to do this?
It’s nice to find someone who can spell ‘colour’ correctly. :pleased:
Ken Stroker 02-15-2006, 02:15 AM Track can be found in UserGuide.pdf in section 3.3 User control properties.
For the cookies, you won't exactly find them anywhere. As you do your own research and fiddling, keep them in mind and the answers may come. If not, don't fret too much over it.
Clamp sat immediately like so:
sat+=ctl(1);
if(sat<0){sat=0;}
if(sat>255){sat=255;}
Good catch with the extra bracket.
Actually, more about clamping saturation is coming soon.
Ah, the frequency thing. Fun, isn't it? I used Tom's code because it is wicked fast:
sourcecode\codelibrary\recursiveGauss4b-clean.ffp
- read Lum into an array
- use recursive Gauss for high blur
- High Pass the high bur
- another pass for low blur
- calculate the medium frequencies
- tweak, put back together, and output
That's the real quick of it. Once a lot of this other stuff is out of the way, I'll do a stripped-down version for you.
I think I'm the only person in the USA that still spells colour. I have to make an effort to spell it States style. Stroker 02-15-2006, 04:11 AM Okay, clamping and limiting saturation to get rid of funkiness. Right.
Already clamped sat to the 0 to 255 range. While this is good, it's not entirely good. If you can believe it, you can still get some shifts in hue. Egads! Still have your full colour hue code handy? Go ahead and use it to do some comparing.
In similar colour spaces, saturation is limited by the lightness component. You will find this in HSB, HSL, and Lab. You will even find it in our mix-n-match colour space.
We have to limit sat based on lum - not just 0 to 255. Emphasis!
In order to see this, we have to turn to the colour space itself. We have to look our custom space in a 2d/3d manner. Our custom space is a double-cone.
See attachment.
What we need is another variable that hold what the maximum amount of saturation can be. I'm going to use Smax. This variable, Smax, will be calculated directly from lum.
Since we are dealing with a cone and an isosceles triangle, we can use a simple formula.
Get the absolute difference between lum and 128, multiply it by 2 for full 0 to 255 range, and invert.
Smax=255-abs(lum-128)*2;
Can you see it? I hope so because being able to see these kinds of things is what it's all about.
By the attached graphic, it may look like Smax = sat. No! Remember that sat may be lower than Smax.
Going to use Smax to limit sat, and Smax is derived from lum. This means clamp lum to 0 to 255, and then clamp sat using Smax.
New chunk of code:
// tweak
hue+=ctl(0);
sat+=ctl(1);
lum+=ctl(2);
if(lum>255){lum=255;}
if(lum<0){lum=0;}
// limit sat
Smax=255-abs(lum-128)*2;
if(sat>Smax){sat=Smax;}
if(sat<0){sat=0;}
Don't forget to add int Smax in the declares.
Using that last chunk, we are done putting our custom mix-n-match colour space back together. It's all good and proper.
Now, while our space and code is very similiar to Hue/Sat in Photoshop, there is still at least one major difference. That major difference is in Hue/Sat > Lightness slider and our own Lum slider.
- cookie if you can illustrate the difference
- bonus cookie if you can explain the difference
Hang on... it's not all good and proper. There is one last little thing in our code. We still have to deal with sat=0. Gadzooks! Stroker 02-15-2006, 04:18 AM Okay, if sat=0 in the original, then there is no hue - it literally doesn't exist. However, our code will allow us to add saturation. This will result in our code defaulting to hue=0, which is red.
Desaturate some photograph
Run our code and use Sat +255
Not good, eh?
Change this:
sat+=ctl(1);
into this:
if(sat!=0){sat+=ctl(1);}
If there is no saturation in the original, then our code won't add any.
Tada.
Or maybe you can take advantage of this and come up with your own little way to colourize. Cameraken 02-15-2006, 07:32 PM Thanks Stroker.
I got all the tweaks in and its working OK.
You are loosing me a little. I thought colour was Hue+Sat If that’s the case then why does Sat(max) depend on Lum.
Surely if Sat has been extracted correctly then its dependency should be lost.
It makes me question whether sat=max-min is correct.
This obviously took you a long time to work out so I am sure you are correct. I just don’t quite understand it yet.
I guess The fact that sat+=ctl(1) does not work proves you are correct, (image goes Red)
(sat!=0){sat+=ctl(1);} works fine.
Can you point me to any further reading on this. There seems to be nothing on the web. A Google search found this
http://nebulus.org/tutorials/2d/photoshop/color/index.html
Pretty pictures. But not a full explanation.
I think you have already explained this. It has just not sunk in yet.
Difference between Photoshop Hue/Sat > Lightness slider and your Lum slider. Ours is increasing each pixel by a fixed amount hence keeping the contrast. Photoshop looses contrast as the lightness in increased.
Ken Stroker 02-15-2006, 11:12 PM Saturation is limited by Lum in certain cases. Lum is the starting point and everything else follows.
Consider
Lum = 100% or 255
This is pure white and has no saturation
It's just pure white and that's that
If saturation isn't already 0, then it has to be reduced to 0
RGB is a cube and HSL is a double-cone. The double-cone doesn't fit nice and snug into a cube, so something has got to give. Rather, something has got to be limited or clamped.
This is the case for similiar colour spaces. But in order to see exactly how Lum limits Sat for a given space, you have to get 2d/3d. For example, Lab is a sphere. Lab is also Cartesian. This means you have to use Pythagoras to limit Sat based on L. You know, circles and right triangles and stuff.
Once you are comfortable with the idea of L limiting S, or pretty sure you understand it, we'll move on.
I don't think you will anything on the 'net talking about these kinds of things. I've looked and never found anything explicit. I've had to take a lot of bits-n-pieces and do my own detective work. As far as I know, I'm the only guy willing to talk about these nitty-gritty things.
Difference between Photoshop Hue/Sat > Lightness slider and your Lum slider. Ours is increasing each pixel by a fixed amount hence keeping the contrast. Photoshop looses contrast as the lightness in increased.
Oh, pretty close. Not as close as I would like, but close enough for a cookie. Cameraken 02-17-2006, 11:42 AM Thanks Stroker.
There are a lot of advantages to the new code.
The border now zooms with the picture and the border changes with the sliders as the slider is moved.
I must start reading that manual.
Here is the new code.
%ffp
SupportedModes: RGBMode
OnFilterstart:{
//Info("isTileable %d",isTileable);
isTileable=true;
// setZoom(1);
return false;
}
ctl(0):standard,"Border thickness",range=(0,100),val=0,track
ctl(2):standard,"Red coloring",range=(0,255),val=0,track
ctl(3):standard,"Green coloring",range=(0,255),val=0,track
ctl(4):standard,"Blue coloring",range=(0,255),val=0,track
R: ( x < ctl(0) || x > X-ctl(0)-1 || y < ctl(0) || y > Y-ctl(0)-1) ? ctl(2) : r
G: ( x < ctl(0) || x > X-ctl(0)-1 || y < ctl(0) || y > Y-ctl(0)-1) ? ctl(3) : g
B: ( x < ctl(0) || x > X-ctl(0)-1 || y < ctl(0) || y > Y-ctl(0)-1) ? ctl(4) : b
A:a
Also I found this. I don’t know if its any use, Or if it can be converted.
http://www.mathworks.com/matlabcentral/files/1560/LMS_Equalizer.m
Ken Cameraken 02-18-2006, 07:54 PM Back to HsY
Stroker.
I have been re-reading this thread. (Several Times)
From post 3
Now change the blending mode of the H/S Ad-Layer to Hue, Colour, or Sat. Different, eh? This is because you are now using HsY and seeing Y or Luminosity.
Yes it IS Different in RGB and CMYK but NOT in Lab. I guess this should not be happening at all in Any colour space and sorta proves what you are saying is correct.
From post 6
There is a big difference in the two pieces of code
The first makes the blacks go red
The second the blacks stay black (well blacker)
Changing HsY to HSL (or any other space like CMYK) means that there will be some loss and that is the reason for the funkiness clamping
I guess you can’t fit a square peg in a round hole OR You can’t fit a cube into a double cone.
Ken Stroker 02-19-2006, 04:02 AM Okay, the Lab thing. When you are in Lab mode, everything is done using Lab - no HsY to muck about with. It's pretty nice because Lab filters are a bit more straight forward. That is, you don't have to really worry about converting spaces for basic operations that would normally require a different space.
Colour blending mode in Lab is done using a and b channels - not hue and sat as we are used to them. Oh, I feel a can of worms coming. Can you feel it? Can you?
The HsY thing was using Lightness, but we want to use Lum as Photoshop defines and uses it. I originally went with Lightness out of convenience and to acclimate ya'll to certain ideas. The main idea being that the L* component will limit the Sat component.
Using Lightness, finding Smax was fairly easy. Using Lum to find Smax is going to be a bit trickier. Time to get funky. See attachment.
Upper-Left
This is the HSL double-cone kind of unwrapped to a cylinder. Across the top is pure white and the bottom is pure black. Right in the middle at 128 is where Smax=255.
Upper-Right
This is a greyscale representation of Smax. See how Smax=255 when Lightness=128? And then Smax fades to 0 as you get closer to the top or bottom? Yeah, finding Smax was easy.
Lower-Left
This is HsY unwrapped to a cylinder in the same manner. What was once a horizontal line is now all wiggly. Well, sort of wiggly.
Lower-Right
This is greyscale Smax for HsY. Definitely funky. Did we step in something funky? Oh, yeah.
If we can find were Smax=255 in HsY, then the rest should be easy to fade to Smax=0 at the top and bottom.
Is there a pattern to this? Is there a simple formula? There is, and it has been sitting right in front of our faces this whole time. Big cookie to anybody that figures it out before I give up the funk.
- = Warning = -
You may want to brush up on your triangles and ratios. Cameraken 02-19-2006, 01:07 PM HSL
Lightness = (Max-Min)/2
Sat = Max-Min
SatMax = 255-abs(lightness-128)x2
HSY (HS/Lum)
Lum = Rx0.30 + Gx0.59 + Bx0.11
Sat = Max-Min
SatMax = I guess the formula is going to be very similar to above as it will still be based on Lum.
Then if Sat>SatMax then Sat=SatMax
I sure did a lot of reading. These things are just not out there. I really appreciate you taking the time Stroker. The last link here is to a PDF which is interesting.
http://www.13thmonkey.org/~boris/photopnmtools/saturation-test.html
http://en.wikipedia.org/wiki/HSV_color_space#Transformation_from_RGB_to_HSV
http://support.microsoft.com/kb/q29240/
http://www.prip.tuwien.ac.at/~hanbury/hsy2rgb.m
http://www.prip.tuwien.ac.at/~hanbury/colour_histogram/
http://www.prip.tuwien.ac.at/~hanbury/Online_docs.html
www.prip.tuwien.ac.at/~hanbury/SCIA_Hanbury.pdf
Ken Stroker 02-20-2006, 05:55 AM You are welcome, Ken and others. And thank you for listening. It's not that often I come out and play like this. Well, not much lately. So, I am enjoying myself rather muchly.
There are several reasons why what I'm talking about is rather exclusive. One of the main things is that HsY is an Adobe thing and they don't document things like this. Another reason is that Smax is something that I invented for working with HsY. If I remember, I'll tell you the really real reason behind Smax. Seems like the rabbit hole just keeps on getting deeper, eh?
Ken, it's good to see you getting out there and doing research on your own. And it's good to see Roland steam-rollin' some of this stuff.
Roland, try this:
setZoom(-888);
It comes in handy from time to time.
Sooner than I wanted, but I'm gonna move on a bit further.
Smax based on Lum. I said that there was a magic formula and that it has been staring us right in the face. Going to use a variable of double type called Lxyz.
double Lxyz;
// get full colour hue
r=hsl2rgb(hue,255,128,0);
g=hsl2rgb(hue,255,128,1);
b=hsl2rgb(hue,255,128,2);
Lxyz =r*0.30 + g*0.59 + b*0.11;
Oh! Look at the formula for Lxyz. Look familiar? I should hope so.
Not completely apparent in that code chunk, but Lxyz comes immediately after getting full colour hue. This is so we can find the point where Smax=255 for *any* given hue. This will give us that funky wiggle.
Lxyz is also a double. I'm not exactly sure why it has to be a double, but it does. I think it has to do with precision in the next few steps.
Now that we have Lxyz as the point where Smax=255, it's just a simple matter of triangles and ratios to find Smax for any given value of Lum. Coming soon.
Kind of funny, isn't it? We have to have Hue in order to limit Sat based on Lum. Some things just aren't as independant as we thought. Stroker 02-20-2006, 06:42 AM Oh, triangles. Lovely triangles and ratios. You like percentages, don't you?
Some where around here I posted a graphic with a triangle. One corner was red, one corner was black, and the last corner was white. We are going to take another look at that triangle, but in a different light.
You do remember Geometry one-oh-one, right? Check out the attachment and be amazed at how much you remember. Stroker 02-20-2006, 06:44 AM Using that, we can easily come up with what's in the attachment.
Since the ratios are percentages, have to multiply by 255. The rest should be cake. Stroker 02-20-2006, 06:47 AM New code chunk:
// get full colour hue
r=hsl2rgb(hue,255,128,0);
g=hsl2rgb(hue,255,128,1);
b=hsl2rgb(hue,255,128,2);
Lxyz =r*0.30 + g*0.59 + b*0.11;
if(lum>=Lxyz){
Smax=255* ((255-lum)/(255-Lxyz));
} // Lum > Lxyz
if(lum<=Lxyz){
Smax=(lum/Lxyz)*255;
} // Lum < Lxyz
if(sat>Smax){sat=Smax;}
if(sat<0){sat=0;}
Previously scaling down to Sat was easy because L* was right in the middle of min and max. Well, Lum isn't always going to be in the middle of min and max. So, how do we scale down to Sat? How do we compensate for a wiggly reference?
Hmmm.... Stroker 02-21-2006, 01:42 AM Something just for fun. The tweaks I added are very sloppy. Forgive me?
%ffp
supportedmodes: RGBMode
ctl(0):standard,"T1 Mult",range=(0,300),val=100,track
ctl(1):standard,"T2 Add",range=(0,100),val=0,track
OnFilterStart:{
//setPreviewCursor (32515);
setZoom(1);
return false;
}
ForEveryTile:{
int x,y,z;
int c1,c2,c3,c4,c5,c6,c7,c8,c9;
int gx,gy;
float distance;
float final;
int finint;
for (z=0;z<Z;z++){
for (y=y_start; y<y_end; y++){
//if(updateProgress(y,y_end)) abort();
for (x=x_start; x<x_end; x++){
// 1 2 3
// 4 5 6
// 7 8 9
c1=src(x-1,y-1,z);
c2=src(x,y-1,z);
c3=src(x+1,y-1,z);
c4=src(x-1,y,z);
// c5
c6=src(x+1,y,z);
c7=src(x-1,y+1,z);
c8=src(x,y+1,z);
c9=src(x+1,y+1,z);
gx= -c1 -c4*2 -c7 +c3 +c6*2 +c9;
gy= -c1 -c2*2 -c3 +c7 +c8*2 +c9;
distance=sqrt((float)gx*gx + gy*gy);
final=distance*(float)ctl(0)/100.00;
finint=255-(int)final+ctl(1);
pset(x,y,z,finint);
}}}// x, y,z
return true;
} // for every tile Cameraken 02-21-2006, 07:18 PM Thanks Stroker.
I’ve been a bit busy with more OPR Pictures. And a faulty Central Heating Boiler.
The code from Post 49 is great for the artists here. I’ve added an example below.
I have not got the code from post 45 and Post 48 working yet. I’m getting some strange results. I think I need to take something out.
I will try again tomorrow.
Fancy this next Rô
http://www.codeproject.com/cpp/howtofft.asp
Ken Stroker 02-21-2006, 09:20 PM Those code chunks won't exactly work because we are not quite done putting it all back together.
The next problem is dealing with Lum and Sat. Since Lum isn't always in the middle of min and max, we need a different approach. I've tried several different approaches and only one seems to work decently.
Take care of Smax and sat. Get hue in full colour. Scale RGB down to sat using range 0 to sat. Use another variable to get the luminosity of the scaled down RGB. Take the difference between this temporary luminosity and the desired luminosity. Add that difference to all RGB values. Since we are adding the same value to RGB, hue and sat will be preserved. Plus, in the end, we will have the desired luminosity.
Notice that the temporary lum variable, temL, is also double.
Final RGB <> HsY code:
%ffp
SupportedModes: RGBMode
ctl(0):standard,"Hue",range=(-128,128),val=0,track
ctl(1):standard,"Sat",range=(-255,255),val=0,track
ctl(2):standard,"Lum",range=(-255,255),val=0,track
ForEveryTile:{
int x,y,r,g,b;
int min,max;
int hue,sat,lum;
int Smax;
double Lxyz;
double tempL;
int final;
for (y=y_start; y<y_end; y++){
//if(updateProgress(y,y_end)) abort();
for (x=x_start; x<x_end; x++){
r=src(x,y,0);
g=src(x,y,1);
b=src(x,y,2);
hue=rgb2hsl(r,g,b,0);
min=min(r,min(g,b));
max=max(r,max(g,b));
sat=max-min;
lum=r*0.30 + g*0.59 + b*0.11;
// tweak
hue+=ctl(0);
if(sat!=0){sat+=ctl(1);}
lum+=ctl(2);
if(lum>255){lum=255;}
if(lum<0){lum=0;}
// get full colour hue
r=hsl2rgb(hue,255,128,0);
g=hsl2rgb(hue,255,128,1);
b=hsl2rgb(hue,255,128,2);
Lxyz =r*0.30 + g*0.59 + b*0.11;
if(lum>=Lxyz){
Smax=255* ((255-lum)/(255-Lxyz));
} // Lum > Lxyz
if(lum<=Lxyz){
Smax=(lum/Lxyz)*255;
} // Lum < Lxyz
if(sat>Smax){sat=Smax;}
if(sat<0){sat=0;}
// scale to sat
r=scl(r,0,255,0,sat);
g=scl(g,0,255,0,sat);
b=scl(b,0,255,0,sat);
// calc small lum and get difference
tempL= r*0.30 + g*0.59 + b*0.11;
tempL=lum-tempL;
// add the difference
r+=tempL;
g+=tempL;
b+=tempL;
// output
pset(x,y,0,r);
pset(x,y,1,g);
pset(x,y,2,b);
} // end x preview
} // end y preview
return true;
} // end for every tile
There. You can now RGB <> HsY with a fair amount of accuracy. Isn't it beautiful?
Why would you want to do this? I don't know about you, but I like being able to extend a filter beyond the filter itself. That is, making it blending mode friendly for further tweaks. If I want a filter that works with Colour blending mode (Hue + Sat), I can now do that. If I want a filter that manipulates Lum for contrast, I can now do that.
Another good thing is making greyscale masks based on HsY. Practice your ChOps and take it to the code. If we get far enough, I'll tell ya'll about my Trigs in Space. Stroker 02-21-2006, 09:25 PM Let's play Connect the Dots.
Sobel (http://en.wikipedia.org/wiki/Sobel)
Canny Edge Detection (http://www.pages.drexel.edu/~weg22/can_tut.html)
Gradient (http://en.wikipedia.org/wiki/Gradient)
Flow Up and Down (http://tech-slop.serveit.org/wiki/index.php?title=Vector_Flow_Fields_Up_and_Down)
Diplacement Sharpening (http://tech-slop.serveit.org/wiki/index.php?title=Displacement_Sharpening)
Can you see the bigger picture? Stroker 02-21-2006, 11:52 PM I'm about to jump out of my skin. How about a quick-n-sleazy tutorial? I call this High Pass Masking, but it might be known by other names.
- Start with some photograph
- Copy it and High Pass to taste
- Add an Adjustment Layer > Gradient Map
-- for the G-Map, use Black <> White <> Black
-- start with the white stopper at 50% and tweak from there
What did we just do? We used High Pass for edge detection. The good thing is that it has radius. The bad thing is that it fails in the delta change department. High Pass + delta change = bad! Well, bad for current purposes.
To fill in the holes, all you have to do is complement with regular ol' find edges.
Oh! Did I just say that? Fancy this next Rô
http://www.codeproject.com/cpp/howtofft.aspOK, You guys up to mind reading again?
Yes I have read that - and about 50 others in the last couple of days.
The book he quotes, "Numerical recipes in C", is very interesting. I'm getting comfortable with the basic FFT routine, but the 2-dimensional version (which we need here) still scares me a bit.
Actually, I was quite surprised at how (relatively) simple it is. What complicates matters is when you try to get it running faster and faster.
However, our need for bigger and bigger images is just about satisfied. It pretty hard to justify anything bigger than 3000 pixels on each side.
Processing power and memory capacities continue to rise exponentially.
When theses guys started with these techniques they were using "fast" 100MHz computers, but now 1GHz is pretty slow.
Which means that instead of talking of reducing execution times from 5s to 2s, we have something that may take 500ms and reduce to 200ms. Which, for a one-off process, nobody is even going to notice.
Rô Cameraken 02-22-2006, 07:43 PM Wooha
Hue, Sat and Lum all working fine. Thanks Stroker.
Ken Stroker 02-23-2006, 01:39 AM Kudos, Ken.
Now, my way is a bit long-winded. Ralph of Simpel Filter (http://www.simpelfilter.de) has provided his snippet for HsY > RGB.
--------------snip-------
//HsY to RGB - Y hier mit v bezeichnet
//Scale to float
hf=6*r/255.0; sf=g/255.0; vf=b/255.0;
// H is given on [0, 6] or UNDEFINED. S and V are given on [0, 1].
// RGB are each returned on [0, 1].
if (sf == 0.0) {rf = vf; gf = vf; bf = vf;}
else {
i = floor(hf);
switch (i) {
case 6:
case 0: hf = hf; bf = vf - sf*(0.299 + 0.587*hf); gf = bf
+ sf*hf; rf = bf + sf; break;
case 1: hf = 2.0 - hf; bf = vf - sf*(0.587 + 0.299*hf);
rf = bf + sf*hf; gf = bf + sf; break;
case 2: hf = hf - 2.0; rf = vf - sf*(0.587 + 0.114*hf);
bf = rf + sf*hf; gf = rf + sf; break;
case 3: hf = 4.0 - hf; rf = vf - sf*(0.114 + 0.587*hf);
gf = rf + sf*hf; bf = rf + sf; break;
case 4: hf = hf - 4.0; gf = vf - sf*(0.114 + 0.299*hf);
rf = gf + sf*hf; bf = gf + sf; break;
case 5: hf = 6.0 - hf; gf = vf - sf*(0.299 + 0.114*hf);
bf = gf + sf*hf; rf = gf + sf;
}
}
//Scale to int
k1=rf*255;
k2=gf*255;
k3=bf*255;
------------------
It's a bit more elegant and is a bit more accurate.
For the most part, I'm going to bow out for now. But I will watch for questions and things that tickle my fancy. Stroker 02-23-2006, 08:05 AM Grrr...!
Basic Lab manipulation using FM's built-in polar functions.
%ffp
supportedmodes: labmode
ctl(0):"Theta",range=(-512,512),val=0,track
ctl(1):"Rho",range=(0,300),val=100,track
ForEveryTile:{
int aa,bb;
int theta,rho;
for(x=x_start;x<x_end;x++){
for(y=y_start;y<y_end;y++){
// grab and normalize to 0
aa=src(x,y,1)-128;
bb=src(x,y,2)-128;
// convert to polar
theta=c2d(aa,bb);
rho=c2m(aa,bb);
// modify polar
theta=theta+ctl(0);
rho=rho*ctl(1)/100;
// back to Cartesian
aa=r2x(theta,rho);
bb=r2y(theta,rho);
// output normalized to 128
pset(x,y,1,aa+128);
pset(x,y,2,bb+128);
}} // y x
return true;
} // for every tile Stroker 02-24-2006, 08:26 AM Emboss a bit heavy on precision. If you do it integer style, it's much faster.
%ffp
ctl(0):"Angle",range=(-180,180),val=0,track
ctl(1):"Height",range=(0,40),val=5,track
ctl(2):"Amount",range=(0,300),val=100,track
supportedmodes: rgbmode
OnFilterStart:{
setZoom(1);
set_edge_mode (3);
return false;
}
ForEveryTile:{
int x,y,z;
int v1,v2,final;
float angle,gx,gy;
float pi = 3.14159;
for (z=0;z<3;z++){
for (y=y_start; y<y_end; y++){
//if(updateProgress(y,y_end)) abort();
for (x=x_start; x<x_end; x++){
angle=pi*ctl(0)/180.00;
gx=fcos(angle)*ctl(1)/5.0;
gy=fsin(angle)*ctl(1)/5.0;
v1=iget(x+gx,y+gy,z,0,3);
v2=iget(x-gx,y-gy,z,0,3);
final=(v1+(255-v2))/2;
final=128+(128-final)*ctl(2)/100;
pset(x,y,z,final);
}}} // x y z
return true;
} // for every pixel Stroker 02-27-2006, 04:53 PM I adore the subject of desaturating. What I don't like is Channel Mixer for desaturating. IMHO, CM is poor for desaturating because it's so one directional.
Here is a variation of Channel Mixer specifically for desaturating.
- not in the code, but I call it Mono Mixer
- Subtract is inverted and works like Multiply
- Substrate is like Constant in CM
Use it wisely.
%ffp
ctl(0):standard,"Substrate",range=(-226,256),val=0,pos=(280,5),track
ctl(1):standard,"R",range=(-200,200),val=33,pos=(260,25),track
ctl(2):combobox(vscroll,extendedui),"Add\nSubtract\nLinear Light",val=0,pos=(390,23),size=(55,80),action=preview
ctl(3):standard,"G",range=(-200,200),val=33,pos=(260,55),track
ctl(4):combobox(vscroll,extendedui),"Add\nSubtract\nLinear Light",val=0,pos=(390,53),size=(55,80),action=preview
ctl(5):standard,"B",range=(-200,200),val=33,pos=(260,85),track
ctl(6):combobox(vscroll,extendedui),"Add\nSubtract\nLinear Light",val=0,pos=(390,83),size=(55,83),action=preview
supportedmodes: rgbmode
ForEveryTile:{
int x,y;
int r,g,b;
float addr,addg,addb;
int final;
for (y=y_start; y<y_end; y++){
//if(updateProgress(y,y_end)) abort();
for (x=x_start; x<x_end; x++){
r=src(x,y,0);
g=src(x,y,1);
b=src(x,y,2);
addr = addg = addb = 0;
switch(ctl(2)){ // R
case 0: // add
addr=(float)r*(float)ctl(1)/100.00;
break;
case 1: // subtract
addr=(255.00-(float)r)*(float)ctl(1)/-100.00;
break;
case 2: // linear light
addr=((float)r-128.00)*(float)ctl(1)/200.00;
break;
} // R switch
switch(ctl(4)){ // G
case 0: // add
addg=(float)g*(float)ctl(3)/100.00;
break;
case 1: // subtract
addg=(255.00-(float)g)*(float)ctl(3)/-100.00;
break;
case 2: // linear light
addg=((float)g-128.00)*(float)ctl(3)/200.00;
break;
} // G switch
switch(ctl(6)){ // B
case 0: // add
addb=(float)b*(float)ctl(5)/100.00;
break;
case 1: // subtract
addb=(255.00-(float)b)*(float)ctl(5)/-100.00;
break;
case 2: // linear light
addb=((float)b-128.00)*(float)ctl(5)/200.00;
break;
} // Bswitch
final=ctl(0) + addr + addg + addb;
pset(x,y,0,final);
pset(x,y,1,final);
pset(x,y,2,final);
}} // x y
return true;
} // for every pixel Cameraken 02-28-2006, 05:58 AM I like the last one Stroker.
Converting from colour to Black and White can be controversial. I read your “Fighting the Status Quo”
With many pictures it doesn’t seem to matter which method is used but with others it can make a big difference.
I particularly like one method (which I think was yours)
* create a new blank Layer on top of your Background
* Fill the new Layer with 50% Grey
* Change the new Layer Blending to Color (or Saturation)
This code gives a lot more control. It's going to need some practice.
Linear Light = Add/Subtract using 128 as the mid-point
Mono Mixer is definitely going in my toolbox.
Thanks Again.
Ken. Stroker 02-28-2006, 06:33 AM Ken, you tickle me. Fighting the Status Quo is one of my favorites. I've gotten into some heated arguements over that one. Oh, the irony.
Maybe one of these days I'll wage war again. Show you guys and gals a few uber tricks, and maybe have a chat about contrast while we're at it.
Contrast: to set in opposition in order to show or emphasize differences.
Something to think about - and very relevant when it comes to desaturating. Stroker 02-28-2006, 08:51 AM Lab mixer for desaturating much like Mono Mixer.
%ffp
supportedmodes: labmode
ctl(0):"a center",range=(-100,100),val=0,track,pos=(280,5)
ctl(1):"a weight",range=(-200,200),val=50,track,pos=(280,15)
ctl(2):"b center",range=(-100,100),val=0,track,pos=(280,40)
ctl(3):"b weight",range=(-200,200),val=50,track,pos=(280,50)
ForEveryTile:{
int ll,aa,bb,x,y,final;
for(x=x_start;x<x_end;x++){
for(y=y_start;y<y_end;y++){
ll=src(x,y,0);
aa=src(x,y,1)-128+ctl(0);
bb=src(x,y,2)-128+ctl(2);
final = ll + aa*ctl(1)/100 + bb*ctl(3)/100;
pset(x,y,0,final);
pset(x,y,1,128);
pset(x,y,2,128);
}} // y x
return true;
} // for every tile
There are several things hidden in the code. Ramble coming soon.
Dichotomy: division into two usually contradictory parts. Cameraken 02-28-2006, 06:57 PM Oh no! Not contrast. I’ve seen debates about that as well (Where is Flora).
Did you ever write the Shadow/Highlight Filter. (Us Poor PS7 Users).
I tried writing that one myself. I got something working but it does not seem to do exactly the same thing. I remember reading that you had figured out what it was doing.
“Contrast: to set in opposition in order to show or emphasize differences.”
That definition sounds good but a contrast reduction can bring out more detail. Especially in some of the older restoration projects. But I think this depends on global contrast (low frequency) or local contrast (high frequency)
Shadow/Highlight - Now that would be a good project. :bigthmb:
http://www.photoshoptechniques.com/forum/showthread.php?t=14755
http://tech-slop.serveit.org/wiki/index.php?title=Contrast
http://tech-slop.serveit.org/wiki/index.php?title=Lum_Frequencies
I’ve just downloaded TS_Lum FrequenciesV? I noticed the picture on your webpage was different to my version, Version Numbers would be good. It’s changed a lot. Looks like I need to learn this one all over again.
And talking of contrast.
Ro.
Wow, great stuff, you are flying with this.
http://www.retouchpro.com/forums/showthread.php?t=12957
Ken. WARNING! If you are experimenting the source codes in the codelibrary beware of the filter named "FilterWithoutDialog.ffp".
I ran that one, just to see what happened, and then spent half an hour trying to get FM to appear again - it just blinked and disappeared every time I tried to call it up.
Not exactly sure what I did to get it back - I think it was when I pressed the Shift (or maybe Ctrl) key while trying to open the program.
Rô Stroker 02-28-2006, 11:36 PM Heh. My wiki is behind the times on a few things. I ran into a version problem with L* Frequencies and taking care of the LF nodes ground to a halt. I finally got the version problem taken of, so I should update the wiki nodes, eh? Maybe in a few days or so.
When dinking with str0 in FM, be wary when saving files.
One of these days I'll talk about Shadow/Highlight. Rather, show some of the tricks involved. I'll do it on the sly at my wiki for various reasons.
Roland, same thing happened to me with FilterWithoutDialog. All I did was restart Photoshop because that's the only sure fire way I know of to break the vicious cycle.
I'll do my best to field any questions, Ro. Cameraken 03-08-2006, 06:58 PM Stuck Again.
I have been trying to use stock tools to split Highlights, Midtones and Shadows.
Using Select > Colour Range I am getting gaps.
Even selecting just the Highlights (claw) and Shadows I still get a gap.
I decided to use FM.
I wrote this code to split Highlights, Midtones and Shadows onto the channels however I am back to a similar problem
Using a Black to White gradient as a test picture and the code below I don’t get gaps anymore But the midtones are not an equal third of the picture.
Now I thought this was because I used
lum=r*0.30 + g*0.59 + b*0.11
So I replaced it with
lum=(r+g+b)/3
but my test picture is still the same. The midtones don’t cover 1/3 of the picture like I would expect.
Questions
Have I done something wrong?
If Not. Then why do the midtones not cover 1/3 of the picture?
%ffp
ForEveryPixel:{
int low,mid,high,lum;
//lum= r*0.30 + g*0.59 + b*0.11;
lum=(r+g+b)/3;
low=(lum<85)? lum:0;
mid=(lum>86) & (lum<170)? lum:0;
high=(lum>171)? lum:0;
R=low;
G=mid;
B=high;
}
Ken Ken,
The good news: You are not going crazy. Your code is correct.
The bad news: Photoshop gradients are not linear
Put an eyedropper at the transition lines, then go back and see what the readings are at these points in the original gradient.
The channel values will fit in fine. However, they are not at 1/3 intervals.
Maybe stroker can explain to us why that happens.
Meanwhile, if you want to generate a perfectly linear gradient, then do it with FM!! :wink:
Rô Stroker 03-08-2006, 09:41 PM When you lay the gradient, make sure Smoothness is set to 0%.
Smoothness 0% = linear interpolation
Smoothness 100% = b-spline interpolation
This tid-bit took me a long time to figure out. Chris Cox of Adobe confirmed, and you can see this for yourself with a little help from Histogram. Since then, whenever I lay a gradient, I *always* double-check Smoothness.
Gonna hide under my rock again for a bit.
edit:
Roland, how good are you with geo/trig?
Basic 3D Math (http://www.geocities.com/SiliconValley/2151/math3d.html)
Yeah, I got something brewing, but ran into a funky anomoly. Thanks, stroker.
I knew you'd have something interesting to add.
Problem is, I can't see how that works out.
The smoothness setting is in the Gradient Map editor. Setting this to "0" really does explain a whole bunch of quirky things that I'd seen on the past. :) :)
The Gradient Map, however, does not generate the gradient, just translates it.
So the question stands, how can I generate a black-to-white linear (as in "x" vs. R,G,B) gradient? (in Photoshop, without resorting to FM?)
.....or, maybe I missed something.
(Trigonometry and 3D math never were my favourites - but I can usually muddle my way through. - What's up?)
Rô Stroker 03-09-2006, 12:32 PM Gradient tool has Smoothness as well. When you lay down the initial gradient with the tool, make sure it is Smoothness 0 right from the begining.
Yes?
Meh. Don't worry about the geo/trig thing. Gonna put it on the back burner for a few. Cameraken 03-09-2006, 07:27 PM Thanks.
The Gradient Was the problem. I set the Smoothness to Zero and my code works fine.
(Smoothness is in the same place as when using Gradient Map – Click on the Gradient)
This does explain a lot. I have been setting Smoothness to Zero when putting Sat back without realising the reason why.
Ken Smoothness is in the same place as when using Gradient Map – Click on the Gradient
(insert here a couple of smileys smacking themselves on the forehead)
Thanks, Ken. :bigthmb:
(insert a couple more here )
Rô Stroker 03-11-2006, 08:28 AM Lab is a sphere. Lab is Cartesian. This good for a lot of simple tricks that just plain rock.
Basically use Pythagoras to measure distance from the center of the Lab sphere. The ranges are 0 to 255 with the center at 128. But because of the math involved, better to offset for a range of -128 to 127. This means that the maximum distance will be 128. Have to multiply that by 2 to get back to 0 to 255.
This isn't exactly mid-tone as most are used to. This is more of a 3d mid-tone and is quite different from Colour Range. Personally, I adore it. Plus this is the beginning of some cool Lab tricks.
%ffp
SupportedModes: LABMode
ForEveryTile:{
int x,y,ll,aa,bb;
float dx,dy,dz;
float dist;
int final;
for (y=y_start; y<y_end; y++){
//if(updateProgress(y,y_end)) abort();
for (x=x_start; x<x_end; x++){
// grab values and normalize to 0
ll=src(x,y,0)-128;
aa=src(x,y,1)-128;
bb=src(x,y,2)-128;
// get delta and convert to float
dx=(float)ll;
dy=(float)aa;
dz=(float)bb;
// pythagoras
dist=sqrt(dx*dx + dy*dy + dz*dz);
// convert, invert, and multiply by 2
final=255-(int)dist*2;
// output to greyscale
pset(x,y,0,final);
pset(x,y,1,128);
pset(x,y,2,128);
}} // x,y
return true;
} // for every tile Stroker 03-12-2006, 10:52 AM Pretty much the same above but with a little extra junk. Just added some controls to define the center of the greyscale. Few other minor changes.
As it is, it is pretty good as a simple colour range do-hickey.
Things you may want to add:
- controls to level the greyscale
- right-click Preview to set controls
%ffp
ctl(0):"L",range=(0,255),val=128,track
ctl(1):"a",range=(-128,127),val=0,track
ctl(2):"b",range=(-128,127),val=0,track
ctl(3):checkbox,"Invert",val=0
SupportedModes: LABMode
ForEveryTile:{
int x,y,ll,aa,bb;
float dx,dy,dz;
float dist;
int final;
for (y=y_start; y<y_end; y++){
//if(updateProgress(y,y_end)) abort();
for (x=x_start; x<x_end; x++){
// grab values and normalize to 0
ll=src(x,y,0);
aa=src(x,y,1)-128;
bb=src(x,y,2)-128;
// get delta and convert to float
dx=ll-ctl(0);
dy=aa-ctl(1);
dz=bb-ctl(2);
// pythagoras
dist=sqrt(dx*dx + dy*dy + dz*dz);
// convert, invert, and multiply by 2
final=255-(int)dist*2;
// output to greyscale
if(ctl(3)==1){final=255-final;}
pset(x,y,0,final);
pset(x,y,1,128);
pset(x,y,2,128);
}} // x,y
return true;
} // for every tile
Basic Pythagoras was fun. How about some polar next? Stroker 03-13-2006, 06:47 AM Let's get a little polar in Lab space.
Rather than use Pythagoras and trig functions, going to use FM's built-in polar functions. I believe these polar functions can be found in UserGuide.pdf near the trig functions.
%ffp
ctl(0):checkbox,"L* = 128",val=0
supportedmodes: labmode
ForEveryTile:{
int x,y;
int ll,aa,bb;
int theta,rho,maxrho;
for (y=y_start; y<y_end; y++){
//if(updateProgress(y,y_end)) abort();
for (x=x_start; x<x_end; x++){
// grab values
//ll=src(x,y,0);
aa=src(x,y,1)-128;
bb=src(x,y,2)-128;
// convert to polar
theta=c2d(aa,bb);
//rho=c2m(aa,bb);
rho=127;
// back to Cartesian
aa=r2x(theta,rho);
bb=r2y(theta,rho);
// output with L* option
if(ctl(0)==1){pset(x,y,0,128);}
pset(x,y,1,aa+128);
pset(x,y,2,bb+128);
}}// x, y
return true;
} // for every tile Cameraken 03-13-2006, 02:50 PM Basic Pythagoras was fun. How about some polar next?
OK Great. I hope you can show some uses for Lab.
The code from post 90 is like a channel mixer with monochrome checked. Which is useful as the channel mixer is grayed out in Lab.
Post 91.
Stroker. Could you explain what this code is doing please? It looks like hue.
The Polar functions are in the user guide on page 28
Polar coordinate functions r2x(d,m), r2y(d,m), c2d(x,y), c2m(x,y)
There is also some sample code to convert RGB to Lab (RGB_to_Lab_and_back.ffp). Does this work as well as using Photoshop?
Ken Stroker 03-14-2006, 07:24 AM Okay, Lab is Cartesian. Uses x, y, and maybe even z. Imagine a square or a cube.
HSL et al are polar. These are things that are round. Angles and magnitudes. Theta and rho. Cylinders, spheres, and cones.
Even though Lab is a sphere, it is Cartesian. This leads to some interesting things. For example, if you want to rotate hue in Lab like you would in HSL, you have to convert to a polar system, and then convert back to Cartesian. Covnerting back and forth between Cartesian and polar is basic trigonometry.
One of the very basic things to know is Pythagoras' Formula. I'm sure you've seen this.
a^2 + b^2 = c^2
That will give us the distance, magnitude, or rho between origin and an arbitrary point. In colour systems, this is commonly called saturation. No matter the name, it's all effectively the same.
In post 90, we used Pythagoras to get 3d distance from the center, which is 50% grey.
a^2 + b^2 + c^2 = r^2
r = sqr( a*a + b*b + c*c )
To get the distance between two arbitrary 3d points, it would look something like this:
(x2-x1)^2 + (y2-y1)^2 + (z2+z1)^2 = r^2
r = sqr( (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) + (z2-z1) )
In Lab space, this is an excellant trick for converting colour ranges to greyscale. With a little savvy, I find this method better than Photoshop's own Select > Colour Range.
Now, post 91 is also a trick of trig, but with a focus on polar instead of just Pythagoras.
theta = hue
rho = saturation
Convert a and b to theta and rho, set rho to a given value, convert back to a and b. What you are effectively doing is setting saturation to a fixed number. The code basically says set saturation to 127. It is doing this in Lab space using a polar system.
Few more tricks, then the rabbit hole is going to get deep again. Stroker 03-14-2006, 08:28 AM Almost forgot about rgb2lab and lab2rgb.
For the majority of applications, they work fine. However, be aware that you might run into some shifts due to gamma. Largely negligable, but might make a difference if you are into the device independance thing in Lab. Cameraken 03-15-2006, 01:19 PM Thank You for the explanation Stroker.
I’m just about keeping up. But I’m having to do a lot of searching.
Further Math’s ‘A’ Level was a Long Long time ago and now I can’t find my log books. :grin:
http://en.wikipedia.org/wiki/Lab_color_space
The more I play with the greyscale converter – The more I like it. Another one for my Toolbox.
Ken Stroker 03-15-2006, 11:38 PM Polar is good. But let's try something a little bit different.
To do colour balance in Lab, you just add to a and b channels. There is a way of doing this that is similiar to polar but remains in Cartesian.
Gradient
This is simply an amount. That's it. Displace works by using an x gradient and a y gradient. You know, move things up/down using this gradient and move things left/right using that gradient.
Vector
More than one gradient. All gradients at 90 degrees to each other. For example, ( x+3, y-5) is a vector. So is ( x-2, y+10, z+3).
Normal
A normal is a vector with a unit length of 1. If you use Pythagoras to get the distance of a normal, it will be 1. I guess you can think of a normal as an angle using two (or more) numbers. Or something like that.
Rough analogy:
- hue = x grad and y grad as a normal
- sat = amount to travel in the direction of the normal
Now, it is possible to convert a vector to a normal. This is done simply by dividing each axis by the distance.
x=3
y=-4
distance=5
x grad = 3/5 = 0.6
y grad = -4/5 = -0.8
0.6^2 + -0.8^2 = 1^2
0.36 + 0.64 = 1
Any distance you multiply by 0.6 and -0.8 will go in the same direction as the original 3 and -4.
Cool beans.
Here is a simple example using that junk to colour balance in Lab.
%ffp
ctl(0):standard,"x grad",range=(-100,100),val=10,track
ctl(1):standard,"y grad",range=(-100,100),val=10,track
ctl(2):standard,"u",range=(-100,100),val=10,track
ctl(3):checkbox,"Colourize",val=0
supportedmodes: labmode
ForEveryTile:{
int aa,bb,adda,addb;
float distance,nx,ny;
for(x=x_start;x<x_end;x++){
for(y=y_start;y<y_end;y++){
// grab values
// not going to bother with 128
aa=src(x,y,1);
bb=src(x,y,2);
// Pythagoras to get distance
distance=sqrt( (float)ctl(0)*ctl(0) + ctl(1)*ctl(1) );
// normalize the vector
nx=ctl(0)/distance;
ny=ctl(1)/distance;
// amount to add to each axis
adda=ctl(2)*nx;
addb=ctl(2)*ny;;
// output with option
if(ctl(3)==0){l
pset(x,y,1,aa+adda);
pset(x,y,2,bb+addb);
}else{
pset(x,y,1,adda+128);
pset(x,y,2,addb+128);
}
}} // y x
return true;
} // for every tile
Kind of a weird way of going about it, but it has advantages. Cameraken 03-16-2006, 02:29 PM Stroker
There is an error in the above code
// output with option
if(ctl(3)==0){l
I changed it to
if(ctl(3)==0){
Does that look correct?
Changing that seems to get the colourize working.But I don’t think its doing what it should.
Ken Stroker 03-17-2006, 04:42 AM Nice catch with that pesky character. Wonder how that snuck in?
The last code does work as it should. It just doesn't work as most folks are used to. Hard to explain. If I think about it, I might be able to come with a way of explaining it.
Here comes a subject I adore.
Contrast is finding the difference and amplifying it. It can be anything you desire. Can be difference in saturation between subjects. Can be shades of red and shades of blue. A common use of contrast is Levels. Basically contrast between high/low in each seperate channel.
Earlier I showed how to get 3d mid-tones in Lab. You know, the distance from the center thing. With vectors and normals, you can actually move values away from the mid-tone center in a 3d manner. This is a form of contrast.
Very basic example for Lab:
%ffp
ctl(0):standard,"Amount",range=(-100,100),val=10,track
ctl(1):checkbox,"Absolute Distance",val=0
supportedmodes: labmode
ForEveryTile:{
int ll,aa,bb;
float distance,nx,ny,nz;
for(x=x_start;x<x_end;x++){
for(y=y_start;y<y_end;y++){
// grab values
// bother with 128 to make things easier
ll=src(x,y,0)-128;
aa=src(x,y,1)-128;
bb=src(x,y,2)-128;
// Pythagoras to get distance
distance=sqrt( (float)ll*ll + aa*aa + bb*bb);
//distance hack, move straight up
if(distance==0){
nx=1;
ny=0;
nz=0;
}else{
// normalize the vector
nx=ll/distance;
ny=aa/distance;
nz=bb/distance;
} // end distance hack
// add with option
if(ctl(1)==0){
// relative
ll=ll+nx*ctl(0);
aa=aa+ny*ctl(0);
bb=bb+nz*ctl(0);
}else{
// absolute distance
ll=nx*ctl(0);
aa=ny*ctl(0);
bb=nz*ctl(0);
}
// output, don't forget 128
pset(x,y,0,ll+128);
pset(x,y,1,aa+128);
pset(x,y,2,bb+128);
}} // y x
return true;
} // for every tile
With positive values for Amount, you will move values away from the center of the Lab sphere. With negative values for Amount, you will move values through the center and out the other side for an invert effect.
(If you want to, you don't have to push values away from the center. With some controls and offsetting, you can actually push values away from any given point. To tickle your brain, imagine using gravity instead of vectors. With gravity, you can pull towards and push away from several points. How would you like a magnet effect for contrast and colour balance?)
As the code is, it works just like Levels - except you are doing all 3 channels at once in a simple manner. Since this can be done with Levels, it may seem a bit silly. Believe me, it's not. This is just a building block. Cameraken 03-18-2006, 02:23 PM That works just fine Stroker, Thank you.
It sounds like there is better to come. :happy:
I found this great thread on HSB
http://photoshoptechniques.com/forum/showthread.php?t=10495&page=2
Pity I didn’t find it sooner. I may have won more cookies.
Ken. Stroker 03-19-2006, 05:25 AM Dude, I haven't seen that thread in awhile. Now that's an old school rampage. If I remember correctly, I made some gnarly mistakes in that thread and had to do some follow-ups to ammend.
Let's talk about gradients some more.
With trigonometry you can do a lot of things with triangles, especially if they are right triangles. The Photoshop's Gradient tool is proof positive of this. How does the Gradient tool work? Vectors and trig.
When you drag a the Gradient tool, you are defining a 2d vector in screen space. Basically a position vector in x and y. As you go through the x and y loops, you use one point on the defined vector to define a second vector. Then you make it a right triangle. Using a simple formula, you can get another angle in the right triangle. After that, it should be cake to finish it up.
The forumula to get the angle is simply:
p1 * p2 = |p1| * |p2| * cos(angle)
cos(angle) = p1 * p2 / |p1| * |p2|
angle = acos( p1 * p2 / |p1| * |p2| )
Well, maybe not simply. Maybe if you read Understanding the Dot Product (http://www.mvps.org/directx/articles/math/dot/) enough times will click. Or maybe the attached graphic will help.
The graphic attached shows distance along a vector. Just a simple percentage with the length of v1 = 100%. The length of p3 along v1 = 50%.
And that's how the Linear Gradient tool in Photoshop works.
Um, yeah. Stroker 03-19-2006, 05:39 AM I mentioned that Levels is a common method for increasing contrast. Once in a great while I take issue with this. Why? Because each channel is done seperately and you end up with a square. If you do this in 3d space, you end up with a box.
While the two points and the line between maybe good and gradual, sometimes I don't like what happens to the rest of the space around the 'vector'. Stroker 03-19-2006, 05:50 AM Why not create a gradient based on colour values instead of pos x and y? Why not make it 3d? Why not in Lab which is good for our eyes instead of RGB which is good for monitors?
Here is an early version of one of my personal filters that does just that.
%ffp
Category:"Tech Slop Lab"
Title:"3D Linear Grad"
Author:"JLHalmich"
Copyright: "2005 by JLHalmich"
Organization:"Tech Slop"
Version:"beta 1"
Filename: "TS_3dlineargrad.8bf"
Description: "Linear gradient in 3d Lab space"
About: "!t Plug-in !V\n!C\n!c\n!D"
SupportedModes: LabMode
dialog: size=(395,175)
ctl[CTL_OK]: MODIFY, pos=(315,155)
ctl[CTL_CANCEL]: MODIFY, pos=(355,155)
ctl(0):standard,"L",pos=(260,15),range=(0,255),val=25,track
ctl(1):standard,"a",pos=(260,25),range=(-127,127),val=-25,track
ctl(2):standard,"b",pos=(260,35),range=(-128,128),val=-25,track
ctl(3):standard,"L",pos=(260,65),range=(0,255),val=240,track
ctl(4):standard,"a",pos=(260,75),range=(-127,127),val=25,track
ctl(5):standard,"b",pos=(260,85),range=(-127,127),val=25,track
ctl(6):checkbox,"LL against Lightness",pos=(260,105)
ctl(7):groupbox,"Black", pos=(240,5),size=(150,45)
ctl(8):groupbox,"White",pos=(240,55),size=(150,45)
ctl(9):standard,"Blend",pos=(260,120),val=255,track
OnFilterStart:{
setPreviewCursor (32515);
return false;
}
OnCtl(n):{
if (e==FME_RIGHTCLICKED_DOWN && n==CTL_PREVIEW){
int x,y,ll,aa,bb;
x = getPreviewCoordX();//*scaleFactor;
y = getPreviewCoordY();//*scaleFactor;
ll=src(x,y,0);
aa=src(x,y,1)-128;
bb=src(x,y,2)-128;
if(getAsyncKeyState(VK_CONTROL)>=0){
// white
setCtlVal(3,ll);
setCtlVal(4,aa);
setCtlVal(5,bb);
} else {
// black
setCtlVal(0,ll);
setCtlVal(1,aa);
setCtlVal(2,bb);
}
doAction(CA_PREVIEW);
} // end right click
return false;
} // on ctl
ForEveryTile:{
int x,y,ll,aa,bb,c,a;
int final;
//float pi = 3.14159;
// p1 with normalization
int p1x,p1y,p1z;
float p1dist;
float p1nx,p1ny,p1nz;
//p2 without normalization
int p2x,p2y,p2z;
float p2dist;
// theta junk
float pretheta,theta;
float adj;
// can pre-calculate some junk before the loop
// do p1 with offset
p1x=ctl(3)-ctl(0);
p1y=ctl(4)-ctl(1);
p1z=ctl(5)-ctl(2);
// get length of p1
p1dist = sqrt( (float)p1x*p1x + p1y*p1y + p1z*p1z);
//normalize p1
p1nx=(float)p1x/p1dist;
p1ny=(float)p1y/p1dist;
p1nz=(float)p1z/p1dist;
for (y=y_start; y<y_end; y++){
//if(updateProgress(y,y_end)) abort();
for (x=x_start; x<x_end; x++){
c = srcp (x,y);
//Explode it into the color values
ll = Rval(c); //c & 0xff;
aa = Gval(c)-128; //c >> 8 & 0xff;
bb = Bval(c)-128; //c >> 16 & 0xff;
//if (Z>3) a = Aval(c); //c >> 24 & 0xff;
// p2
p2x=ll-ctl(0);
p2y=aa-ctl(1);
p2z=bb-ctl(2);
p2dist = sqrt( (float)p2x*p2x + p2y*p2y + p2z*p2z);
// get angle, aka theta
// don't forget: float and radians
// p1 * p2 = |p1| * |p2| * cos(angle)
// cos(angle)= p1 * p2 / |p1| * |p2|
pretheta=(p1x*p2x + p1y*p2y + p1z*p2z) / (p1dist*p2dist);
theta=acos(pretheta);
if(theta==0){ // theta hack, seems around 99.99%
final=(p2dist/p1dist)*255;
}else{
// now solve to adj
adj=fcos(theta)*p2dist;
final=(adj/p1dist)*255;
}
// write the values back
if(doingProxy==false){
if(ctl(6)==1){final=(final-ll)/2+128;}
pset(x,y,0,final);
pset(x,y,1,128);
pset(x,y,2,128);
}else{
ll=blend(ll,final,0,0,ctl(9));
aa=blend(aa+128,128,0,0,ctl(9));
bb=blend(bb+128,128,0,0,ctl(9));
pset(x,y,0,ll);
pset(x,y,1,aa);
pset(x,y,2,bb);
}
}} // x y
return true;
} // for every pixel
With that code, I can create dichotomy between two colour values in 3d Lab space.
I originally wanted this for increasing contrast in fleshtones. As I got to playing with it, I was rather surprised at how well it works with a variety of photographs. For example, helping with desaturating photographs of sunsets with tricky hues.
Oh, I almost forgot. That code has some right-click functionality. Right click Preview to sample White. Ctrl + right click Preview to sample Black. Two other optional do-hickies which aren't too hard to figure out. Stroker 03-19-2006, 06:15 AM Here's one to ponder. Cameraken 03-19-2006, 07:10 PM The Rabbit hole gets deeper
Phew. You weren’t joking
There is some good stuff at the X-zone.
But I found this a little easier. Thanks to Philip Harrison
http://www.netsoc.tcd.ie/~jgilbert/maths_site/applets/vectors/the_dot_product.html
When LW < LB the image goes negative (that’s understandable)
When LW > 200(approx) the image starts to get darker???
Right Click on Preview to sample White
Ctrl + right click Preview to sample Black
Alt + right click Preview seems to give threshold??
Sorry I can’t find another, I was expecting Mid Tones. And I can’t read the code
Oh dear. I’ve so much to learn.
Re the HSB thread.
I did read the follow up Addendums. It was good of you to add that. I don’t think many would have noticed
Vectors made easy
http://www.bbc.co.uk/schools/gcsebitesize/maths/shapeih/transformationshrev2.shtml
http://www.euclideanspace.com/maths/algebra/vectors/
Ken. (AKA March Hare) Stroker 03-22-2006, 01:03 AM Vectors and things are cool, but can be hard to wrap around your head. I'm glad you found a link that makes more sense to you.
Visualize what's going on?
Imagine a sphere. This is the Lab sphere. In this sphere, pick two points. Between these two points in a vector and even a normal. If you can imagine it, visualize two planes in the sphere. Each of these planes are parallel to each other and will never touch each other. The two previously defined points are on these planes.
- between the planes, points closer to one plane will darker
- between the planes, points closer to the other plane will be brighter
- points not between the planes will be either black or white
It is a truly 3d gradient.
In the last code,
- right click sets the white point
- ctrl + right click sets the black point
However, alt + click will behave as right click and that probably explains the threshhold effect you are seeing. This is because the two points are rather close together. If you use Levels to set black and white really close together, what do you get? A threshhold effect.
Another little thing that I truly adore is Linear Light. Over 128 goes in one direction and less than 128 goes in the opposite direction. We can actually use the 3d gradient to push values away from each other in a linear 3d fashion. Use the vector/normal as a direction and the gradient as a magnitude. Using this method, it becomes almost trivial to boost contrast in subtle shades and things. Need a little more contrast between slightly red cheeks and slightly yellow jowel? Not a problem.
Early beta of just such a thing:
%ffp
SupportedModes: LabMode
dialog: size=(395,175)
ctl[CTL_OK]: MODIFY, pos=(315,155)
ctl[CTL_CANCEL]: MODIFY, pos=(355,155)
ctl(0):standard,"L",pos=(260,15),range=(0,255),val=25,track
ctl(1):standard,"a",pos=(260,25),range=(-127,127),val=-25,track
ctl(2):standard,"b",pos=(260,35),range=(-128,128),val=-25,track
ctl(3):standard,"L",pos=(260,65),range=(0,255),val=240,track
ctl(4):standard,"a",pos=(260,75),range=(-127,127),val=25,track
ctl(5):standard,"b",pos=(260,85),range=(-127,127),val=25,track
//ctl(6):checkbox,"LL against Lightness",pos=(260,105)
ctl(7):groupbox,"Black", pos=(240,5),size=(150,45)
ctl(8):groupbox,"White",pos=(240,55),size=(150,45)
//ctl(9):standard,"Offset",pos=(260,120),range=(-100,100),val=0,track
ctl(10):standard,"Expand",pos=(270,130),range=(-200,200),val=50,track
OnFilterStart:{
setPreviewCursor (32515);
return false;
}
OnCtl(n):{
if (e==FME_RIGHTCLICKED_DOWN && n==CTL_PREVIEW){
int x,y,ll,aa,bb;
x = getPreviewCoordX();//*scaleFactor;
y = getPreviewCoordY();//*scaleFactor;
ll=src(x,y,0);
aa=src(x,y,1)-128;
bb=src(x,y,2)-128;
if(getAsyncKeyState(VK_CONTROL)>=0){
// white
setCtlVal(3,ll);
setCtlVal(4,aa);
setCtlVal(5,bb);
} else {
// black
setCtlVal(0,ll);
setCtlVal(1,aa);
setCtlVal(2,bb);
}
doAction(CA_PREVIEW);
} // end right click
return false;
} // on ctl
ForEveryTile:{
int x,y,ll,aa,bb,c,a;
int final;
//float pi = 3.14159;
// p1 with normalization
int p1x,p1y,p1z;
float p1dist;
float p1nx,p1ny,p1nz;
//p2 without normalization
int p2x,p2y,p2z;
float p2dist;
// theta junk
float pretheta,theta;
float adj;
// can pre-calculate some junk before the loop
// do p1 with offset
p1x=ctl(3)-ctl(0);
p1y=ctl(4)-ctl(1);
p1z=ctl(5)-ctl(2);
// get length of p1
p1dist = sqrt( (float)p1x*p1x + p1y*p1y + p1z*p1z);
//normalize p1
p1nx=(float)p1x/p1dist;
p1ny=(float)p1y/p1dist;
p1nz=(float)p1z/p1dist;
for (y=y_start; y<y_end; y++){
//if(updateProgress(y,y_end)) abort();
for (x=x_start; x<x_end; x++){
c = srcp (x,y);
//Explode it into the color values
ll = Rval(c); //c & 0xff;
aa = Gval(c)-128; //c >> 8 & 0xff;
bb = Bval(c)-128; //c >> 16 & 0xff;
//if (Z>3) a = Aval(c); //c >> 24 & 0xff;
// p2
p2x=ll-ctl(0);
p2y=aa-ctl(1);
p2z=bb-ctl(2);
p2dist = sqrt( (float)p2x*p2x + p2y*p2y + p2z*p2z);
// get angle, aka theta
pretheta=(p1x*p2x + p1y*p2y + p1z*p2z) / (p1dist*p2dist);
// don't forget: float + radians
theta=acos(pretheta);
// solve for adj using theta
// conert to linear light form using *100-50
if(theta==0){ // theta hack, seems around 99.99%
final=(p2dist/p1dist)*100-50;
}else{
// now solve to adj
adj=fcos(theta)*p2dist;
final=(adj/p1dist)*100-50;
}
// add it all up
ll=ll+p1nx*final*ctl(10)/100;
aa=aa+p1ny*final*ctl(10)/100;
bb=bb+p1nz*final*ctl(10)/100;
pset(x,y,0,ll);
pset(x,y,1,aa+128);
pset(x,y,2,bb+128);
}} // x y
return true;
} // for every pixel
I've been playing with that code for quite some time and it never ceases to amaze me.
Now, masking using trig in the Lab sphere is a lot of fun - doesn't have to be a point-to-point black-to-white gradient. I'll touch on this later.
(Roland, I got your message finally. I'll get back to you soon.) Stroker 03-24-2006, 03:51 AM Meh. Getting sucked into other things. Some quick touchies from Uncle Stroker.
- Used Pythagoras to get distance from a point. Even did some pushing around.
- Used vectors/normals to get distance along a vector. Again, some pushing around.
To get the distance along the vector, used trig one way. However, you can get the distance from a vector using trig the other way. Instead of solving for adj, just solve for opp and go from there. Using this method, you can actually bore holes in the Lab sphere from one point to another. Instead of dichotomy, you are actually connecting. This is a good way of masking some vector operations.
Another good way to push values around or mask is to use theta. Instead of solving for adj or opp, just stop at theta and use that.
Once you start getting the hang of it, you can do some uber ChOps in 3d space. Do you want shades of orange that fade into magenta that are between the two? Not a problem. Harsh coloured lights causing extremely tinted highlights? Not a problem. That funky over-saturated event horizon? Not a problem.
(The event horizon thing is something that I had been wrestling with for quite some time. It's this funny space that exists between light and dark where the change takes place. I call it event horizon, but terminator might be more appropriate. Using trig in Lab, I have finally found a way to isolate it. So far just an annoying curiosity.)
Been getting sucked into several things. Of note, Hue and Sat in Lab space as Photoshop uses them. Been using theta and rho, but Photoshop cubes the sphere and things get funky. Haven't decided on a rampage yet. Another thing I've been messing with is manipulating Hue sort of like Levels. I've done this in RGB, but it is so much easier in Lab space with FM. It is very nice being able to manipulate tonal range in such a manner.
I haven't forgotten about you, Roland. I'm getting there. Cameraken 03-27-2006, 12:50 PM Thanks Stroker.
The effect of the Linear Light code can be quite subtle.
I am still having difficulty with this whole vector thing.
I am slowly working my way through this.
http://www.netcomuk.co.uk/~jenolive/vecfind.html
Ken. Stroker 03-29-2006, 11:32 AM Oh, my. It appears that I've done it again. I went to MTU for programming and math, and yet I still made a very bad mistake. I don't mind when I make a mistake and keep it to myself, but I have to beat my own butt when I pass my mistakes on to others.
I work in my own little world. I understand things in my world in my own special way. However, I can't always properly express those things to others. That is, I have my own nomenclature and it doesn't always match things outside of my world.
unit vector - vector with unit length of 1
normal - vector that is 90 degrees to another vector in a special way
Through this whole thing I've been using normal to mean unit vector. Shame on me.
I need a classroom with a chaulkboard. Cameraken 03-29-2006, 06:00 PM Thanks Stroker.
Hey, Stroker No worries. I don’t think I’ll ever catch up to you.
Your input to this thread has been absolutely fantastic and I can’t thank you enough for taking the time to do all this for us.
Craig has requested another filter
http://www.retouchpro.com/forums/showthread.php?t=13201
I will try to write this as it does not sound too difficult.
My main concern is that it will need a ‘feather’ to blend in.
I’m not sure of the code for that.
Ken Cameraken 04-06-2006, 02:16 PM Anyone good at maths?
I have plotted some input values (grey) and tabulated the required RGB output values.
As you will see from the graph I have taken 21 samples. There are a few anomalies in there but it’s showing some sort of pattern
The output values of Green are easy (Grey – 15 will give something approx)
The Red values are giving me problems
At low Grey values, Red needs to be double Grey (Red = Grey*2) but at higher values Red equals Grey (Red = Grey)
I could do this with a load of ‘If’ Statements but I’m sure there is an easier way.
Any Ideas?
Ken Stroker 04-06-2006, 02:52 PM red = grey + grey*(255-grey)/128
??? Cameraken 04-07-2006, 05:17 AM Thanks Stroker.
That’s looking pretty good.
The only problem is that input values over 125 are sending the output reds over 255
I guess I could knock these down by a percentage to keep them in range?
I’ll post this code as soon as I get it working.
Ken Cameraken 04-07-2006, 02:02 PM Here it is.
Ken’s colourizing skin Filter.
It may be small but it works great.
The graph in post 145 shows how real skintones look in b&w. And the graph below shows how this code puts the colour back. It adds tonal variations to the skin that would be difficult to achieve with a brush
My sample points come from skin charts and real pictures.
Here is how to use it for a base to colour skin.
Run the code on a b&w or colour photo.
In Photoshop add a hide all mask and paint back the skin (and hair)
Add a Hue/Sat Adjustment and adjust the saturation to taste. (Sat -20 to -50 usually looks good)
This is by no means a finished colorize, but it is a good (and quick) start.
Please let me know what you think. If you find it useful I will package it up for our friends.
NB. All the swatches I used were for European skin. I may add other Nationalities.
Thanks for your help Stroker. I changed 128 to 255 as I found it a better fit overall.
Ken.
%ffp
Category :"Ken"
Title :"Ken's Skin Colorizer"
Copyright :"Ken ©2006"
Author :"CameraKen"
Filename :"Ken's Skin Colorizer.8bf"
Description :"European Skin Colorizer"
Version :"1.0"
Dialog :"CameraKen's European Skin Colorizer"
supportedmodes:RGBMode
ForEveryTile:{
int r,g,b,Grey, rv,gv,bv;
for (y=y_start; y<y_end; y++){
if(updateProgress(y,y_end)) abort();
for (x=x_start; x<x_end; x++){
//(Grey=src(x,y,0)+src(x,y,1)+src(x,y,2))/3;
Grey=(src(x,y,0)*0.30+src(x,y,1)*0.59+src(x,y,2)*0.11);
rv=Grey+Grey*(255-Grey)/255;
gv=Grey -15;
bv=Grey-Grey*(255-Grey)/255;
pset(x,y,0, rv );
pset(x,y,1, gv );
pset(x,y,2, bv );
}}
return true;
} Stroker 04-09-2006, 06:53 AM Rock on, Ken. Take the idea a bit further and have different data sets for different sets of skin tones. Snapper might be a good idea. Great. Now I'm going to be messing with this idea.
Back when I started messing with vectors in Lab mode, I started using a technique that I call Spherify. Basically take a point from the center and then shoot it onto the outside of the Lab sphere.
p1 = center of the sphere
p2 = given pixel values
get the vector
convert vector to unit vector
final values = unit vector * 127
Interesting technique. Can show you a few things about an image with a glance. But it does take a bit of getting used to.
As I started looking at the final Spherify images more and more often, I started to see another data set that I wanted. I eventually took Spherify and ChOpped it into Event Horizon.
This is kind of a weird way of dividing a photograph into mids, highs, and lows. A bizarre mix of Lightness and saturation/rho.
When you run the code, you will be left with a mess. You can ignore the mess because the good stuff is in the individual channels.
L = mids
a = highs
b = lows
These channels are an interesting place to work with contrast, saturation, and things.
%ffp
Category:"Tech Slop Lab"
Title:"Event Horizon"
Author:"JLHalmich"
Copyright: "2006 by JLHalmich"
Organization:"Tech Slop Lab"
Version:"beta 1"
Filename: "TS_eventhorizon.8bf"
Description: "Lightness and Rho for\nHigh/Mid/Low"
About: "!t Plug-in !V\n!C\n!c\n!D"
supportedmodes: labmode
dialog: size=(240,185)
ctl[CTL_OK]: MODIFY, pos=(160,165)
ctl[CTL_CANCEL]: MODIFY, pos=(200,165)
ForEveryTile:{
int x,y,ll,aa,bb,rho;
float p1dist,p1nx,p1ny,p1nz;
for (y=y_start; y<y_end; y++){
//if(updateProgress(y,y_end)) abort();
for (x=x_start; x<x_end; x++){
// grab values
ll=src(x,y,0)-128;
aa=src(x,y,1)-128;
bb=src(x,y,2)-128;
// get distance
p1dist=sqrt((float)ll*ll + aa*aa + bb*bb);
// unit vector
p1nx=(float)ll/p1dist;
p1ny=(float)aa/p1dist;
p1nz=(float)bb/p1dist;
// spherify a and b, then get rho
// rho*2 for range of 0 to 255
aa=p1ny*127;
bb=p1nz*127;
rho=c2m(aa,bb)*2;
// reset and recalc a and b for high/low
aa=0; bb=0;
if(ll>=0){aa=255-rho;}
if(ll<0){bb=255-rho;}
// output
pset(x,y,0,rho);
pset(x,y,1,aa);
pset(x,y,2,bb);
}}// x, y
return true;
} // for every tile Cameraken 04-09-2006, 05:59 PM Thanks Stroker. Glad you like it.
The graphs are the important bit. Once we have these the rest could be done with curves. But FM is easier.
I’ve tried it on a lot of b&w pictures and it’s worked OK with all of them. The more tones in the b&w the better the colour.
It’s interesting that there is this pattern to the way skintones desaturate. If all colours desaturate slightly differently it may be possible to make good guesses at what the colours were before the picture was made b&w (just a thought) I may try grass, skies, wood, hair etc as well.
I intend to add other skintones and a sat slider and a couple of adjustment to this. I’ll keep playing with it.
Snapper(Gen-A) – I don’t understand? This seems to add grain and alter transparency. Would I not be better altering saturation? Or are you suggesting I use the BMP method of loading a file?
There is another bitmap loader here.
http://groups.yahoo.com/group/FMML/message/3622
Event Horizon
Interesting Stroker. But what can I do with it? Its messed up the picture so adjusting Mids, Highs and Lows would be of no use. Or can you ‘see’ something in the channels that I can’t?
Ken Stroker 04-10-2006, 12:45 AM Yes, I was refering to the bitmap loader code. Each horizontal line in the bitmap can be a different data set of values. It would be like having a bunch of gradient maps or Curves presets in one little bitmap.
The problem you are going to run into is one of variance. How would you handle a peach forehead and rosie cheeks that have the same brightness values? Delve into segmentation? Some post filter painting? While there is a general pattern, variance is key and may not be easy.
Event Horizon - the secrets are held in the individual channels. I'll see about putting something together. However, I'm leaving tomorrow and won't be back for a week or so. Cameraken 04-14-2006, 04:27 AM I improved the code slightly by changing
gv=Grey-15 to gv-gv*(255-gv)/1000
This keeps the lows and mids the same but adds a little more green to the highlights.
I now understand why most use CMYK for skin.
RGB values contain the colour and the luminosity. And that is why my graphs were not uniform.
In CMYK the Black is split out. So it is easier to talk percentages.
SupportedModes: CMYKMODE
test=(c+m+y);
This works OK. But
SupportedModes: CMYKMODE
test=(c+m+y+k);
This does not work. How do I access the Black channel?
There seems to be nothing at FM about this but there is are FM command rgb2cmyk and cmyk2rgb??? But there is nothing in the Wiki.
I have also got some code to convert RGB to CMYK but they talk about ‘Normalising’ the data. Is this just a case of changing 0-255 to 0-1 ?
RGB -> CMYK
Black=minimum(1-Red,1-Green,1-Blue)
Cyan=(1-Red-Black)/(1-Black)
Magenta=(1-Green-Black)/(1-Black)
Yellow=(1-Blue-Black)/(1-Black)
CMYK -> RGB
Red=1-minimum(1,Cyan*(1-Black)+Black)
Green=1-minimum(1,Magenta*(1-Black)+Black)
Blue=1-minimum(1,Yellow*(1-Black)+Black)
C, M, Y, K, R, G, and B have a range of [0;1].
http://www.scarse.org/docs/color_faq.html#rgb
http://www.scarse.org/docs/color_faq.html
Ken Kraellin 04-14-2006, 12:25 PM ken and stroker, et al,
just to show my ignorance on all this, Paint Shop Pro doesnt allow working in cmyk, so, i'm guessing from your line "SupportedModes: CMYKMODE" that this code wouldnt work in psp. true?
craig Stroker 04-14-2006, 02:39 PM I'm mostly back but not quite.
When dealing with CMYK, things are a bit different. For one, CMYK is subtractive for print as oppossed to additive for monitors. Then there is the CMY <> K paradigm. Even though K is a channel, it's not a true channel.
Then there is the gamut and ICC thing, which I'm not very fluent with. I'm not sure how well Photoshop and FM will get along when doing CMYK with FM. Right now I can't say that I recommend using FM for CMYK if print is your thing. I honestly don't know.
%ffp
supportedmodes: cmykmode
ForEveryTile:{
int x,y,k;
for (y=y_start; y<y_end; y++){
//if(updateProgress(y,y_end)) abort();
for (x=x_start; x<x_end; x++){
// grab k
k=src(x,y,3);
pset(x,y,0,255);
pset(x,y,1,255);
pset(x,y,2,255);
pset(x,y,3,k);
}}// x, y
return true;
} // for every tile Cameraken 04-14-2006, 03:03 PM I was thinking more of converting RGB to CMYK to adjust colours (skin is easier in percentage terms) and the convert back to RGB. Or am I wasting my time?
I wrote this but I can’t get it working. It does something but the channels are very dark?
I’m not sure what the problem is? If its my code then I can’t see my error. I’ve found the same formulas at several different sources so they should be OK.
I also tried float but got ‘not yet implemented errors’
Ken.
%ffp
Title :"RGB to CMYK"
Author :"CameraKen"
Dialog :"RGB to CMYK"
supportedmodes:RGBMode
ForEveryTile:{
int Red,Green,Blue,Cyan,Magenta,Yellow,Black,rv,gv,bv;
for (y=y_start; y<y_end; y++){
if(updateProgress(y,y_end)) abort();
for (x=x_start; x<x_end; x++){
//Get to a range of 0 to 1 (Normalise)
rv=(src(x,y,0)/255*100);
gv=(src(x,y,1)/255*100);
bv=(src(x,y,2)/255*100);
// convert to CMY
Black=min(1-rv,1-gv,1-bv);
Cyan=(1-rv-Black)/(1-Black);
Magenta=(1-gv-Black)/(1-Black);
Yellow=(1-bv-Black)/(1-Black);
//Convert CMYK back to RGB
//Red=1-min(1,Cyan*(1-Black)+Black);
//Green=1-min(1,Magenta*(1-Black)+Black);
//Blue=1-min(1,Yellow*(1-Black)+Black);
//CMY have values 0 to 1 so multiply by 255
pset(x,y,0,Cyan*255 );
pset(x,y,1,Magenta*255 );
pset(x,y,2,Yellow*255 );
}}
return true;
} Stroker 04-14-2006, 03:26 PM You got your ranges messed up a little bit. Might as well stick with range 0 to 255.
%ffp
Title :"RGB to CMYK"
Author :"CameraKen"
Dialog :"RGB to CMYK"
ctl(0):standard,"Cyan",range=(-255,255),val=0,track
ctl(1):standard,"Magenta",range=(-255,255),val=0,track
ctl(2):standard,"Yellow",range=(-255,255),val=0,track
supportedmodes:RGBMode
ForEveryTile:{
int Red,Green,Blue,Cyan,Magenta,Yellow,Black;
for (y=y_start; y<y_end; y++){
//if(updateProgress(y,y_end)) abort();
for (x=x_start; x<x_end; x++){
//Get to a range of 0 to 1 (Normalise)
Red=src(x,y,0);
Green=src(x,y,1);
Blue=src(x,y,2);
// convert to CMY
Cyan=255-Red;
Magenta=255-Green;
Yellow=255-Blue;
// not going to bother with Black
// might be +=Black ???
/**
Black=min(Cyan,min(Magenta,Yellow));
Cyan-=Black;
Magenta-=Black;
Yellow-=Black;
**/
// modify
Cyan+=ctl(0);
Magenta+=ctl(1);
Yellow+=ctl(2);
// back to RGB
Red=255-Cyan;
Green=255-Magenta;
Blue=255-Yellow;
//Convert CMYK back to RGB
//Red=1-min(1,Cyan*(1-Black)+Black);
//Green=1-min(1,Magenta*(1-Black)+Black);
//Blue=1-min(1,Yellow*(1-Black)+Black);
//CMY have values 0 to 1 so multiply by 255
pset(x,y,0,Red);
pset(x,y,1,Green);
pset(x,y,2,Blue);
}}
return true;
} Cameraken 04-14-2006, 07:28 PM Thanks Stroker.
I searched for ages to get a RGB to CMYK conversion.
I tried + and – black and +=Black seemed closest although it’s not the same as Photoshop.
Adjusting skin tones in CMYK is easier than RGB because you can work in percentages.
An average Caucasian should be around C-15%-17%; M-32%-38%; Y-53%-55%;
K-0% (ignore the black as it doesn't contribute to colour)
In RGB the Black content is included in the RGB values and makes the correction more difficult (as my graphs showed)
Maybe there is an easy way by using Hue/Sat (with Lum removed) as well. But the only way I know is CMY.
I understand that many now use LAB .”normal" skin B would usually be 5 to 10 points higher than A. Which is another method I will look at.
Ken. Stroker 04-15-2006, 01:17 AM I did some messing around and I'm pretty sure I understand the process.
Here is the code to translate one percentage to another percentage. F in the controls means From, and T means To.
Cyan F = 20
Cyan T = 25
That means take cyan of 20% and make it 25%. The rest will follow. (This is actually very similiar to my Colour2Colour filter.)
* You will have to pardon the percentages being in the range of 0 to 100 instead of 0 to 1. Going to 100 is a simple way of hacking out some of the calculations.
One thing to watch out for is K/black. The formula used is very crude compared to what Photoshop does. I don't know how much of a difference this will make to some of you folks, but seems okay to me because I don't do this kind of thing.
If you understand what's going on, you may want to eventually do it straight-up RGB. After all, the simple formulas used in the code are nothing more than simple inversions. Or not depending on what you are comfortable with.
Clean up it, add features, package it, make lots of money, and sent me 10%.
%ffp
ctl(0):standard,"Cyan F",range=(0,100),val=15,track
ctl(1):standard,"Magenta F",range=(0,100),val=30,track
ctl(2):standard,"Yellow F",range=(0,100),val=50,track
ctl(3):standard,"Cyan T",range=(0,100),val=15,track
ctl(4):standard,"Magenta T",range=(0,100),val=30,track
ctl(5):standard,"Yellow T",range=(0,100),val=50,track
supportedmodes: rgbmode
ForEveryTile:{
int x,y,red,green,blue,range;
int black,cyan,magenta,yellow;
for (y=y_start; y<y_end; y++){
//if(updateProgress(y,y_end)) abort();
for (x=x_start; x<x_end; x++){
// grab values
cyan=255-src(x,y,0);
magenta=255-src(x,y,1);
yellow=255-src(x,y,2);
// get k or black
black=min(cyan,min(magenta,yellow));
// subtract black
// range 0 to 255 ---> range 0 to 100
cyan=(cyan-black)/2.55;
magenta=(magenta-black)/2.55;
yellow=(yellow-black)/2.55;
// no need for this
//range=255-black;
// scale cyan
if(cyan<=ctl(0)){
cyan=scl(cyan,0,ctl(0),0,ctl(3));
} else {
cyan=scl(cyan,ctl(0),100,ctl(3),100);
}
// scale magenta
if(magenta<=ctl(1)){
magenta=scl(magenta,0,ctl(1),0,ctl(4));
} else {
magenta=scl(magenta,ctl(1),100,ctl(4),100);
}
// scale yellow
if(yellow<=ctl(2)){
yellow=scl(yellow,0,ctl(2),0,ctl(5));
} else {
yellow=scl(yellow,ctl(2),100,ctl(5),100);
}
// back to rgb
// range 0 to 100 ---> range 0 to 255
// order of operations, so it's fine
red=255-cyan*2.55-black;
green=255-magenta*2.55-black;
blue=255-yellow*2.55-black;
// output
pset(x,y,0,red);
pset(x,y,1,green);
pset(x,y,2,blue);
}}// x, y
return true;
} // for every tile Cameraken 04-15-2006, 02:05 PM Thanks for the code Stroker. However I am beginning to question this conversion code.
I just loaded a picture with skin of C18%, M56% Y72%.
Yellow and Magenta work fine but Cyan did not.
I am beginning to suspect the conversion.
FM has two built in functions
rgb2cmyk
cmyk2rgb
Harald Heim has given us the internal code of these half way down this page
http://groups.yahoo.com/group/FMML/message/3220
static int fm_rgb2cmyk(int r, int g, int b, int z)
{
int k;
int bitMultiply= (bitDepthMode == 16 ? 128 : 1);
k=255*bitMultiply-max(r,max(g,b));
if (z == 0)
return 255*bitMultiply-r-k;
else if (z == 1)
return 255*bitMultiply-g-k;
else if (z == 2)
return 255*bitMultiply-b-k;
else if (z == 3)
return k;
else
return 0;
}
static int fm_cmyk2rgb(int c, int m, int y, int k, int z)
{
int bitMultiply= (bitDepthMode == 16 ? 128 : 1);
if (z == 0)
return 255*bitMultiply-c-k;
else if (z == 1)
return 255*bitMultiply-m-k;
else if (z == 2)
return 255*bitMultiply-y-k;
else
return 0;
I would really like to get these working for the sake of comparison. Reading the internal code it appears that fm_cmyk2rgb is expecting c,m,y,k but I can’t get it to accept a value for k. Also fm_rgb2cmyk seems to give dark results like I was getting originally.
If we get RGB to CMYK working then my next project will be a
RGBCMYK mono channel mixer.
This would be useful for Photoshop users and especially Paint Shop Pro users who do not have access to CMYK.
If you understand what's going on, you may want to eventually do it straight-up RGB. After all, the simple formulas used in the code are nothing more than simple inversions. Or not depending on what you are comfortable with.
Yes. I agree. But skin and channel mixer are two exceptions I think. When restoring an old B&W photo I always look at All the channels. Very often more detail can be obtained starting with a CMY mix.
Ken Stroker 04-16-2006, 11:50 AM Harald's rgb2cmyk and cmyk2rgb internal code use the exact same method that we've been using. Written differently, but still the same. I messed with the functions and had no problem with k. Hmmm.
One interesting difference:
int bitMultiply= (bitDepthMode == 16 ? 128 : 1);
Do you know what that is? That is 16-bit compatability. If you look closely, you'll find that it means something rather sinister. The plot sickens. dun dun duuunnnnn
The cyan problem you are running into is a logic bomb related to the conversion (you are right). Our code is no where near as sophisticated as Photoshop. Our simpleness has spawned ugly. Can you find it? Can you hack it?
* There is another logic bomb in there. Consider: CMY are percentages, but a percentages of what?
Logic bombs are the good stuffs of programming. Cameraken 04-16-2006, 02:59 PM I started my Channel Mixer which demonstrates the problem better.
The Cyan slider does virtually nothing.
The Magenta and Yellows sliders do something but because CMYK is subtractive it seems to be taking away rather than adding. However invert these and we are back to RGB. So the whole point of doing this is lost.
The main problem seems to be that some RGB has to be present to see the effect of the CMY sliders.
I read in the usergroup that RGB>LAB>CMYK may be better that RGB>CMYK
There is something very wrong here? We need a new formula.
I very often drag a CMYK layer to a RGB layer. So it is possible to display CMYK in RGB space.
Percentages of What? Good question. Never thought about that.
R128, G128, B128 = C52%, M43%, Y43%, K8%
So adding black back would make them all 50%ish. That sounds OK.
R255, G255, B255 = C0%, M0%, Y0%, K0%
R0, G0, B0 = C75%, M68%, Y67%, K90%. Hmmm I see what you mean.
The above figures are taken with the eyedropper in Photoshop.
The figures from this online conversion tool make more sense
http://www.forret.com/tools/color.asp?R=128&G=128&B=128
Ken
%ffp
Title :"RGB and CMYK Channel Mixer"
ctl(0):standard,"Red",range=(0,100),val=30,track
ctl(1):standard,"Green",range=(0,100),val=59,track
ctl(2):standard,"Blue",range=(0,100),val=11,track
ctl(3):standard,"Cyan",range=(0,100),val=0,track
ctl(4):standard,"Magenta",range=(0,100),val=0,track
ctl(5):standard,"Yellow",range=(0,100),val=0,track
ctl(6):standard,"Black",range=(0,100),val=0,track
supportedmodes: rgbmode
ForEveryTile:{
int x,y,red,green,blue,range;
int black,cyan,m |