Stroker
05-21-2006, 12:01 PM
Okay, finally getting around to this Shadow/Highlight thing. I've had more than a few requests, so maybe get this ball rolling, eh?
Now, I do understand how S/H works and the ChOps involved, but I don't entirely like it. A little too much reliance on High Pass for my tastes and no control over the masking. Well, there is some control over the masking, but not the kind that I like working with.
The first thing is to come up with a plan of attack. If you were to do it by hand, how would you do it?
The first thing for me is the base mask. Lots of tricks and variations that I use, so I had to boil it down to a few basic steps that I could do in FM. Basically extract Lum, Gauss it, and Levels. Right away that is 3 controls: blur, high point, and low point. Not terribly sophisticated, but I'm mostly happy with this masking process.
Once you have the mask, how to tweak the highs and low? For this, I chose something that I like to call Linear Gamma. It's mot really gamma, but the basic idea is the same. All you really do is take 128 and wiggle it. The scl() function will do for this. If these tweaks get more sophisticated, will move to a weight-based tweak and away from scl().
One thing I would like to point out right now is this:
final=blend(l1,l2,0,0,255-mask);
Notice that the mask value has been inverted right in the blend() function. This is because blend() didn't like the way I had set things up. It was either invert the blend() mask value or invert everything else. So, I took the easy way out.
Currently, this is proof of concept. This is the main reason why output is Lum only. However, this will eventually pose and interesting foible when doing this kind of work. After all, the name of the game is contrast.
%ffp
SupportedModes: RGBMode
//dialog: size=(240,250)
dialog: color=lightgrey
//ctl[CTL_OK]: MODIFY, pos=(160,230)
//ctl[CTL_CANCEL]: MODIFY, pos=(200,230)
ctl(0): "q",Range=(1,512),val=5
ctl(1): "High",range=(0,255),val=192
ctl(2): "Low",range=(0,255),val=64
ctl(3): "High Tweak",range=(10,245),val=128
ctl(4): "Low Tweak",range=(10,245),val=128
ctl(5): checkbox,"Show Mask",val=0
//------------------------------------------------------------------------------------------
// Here comes the filter code
OnFilterstart:{
isTileable=0;
// setZoom(1);
return false;
}
ForEveryTile:
{
int c;
int xw = x_end-x_start;
int yw = y_end-y_start;
int radius = min(ctl(0),xw/2,yw/2);
float q = (float)(radius)/scaleFactor;
float b0 = 1.57825 + 2.44413*q + 1.4281*q*q + 0.422205*q*q*q;
float b1 = 2.44413*q + 2.85619*q*q + 1.26661*q*q*q;
float b2 = -(1.4281*q*q + 1.26661*q*q*q );
float b3 = 0.422205*q*q*q;
float b1_b0 = b1/b0;
float b2_b0 = b2/b0;
float b3_b0 = b3/b0;
float BB = 1.0 - (b1_b0+b2_b0+b3_b0);
float wn,wn1,wn2,wn3;
int r,g,b,lum,thediff;
int mask,l1,l2,final;
//const int startclock = clock();
// temporary constant
z=0;
// z = 0 = luminosity
// z = 1 = guass
// z = 2 = high pass
// will have to rearrange how these are written
allocArray(0,X,Y,0,1);
allocArray(1,X,Y,0,1);
allocArray(2,X,Y,0,1);
// put luminosity into arrray
for(x=x_start;x<x_end;x++){
for(y=y_start;y<y_end;y++){
r=src(x,y,0);
g=src(x,y,1);
b=src(x,y,2);
lum=r*0.30 + g*0.59 + b*0.11;
putArray(0,x,y,0,lum);
}} // end y and x
// copy lum to 1 for gaussing
copyArray(0,1);
// for (z=0; z<Z; z++){ // loop through all channels
//
// forward pass
//
// warmup forward - no write
for (y=y_start; y<y_end; y++){ // loop through all rows
// initialize row
wn1 = (float)(getArray(1,x_start+2*radius,y,0)); // w[n-1]
wn2 = (float)(getArray(1,x_start+2*radius,y,0)); // w[n-2]
wn3 = (float)(getArray(1,x_start+2*radius,y,0)); // w[n-3]
for (x=x_start+2*radius; x>x_start; x--){ // loop through all columns
wn = BB*(float)(getArray(1,x,y,0)) + (b1_b0*wn1+b2_b0*wn2+b3_b0*wn3);
wn3=wn2;
wn2=wn1;
wn1=wn;
} // for x
// real forward pass
for (x=x_start; x<x_end; x++){ // loop through all columns
wn = BB*(float)(getArray(1,x,y,0)) + (b1_b0*wn1+b2_b0*wn2+b3_b0*wn3);
putArray(1,x,y,0, (int)wn );
// update
wn3=wn2;
wn2=wn1;
wn1=wn;
} // for x
//Update progress bar and cancel if ESC key was pressed
//if ( updateProgress(4*z+1,4*Z) ) break;
//
// backward pass
//
// real backward pass
for (x=x_end-1; x>=x_start; x--){ // loop through all columns
wn = BB*(float)(getArray(1,x,y,0)) + (b1_b0*wn1+b2_b0*wn2+b3_b0*wn3);
//hmm.. this seems to be working so far
//pset(x,y,z, (int)wn );
putArray(1,x,y,0,(int)wn);
// update
wn3=wn2;
wn2=wn1;
wn1=wn;
} // for x
} // for y
//Update progress bar and cancel if ESC key was pressed
//if ( updateProgress(4*z+2,4*Z) ) break;
// NOW DO COLUMNS
//
// forward pass
//
// warmup forward pass
for (x=x_start; x<x_end; x++){ // loop through all rows
// initialize row
wn1 = (float)(getArray(1,x,y_start+2*radius,0)); // w[n-1]
wn2 = (float)(getArray(1,x,y_start+2*radius,0)); // w[n-2]
wn3 = (float)(getArray(1,x,y_start+2*radius,0)); // w[n-3]
for (y=y_start+2*radius; y>y_start; y--){ // loop through all columns
wn = BB*(float)(getArray(1,x,y,0)) + (b1_b0*wn1+b2_b0*wn2+b3_b0*wn3);
// update
wn3=wn2;
wn2=wn1;
wn1=wn;
} // for y
// real forward pass
for (y=y_start; y<y_end; y++){ // loop through all columns
wn = BB*(float)(getArray(1,x,y,0)) + (b1_b0*wn1+b2_b0*wn2+b3_b0*wn3);
putArray(1,x,y,0, (int)wn );
// update
wn3=wn2;
wn2=wn1;
wn1=wn;
} // for y
//Update progress bar and cancel if ESC key was pressed
//if ( updateProgress(4*z+3,4*Z) ) break;
//
// backward pass
//
// real backward pass
for (y=y_end-1; y>=y_start; y--){ // loop through all columns
wn = BB*(float)(getArray(1,x,y,0)) + (b1_b0*wn1+b2_b0*wn2+b3_b0*wn3);
putArray(1,x,y,0, (int)wn );
// update
wn3=wn2;
wn2=wn1;
wn1=wn;
} // for y
} // for x
//Update progress bar and cancel if ESC key was pressed
//if ( updateProgress(4*z+4,4*Z) ) break;
// } // for z
//0=lum
//1=gauss, mask
// time to loop the tweaks
for(x=x_start;x<x_end;x++){
for(y=y_start;y<y_end;y++){
// grab values
mask=getArray(1,x,y,0);
l1=getArray(0,x,y,0);
l2=l1;
// tweak the mask
// notice that values are clipped
mask=scl(mask,ctl(2),ctl(1),0,255);
if(mask>255){mask=255;}
if(mask<0){mask=0;}
// scale high
if(l1<128){
l1=scl(l1,0,128,0,ctl(3));
}else{
l1=scl(l1,128,255,ctl(3),255);
}
// scale low
if(l2<128){
l2=scl(l2,0,128,0,ctl(4));
}else{
l2=scl(l2,128,255,ctl(4),255);
}
// final output
if(ctl(5)==0){ // output final luminosity with blending
final=blend(l1,l2,0,0,255-mask);
pset(x,y,0,final);
pset(x,y,1,final);
pset(x,y,2,final);
}else{ // output mask
pset(x,y,0,mask);
pset(x,y,1,mask);
pset(x,y,2,mask);
}
}} // end y and x
//Stop processing and apply the effect
return true;
}
Now, I do understand how S/H works and the ChOps involved, but I don't entirely like it. A little too much reliance on High Pass for my tastes and no control over the masking. Well, there is some control over the masking, but not the kind that I like working with.
The first thing is to come up with a plan of attack. If you were to do it by hand, how would you do it?
The first thing for me is the base mask. Lots of tricks and variations that I use, so I had to boil it down to a few basic steps that I could do in FM. Basically extract Lum, Gauss it, and Levels. Right away that is 3 controls: blur, high point, and low point. Not terribly sophisticated, but I'm mostly happy with this masking process.
Once you have the mask, how to tweak the highs and low? For this, I chose something that I like to call Linear Gamma. It's mot really gamma, but the basic idea is the same. All you really do is take 128 and wiggle it. The scl() function will do for this. If these tweaks get more sophisticated, will move to a weight-based tweak and away from scl().
One thing I would like to point out right now is this:
final=blend(l1,l2,0,0,255-mask);
Notice that the mask value has been inverted right in the blend() function. This is because blend() didn't like the way I had set things up. It was either invert the blend() mask value or invert everything else. So, I took the easy way out.
Currently, this is proof of concept. This is the main reason why output is Lum only. However, this will eventually pose and interesting foible when doing this kind of work. After all, the name of the game is contrast.
%ffp
SupportedModes: RGBMode
//dialog: size=(240,250)
dialog: color=lightgrey
//ctl[CTL_OK]: MODIFY, pos=(160,230)
//ctl[CTL_CANCEL]: MODIFY, pos=(200,230)
ctl(0): "q",Range=(1,512),val=5
ctl(1): "High",range=(0,255),val=192
ctl(2): "Low",range=(0,255),val=64
ctl(3): "High Tweak",range=(10,245),val=128
ctl(4): "Low Tweak",range=(10,245),val=128
ctl(5): checkbox,"Show Mask",val=0
//------------------------------------------------------------------------------------------
// Here comes the filter code
OnFilterstart:{
isTileable=0;
// setZoom(1);
return false;
}
ForEveryTile:
{
int c;
int xw = x_end-x_start;
int yw = y_end-y_start;
int radius = min(ctl(0),xw/2,yw/2);
float q = (float)(radius)/scaleFactor;
float b0 = 1.57825 + 2.44413*q + 1.4281*q*q + 0.422205*q*q*q;
float b1 = 2.44413*q + 2.85619*q*q + 1.26661*q*q*q;
float b2 = -(1.4281*q*q + 1.26661*q*q*q );
float b3 = 0.422205*q*q*q;
float b1_b0 = b1/b0;
float b2_b0 = b2/b0;
float b3_b0 = b3/b0;
float BB = 1.0 - (b1_b0+b2_b0+b3_b0);
float wn,wn1,wn2,wn3;
int r,g,b,lum,thediff;
int mask,l1,l2,final;
//const int startclock = clock();
// temporary constant
z=0;
// z = 0 = luminosity
// z = 1 = guass
// z = 2 = high pass
// will have to rearrange how these are written
allocArray(0,X,Y,0,1);
allocArray(1,X,Y,0,1);
allocArray(2,X,Y,0,1);
// put luminosity into arrray
for(x=x_start;x<x_end;x++){
for(y=y_start;y<y_end;y++){
r=src(x,y,0);
g=src(x,y,1);
b=src(x,y,2);
lum=r*0.30 + g*0.59 + b*0.11;
putArray(0,x,y,0,lum);
}} // end y and x
// copy lum to 1 for gaussing
copyArray(0,1);
// for (z=0; z<Z; z++){ // loop through all channels
//
// forward pass
//
// warmup forward - no write
for (y=y_start; y<y_end; y++){ // loop through all rows
// initialize row
wn1 = (float)(getArray(1,x_start+2*radius,y,0)); // w[n-1]
wn2 = (float)(getArray(1,x_start+2*radius,y,0)); // w[n-2]
wn3 = (float)(getArray(1,x_start+2*radius,y,0)); // w[n-3]
for (x=x_start+2*radius; x>x_start; x--){ // loop through all columns
wn = BB*(float)(getArray(1,x,y,0)) + (b1_b0*wn1+b2_b0*wn2+b3_b0*wn3);
wn3=wn2;
wn2=wn1;
wn1=wn;
} // for x
// real forward pass
for (x=x_start; x<x_end; x++){ // loop through all columns
wn = BB*(float)(getArray(1,x,y,0)) + (b1_b0*wn1+b2_b0*wn2+b3_b0*wn3);
putArray(1,x,y,0, (int)wn );
// update
wn3=wn2;
wn2=wn1;
wn1=wn;
} // for x
//Update progress bar and cancel if ESC key was pressed
//if ( updateProgress(4*z+1,4*Z) ) break;
//
// backward pass
//
// real backward pass
for (x=x_end-1; x>=x_start; x--){ // loop through all columns
wn = BB*(float)(getArray(1,x,y,0)) + (b1_b0*wn1+b2_b0*wn2+b3_b0*wn3);
//hmm.. this seems to be working so far
//pset(x,y,z, (int)wn );
putArray(1,x,y,0,(int)wn);
// update
wn3=wn2;
wn2=wn1;
wn1=wn;
} // for x
} // for y
//Update progress bar and cancel if ESC key was pressed
//if ( updateProgress(4*z+2,4*Z) ) break;
// NOW DO COLUMNS
//
// forward pass
//
// warmup forward pass
for (x=x_start; x<x_end; x++){ // loop through all rows
// initialize row
wn1 = (float)(getArray(1,x,y_start+2*radius,0)); // w[n-1]
wn2 = (float)(getArray(1,x,y_start+2*radius,0)); // w[n-2]
wn3 = (float)(getArray(1,x,y_start+2*radius,0)); // w[n-3]
for (y=y_start+2*radius; y>y_start; y--){ // loop through all columns
wn = BB*(float)(getArray(1,x,y,0)) + (b1_b0*wn1+b2_b0*wn2+b3_b0*wn3);
// update
wn3=wn2;
wn2=wn1;
wn1=wn;
} // for y
// real forward pass
for (y=y_start; y<y_end; y++){ // loop through all columns
wn = BB*(float)(getArray(1,x,y,0)) + (b1_b0*wn1+b2_b0*wn2+b3_b0*wn3);
putArray(1,x,y,0, (int)wn );
// update
wn3=wn2;
wn2=wn1;
wn1=wn;
} // for y
//Update progress bar and cancel if ESC key was pressed
//if ( updateProgress(4*z+3,4*Z) ) break;
//
// backward pass
//
// real backward pass
for (y=y_end-1; y>=y_start; y--){ // loop through all columns
wn = BB*(float)(getArray(1,x,y,0)) + (b1_b0*wn1+b2_b0*wn2+b3_b0*wn3);
putArray(1,x,y,0, (int)wn );
// update
wn3=wn2;
wn2=wn1;
wn1=wn;
} // for y
} // for x
//Update progress bar and cancel if ESC key was pressed
//if ( updateProgress(4*z+4,4*Z) ) break;
// } // for z
//0=lum
//1=gauss, mask
// time to loop the tweaks
for(x=x_start;x<x_end;x++){
for(y=y_start;y<y_end;y++){
// grab values
mask=getArray(1,x,y,0);
l1=getArray(0,x,y,0);
l2=l1;
// tweak the mask
// notice that values are clipped
mask=scl(mask,ctl(2),ctl(1),0,255);
if(mask>255){mask=255;}
if(mask<0){mask=0;}
// scale high
if(l1<128){
l1=scl(l1,0,128,0,ctl(3));
}else{
l1=scl(l1,128,255,ctl(3),255);
}
// scale low
if(l2<128){
l2=scl(l2,0,128,0,ctl(4));
}else{
l2=scl(l2,128,255,ctl(4),255);
}
// final output
if(ctl(5)==0){ // output final luminosity with blending
final=blend(l1,l2,0,0,255-mask);
pset(x,y,0,final);
pset(x,y,1,final);
pset(x,y,2,final);
}else{ // output mask
pset(x,y,0,mask);
pset(x,y,1,mask);
pset(x,y,2,mask);
}
}} // end y and x
//Stop processing and apply the effect
return true;
}