lazarus/lcl/graphutil.pp

864 lines
23 KiB
ObjectPascal

{ $Id$ }
{
/***************************************************************************
graphutil.pp
------------
Graphic utility functions.
***************************************************************************/
*****************************************************************************
This file is part of the Lazarus Component Library (LCL)
See the file COPYING.modifiedLGPL.txt, included in this distribution,
for details about the license.
*****************************************************************************
}
unit GraphUtil;
{$mode objfpc}{$H+}
interface
uses
SysUtils, Types, Math,
// LCL
Graphics, LCLType, LCLIntf,
// LazUtils
GraphType;
function ColorToGray(const AColor: TColor): Byte;
procedure ColorToHLS(const AColor: TColor; out H, L, S: Byte);
procedure RGBtoHLS(const R, G, B: Byte; out H, L, S: Byte);
function HLStoColor(const H, L, S: Byte): TColor;
procedure HLStoRGB(const H, L, S: Byte; out R, G, B: Byte);
// HSV functions are copied from mbColorLib without changes
procedure ColorToHSV(c: TColor; out H, S, V: Double);
function HSVToColor(H, S, V: Double): TColor;
procedure RGBToHSV(R, G, B: Integer; out H, S, V: Double);
procedure HSVtoRGB(H, S, V: Double; out R, G, B: Integer);
procedure RGBtoHSVRange(R, G, B: integer; out H, S, V: integer);
procedure HSVtoRGBRange(H, S, V: Integer; out R, G, B: Integer);
function HSVRangeToColor(H, S, V: Integer): TColor;
function HSVtoRGBTriple(H, S, V: integer): TRGBTriple;
function HSVtoRGBQuad(H, S, V: integer): TRGBQuad;
function GetHValue(Color: TColor): integer;
function GetSValue(Color: TColor): integer;
function GetVValue(Color: TColor): integer;
// specific things:
{ Draw gradient from top to bottom with parabolic color grow }
procedure DrawVerticalGradient(Canvas: TCanvas; ARect: TRect; TopColor, BottomColor: TColor);
{ Draw nice looking window with Title }
procedure DrawGradientWindow(Canvas: TCanvas; WindowRect: TRect; TitleHeight: Integer; BaseColor: TColor);
{ Stretch-draw a bitmap in an anti-aliased way }
procedure AntiAliasedStretchDrawBitmap(SourceBitmap, DestBitmap: TCustomBitmap;
DestWidth, DestHeight: integer);
{ Converts a bitmap to grayscale taking filtering parameters into account. }
procedure BitmapGrayscale(ABitmap: TCustomBitmap; RedFilter, GreenFilter, BlueFilter: Single);
{ Draw arrows }
type TScrollDirection=(sdLeft,sdRight,sdUp,sdDown);
TArrowType = (atSolid, atArrows);
const NiceArrowAngle=45*pi/180;
procedure DrawArrow(Canvas:TCanvas;Direction:TScrollDirection; Location: TPoint; Size: Longint; ArrowType: TArrowType=atSolid);
procedure DrawArrow(Canvas:TCanvas;p1,p2: TPoint; ArrowType: TArrowType=atSolid);
procedure DrawArrow(Canvas:TCanvas;p1,p2: TPoint; ArrowLen: longint; ArrowAngleRad: float=NiceArrowAngle; ArrowType: TArrowType=atSolid);
procedure FloodFill(Canvas: TCanvas; X, Y: Integer; lColor: TColor; FillStyle: TFillStyle);
procedure ScaleImg(AImage: TCustomBitmap; AWidth, AHeight: Integer);
// delphi compatibility
procedure ColorRGBToHLS(clrRGB: COLORREF; var Hue, Luminance, Saturation: Word);
function ColorHLSToRGB(Hue, Luminance, Saturation: Word): TColorRef;
function ColorAdjustLuma(clrRGB: TColor; n: Integer; fScale: BOOL): TColor;
function GetHighLightColor(const Color: TColor; Luminance: Integer = 19): TColor;
function GetShadowColor(const Color: TColor; Luminance: Integer = -50): TColor;
// misc
function NormalizeRect(const R: TRect): TRect;
procedure WaveTo(ADC: HDC; X, Y, R: Integer);
implementation
uses
fpimage, fpcanvas, IntfGraphics, LazCanvas;
//TODO: Check code on endianess
procedure ExtractRGB(RGB: TColorRef; var R, G, B: Byte); inline;
begin
R := RGB and $FF;
G := (RGB shr 8) and $FF;
B := (RGB shr 16) and $FF;
end;
function ColorToGray(const AColor: TColor): Byte;
var
RGB: TColorRef;
begin
if AColor = clNone
then RGB := 0
else RGB := ColorToRGB(AColor);
Result := Trunc(0.222 * (RGB and $FF) + 0.707 * ((RGB shr 8) and $FF) + 0.071 * (RGB shr 16 and $FF));
end;
procedure ColorToHLS(const AColor: TColor; out H, L, S: Byte);
var
R, G, B: Byte;
RGB: TColorRef;
begin
RGB := ColorToRGB(AColor);
ExtractRGB(RGB, R, G, B);
RGBtoHLS(R, G, B, H, L, S);
end;
function HLStoColor(const H, L, S: Byte): TColor;
var
R, G, B: Byte;
begin
HLStoRGB(H, L, S, R, G, B);
Result := R or (G shl 8) or (B shl 16);
end;
procedure RGBtoHLS(const R, G, B: Byte; out H, L, S: Byte);
var aDelta, aMin, aMax: Byte;
begin
aMin := Math.min(Math.min(R, G), B);
aMax := Math.max(Math.max(R, G), B);
aDelta := aMax - aMin;
if aDelta > 0 then
begin
if aMax = B
then H := round(170 + 42.5*(R - G)/aDelta) { 2*255/3; 255/6 }
else if aMax = G
then H := round(85 + 42.5*(B - R)/aDelta) { 255/3 }
else if G >= B
then H := round(42.5*(G - B)/aDelta)
else H := round(255 + 42.5*(G - B)/aDelta);
end;
L := (aMax + aMin) div 2;
if (L = 0) or (aDelta = 0)
then S := 0
else if L <= 127
then S := round(255*aDelta/(aMax + aMin))
else S := round(255*aDelta/(510 - aMax - aMin));
end;
procedure HLSToRGB(const H, L, S: Byte; out R, G, B: Byte);
var hue, chroma, x: Single;
begin
if S > 0 then
begin { color }
hue:=6*H/255;
chroma := S*(1 - abs(0.0078431372549*L - 1)); { 2/255 }
G := trunc(hue);
B := L - round(0.5*chroma);
x := B + chroma*(1 - abs(hue - 1 - G and 254));
case G of
0: begin
R := B + round(chroma);
G := round(x);
end;
1: begin
R := round(x);
G := B + round(chroma);
end;
2: begin
R := B;
G := B + round(chroma);
B := round(x);
end;
3: begin
R := B;
G := round(x);
inc(B, round(chroma));
end;
4: begin
R := round(x);
G := B;
inc(B, round(chroma));
end;
otherwise
R := B + round(chroma);
G := B;
B := round(x);
end;
end else
begin { grey }
R := L;
G := L;
B := L;
end;
end;
procedure DrawArrow(Canvas: TCanvas; Direction: TScrollDirection;
Location: TPoint; Size: Longint; ArrowType: TArrowType);
const ScrollDirectionX:array[TScrollDirection]of longint=(-1,+1,0,0);
ScrollDirectionY:array[TScrollDirection]of longint=(0,0,-1,+1);
begin
DrawArrow(Canvas,Location,
point(ScrollDirectionX[Direction]*size+Location.x,ScrollDirectionY[Direction]*size+Location.y),
max(5,size div 10),
NiceArrowAngle,ArrowType);
end;
procedure DrawArrow(Canvas: TCanvas; p1, p2: TPoint; ArrowType: TArrowType);
begin
DrawArrow(Canvas,p1,p2,round(sqrt(sqr(p1.x-p2.x)+sqr(p1.y-p2.y))/10),NiceArrowAngle,ArrowType);
end;
procedure DrawArrow(Canvas: TCanvas; p1, p2: TPoint; ArrowLen: longint;
ArrowAngleRad: float; ArrowType: TArrowType);
var
LineAngle: float;
sinAngle, cosAngle: float;
ArrowPoint1, ArrowPoint2: TPoint;
begin
LineAngle:=arctan2(p2.y-p1.y,p2.x-p1.x);
SinCos(pi + LineAngle - ArrowAngleRad, sinAngle, cosAngle);
ArrowPoint1.x := round(ArrowLen * cosAngle) + p2.x;
ArrowPoint1.y := round(ArrowLen * sinAngle) + p2.y;
SinCos(pi + LineAngle + ArrowAngleRad, sinAngle, cosAngle);
ArrowPoint2.x := round(ArrowLen * cosAngle) + p2.x;
ArrowPoint2.y := round(ArrowLen * sinAngle) + p2.y;
Canvas.Line(p1,p2);
case ArrowType of
atSolid: begin
canvas.Polygon([ArrowPoint1,p2,ArrowPoint2]);
end;
atArrows: begin
Canvas.LineTo(ArrowPoint1.x,ArrowPoint1.y);
Canvas.Line(p2.x,p2.y,ArrowPoint2.x,ArrowPoint2.y);
end;
end;
end;
type
ByteRA = array [1..1] of byte;
Bytep = ^ByteRA;
LongIntRA = array [1..1] of LongInt;
LongIntp = ^LongIntRA;
procedure FloodFill(Canvas: TCanvas; X, Y: Integer; lColor: TColor;
FillStyle: TFillStyle);
//Written by Chris Rorden
// Very slow, because uses Canvas.Pixels.
//A simple first-in-first-out circular buffer (the queue) for flood-filling contiguous voxels.
//This algorithm avoids stack problems associated simple recursive algorithms
//http://steve.hollasch.net/cgindex/polygons/floodfill.html [^]
const
kFill = 0; //pixels we will want to flood fill
kFillable = 128; //voxels we might flood fill
kUnfillable = 255; //voxels we can not flood fill
var
lWid,lHt,lQSz,lQHead,lQTail: integer;
lQRA: LongIntP;
lMaskRA: ByteP;
procedure IncQra(var lVal, lQSz: integer);//nested inside FloodFill
begin
inc(lVal);
if lVal >= lQSz then
lVal := 1;
end; //nested Proc IncQra
function Pos2XY (lPos: integer): TPoint;
begin
result.X := ((lPos-1) mod lWid)+1; //horizontal position
result.Y := ((lPos-1) div lWid)+1; //vertical position
end; //nested Proc Pos2XY
procedure TestPixel(lPos: integer);
begin
if (lMaskRA^[lPos]=kFillable) then begin
lMaskRA^[lPos] := kFill;
lQra^[lQHead] := lPos;
incQra(lQHead,lQSz);
end;
end; //nested Proc TestPixel
procedure RetirePixel; //nested inside FloodFill
var
lVal: integer;
lXY : TPoint;
begin
lVal := lQra^[lQTail];
lXY := Pos2XY(lVal);
if lXY.Y > 1 then
TestPixel (lVal-lWid);//pixel above
if lXY.Y < lHt then
TestPixel (lVal+lWid);//pixel below
if lXY.X > 1 then
TestPixel (lVal-1); //pixel to left
if lXY.X < lWid then
TestPixel (lVal+1); //pixel to right
incQra(lQTail,lQSz); //done with this pixel
end; //nested proc RetirePixel
var
lTargetColorVal,lDefaultVal: byte;
lX,lY,lPos: integer;
lBrushColor: TColor;
begin //FloodFill
if FillStyle = fsSurface then begin
//fill only target color with brush - bounded by nontarget color.
if Canvas.Pixels[X,Y] <> lColor then exit;
lTargetColorVal := kFillable;
lDefaultVal := kUnfillable;
end else begin //fsBorder
//fill non-target color with brush - bounded by target-color
if Canvas.Pixels[X,Y] = lColor then exit;
lTargetColorVal := kUnfillable;
lDefaultVal := kFillable;
end;
//if (lPt < 1) or (lPt > lMaskSz) or (lMaskP[lPt] <> 128) then exit;
lHt := Canvas.Height;
lWid := Canvas.Width;
lQSz := lHt * lWid;
//Qsz should be more than the most possible simultaneously active pixels
//Worst case scenario is a click at the center of a 3x3 image: all 9 pixels will be active simultaneously
//for larger images, only a tiny fraction of pixels will be active at one instance.
//perhaps lQSz = ((lHt*lWid) div 4) + 32; would be safe and more memory efficient
if (lHt < 1) or (lWid < 1) then exit;
getmem(lQra,lQSz*sizeof(longint)); //very wasteful -
getmem(lMaskRA,lHt*lWid*sizeof(byte));
for lPos := 1 to (lHt*lWid) do
lMaskRA^[lPos] := lDefaultVal; //assume all voxels are non targets
lPos := 0;
// MG: it is very slow to access the whole (!) canvas with pixels
for lY := 0 to (lHt-1) do
for lX := 0 to (lWid-1) do begin
lPos := lPos + 1;
if Canvas.Pixels[lX,lY] = lColor then
lMaskRA^[lPos] := lTargetColorVal;
end;
lQHead := 2;
lQTail := 1;
lQra^[lQTail] := ((Y * lWid)+X+1); //NOTE: both X and Y start from 0 not 1
lMaskRA^[lQra^[lQTail]] := kFill;
RetirePixel;
while lQHead <> lQTail do
RetirePixel;
lBrushColor := Canvas.Brush.Color;
lPos := 0;
for lY := 0 to (lHt-1) do
for lX := 0 to (lWid-1) do begin
lPos := lPos + 1;
if lMaskRA^[lPos] = kFill then
Canvas.Pixels[lX,lY] := lBrushColor;
end;
freemem(lMaskRA);
freemem(lQra);
end;
procedure ScaleImg(AImage: TCustomBitmap; AWidth, AHeight: Integer);
var
srcImg: TLazIntfImage = nil;
destCanvas: TLazCanvas = nil;
begin
if (AImage.Width = AWidth) and (AImage.Height = AHeight) then
exit;
try
// Create the source LazIntfImage
srcImg := AImage.CreateIntfImage;
// Create the destination LazCanvas
destCanvas := TLazCanvas.Create(srcImg);
// Execute the canvas.StretchDraw
destCanvas.StretchDraw(0, 0, AWidth, AHeight, srcImg);
// Reload the stretched image into the CustomBitmap
AImage.LoadFromIntfImage(srcImg);
AImage.SetSize(AWidth, AHeight);
finally
destCanvas.Free;
srcImg.Free;
end;
end;
procedure ColorRGBToHLS(clrRGB: COLORREF; var Hue, Luminance, Saturation: Word);
var
H, L, S: Byte;
begin
ColorToHLS(clrRGB, H, L, S);
Hue := H;
Luminance := L;
Saturation := S;
end;
function ColorHLSToRGB(Hue, Luminance, Saturation: Word): TColorRef;
begin
Result := HLStoColor(Hue, Luminance, Saturation);
end;
function ColorAdjustLuma(clrRGB: TColor; n: Integer; fScale: BOOL): TColor;
var
H, L, S: Byte;
begin
// what is fScale?
ColorToHLS(clrRGB, H, L, S);
Result := HLStoColor(H, L + n, S);
end;
function GetHighLightColor(const Color: TColor; Luminance: Integer): TColor;
begin
Result := ColorAdjustLuma(Color, Luminance, False);
end;
function GetShadowColor(const Color: TColor; Luminance: Integer): TColor;
begin
Result := ColorAdjustLuma(Color, Luminance, False);
end;
function NormalizeRect(const R: TRect): TRect;
begin
if R.Left <= R.Right then
begin
Result.Left := R.Left;
Result.Right := R.Right;
end
else
begin
Result.Left := R.Right;
Result.Right := R.Left;
end;
if R.Top <= R.Bottom then
begin
Result.Top := R.Top;
Result.Bottom := R.Bottom;
end
else
begin
Result.Top := R.Bottom;
Result.Bottom := R.Top;
end;
end;
procedure DrawVerticalGradient(Canvas: TCanvas; ARect: TRect; TopColor, BottomColor: TColor);
var
y, h: Integer;
r1, g1, b1: byte;
r2, g2, b2: byte;
dr, dg, db: integer;
function GetColor(pos, total: integer): TColor;
function GetComponent(c1, dc: integer): integer;
begin
Result := Round(dc / sqr(total) * sqr(pos) + c1);
end;
begin
Result :=
GetComponent(r1, dr) or
(GetComponent(g1, dg) shl 8) or
(GetComponent(b1, db) shl 16);
end;
begin
ExtractRGB(ColorToRGB(TopColor), r1, g1, b1);
ExtractRGB(ColorToRGB(BottomColor), r2, g2, b2);
dr := r2 - r1;
dg := g2 - g1;
db := b2 - b1;
h := ARect.Bottom - ARect.Top;
for y := ARect.Top to ARect.Bottom do
begin
Canvas.Pen.Color := GetColor(y - ARect.Top, h);
Canvas.Line(ARect.Left, y, ARect.Right, y);
end;
end;
procedure DrawGradientWindow(Canvas: TCanvas; WindowRect: TRect; TitleHeight: Integer; BaseColor: TColor);
begin
Canvas.Brush.Color := BaseColor;
Canvas.FrameRect(WindowRect);
InflateRect(WindowRect, -1, -1);
WindowRect.Bottom := WindowRect.Top + TitleHeight;
DrawVerticalGradient(Canvas, WindowRect, GetHighLightColor(BaseColor), GetShadowColor(BaseColor));
end;
procedure AntiAliasedStretchDrawBitmap(SourceBitmap, DestBitmap: TCustomBitmap;
DestWidth, DestHeight: integer);
var
DestIntfImage, SourceIntfImage: TLazIntfImage;
DestCanvas: TLazCanvas;
begin
DestIntfImage := TLazIntfImage.Create(0, 0);
try
DestIntfImage.LoadFromBitmap(DestBitmap.Handle, DestBitmap.MaskHandle);
DestCanvas := TLazCanvas.Create(DestIntfImage);
try
SourceIntfImage := SourceBitmap.CreateIntfImage;
try
DestCanvas.Interpolation := TFPBaseInterpolation.Create;
try
DestCanvas.StretchDraw(0, 0, DestWidth, DestHeight, SourceIntfImage);
DestBitmap.LoadFromIntfImage(DestIntfImage);
finally
DestCanvas.Interpolation.Free;
end;
finally
SourceIntfImage.Free;
end;
finally
DestCanvas.Free;
end;
finally
DestIntfImage.Free;
end;
end;
{ Converts a bitmap to grayscale by taking filtering parameters into account
Examples:
BitmapGrayscale(Image1.Picture.Bitmap, 0.30, 0.59, 0.11); // Neutral filter
BitmapGrayscale(Image1.Picture.Bitmap, 1.00, 0.00, 0.00); // Red filter
BitmapGrayscale(Image1.Picture.Bitmap, 0.00, 1.00, 0.00); // Green filter
BitmapGrayscale(Image1.Picture.Bitmap, 0.00, 0.00, 1.00); // Blue filter
BitmapGrayscale(Image1.Picture.Bitmap, 0.00, 0.50, 0.50); // Cyan filter
BitmapGrayscale(Image1.Picture.Bitmap, 0.50, 0.00, 0.50); // Magenta filter
BitmapGrayscale(Image1.Picture.Bitmap, 0.50, 0.50, 0.00); // Yellow filter
}
procedure BitmapGrayscale(ABitmap: TCustomBitmap; RedFilter, GreenFilter, BlueFilter: Single);
var
IntfImg: TLazIntfImage = nil;
x, y: Integer;
TempColor: TFPColor;
Gray: Word;
sum: Single;
begin
// Normalize filter factors to avoid word overflow.
sum := RedFilter + GreenFilter + BlueFilter;
if sum = 0.0 then
exit;
RedFilter := RedFilter / sum;
GreenFilter := GreenFilter / sum;
BlueFilter := BlueFilter / sum;
IntfImg := ABitmap.CreateIntfImage;
try
IntfImg.BeginUpdate;
try
for y := 0 to IntfImg.Height - 1 do
for x := 0 to IntfImg.Width - 1 do
begin
TempColor := IntfImg.Colors[x, y];
Gray := word(Round(TempColor.Red * RedFilter + TempColor.Green * GreenFilter + TempColor.Blue * BlueFilter));
TempColor.Red := Gray;
TempColor.Green := Gray;
TempColor.Blue := Gray;
IntfImg.Colors[x, y] := TempColor;
end;
finally
IntfImg.EndUpdate;
end;
ABitmap.LoadFromIntfImage(IntfImg);
finally
IntfImg.Free;
end;
end;
procedure WaveTo(ADC: HDC; X, Y, R: Integer);
var
Direction, Cur: Integer;
PenPos, Dummy: TPoint;
begin
dec(R);
// get the current pos
MoveToEx(ADC, 0, 0, @PenPos);
MoveToEx(ADC, PenPos.X, PenPos.Y, @Dummy);
Direction := 1;
// vertical wave
if PenPos.X = X then
begin
Cur := PenPos.Y;
if Cur < Y then
while (Cur < Y) do
begin
X := X + Direction * R;
LineTo(ADC, X, Cur + R);
Direction := -Direction;
inc(Cur, R);
end
else
while (Cur > Y) do
begin
X := X + Direction * R;
LineTo(ADC, X, Cur - R);
Direction := -Direction;
dec(Cur, R);
end;
end
else
// horizontal wave
begin
Cur := PenPos.X;
if (Cur < X) then
while (Cur < X) do
begin
Y := Y + Direction * R;
LineTo(ADC, Cur + R, Y);
Direction := -Direction;
inc(Cur, R);
end
else
while (Cur > X) do
begin
Y := Y + Direction * R;
LineTo(ADC, Cur - R, Y);
Direction := -Direction;
dec(Cur, R);
end;
end;
end;
function RGBtoRGBTriple(R, G, B: byte): TRGBTriple;
begin
with Result do
begin
rgbtRed := R;
rgbtGreen := G;
rgbtBlue := B;
end
end;
function RGBtoRGBQuad(R, G, B: byte): TRGBQuad;
begin
with Result do
begin
rgbRed := R;
rgbGreen := G;
rgbBlue := B;
rgbReserved := 0;
end
end;
function RGBTripleToColor(RGBTriple: TRGBTriple): TColor;
begin
Result := RGBTriple.rgbtBlue shl 16 + RGBTriple.rgbtGreen shl 8 + RGBTriple.rgbtRed;
end;
{ Assumes R, G, B to be in range 0..255. Calculates H, S, V in range 0..1
From: http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c }
procedure RGBToHSV(R, G, B: Integer; out H, S, V: Double);
var
rr, gg, bb: Double;
cmax, cmin, delta: Double;
begin
rr := R / 255;
gg := G / 255;
bb := B / 255;
cmax := MaxValue([rr, gg, bb]);
cmin := MinValue([rr, gg, bb]);
delta := cmax - cmin;
if delta = 0 then
begin
H := 0;
S := 0;
end else
begin
if cmax = rr then
H := (gg - bb) / delta + IfThen(gg < bb, 6, 0)
else if cmax = gg then
H := (bb - rr) / delta + 2
else if (cmax = bb) then
H := (rr -gg) / delta + 4;
H := H / 6;
S := delta / cmax;
end;
V := cmax;
end;
procedure ColorToHSV(c: TColor; out H, S, V: Double);
begin
RGBToHSV(GetRValue(c), GetGValue(c), GetBValue(c), H, S, V);
end;
{ Assumes H, S, V in the range 0..1 and calculates the R, G, B values which are
returned to be in the range 0..255.
From: http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
}
procedure HSVtoRGB(H, S, V: Double; out R, G, B: Integer);
var
i: Integer;
f: Double;
p, q, t: Double;
procedure MakeRgb(rr, gg, bb: Double);
begin
R := Round(rr * 255);
G := Round(gg * 255);
B := Round(bb * 255);
end;
begin
i := floor(H * 6);
f := H * 6 - i;
p := V * (1 - S);
q := V * (1 - f*S);
t := V * (1 - (1 - f) * S);
case i mod 6 of
0: MakeRGB(V, t, p);
1: MakeRGB(q, V, p);
2: MakeRGB(p, V, t);
3: MakeRGB(p, q, V);
4: MakeRGB(t, p, V);
5: MakeRGB(V, p, q);
else MakeRGB(0, 0, 0);
end;
end;
function HSVToColor(H, S, V: Double): TColor;
var
r, g, b: Integer;
begin
HSVtoRGB(H, S, V, r, g, b);
Result := RgbToColor(r, g, b);
end;
procedure RGBToHSVRange(R, G, B: integer; out H, S, V: integer);
var
Delta, Min, H1, S1: double;
begin
Min := MinIntValue([R, G, B]);
V := MaxIntValue([R, G, B]);
Delta := V - Min;
if V = 0.0 then S1 := 0 else S1 := Delta / V;
if S1 = 0.0 then
H1 := 0
else
begin
if R = V then
H1 := 60.0 * (G - B) / Delta
else if G = V then
H1 := 120.0 + 60.0 * (B - R) / Delta
else if B = V then
H1 := 240.0 + 60.0 * (R - G) / Delta;
if H1 < 0.0 then H1 := H1 + 360.0;
end;
h := round(h1);
s := round(s1*255);
end;
procedure HSVtoRGBRange(H, S, V: Integer; out R, G, B: Integer);
var
t: TRGBTriple;
begin
t := HSVtoRGBTriple(H, S, V);
R := t.rgbtRed;
G := t.rgbtGreen;
B := t.rgbtBlue;
end;
function HSVtoRGBTriple(H, S, V: integer): TRGBTriple;
const
divisor: integer = 255*60;
var
f, hTemp, p, q, t, VS: integer;
begin
if H > 360 then H := H - 360;
if H < 0 then H := H + 360;
if s = 0 then
Result := RGBtoRGBTriple(V, V, V)
else
begin
if H = 360 then hTemp := 0 else hTemp := H;
f := hTemp mod 60;
hTemp := hTemp div 60;
VS := V*S;
p := V - VS div 255;
q := V - (VS*f) div divisor;
t := V - (VS*(60 - f)) div divisor;
case hTemp of
0: Result := RGBtoRGBTriple(V, t, p);
1: Result := RGBtoRGBTriple(q, V, p);
2: Result := RGBtoRGBTriple(p, V, t);
3: Result := RGBtoRGBTriple(p, q, V);
4: Result := RGBtoRGBTriple(t, p, V);
5: Result := RGBtoRGBTriple(V, p, q);
else Result := RGBtoRGBTriple(0,0,0)
end;
end;
end;
function HSVtoRGBQuad(H, S, V: integer): TRGBQuad;
const
divisor: integer = 255*60;
var
f, hTemp, p, q, t, VS: integer;
begin
if H > 360 then H := H - 360;
if H < 0 then H := H + 360;
if s = 0 then
Result := RGBtoRGBQuad(V, V, V)
else
begin
if H = 360 then hTemp := 0 else hTemp := H;
f := hTemp mod 60;
hTemp := hTemp div 60;
VS := V*S;
p := V - VS div 255;
q := V - (VS*f) div divisor;
t := V - (VS*(60 - f)) div divisor;
case hTemp of
0: Result := RGBtoRGBQuad(V, t, p);
1: Result := RGBtoRGBQuad(q, V, p);
2: Result := RGBtoRGBQuad(p, V, t);
3: Result := RGBtoRGBQuad(p, q, V);
4: Result := RGBtoRGBQuad(t, p, V);
5: Result := RGBtoRGBQuad(V, p, q);
else Result := RGBtoRGBQuad(0,0,0)
end;
end;
end;
function HSVRangeToColor(H, S, V: integer): TColor;
begin
Result := RGBTripleToColor(HSVtoRGBTriple(H, S, V));
end;
function GetHValue(Color: TColor): integer;
var
s, v: integer;
begin
RGBToHSVRange(GetRValue(Color), GetGValue(Color), GetBValue(Color), Result, s, v);
end;
function GetSValue(Color: TColor): integer;
var
h, v: integer;
begin
RGBToHSVRange(GetRValue(Color), GetGValue(Color), GetBValue(Color), h, Result, v);
end;
function GetVValue(Color: TColor): integer;
var
h, s: integer;
begin
RGBToHSVRange(GetRValue(Color), GetGValue(Color), GetBValue(Color), h, s, Result);
end;
end.