unit SLColorPicker; {$MODE DELPHI} interface uses LCLIntf, LCLType, LMessages, SysUtils, Classes, Controls, Graphics, Forms, mbColorPickerControl; type TSLColorPicker = class(TmbColorPickerControl) private FHue, FSat, FLum: Double; FMaxHue, FMaxSat, FMaxLum: integer; procedure DrawMarker(x, y: integer); function GetHue: Integer; function GetLum: Integer; function GetSat: Integer; procedure SetHue(H: integer); procedure SetLum(L: integer); procedure SetSat(S: integer); procedure SetMaxHue(H: Integer); procedure SetMaxLum(L: Integer); procedure SetMaxSat(S: Integer); procedure UpdateCoords; protected procedure CorrectCoords(var x, y: integer); procedure CreateWnd; override; function GetGradientColor2D(X, Y: Integer): TColor; override; procedure KeyDown(var Key: Word; Shift: TShiftState); override; procedure MouseMove(Shift: TShiftState; X, Y: Integer); override; procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override; procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override; procedure Resize; override; procedure Paint; override; procedure SelectColor(x, y: integer); procedure SetSelectedColor(c: TColor); override; public constructor Create(AOwner: TComponent); override; function GetColorAtPoint(x, y: integer): TColor; override; property ColorUnderCursor; published property Hue: integer read GetHue write SetHue default 0; property Saturation: integer read GetSat write SetSat default 0; property Luminance: integer read GetLum write SetLum default 240; property MaxHue: Integer read FMaxHue write SetMaxHue default 359; property MaxSaturation: Integer read FMaxSat write SetMaxSat default 240; property MaxLuminance: Integer read FMaxLum write SetMaxLum default 240; property SelectedColor default clWhite; property MarkerStyle default msCircle; property OnChange; end; implementation uses Math, ScanLines, RGBHSLUtils, HTMLColors, mbUtils; { TSLColorPicker } constructor TSLColorPicker.Create(AOwner: TComponent); begin inherited; FMaxHue := 359; FMaxSat := 240; FMaxLum := 240; FGradientWidth := FMaxSat + 1; // x --> Saturation FGradientHeight := FMaxLum + 1; // y --> Luminance SetInitialBounds(0, 0, FGradientWidth, FGradientHeight); FSelected := clWhite; RGBToHSL(FSelected, FHue, FSat, FLum); HintFormat := 'S: %hslS L: %l'#13'Hex: %hex'; MarkerStyle := msCircle; end; procedure TSLColorPicker.CorrectCoords(var x, y: integer); begin Clamp(x, 0, Width - 1); Clamp(y, 0, Height - 1); end; procedure TSLColorPicker.CreateWnd; begin inherited; CreateGradient; UpdateCoords; end; procedure TSLColorPicker.DrawMarker(x, y: integer); var c: TColor; begin c := not GetColorAtPoint(x, y); // "not" --> invert color bits InternalDrawMarker(x, y, c); end; function TSLColorPicker.GetColorAtPoint(x, y: integer): TColor; var S, L: Double; begin S := x / (Width - 1); L := 1.0 - y / (Height - 1); Result := HSLToRGB(FHue, S, L); // Result := HSLToRGB(FHue, x/(Width - 1), (Height - 1 - y) / (Height - 1)); if WebSafe then Result := GetWebSafe(Result); end; { This picker has Saturation along the X and Luminance along the Y axis. NOTE: The HSL conversion (HSLtoColor) seems to be wrong but it produces the display seen elsewhere } function TSLColorPicker.GetGradientColor2D(X, Y: Integer): TColor; begin // Result := HSLtoColor(FHue, x / FMaxSat, (FMaxLum - y) / FMaxLum); // wrong formula Result := HSLtoRGB(FHue, x / FMaxSat, (FMaxLum - y) / FMaxLum); // correct, but looks wrong... end; function TSLColorPicker.GetHue: Integer; begin Result := round(FHue * FMaxHue); end; function TSLColorPicker.GetLum: Integer; begin Result := round(FLum * FMaxLum); end; function TSLColorPicker.GetSat: Integer; begin Result := round(FSat * FMaxSat); end; procedure TSLColorPicker.KeyDown(var Key: Word; Shift: TShiftState); var eraseKey: Boolean; delta: Integer; begin eraseKey := true; delta := IfThen(ssCtrl in Shift, 10, 1); case Key of VK_LEFT : SelectColor(mx - delta, my); VK_RIGHT : SelectColor(mx + delta, my); VK_UP : SelectColor(mx, my - delta); VK_DOWN : SelectColor(mx, my + delta); else eraseKey := false; end; { case Key of VK_LEFT: if (mdx - delta >= 0) then begin Dec(mdx, delta); SelectionChanged(mdx, mdy); FManual := true; DoChange; end; VK_RIGHT: if (mdx + delta < Width) then begin Inc(mdx, delta); SelectionChanged(mdx, mdy); FManual := true; DoChange; end; VK_UP: if (mdy - delta >= 0) then begin Dec(mdy, delta); SelectionChanged(mdx, mdy); FManual := true; DoChange; end; VK_DOWN: if (mdy + delta < Height) then begin Inc(mdy, delta); SelectionChanged(mdx, mdy); FManual := true; DoChange; end; else eraseKey := false; end; } if eraseKey then Key := 0; inherited; end; procedure TSLColorPicker.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin inherited; if csDesigning in ComponentState then Exit; if (Button = mbLeft) then SelectColor(X, Y); SetFocus; end; procedure TSLColorPicker.MouseMove(Shift: TShiftState; X, Y: Integer); begin inherited; if csDesigning in ComponentState then Exit; if (ssLeft in Shift) then SelectColor(X, Y); end; procedure TSLColorPicker.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin inherited; if csDesigning in ComponentState then Exit; if (Button = mbLeft)then SelectColor(X, Y); end; procedure TSLColorPicker.Paint; begin Canvas.StretchDraw(ClientRect, FBufferBMP); UpdateCoords; DrawMarker(mx, my); end; procedure TSLColorPicker.Resize; begin inherited; UpdateCoords; end; procedure TSLColorPicker.SelectColor(x, y: integer); var S, L: Double; begin CorrectCoords(x, y); S := x / (Width - 1); L := 1 - y / (Height - 1); if (S = FSat) and (L = FLum) then exit; FSat := S; FLum := L; FSelected := HSLtoRGB(FHue, FSat, FLum); Invalidate; UpdateCoords; DoChange; end; procedure TSLColorPicker.SetHue(H: integer); begin Clamp(H, 0, FMaxHue); if GetHue() <> H then begin FHue := h / FMaxHue; FSelected := HSLtoRGB(FHue, FSat, FLum); CreateGradient; UpdateCoords; Invalidate; DoChange; end; end; procedure TSLColorPicker.SetLum(L: integer); begin Clamp(L, 0, FMaxLum); if GetLum() <> L then begin FLum := L / FMaxLum; FSelected := HSLtoRGB(FHue, FSat, FLum); UpdateCoords; Invalidate; DoChange; end; end; procedure TSLColorPicker.SetMaxHue(H: Integer); begin if H = FMaxHue then exit; FMaxHue := H; CreateGradient; //if FChange and Assigned(OnChange) then OnChange(Self); Invalidate; end; procedure TSLColorPicker.SetMaxLum(L: Integer); begin if L = FMaxLum then exit; FMaxLum := L; FGradientHeight := FMaxLum + 1; CreateGradient; //if FChange and Assigned(OnChange) then OnChange(Self); Invalidate; end; procedure TSLColorPicker.SetMaxSat(S: Integer); begin if S = FMaxSat then exit; FMaxSat := S; FGradientWidth := FMaxSat + 1; CreateGradient; //if FChange and Assigned(OnChange) then OnChange(Self); Invalidate; end; procedure TSLColorPicker.SetSat(S: integer); begin Clamp(S, 0, FMaxSat); if GetSat() <> S then begin FSat := S / FMaxSat; FSelected := HSLtoRGB(FHue, FSat, FLum); UpdateCoords; Invalidate; DoChange; end; end; procedure TSLColorPicker.SetSelectedColor(c: TColor); var H, S, L: Double; needNewGradient: Boolean; begin if WebSafe then c := GetWebSafe(c); if c = GetSelectedColor then exit; RGBToHSL(c, H, S, L); // ColorToHSL(c, H, S, L); needNewGradient := (FHue <> H); FHue := H; FSat := S; FLum := L; FSelected := c; UpdateCoords; if needNewGradient then CreateGradient; Invalidate; DoChange; end; procedure TSLColorPicker.UpdateCoords; begin mx := round(FSat * (Width - 1)); my := round((1.0 - FLum) * (Height - 1)); end; end.