Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Source for simple image manipulation?

167 views
Skip to first unread message

Aleksandar Momcilovic

unread,
Aug 13, 2004, 7:35:55 AM8/13/04
to
I need Delphi (or at least C/C++) source for simple Image manipulation.
What I need is color balance (RGB), brightess, contrast, saturation.
Does anyone know where can I find some?

Aleksandar


Lord Crc

unread,
Aug 14, 2004, 1:08:17 PM8/14/04
to

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.

Aleksandar Momcilovic

unread,
Aug 16, 2004, 4:22:18 AM8/16/04
to
Thanks a lot!!

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...

Lord Crc

unread,
Aug 17, 2004, 2:34:17 PM8/17/04
to
On Mon, 16 Aug 2004 10:22:18 +0200, "Aleksandar Momcilovic"
<sashaATlenco-software.no> wrote:

>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.

Aleksandar Momcilovic

unread,
Aug 18, 2004, 2:59:22 AM8/18/04
to
Thank you a lot!
That really helped a lot, especially for me to grasp a bit color theory and
to investigate and learn more of it.
Now I have two code paths "correct one", which works in diff. color space,
and "fast one", working in RGB with my own interpretation of terms
"contrast" and "color balance".. Fast version works with LUTs and with
dynamic preview all are happy now :).

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 ;)

...


0 new messages