unit SLColorPicker;

{$MODE DELPHI}

interface

uses
  LCLIntf, LCLType, SysUtils, Classes, Controls, Graphics, Forms,
  mbColorConv, mbColorPickerControl;

type
  TSLColorPicker = class(TmbHSLVColorPickerControl)
  private
    FHint: array[TBrightnessMode] of string;
    function GetHint(AMode: TBrightnessMode): String;
    procedure SetHint(AMode: TBrightnessMode; AText: String);
  protected
    procedure CorrectCoords(var x, y: integer);
    procedure CreateWnd; override;
    procedure DrawMarker(x, y: integer);
    function GetGradientColor2D(X, Y: Integer): TColor; override;
    procedure Resize; override;
    procedure Paint; override;
    procedure SelectColor(x, y: integer); override;
    procedure SetBrightnessMode(AMode: TBrightnessMode); override;
    procedure SetMaxLum(L: Integer); override;
    procedure SetMaxSat(S: Integer); override;
    procedure SetMaxVal(V: Integer); override;
    procedure SetRelLum(L: Double); override;
    procedure SetRelSat(S: Double); override;
    procedure SetRelVal(V: Double); override;
    procedure SetSelectedColor(c: TColor); override;
    procedure UpdateCoords;
  public
    constructor Create(AOwner: TComponent); override;
    function GetColorAtPoint(x, y: integer): TColor; override;
    property ColorUnderCursor;
  published
    property Hue default 0;
    property Saturation default 0;
    property Luminance default 255;
    property Value default 255;
    property MaxHue default 360;
    property MaxSaturation default 255;
    property MaxLuminance default 255;
    property MaxValue default 255;
    property SelectedColor default clWhite;
    property MarkerStyle default msCircle;
    property SLHintFormat: String index bmLuminance read GetHint write SetHint;
    property SVHintFormat: String index bmValue read GetHint write SetHint;
    property OnChange;
  end;

implementation

uses
  HTMLColors, mbUtils;

{ TSLColorPicker }

constructor TSLColorPicker.Create(AOwner: TComponent);
begin
  inherited;
  FGradientWidth := FMaxSat + 1;                       // x --> Saturation
  case BrightnessMode of
    bmLuminance : FGradientHeight := FMaxLum + 1;      // y --> Luminance
    bmValue     : FGradientHeight := FMaxVal + 1;      // y --> value
  end;
  SetInitialBounds(0, 0, FGradientWidth, FGradientHeight);
  FHue := 0;
  FSat := 1.0;
  FLum := 1.0;
  FVal := 1.0;
  SLHintFormat := 'S: %hslS L: %l' + LineEnding + 'Hex: %hex';
  SVHintFormat := 'S: %hslS V: %v' + LineEnding + '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, LV: Double;
begin
  S := x / (Width - 1);
  LV := 1.0 - y / (Height - 1);
  Result := HSLVtoColor(FHue, S, LV, LV);
end;

{ This picker has Saturation along the X and Luminance or Value on the Y axis. }
function TSLColorPicker.GetGradientColor2D(X, Y: Integer): TColor;
begin
  Result := HSLVtoColor(FHue, x/FMaxSat, 1.0 - y/FMaxLum, 1.0 - y/FMaxVal);
end;

function TSLColorPicker.GetHint(AMode: TBrightnessMode): String;
begin
  Result := FHint[AMode];
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, LV: Double;
begin
  CorrectCoords(x, y);
  S := x / (Width - 1);
  LV := 1 - y / (Height - 1);

  case BrightnessMode of
    bmLuminance:
      begin
        if (S = FSat) and (LV = FLum) then
          exit;
        FLum := LV;
      end;
    bmValue:
      begin
        if (S = FSat) and (LV = FVal) then
          exit;
        FVal := LV;
      end;
  end;
  FSat := S;
  FSelected := HSLVtoColor(FHue, FSat, FLum, FVal);

  Invalidate;
  UpdateCoords;
  DoChange;
end;

procedure TSLColorPicker.SetBrightnessMode(AMode: TBrightnessMode);
begin
  inherited;
  HintFormat := FHint[AMode];
end;

procedure TSLColorPicker.SetHint(AMode: TBrightnessMode; AText: String);
begin
  FHint[AMode] := AText;
end;

procedure TSLColorPicker.SetMaxLum(L: Integer);
begin
  if L = FMaxLum then
    exit;
  if BrightnessMode = bmLuminance then
    FGradientHeight := L + 1;
  inherited;
end;

procedure TSLColorPicker.SetMaxSat(S: Integer);
begin
  if S = FMaxSat then
    exit;
  FGradientWidth := S + 1;  // inherited will re-create the gradient
  inherited;
end;

procedure TSLColorPicker.SetMaxVal(V: Integer);
begin
  if V = FMaxVal then
    exit;
  if BrightnessMode = bmValue then
    FGradientHeight := V + 1;
  inherited;
end;

procedure TSLColorPicker.SetRelLum(L: Double);
begin
  Clamp(L, 0.0, 1.0);
  if FLum <> L then
  begin
    FLum := L;
    if BrightnessMode = bmLuminance then
    begin
      FSelected := HSLtoColor(FHue, FSat, FLum);
      UpdateCoords;
      Invalidate;
    end;
    DoChange;
  end;
end;

procedure TSLColorPicker.SetRelSat(S: Double);
begin
  Clamp(S, 0.0, 1.0);
  if FSat <> S then
  begin
    FSat := S;
    FSelected := HSLVtoColor(FHue, FSat, FLum, FVal);
    UpdateCoords;
    Invalidate;
    DoChange;
  end;
end;

procedure TSLColorPicker.SetSelectedColor(c: TColor);
var
  H: Double = 0;
  S: Double = 0;
  L: Double = 0;
  V: Double = 0;
  needNewGradient: Boolean;
begin
  if WebSafe then
    c := GetWebSafe(c);
  if c = GetSelectedColor then
    exit;

  ColorToHSLV(c, H, S, L, V);
  needNewGradient := (FHue <> H);
  FHue := H;
  FSat := S;
  case BrightnessMode of
    bmLuminance : FLum := L;
    bmValue     : FVal := V;
  end;
  FSelected := c;
  UpdateCoords;
  if needNewGradient then
    CreateGradient;
  Invalidate;
  DoChange;
end;

procedure TSLColorPicker.SetRelVal(V: Double);
begin
  Clamp(V, 0.0, 1.0);
  if FVal <> V then
  begin
    FVal := V;
    if BrightnessMode = bmValue then
    begin
      FSelected := HSVtoColor(FHue, FSat, FVal);
      UpdateCoords;
      Invalidate;
    end;
    DoChange;
  end;
end;

procedure TSLColorPicker.UpdateCoords;
begin
  mx := round(FSat * (Width - 1));
  case BrightnessMode of
    bmLuminance : my := round((1.0 - FLum) * (Height - 1));
    bmValue     : my := round((1.0 - FVal) * (Height - 1));
  end;
end;


end.