unit HSColorPicker;

{$IFDEF FPC}
  {$MODE DELPHI}
{$ENDIF}

interface

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

type

  { THSColorPicker }

  THSColorPicker = class(TmbHSLVColorPickerControl)
  private
    FLumDisp, FValDisp: Double;  // Lum and Value used for display
  protected
    procedure CreateWnd; override;
    procedure DrawMarker(x, y: integer);
    function GetGradientColor2D(x, y: Integer): TColor; override;
    function GetSelectedColor: TColor; override;
    procedure Paint; override;
    function PredictColor: TColor;
    procedure Resize; override;
    procedure SelectColor(x, y: Integer); override;
    procedure SetMaxHue(H: Integer); override;
    procedure SetMaxSat(S: Integer); override;
    procedure SetRelHue(H: Double); override;
    procedure SetRelSat(S: Double); override;
    procedure SetSelectedColor(c: TColor); override;
    procedure UpdateCoords;
  public
    constructor Create(AOwner: TComponent); override;
    function GetColorAtPoint(x, y: Integer): TColor; override;
  published
    property SelectedColor default clRed;
    property Hue default 0;
    property Saturation default 255;
    property Luminance default 127;
    property Value default 255;
    property MaxHue default 360;
    property MaxSaturation default 255;
    property MaxLuminance default 255;
    property MaxValue default 255;
    property MarkerStyle default msCross;
    property OnChange;
  end;


implementation

uses
  Math, mbUtils;

{ THSColorPicker }

constructor THSColorPicker.Create(AOwner: TComponent);
begin
  inherited;
  FGradientWidth := FMaxHue;  // We want to skip the point at 360° --> no +1
  FGradientHeight := FMaxSat + 1;
  SetInitialBounds(0, 0, FGradientWidth, FGradientHeight);
  FHue := 0;
  FSat := 1.0;
  FLum := 0.5;
  FLumDisp := 0.5;
  FVal := 1.0;
  FValDisp := 1.0;
  FSelected := clRed;
  CreateGradient;
  HintFormat := 'H: %h S: %hslS'#13'Hex: %hex';
  MarkerStyle := msCross;
end;

procedure THSColorPicker.CreateWnd;
begin
  inherited;
  CreateGradient;
end;

procedure THSColorPicker.DrawMarker(x, y: integer);
var
  c: TColor;
  dummy: Double = 0;
begin
  CorrectCoords(x, y);
  ColorToHSLV(FSelected, FHue, FSat, dummy, dummy);
  if Focused or (csDesigning in ComponentState) then
    c := clBlack
  else
    case BrightnessMode of
      bmLuminance: c := clWhite;
      bmValue    : c := clGray;
    end;
  InternalDrawMarker(x, y, c);
end;

function THSColorPicker.GetColorAtPoint(x, y: Integer): TColor;
var
  H, S: Double;
begin
  if InRange(x, 0, Width - 1) and InRange(y, 0, Height - 1) then
  begin
    H := x / Width;   // Width = FMaxHue
    S := 1 - y / (Height - 1);
    Result := HSLVtoColor(H, S, FLum, FVal);
  end else
    Result := clNone;
end;

function THSColorPicker.GetGradientColor2D(x, y: Integer): TColor;
var
  H, S: Double;
begin
  H := x / FMaxHue;
  S := 1 - y / FMaxSat;
  Result := HSLVtoColor(H, S, FLumDisp, FValDisp);
end;

function THSColorPicker.GetSelectedColor: TColor;
begin
  Result := HSLVtoColor(FHue, FSat, FLum, FVal);
end;

procedure THSColorPicker.Paint;
begin
  Canvas.StretchDraw(ClientRect, FBufferBmp);
  DrawMarker(mx, my);
end;

function THSColorPicker.PredictColor: TColor;
begin
  Result := GetColorUnderCursor;
end;

procedure THSColorPicker.Resize;
begin
  SetSelectedColor(FSelected);
  inherited;
end;

procedure THSColorPicker.SelectColor(x, y: Integer);
var
  H: Double = 0;
  S: Double = 0;
  L: Double = 0;
  V: Double = 0;
  c: TColor;
begin
  CorrectCoords(x, y);
  mx := x;
  my := y;
  c := GetColorAtPoint(x, y);
  if WebSafe then c := GetWebSafe(c);

  ColorToHSLV(c, H, S, L, V);
  {
  if (H = FHue) and (S = FSat) then
    exit;
   }
  FHue := H;
  FSat := S;
  FSelected := HSLVtoColor(FHue, FSat, FLum, FVal);

  Invalidate;
  DoChange;
end;

procedure THSColorPicker.SetMaxHue(H: Integer);
begin
  if H = FMaxHue then
    exit;
  FGradientWidth := H + 1;
  inherited;
end;

procedure THSColorPicker.SetMaxSat(S: Integer);
begin
  if S = FMaxSat then
    exit;
  FGradientHeight := S + 1;
  inherited;
end;

procedure THSColorPicker.SetRelHue(H: Double);
begin
  Clamp(H, 0, 1 - 1/FMaxHue);  // Don't use H=360°
  if H = FHue then
    exit;

  FHue := H;
  FSelected := GetSelectedColor;
  UpdateCoords;
  Invalidate;
  DoChange;
end;

procedure THSColorPicker.SetRelSat(S: Double);
begin
  Clamp(S, 0.0, 1.0);
  if S = FSat then
    exit;

  FSat := S;
  FSelected := GetSelectedColor;
  UpdateCoords;
  Invalidate;
  DoChange;
end;

// NOTE: In the picker display only the hue and the saturation of the input
// color are used, the luminance is replaced by the preset value of the picker.
// --> The selected color in the üicker display in general is different from the
//     input color.
procedure THSColorPicker.SetSelectedColor(c: TColor);
var
  H: Double = 0;
  S: Double = 0;
  L: Double = 0;
  V: Double = 0;
begin
  if WebSafe then
    c := GetWebSafe(c);

  ColorToHSLV(c, H, S, L, V);
  if (H = FHue) and (S = FSat) then
    exit;

  FSelected := c;
  FHue := H;
  FSat := S;
  case BrightnessMode of
    bmLuminance : FLum := L;
    bmValue     : FVal := V;
  end;

  UpdateCoords;
  Invalidate;
  DoChange;
end;

procedure THSColorPicker.UpdateCoords;
begin
  mx := Round(FHue * Width);
  my := Round((1.0 - FSat) * Height);
  CorrectCoords(mx, my);
end;

end.