summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGraeme Geldenhuys <graemeg@gmail.com>2009-09-27 21:48:18 +0200
committerGraeme Geldenhuys <graemeg@gmail.com>2009-09-27 23:09:03 +0200
commiteb41be96437129fda14cd043d8d2fdfb97cc11d4 (patch)
treee5aec603aa10916ed8f5d100e835aa011374a354 /src
parent1dfb899821861c36e0c16938f8a15215e8491d19 (diff)
downloadfpGUI-eb41be96437129fda14cd043d8d2fdfb97cc11d4.tar.xz
ColorWheel and ValueBar implementation including color mapping routines.
These components will allow us to implement a color selection dialog.
Diffstat (limited to 'src')
-rw-r--r--src/gui/fpg_colormapping.pas138
-rw-r--r--src/gui/fpg_colorwheel.pas573
2 files changed, 711 insertions, 0 deletions
diff --git a/src/gui/fpg_colormapping.pas b/src/gui/fpg_colormapping.pas
new file mode 100644
index 00000000..9f486bbf
--- /dev/null
+++ b/src/gui/fpg_colormapping.pas
@@ -0,0 +1,138 @@
+{
+ fpGUI - Free Pascal GUI Library
+
+ Copyright (C) 2006 - 2009 See the file AUTHORS.txt, included in this
+ distribution, for details of the copyright.
+
+ See the file COPYING.modifiedLGPL, included in this distribution,
+ for details about redistributing fpGUI.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ Description:
+ This unit contains some color conversion/mapping functions.
+}
+
+unit fpg_ColorMapping;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+ Classes, fpg_base;
+
+ // IN these functions the following definitions apply:
+ // Hue: 0 - 1535
+ // red = 0 green = 512 blue = 1024
+ // Saturation: 0 - 1
+ // grey (no color) = 0 max color = 1
+ // Value: 0 - 1
+ // black =0 max brightness = 1
+
+procedure RGBToHSV(C: TfpgColor; out Hue: longint; out Saturation, Value: double);
+function HSVToRGB(const H: longint; const S, V: double): TfpgColor;
+
+implementation
+
+uses
+ Math;
+
+procedure RGBToHSV(C: TfpgColor; out Hue: longint; out Saturation, Value: double);
+var
+ r, g, b: longint;
+ hi, lo: longint;
+ d: longint;
+ rgb: TRGBTriple;
+begin
+ rgb := fpgColorToRGBTriple(C);
+ r := rgb.Red;
+ g := rgb.Green;
+ b := rgb.Blue;
+ hi := max(max(r, g), b);
+ lo := min(min(r, g), b);
+ d := hi - lo;
+ Value := hi / 256;
+ if d > 0 then
+ begin
+ if r = hi then
+ Hue := 256 * (g - b) div d
+ else if g = hi then
+ Hue := 512 + 256 * (b - r) div d
+ else
+ Hue := 1024 + 256 * (r - g) div d;
+ if Hue < 0 then
+ Hue := Hue + 1536;
+ end
+ else
+ Hue := 0; // doesn't matter (grey: Sat = 0)
+
+ if hi > 0 then
+ Saturation := d / hi
+ else
+ Saturation := 0; // doesn't matter (black: Val = 0
+end;
+
+function HSVToRGB(const H: longint; const S, V: double): TfpgColor;
+var
+ r, g, b: longint;
+ rgb: TRGBTriple;
+begin
+ if (h < 0) or (h > 1535) or (S < 0) or (S > 1) or (V < 0) or (V > 1) then
+ begin
+ // Invalid value, use black
+ Result := 0;
+ exit;
+ end;
+ case h div 256 of
+ 0:
+ begin
+ r := 255;
+ g := h;
+ b := 0;
+ end;
+ 1:
+ begin
+ r := 511 - h;
+ g := 255;
+ b := 0;
+ end;
+ 2:
+ begin
+ r := 0;
+ g := 255;
+ b := h - 512;
+ end;
+ 3:
+ begin
+ r := 0;
+ g := 1023 - h;
+ b := 255;
+ end;
+ 4:
+ begin
+ r := h - 1024;
+ g := 0;
+ b := 255;
+ end;
+ 5:
+ begin
+ r := 255;
+ g := 0;
+ b := 1535 - h;
+ end;
+ end;
+ r := Trunc(V * (255 - S * (255 - r)));
+ g := Trunc(V * (255 - S * (255 - g)));
+ b := Trunc(V * (255 - S * (255 - b)));
+ rgb.Red := r;
+ rgb.Green := g;
+ rgb.Blue := b;
+ Result := RGBTripleTofpgColor(rgb);
+end;
+
+
+end.
+
diff --git a/src/gui/fpg_colorwheel.pas b/src/gui/fpg_colorwheel.pas
new file mode 100644
index 00000000..103ce35d
--- /dev/null
+++ b/src/gui/fpg_colorwheel.pas
@@ -0,0 +1,573 @@
+{
+ fpGUI - Free Pascal GUI Library
+
+ Copyright (C) 2006 - 2009 See the file AUTHORS.txt, included in this
+ distribution, for details of the copyright.
+
+ See the file COPYING.modifiedLGPL, included in this distribution,
+ for details about redistributing fpGUI.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+ Description:
+ This unit implements color selectors using a ColorWheel and
+ a ValueBar. Color results are in HSV format.
+}
+
+unit fpg_ColorWheel;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+ Classes, fpg_base, fpg_main, fpg_widget;
+
+type
+ // forward declaration
+ TfpgValueBar = class;
+
+ TfpgColorWheel = class(TfpgWidget)
+ protected
+ FValueBar: TfpgValueBar;
+ FHue: longint;
+ FSaturation: double;
+ FMarginWidth: longint;
+ FCursorSize: longint;
+ FWhiteAreaPercent: longint; // 0 to 50 percent of circle radius that is pure white
+ FOnChange: TNotifyEvent;
+ FImage: TfpgImage; // cached colorwheel image
+ procedure HSFromPoint(X, Y: longint; out H: longint; out S: double);
+ procedure DrawCursor;
+ procedure SetMarginWidth(NewWidth: longint);
+ procedure SetCursorSize(NewSize: longint);
+ procedure SetValueBar(AValueBar: TfpgValueBar);
+ procedure SetWhiteAreaPercent(WhiteAreaPercent: longint);
+ procedure Notification(AComponent: TComponent; Operation: TOperation); override;
+ function DrawWidth: longint;
+ function DrawHeight: longint;
+ procedure Change;
+ procedure HandlePaint; override;
+ procedure HandleLMouseDown(x, y: integer; shiftstate: TShiftState); override;
+ procedure HandleMouseMove(x, y: integer; btnstate: word; shiftstate: TShiftState); override;
+ procedure HandleLMouseUp(x, y: integer; shiftstate: TShiftState); override;
+ public
+ constructor Create(AOwner: TComponent); override;
+ destructor Destroy; override;
+ property Hue: longint Read FHue;
+ property Saturation: double Read FSaturation;
+ procedure SetSelectedColor(const NewColor: TfpgColor);
+ published
+ property BackgroundColor;
+ property ValueBar: TfpgValueBar Read FValueBar Write SetValueBar;
+ property MarginWidth: longint Read FMarginWidth Write SetMarginWidth;
+ property CursorSize: longint Read FCursorSize Write SetCursorSize;
+ property WhiteAreaPercent: longint Read FWhiteAreaPercent Write SetWhiteAreaPercent;
+ property OnChange: TNotifyEvent Read FOnChange Write FOnChange;
+ end;
+
+
+ TfpgValueBar = class(TfpgWidget)
+ protected
+ FColorWheel: TfpgColorWheel;
+ FHue: longint;
+ FSaturation: double;
+ FValue: double;
+ FMarginWidth: longint;
+ FCursorHeight: longint;
+ FOnChange: TNotifyEvent;
+ procedure DrawCursor;
+ procedure SetMarginWidth(NewWidth: longint);
+ procedure SetValue(Value: double);
+ procedure SetCursorHeight(CursorHeight: longint);
+ function GetSelectedColor: TfpgColor;
+ procedure Change;
+ function DrawWidth: longint;
+ function DrawHeight: longint;
+ function ValueFromY(Y: longint): real;
+ procedure DrawLine(Y: longint);
+ procedure HandlePaint; override;
+ procedure HandleLMouseDown(x, y: integer; shiftstate: TShiftState); override;
+ procedure HandleMouseMove(x, y: integer; btnstate: word; shiftstate: TShiftState); override;
+ procedure HandleLMouseUp(x, y: integer; shiftstate: TShiftState); override;
+ public
+ constructor Create(AOwner: TComponent); override;
+ procedure SetHS(Hue: longint; Sat: double);
+ published
+ property BackgroundColor;
+ property Value: double Read FValue Write SetValue;
+ property SelectedColor: TfpgColor Read GetSelectedColor;
+ property MarginWidth: longint Read FMarginWidth Write SetMarginWidth;
+ property CursorHeight: longint Read FCursorHeight Write SetCursorHeight;
+ property OnChange: TNotifyEvent Read FOnChange Write FOnChange;
+ end;
+
+
+implementation
+
+uses
+ fpg_ColorMapping;
+
+const
+ RadToHue: double = 1536 / (2 * pi);
+
+
+function AngleFrom(x, y: double): double;
+ // Quadrants are laid out as follows:
+ //
+ // 1|0
+ // ---+----
+ // 2|3
+begin
+ if X = 0 then
+ begin
+ if Y > 0 then
+ Result := pi / 2
+ else
+ Result := 3 * pi / 2;
+ end
+ else
+ begin
+ Result := arctan(abs(y) / abs(x));
+ if (x < 0) and (y >= 0) then
+ // quadrant 1
+ Result := pi - Result
+ else if (x < 0) and (y < 0) then
+ // quadrant 2
+ Result := Result + pi
+ else if (x >= 0) and (y < 0) then
+ // quadrant 3
+ Result := 2 * pi - Result;
+ end;
+end;
+
+
+{ TfpgColorWheel }
+
+function TfpgColorWheel.DrawWidth: longint;
+begin
+ Result := Width - FMarginWidth * 2;
+end;
+
+function TfpgColorWheel.DrawHeight: longint;
+begin
+ Result := Height - FMarginWidth * 2;
+end;
+
+procedure TfpgColorWheel.SetSelectedColor(const NewColor: TfpgColor);
+var
+ Value: double;
+begin
+ RGBToHSV(NewColor, FHue, FSaturation, Value);
+ Change;
+ if FValueBar <> nil then
+ FValueBar.Value := Value;
+end;
+
+procedure TfpgColorWheel.Change;
+begin
+ if FValueBar <> nil then
+ FValueBar.SetHS(FHue, FSaturation);
+ if FOnChange <> nil then
+ FOnChange(self);
+ Invalidate;
+end;
+
+procedure TfpgColorWheel.HandlePaint;
+var
+ x, y: longint;
+ lHue: longint;
+ lsaturation: double;
+ c: TfpgColor;
+begin
+ // clear background rectangle
+ Canvas.Clear(BackgroundColor);
+
+ // margins too big
+ if (Width < MarginWidth * 2) or (Height < MarginWidth * 2) then
+ Exit; //==>
+
+ if csDesigning in ComponentState then
+ begin
+ // When designing, don't draw colors
+ // but draw an outline
+ Canvas.SetLineStyle(1, lsDash);
+ Canvas.DrawRectangle(GetClientRect);
+ Canvas.Color := clHilite1;
+ Canvas.DrawArc(Width div 2, Height div 2, DrawWidth div 2 + 1,
+ DrawHeight div 2 + 1, 45, 180);
+ Canvas.Color := clShadow1;
+ Canvas.DrawArc(Width div 2, Height div 2, DrawWidth div 2 + 1,
+ DrawHeight div 2 + 1, 225, 180);
+ Exit; //==>
+ end;
+
+ if FImage = nil then
+ begin
+ // we must only do this once, because it's very slow
+ FImage := TfpgImage.Create;
+ FImage.AllocateImage(32, DrawWidth, DrawHeight);
+ FImage.UpdateImage;
+ for X := 0 to DrawWidth - 1 do
+ begin
+ for Y := 0 to DrawHeight - 1 do
+ begin
+ // work out hue and saturation for point
+ HSFromPoint(X, Y, lHue, lSaturation);
+ if lSaturation <= 1.0 then
+ begin
+ // point is within wheel
+ C := HSVToRGB(lHue, lSaturation, 1.0);
+ // draw the pixel
+ Canvas.Pixels[X + FMarginWidth, Y + FMarginWidth] := C;
+ FImage.Colors[x, y] := c;
+ end
+ else
+ // point is outside wheel. Also incase color is alias, lookup the RGB values.
+ FImage.Colors[x, y] := fpgColorToRGB(BackgroundColor);
+ end;
+ end;
+ end
+ else
+ begin
+ // paint buffer image seeing that we have it
+ Canvas.DrawImage(FMarginWidth, FMarginWidth, FImage);
+ end;
+
+ DrawCursor;
+end;
+
+procedure TfpgColorWheel.HandleLMouseDown(x, y: integer; shiftstate: TShiftState);
+begin
+ inherited HandleLMouseDown(x, y, shiftstate);
+ Dec(X, FMarginWidth);
+ Dec(Y, FMarginWidth);
+ HSFromPoint(X, Y, FHue, FSaturation);
+ if FSaturation > 1.0 then
+ FSaturation := 1.0;
+ Change;
+ CaptureMouse;
+end;
+
+procedure TfpgColorWheel.HandleMouseMove(x, y: integer; btnstate: word;
+ shiftstate: TShiftState);
+begin
+ inherited HandleMouseMove(x, y, btnstate, shiftstate);
+ // is mouse still captured (LButton down)?
+ if ((btnstate and MOUSE_LEFT) = 0) then
+ Exit; //==>
+ Dec(X, FMarginWidth);
+ Dec(Y, FMarginWidth);
+ HSFromPoint(X, Y, FHue, FSaturation);
+ if FSaturation > 1.0 then
+ FSaturation := 1.0;
+ Change;
+end;
+
+procedure TfpgColorWheel.HandleLMouseUp(x, y: integer; shiftstate: TShiftState);
+begin
+ inherited HandleLMouseUp(x, y, shiftstate);
+ ReleaseMouse;
+end;
+
+constructor TfpgColorWheel.Create(AOwner: TComponent);
+begin
+ inherited Create(AOwner);
+ FMarginWidth := 5;
+ FCursorSize := 5;
+ Width := 100;
+ Height := 100;
+ Name := 'ColorWheel';
+ FWhiteAreaPercent := 10;
+end;
+
+destructor TfpgColorWheel.Destroy;
+begin
+ if FImage <> nil then
+ FImage.Free;
+ inherited Destroy;
+end;
+
+// Calculate hue and saturation for a given point in the color wheel
+procedure TfpgColorWheel.HSFromPoint(X, Y: longint; out H: longint; out S: double);
+var
+ xp, yp: double;
+ halfw, halfh: longint;
+begin
+ halfw := DrawWidth div 2;
+ halfh := DrawHeight div 2;
+ xp := (x - halfw) / halfw; // x as -1..1
+ yp := (y - halfh) / halfh; // y as -1..1
+ H := Trunc(RadToHue * AngleFrom(xp, -yp));
+ S := sqrt(xp * xp + yp * yp);
+ // scale saturation and limit to white, for white area
+ S := S * (1 + (FWhiteAreaPercent / 100.0)) - (FWhiteAreaPercent / 100.0);
+ if S < 0 then
+ S := 0;
+end;
+
+procedure TfpgColorWheel.DrawCursor;
+var
+ Angle: Double;
+ X, Y: longint;
+ S: Double;
+ a: Double;
+ len: longint;
+begin
+ Angle := FHue/RadToHue;
+
+ // Scale distance from centre for white area
+ S := FSaturation;
+ if S > 0 then
+ begin
+ a := FWhiteAreaPercent / 100.0;
+ S := (S * (1 - a)) + a;
+ end;
+
+ // work out point for selected hue and saturation
+ X := Trunc(Width div 2+cos(Angle) * S * (DrawWidth div 2));
+ Y := Trunc(Height div 2+sin(Angle) * S * -(DrawHeight div 2));
+
+ // draw a crosshair with centre at mouse cursor position
+ len := FCursorSize*2 + 2; // length of crosshair lines
+ Canvas.XORFillRectangle($FFFFFF, X-FCursorSize, Y, len, 2);
+ Canvas.XORFillRectangle($FFFFFF, X, Y-FCursorSize, 2, len);
+end;
+
+procedure TfpgColorWheel.SetMarginWidth(NewWidth: longint);
+begin
+ FMarginWidth := NewWidth;
+ if WinHandle = 0 then
+ Exit; //==>
+ Invalidate;
+end;
+
+procedure TfpgColorWheel.SetCursorSize(NewSize: longint);
+begin
+ FCursorSize := NewSize;
+ if WinHandle = 0 then
+ Exit; //==>
+ Invalidate;
+end;
+
+procedure TfpgColorWheel.SetValueBar(AValueBar: TfpgValueBar);
+begin
+ if FValueBar <> nil then
+ // tell the old value bar it's no longer controlled by this wheel
+ FValueBar.FColorWheel := nil;
+ FValueBar := AValueBar;
+ if FValueBar <> nil then
+ begin
+ // Tell value bar it is controlled by this component
+ FValueBar.FColorWheel := Self;
+ // request notification when other is freed
+ FValueBar.FreeNotification(Self);
+ end;
+end;
+
+procedure TfpgColorWheel.SetWhiteAreaPercent(WhiteAreaPercent: longint);
+begin
+ if WhiteAreaPercent > 50 then
+ WhiteAreaPercent := 50;
+
+ if WhiteAreaPercent < 0 then
+ WhiteAreaPercent := 0;
+
+ FWhiteAreaPercent := WhiteAreaPercent;
+ Invalidate;
+end;
+
+procedure TfpgColorWheel.Notification(AComponent: TComponent; Operation: TOperation);
+begin
+ inherited Notification(AComponent, Operation);
+ if Operation = opRemove then
+ begin
+ if AComponent = FValueBar then
+ FValueBar := nil;
+ end;
+end;
+
+
+{ TfpgValueBar }
+
+procedure TfpgValueBar.DrawLine(Y: longint);
+var
+ DrawVal: double;
+ c: TfpgColor;
+begin
+ DrawVal := ValueFromY(Y);
+ C := HSVToRGB(FHue, FSaturation, DrawVal);
+ Canvas.Color := c;
+ Canvas.DrawLine(FMarginWidth, Y, Width - FMarginWidth - 1, Y);
+end;
+
+procedure TfpgValueBar.HandlePaint;
+var
+ y: longint;
+ r: TfpgRect;
+begin
+ // inherited HandlePaint;
+ Canvas.Clear(BackgroundColor);
+
+ if csDesigning in ComponentState then
+ begin
+ // when designing just draw
+ // a rectangle to indicate
+ Canvas.SetLineStyle(1, lsDash);
+ Canvas.DrawRectangle(GetClientRect);
+ if (Width < MarginWidth * 2) or (Height < MarginWidth * 2) then
+ Exit; //==>
+ r := GetClientRect;
+ InflateRect(r, FMarginWidth, FMarginWidth);
+ Canvas.DrawRectangle(r);
+ exit;
+ end;
+
+ // Draw margins
+ r.left := 0;
+ r.setbottom(0);
+ r.setright(FMarginWidth - 1);
+ r.top := Height - 1;
+ Canvas.Color := BackgroundColor;
+ Canvas.FillRectangle(r); // left
+ r.left := Width - FMarginWidth;
+ r.setright(Width - 1);
+ Canvas.FillRectangle(r); // right
+ r.left := FMarginWidth;
+ r.setright(Width - FMarginWidth - 1);
+ r.setbottom(Height - FMarginWidth);
+ r.top := Height - 1;
+ Canvas.FillRectangle(r); // top
+ r.setbottom(0);
+ r.top := FMarginWidth - 1;
+ Canvas.FillRectangle(r); // bottom
+
+ if (Width < MarginWidth * 2) or (Height < MarginWidth * 2) then
+ Exit; //==>
+
+ for Y := 0 to DrawHeight - 1 do
+ DrawLine(Y + FMarginWidth);
+
+ DrawCursor;
+end;
+
+procedure TfpgValueBar.HandleLMouseDown(x, y: integer; shiftstate: TShiftState);
+begin
+ inherited HandleLMouseDown(x, y, shiftstate);
+ FValue := ValueFromY(Y);
+ Change;
+ CaptureMouse;
+end;
+
+procedure TfpgValueBar.HandleMouseMove(x, y: integer; btnstate: word;
+ shiftstate: TShiftState);
+begin
+ inherited HandleMouseMove(x, y, btnstate, shiftstate);
+ if ((btnstate and MOUSE_LEFT) = 0) then
+ Exit; //==>
+ FValue := ValueFromY(Y);
+ Change;
+end;
+
+procedure TfpgValueBar.HandleLMouseUp(x, y: integer; shiftstate: TShiftState);
+begin
+ inherited HandleLMouseUp(x, y, shiftstate);
+ ReleaseMouse;
+end;
+
+constructor TfpgValueBar.Create(AOwner: TComponent);
+begin
+ inherited Create(AOwner);
+ FMarginWidth := 5;
+ FValue := 1.0;
+ Width := 100;
+ Height := 100;
+ Name := 'ValueBar';
+ FCursorHeight := 10;
+end;
+
+procedure TfpgValueBar.SetHS(Hue: longint; Sat: double);
+begin
+ FHue := Hue;
+ FSaturation := Sat;
+ Invalidate;
+ Change;
+end;
+
+procedure TfpgValueBar.SetValue(Value: double);
+begin
+ FValue := Value;
+ Change;
+end;
+
+function TfpgValueBar.DrawWidth: longint;
+begin
+ Result := Width - FMarginWidth * 2;
+end;
+
+function TfpgValueBar.DrawHeight: longint;
+begin
+ Result := Height - FMarginWidth * 2;
+end;
+
+procedure TfpgValueBar.DrawCursor;
+var
+ Y: longint;
+ r: TfpgRect;
+begin
+ if (Width < MarginWidth * 2) or
+ (Height < MarginWidth * 2) then
+ Exit; //==>
+
+ Y := Trunc((FValue * DrawHeight) + FMarginWidth);
+
+ r.SetRect(FMarginWidth-1, Y - (FCursorHeight div 2), DrawWidth+1, FCursorHeight);
+ Canvas.Color := GetSelectedColor;
+ Canvas.FillRectangle(r);
+
+ Canvas.Color := clBlack;
+ Canvas.DrawControlFrame(r);
+end;
+
+procedure TfpgValueBar.SetMarginWidth(NewWidth: longint);
+begin
+ if MarginWidth < 0 then
+ MarginWidth := 0;
+ FMarginWidth := NewWidth;
+ Invalidate;
+end;
+
+procedure TfpgValueBar.SetCursorHeight(CursorHeight: longint);
+begin
+ if CursorHeight < 3 then
+ CursorHeight := 3;
+ FCursorHeight := CursorHeight;
+ Invalidate;
+end;
+
+function TfpgValueBar.GetSelectedColor: TfpgColor;
+begin
+ Result := HSVToRGB(FHue, FSaturation, FValue);
+end;
+
+function TfpgValueBar.ValueFromY(Y: longint): real;
+begin
+ Result := (Y - MarginWidth) / (DrawHeight - 1);
+ if Result < 0 then
+ Result := 0;
+ if Result > 1.0 then
+ Result := 1.0;
+end;
+
+procedure TfpgValueBar.Change;
+begin
+ Invalidate;
+ if FOnChange <> nil then
+ FOnChange(self);
+end;
+
+
+end.
+