Aleksandar
Here's some simple code to change the brightness and contrast.
Changing saturation would require you to convert the RGB values to
another color space (for instance HSL/HSV) and change the saturation
there, then convert it back to RGB.
usage:
begin
RemoveGamma(Image1.Picture.Bitmap);
ChangeBrightnessContrast(Image1.Picture.Bitmap, -5, 5);
ApplyGamma(Image1.Picture.Bitmap);
Image1.Repaint;
end;
here's the unit:
unit GraphicUtils;
interface
uses
Windows, Graphics;
type
// 24 bits per pixel
TPixel24 = packed record
b, g, r: byte;
end;
// 32 bits per pixel
TPixel32 = packed record
case integer of
0: (b, g, r, a: byte);
1: (long: longword);
end;
// define default pixel
// be sure to change both!
type
TPixel = TPixel24;
const
PixelFormat = pf24bit;
type
PPixel = ^TPixel;
TScanline24 = array[0..65535] of TPixel24;
PScanline24 = ^TScanline24;
TScanline32 = array[0..65535] of TPixel32;
PScanline32 = ^TScanline32;
TScanline = array[0..65535] of TPixel;
PScanline = ^TScanline;
// Initializes the gamma tables to the supplied gamma
procedure SetupGammaTables(gamma: double = 2.2);
// Applies gamma correction
procedure ApplyGamma(Bmp: TBitmap);
// Removes gamma correction (makes color space linear)
procedure RemoveGamma(Bmp: TBitmap);
// Changes the brightness and contrast of a bitmap
// Brightness: -255 to 255, 0 = no change
// Contrast: -255 to 255, 0 = no change
procedure ChangeBrightnessContrast(Bmp: TBitmap; Brightness, Contrast:
integer);
implementation
uses
math;
var
gammaTable, invgammaTable: array[0..255] of integer;
function clamp(v: integer): integer;
// clamps the value to 0..255 range
begin
result:= v;
if (v < 0) then
result:= 0;
if (v > 255) then
result:= 255;
end;
function clamp8(v: integer): integer;
// divides value by 256 and clamps result to 0..255 range
begin
result:= v div 256;
if (v < 0) then
result:= 0;
if (v > 255 * 256) then
result:= 255;
end;
procedure ApplyGamma(Bmp: TBitmap);
var
x, y: integer;
p: PPixel;
begin
Bmp.PixelFormat:= PixelFormat;
for y:= 0 to Bmp.Height-1 do
begin
p:= Bmp.ScanLine[y];
for x:= 0 to Bmp.Width-1 do
begin
p^.b:= gammaTable[p^.b];
p^.g:= gammaTable[p^.g];
p^.r:= gammaTable[p^.r];
inc(p);
end;
end;
end;
procedure RemoveGamma(Bmp: TBitmap);
var
x, y: integer;
p: PPixel;
begin
Bmp.PixelFormat:= PixelFormat;
for y:= 0 to Bmp.Height-1 do
begin
p:= Bmp.ScanLine[y];
for x:= 0 to Bmp.Width-1 do
begin
p^.b:= invgammaTable[p^.b];
p^.g:= invgammaTable[p^.g];
p^.r:= invgammaTable[p^.r];
inc(p);
end;
end;
end;
procedure ChangeBrightnessContrast(Bmp: TBitmap; Brightness, Contrast:
integer);
var
x, y: integer;
p: PPixel;
begin
Bmp.PixelFormat:= PixelFormat;
Assert((Brightness >= -255) and (Brightness <= 255), 'Brightness
parameter out of range');
Assert((Contrast >= -255) and (Contrast <= 255), 'Contrast parameter
out of range');
// change contrast range so 0 gets 1 * scaling factor (256)
Contrast:= round(((Contrast / 255) + 1.0) * 256);
for y:= 0 to Bmp.Height-1 do
begin
// get pointer to first pixel in row
p:= Bmp.ScanLine[y];
for x:= 0 to Bmp.Width-1 do
begin
p^.b:= clamp8((p^.b - 128 + Brightness) * Contrast + 128 * 256);
p^.g:= clamp8((p^.g - 128 + Brightness) * Contrast + 128 * 256);
p^.r:= clamp8((p^.r - 128 + Brightness) * Contrast + 128 * 256);
// move pointer to next pixel in row
inc(p);
end;
end;
end;
procedure SetupGammaTables(gamma: double);
var
i: integer;
begin
for i:= 0 to 255 do
begin
gammaTable[i]:= clamp(round(255 * power(i / 255, 1.0 / gamma)));
invgammaTable[i]:= clamp(round(255 * power(i / 255, gamma)));
end;
end;
initialization
SetupGammaTables;
end.
I already managed to implement brightness/contrast/saturation. I am using
TBitmap32, it has a RGB<->HSL color space conversions, so I managed hue as
well. I used similar algorithms.
But I was not satisfied with results. It looked all right to me, but it was
"not the same as Photoshop does", as client requests now. At first it was
great to have those adjustments, now it has to mimic Photoshop behaviour as
well. That's why I hoped that there are some well defined algorithms for
those conversions, some kind of standard, which I didn't know.
Maybe the solution is in adjustments in the linear color space, as you
showed here with "RemoveGamma"?
Do you know an algorithm for color balance, that's the one I miss the most?
Not in the shadows/midtone/highlight manner, just one overall RGB color
balance. Should it be done also in linear space?
Aleksandar
"Lord Crc" <lor...@hotmail.com> wrote in message
news:9ugsh0dhrkrs4eq3i...@4ax.com...
>But I was not satisfied with results. It looked all right to me, but it was
>"not the same as Photoshop does", as client requests now. At first it was
>great to have those adjustments, now it has to mimic Photoshop behaviour as
>well. That's why I hoped that there are some well defined algorithms for
>those conversions, some kind of standard, which I didn't know.
Ah, no, afaik there's no "the true way" of adjusting saturation in an
image, or similar. While surfing the web a bit, I saw that HSL/HSV
should be replaced with CIE-LCH, which is based on CIE-Lab. You can
find a conversion algorithm here
http://www.easyrgb.com/math.php?MATH=M9#text9 (nice site btw :)
CIE-Lab is a more representative color space, and so CIE-LCH is
supposed to be more accurate while beeing more intuative in regards to
saturation and hue. I haven't tried it yet.
>Maybe the solution is in adjustments in the linear color space, as you
>showed here with "RemoveGamma"?
>Do you know an algorithm for color balance, that's the one I miss the most?
>Not in the shadows/midtone/highlight manner, just one overall RGB color
>balance. Should it be done also in linear space?
All image manipulations should be done in linear space.
The Color Balance function in photoshop seems to be doing some
non-linear adjustments of the colors.
I'm by no means educated in the field of color representation and
manipulation, and it's not a trivial thing to get correct. However i
do find it facinating. Here's a routine i just plonked together that
seems to be doing the job (at least a job ;)
procedure ChangeColorBalance(Bmp: TBitmap; Red, Green, Blue: integer;
PreserveLuminosity: boolean);
var
x, y: integer;
p: PPixel;
tp: TPixel;
rlut, glut, blut: array[0..255] of integer;
ln, lo, tl: TColorLab;
trgb: TColorRGB;
ol, nl: integer;
begin
Bmp.PixelFormat:= PixelFormat;
for x:= 0 to 255 do
begin
rlut[x]:= clamp(x + Red);
glut[x]:= clamp(x + Green);
blut[x]:= clamp(x + Blue);
end;
trgb.r:= Red / 255;
trgb.g:= Green / 255;
trgb.b:= Blue / 255;
tl:= XyzToLab(LinearRgbToXyz(trgb));
for y:= 0 to Bmp.Height-1 do
begin
// get pointer to first pixel in row
p:= Bmp.ScanLine[y];
for x:= 0 to Bmp.Width-1 do
begin
if PreserveLuminosity then
begin
lo:= XyzToLab(LinearRgbToXyz(PixelToRgb(p^)));
ln.l:= lo.l;
ln.a:= lo.a + tl.a;
ln.b:= lo.b + tl.b;
p^:= RgbToPixel(XyzToLinearRgb(LabToXyz(ln)));
end
else
begin
p^.b:= blut[p^.b];
p^.g:= glut[p^.g];
p^.r:= rlut[p^.r];
end;
// move pointer to next pixel in row
inc(p);
end;
end;
end;
Here's the unit i made based on the easyrgb web site:
unit ColorSpaces;
interface
type
TColorRGB = record
r, g, b: single;
end;
TColorXYZ = record
x, y, z: single;
end;
TColorLab = record
l, a, b: single;
end;
TColorLch = record
l, c, h: single;
end;
function RgbToXyz(rgb: TColorRGB): TColorXYZ;
function LinearRgbToXyz(rgb: TColorRGB): TColorXYZ;
function XyzToRgb(xyz: TColorXYZ): TColorRGB;
function XyzToLinearRgb(xyz: TColorXYZ): TColorRGB;
function XyzToLab(xyz: TColorXYZ): TColorLab;
function LabToXyz(lab: TColorLab): TColorXYZ;
implementation
uses
math;
const
// Observer = 2°, Illuminant = D65
refX = 95.047;
refY = 100.000;
refZ = 108.883;
refXr = 0.4124;
refXg = 0.3576;
refXb = 0.1805;
refYr = 0.2126;
refYg = 0.7152;
refYb = 0.0722;
refZr = 0.0193;
refZg = 0.1192;
refZb = 0.9505;
function RgbToXyz(rgb: TColorRGB): TColorXYZ;
begin
if ( rgb.r > 0.04045 ) then
rgb.r:= power( ( rgb.r + 0.055 ) / 1.055 , 2.4 )
else
rgb.r:= rgb.r / 12.92;
if ( rgb.g > 0.04045 ) then
rgb.g:= power( ( rgb.g + 0.055 ) / 1.055 , 2.4 )
else
rgb.g:= rgb.g / 12.92;
if ( rgb.b > 0.04045 ) then
rgb.b:= power( ( rgb.b + 0.055 ) / 1.055 , 2.4 )
else
rgb.b:= rgb.b / 12.92;
result:= LinearRgbToXyz(rgb);
end;
function LinearRgbToXyz(rgb: TColorRGB): TColorXYZ;
begin
rgb.r:= rgb.r * 100;
rgb.g:= rgb.g * 100;
rgb.b:= rgb.b * 100;
result.x:= rgb.r * refXr + rgb.g * refXg + rgb.b * refXb;
result.y:= rgb.r * refYr + rgb.g * refYg + rgb.b * refYb;
result.z:= rgb.r * refZr + rgb.g * refZg + rgb.b * refZb;
end;
function XyzToLinearRgb(xyz: TColorXYZ): TColorRGB;
begin
xyz.x:= xyz.x / 100;
xyz.y:= xyz.y / 100;
xyz.z:= xyz.z / 100;
result.r:= xyz.x * 3.2406 + xyz.y * -1.5372 + xyz.z * -0.4986;
result.g:= xyz.x * -0.9689 + xyz.y * 1.8758 + xyz.z * 0.0415;
result.b:= xyz.x * 0.0557 + xyz.y * -0.2040 + xyz.z * 1.0570;
end;
function XyzToRgb(xyz: TColorXYZ): TColorRGB;
begin
result:= XyzToLinearRgb(xyz);
if ( result.r > 0.0031308 ) then
result.r:= 1.055 * power(result.r, ( 1.0 / 2.4 ) ) - 0.055
else
result.r:= 12.92 * result.r;
if ( result.g > 0.0031308 ) then
result.g:= 1.055 * power(result.g, ( 1.0 / 2.4 ) ) - 0.055
else
result.g:= 12.92 * result.g;
if ( result.b > 0.0031308 ) then
result.b:= 1.055 * power(result.b, ( 1.0 / 2.4 ) ) - 0.055
else
result.b:= 12.92 * result.b;
end;
function XyzToLab(xyz: TColorXYZ): TColorLab;
begin
xyz.x:= xyz.x / refX;
xyz.y:= xyz.y / refY;
xyz.z:= xyz.z / refZ;
if ( xyz.x > 0.008856 ) then
xyz.x:= power(xyz.x, 1/3)
else
xyz.x:= ( 7.787 * xyz.x ) + ( 16 / 116 );
if ( xyz.y > 0.008856 ) then
xyz.y:= power(xyz.y, 1/3)
else
xyz.y:= ( 7.787 * xyz.y ) + ( 16 / 116 );
if ( xyz.z > 0.008856 ) then
xyz.z:= power(xyz.z, 1/3)
else
xyz.z:= ( 7.787 * xyz.z ) + ( 16 / 116 );
result.l:= ( 116 * xyz.y ) - 16;
result.a:= 500 * ( xyz.x - xyz.y );
result.b:= 200 * ( xyz.y - xyz.z );
end;
function LabToXyz(lab: TColorLab): TColorXYZ;
function f(v: single): single;
var
v3: single;
begin
v3:= v*v*v;
if v3 > 0.008856 then
result:= v3
else
result:= ( v - 16 / 116 ) / 7.787;
end;
begin
result.y:= (lab.l + 16) / 116;
result.x:= lab.a / 500 + result.y;
result.z:= result.y - lab.b / 200;
result.x:= f(result.x) * refX;
result.y:= f(result.y) * refY;
result.z:= f(result.z) * refZ;
end;
end.
Cheers!
Aleksandar
"Lord Crc" <lor...@hotmail.com> wrote in message
news:9vc4i0d3q5htbnmio...@4ax.com...
....
> I'm by no means educated in the field of color representation and
> manipulation, and it's not a trivial thing to get correct. However i
> do find it facinating. Here's a routine i just plonked together that
> seems to be doing the job (at least a job ;)
...