What defines "dark" and "bright" pixels? I know what it's theoretically meant to do, but I don't see how doing that will make an image look as it does. It seems to just make things lighter or darker, but I guess it's just bringing everything to an equilibrium.
Oh well. I don't know of a script for doing that, perhaps you or I could write one, of course, I need to know the logic behind it first.
I'm not sure exactly how it works, but contrast basically changes adjacent pixels to be further apart in their respective RGB values, or closer together.
I did an experiment in Photoshop, with a graphic that had Red, Orange, Yellow, Green, Blue, Purple, and Red -- with shades in between. Changing the contrast didn't change the core colors at all, but the mixes between did change. When contrast was at +100, all you had were the 6 colors in bars. When at -100, it was an almost perfectly uniform shade of gray all the way across, #808080.
I think the algorithm is complicated. A google search did little to turn up anything useful. You may want to simply use ImageMagick.
Use as an example a grayscale image with 256 levels.
Consider a contrast slider that can go from -128 to +128
at 0, the image is untouched. as you move the slider down towards -128 you remove that many values from the outside ends of the color range, meaning that at a slider position of -50 the actual tonal range expressed in the image is between the grayscal colors of 49 to 206 and a total of 100 different possible tones (shades) No longer can be expresses in their original color and now are their nearest surviving neighbor. this tends to create a increasingly uniform gray image.
Alternately, moving the slider to the positive side, to +50 will remove that many colors from the MIDDLE of the color range, and remove all tones (shades) between -24 through 26. This heightens the difference between dark and light, making a pixel that was previously 127 in value, now to be approximately 77.38 in value. and a pixel that was 129 in value now approximately 207.59 in value.
To do this with colour images what you'll be wanting to do is to separate the colour information in the pixel from its brightness, and then treat the "brightness channel" as if it were a greyscale image; and put the colour information back afterwards. (Trying to do the R, G, B channels separately results in ugly artefacts around edges, because the colour components of the pixels end up going out of sync.)
So you can transform the pixel being worked on out of RGB space and into, say, HSI space - hue, saturation, and intensity. The intensity channel is the one where contrast stretching the like transforms are done.
RGB=>HSI is fairly straightforward. In the following, $R, $G, $B, $H, $S and $I all have their obvious meanings, except for the fact that they're all scaled to the interval [0,1] instead of [0..255].
There are two opportunities for arithemtic exceptions. If the pixel is grey (i.e., $R=$G=$B), then $H will be undefined (what hue is grey, anyway?) - calling it 0 would be good enough. And, if the pixel is black, ($R=$G=$B=0), then $S will also be undefined (it's too dark to see what the saturation is!) - again, 0 is a good enough default.
So after doing the stretching or equalising or whatever operation on the $I channel, you'll be needing to transform the pixel back into RGB space. Because of the use of min() in the definition of $S, there'll need to be a few if() statements.
Note that $H is an angle - it runs from 0 to 2pi radians, and indicates the position of the colour on the usual colour wheel (measured anticlockwise from red). If $H is in the range 0<$H<=2pi/3, (red to green) then
Now the reason I use $r, $g, and $b instead of $R, $G, and $B should be visible in the above three bits of code: $I never got used! We still need to put that in.
There's no doubt quite a bit that can be done to streamline this (pedagogical) code, but I think it gets across the general idea. There are other things that can be done more easily in HSI space than RGB (gamma correction, for example).
I was thinking of a slightly more generalised contrast-stretching function for a bit. Instead of just one slider (and having it centred on 50% grey), have two - where you can shift the lower end of the stretched range independently of the upper. It ended up generalising to the extent that there were four parameters - $old_dark and $old_light are the intensities of the original image between which we want the contrast stretched to, and $new_dark and $new_light are the intensities we want to map them to - intensities in the range [0..$old_dark] are mapped linearly to [0..$new_dark], intensities in the range [$old_light..255] are mapped to [$new_light..255], and intensities in the range [$old_dark..$old_light] are mapped to the range [$new_dark..$new_light].
As well as contrast stretching, one can do other things as well - as an extreme example, $old_dark=0, $old_light=255, $new_light=0, $new_dark=255 would invert the image intensities.