2011. április 24., vasárnap

How to change the colour luminance or brightness

Problem/Question/Abstract:

I am trying to write a routine that paints a box on the screen, but the edges of the box should be either lighter or darker then the color of the box. I want the routine to take a color, say clBlue, then paint the box in blue, and the top area in light blue and the bottom in dark blue, i.e. the same of clYellow, etc.. The brightness would change say 20%-40% each way. Using the Win32 Standard Color dialog, it is possible to set a custom color to say blue, then manipulate a scroll bar to see all the different brightness's (luminosity) of that color. Is it possible to write a routine in Delphi that takes two parameters, a color parameter, and a brightness % change, and returns a new color?

It's possible. The HSL system that Windows uses is imperfect in that different colors having the same L value aren't all of the same perceived brightness. This makes it difficult to do brightness matching. I've put together some code that uses the CIE's L*,a*,b* system to provide a color coordinate system that does a much better job of matching the response of the human visual system.

To use the code, take your original RGB value, then divide each value by 255 so that the resulting numbers range from 0.0 to 1.0. Now feed these values to the RgbToLab function. This converts the RGB coordinates to LAB coordinates, where the first coordinate (L) is scaled from 0 to 100. So now you can modify that L value to change the brightness of the color, then feed the new LAB values to LabToRgb to convert back to RGB. Finally, multiply each of the final result values by 255 and round to the nearest integer.

unit uLabRgb;

interface

type
TVector3 = array[1..3] of Double;

function LabToRgb(Lab: TVector3): TVector3;
function RgbToLab(Rgb: TVector3): TVector3;

implementation

type
TMatrix3 = array[1..3, 1..3] of Double;

const
RgbXyz: TMatrix3 = ((1, 0, 0), (0, 1, 0), (0, 0, 1));
XyzRgb: TMatrix3 = ((1, 0, 0), (0, 1, 0), (0, 0, 1));
{ CCIR recommended values }
PhosphorX: TVector3 = (0.64, 0.30, 0.15);
PhosphorY: TVector3 = (0.33, 0.60, 0.06);
WhitePoint: TVector3 = (0.95, 1.0000, 1.09);
Gamma: Double = 1 / 0.45;

function MultiplyMatrix3ByVector3(const M: TMatrix3; const V: TVector3): TVector3;
var
I: Integer;
J: Integer;
begin
for I := 1 to 3 do
begin
Result[I] := 0.0;
for J := 1 to 3 do
Result[I] := Result[I] + M[I, J] * V[J]
end;
end;

function MultiplyMatrix3ByMatrix3(const M1, M2: TMatrix3): TMatrix3;
var
I: Integer;
J: Integer;
K: Integer;
begin
for I := 1 to 3 do
for J := 1 to 3 do
begin
Result[I, J] := 0.0;
for K := 1 to 3 do
Result[I, J] := Result[I, J] + M1[I, K] * M2[K, J]
end;
end;

function InvertMatrix3(const M: TMatrix3): TMatrix3;
var
I: Integer;
J: Integer;
D: Double;

function Next(I: Integer): Integer;
begin
Result := I + 1;
if Result > 3 then
Result := Result - 3
end;

function Prev(I: Integer): Integer;
begin
Result := I - 1;
if Result < 1 then
Result := Result + 3
end;

begin
D := 0;
for I := 1 to 3 do
D := D + M[1, I] * (M[2, Next(I)] * M[3, Prev(I)] - M[2, Prev(I)] * M[3, Next(I)]);
FillChar(Result, SizeOf(Result), 0);
for I := 1 to 3 do
for J := 1 to 3 do
Result[J, I] := (M[Next(I), Next(J)] * M[Prev(I), Prev(J)] - M[Next(I), Prev(J)] * M[Prev(I), Next(J)]) / D;
end;

function LabToXyz(const Lab: TVector3): TVector3;
var
LL: Double;

function Cube(X: Double): Double;
begin
if X >= (6 / 29) then
Result := X * X * X
else
Result := (108 / 841) * (X - (4 / 29))
end;

begin
LL := (Lab + 16) / 116;
Result := WhitePoint * Cube(LL + Lab / 500);
Result := WhitePoint * Cube(LL);
Result := WhitePoint * Cube(LL - Lab / 200)
end;

function XyzToRgb(const Xyz: TVector3): TVector3;
var
I: Integer;
begin
Result := MultiplyMatrix3ByVector3(XyzRgb, Xyz);
for I := 1 to 3 do
if Result[I] <= 0.0 then
Result[I] := 0
else
Result[I] := Exp(Ln(Result[I]) / Gamma)
end;

function LabToRgb(Lab: TVector3): TVector3;
begin
Result := XyzToRgb(LabToXyz(Lab))
end;

function RgbToXyz(const Rgb: TVector3): TVector3;
var
I: Integer;
begin
Result := Rgb;
for I := 1 to 3 do
if Result[I] <= 0.0 then
Result[I] := 0
else
Result[I] := Exp(Ln(Result[I]) * Gamma);
Result := MultiplyMatrix3ByVector3(RgbXyz, Result)
end;

function XyzToLab(const Xyz: TVector3): TVector3;
var
YY: Double;

function CubeRoot(X: Double): Double;
begin
if X >= (216 / 24389) then
Result := Exp(Ln(X) / 3)
else
Result := (841 / 108) * X + (4 / 29)
end;

begin
YY := CubeRoot(Xyz / WhitePoint);
Result := 116 * YY - 16;
Result := 500 * (CubeRoot(Xyz / WhitePoint) - YY);
Result := 200 * (YY - CubeRoot(Xyz / WhitePoint))
end;

function RgbToLab(Rgb: TVector3): TVector3;
begin
Result := XyzToLab(RgbToXyz(Rgb))
end;

procedure InitTransformationMatrices;
var
I: Integer;
J: Integer;
PhosphorZ: TVector3;
C: TVector3;
CToXyz: TMatrix3;
XyzToC: TMatrix3;
begin
for I := 1 to 3 do
begin
CToXyz[1, I] := PhosphorX[I];
CToXyz[2, I] := PhosphorY[I];
CToXyz[3, I] := 1 - PhosphorX[I] - PhosphorY[I]
end;
XyzToC := InvertMatrix3(CToXyz);
C := MultiplyMatrix3ByVector3(XyzToC, WhitePoint);
for I := 1 to 3 do
for J := 1 to 3 do
RgbXyz[I, J] := CToXyz[I, J] * C[J];
XyzRgb := InvertMatrix3(RgbXyz)
end;

initialization
InitTransformationMatrices;

end.

1 megjegyzés:

1. Thanks! :)