sysrpl20 March 2015, 22:15
The following is a topic related to the Free Pascal Cross Codebot library. You can read more about about Cross Codebot here. If you are interested in gaining git access to the library, which implements the code or classes in this posting, it is now hosted in this github repository.
This article describes how to get started with the Cross Codebot advanced drawing surface. To get started, you can use a surface in conjunction with a normal Lazarus TCanvas object as demonstrated in the example below.
Example:
uses Codebot.Graphics, Codebot.Graphics.Types; procedure TForm1.FormPaint(Sender: TObject); var Surface: ISurface; Brush: IGradientBrush; begin Surface := NewSurface(Canvas); Surface.Ellipse(ClientRect); Brush := NewBrush(ClientRect); Brush.AddStop(clRed, 0); Brush.AddStop(clGreen, 0.5); Brush.AddStop(clBlue, 0.75); Brush.AddStop(clTransparent, 1); Surface.Fill(Brush); end;
Result:
Note how surface objects are interfaces. There is no need to destroy either surface or brush objects. The only restriction is that an ISurface tied to a control (as opposed to IBitmap.Surface) should only exist during Paint or Render handlers.
Here is another example with using the Cross Codebot TContentGrid control. Note how it passes an ISurface in all its draw event handlers.
Example:
procedure TForm1.FormCreate(Sender: TObject);
begin
ContentGrid1.ColCount := 20;
ContentGrid1.RowCount := 10;
ContentGrid1.DefColWidth := 25;
end;
procedure TForm1.ContentGrid1DrawBackground(Sender: TObject; Surface: ISurface;
Rect: TRectI);
var
B: IBrush;
begin
{ Brushes contains several convenient brush patterns }
B := Brushes.Transparent;
{ Align the transparent checker board brush with the scroll bars }
B.Matrix.Translate(-ContentGrid1.ScrollLeft, -ContentGrid1.ScrollTop);
{ Use the FillRect method to both add a path and fill it in one command }
Surface.FillRect(B, Rect);
end;
procedure TForm1.ContentGrid1DrawCell(Sender: TObject; Surface: ISurface; Col,
Row: Integer; Rect: TRectI; State: TDrawState);
var
{ TColorB is the super color type de jour, B is short for Byte }
Color: TColorB;
begin
{ Generate color from a hue }
Color := Hue(Col / (ContentGrid1.ColCount + 1));
{ Alpha is a byte, so lets modify it using the row count }
Color.Alpha := Round((1 - Row / ContentGrid1.RowCount) * $FF);
{ NewBrush with a color creates an ISolidBrush }
Surface.FillRect(NewBrush(Color), Rect);
{ If the grid cell is selected }
if dsSelected in State then
{ Give it a black outline }
Surface.StrokeRect(NewPen(clBlack), Rect);
end;Result:
And finally here is example of drawing curves and using line styles. Note how the TRenderBox (similiar to TPaintBox, but using an ISurface instead of a TCanvas) is quickly updated if the user drags a control point.
{ Our control and drag points }
var
P1, P2, C1, C2: TPointI;
Drag: PPointI;
procedure TForm1.FormCreate(Sender: TObject);
begin
{ Initialize control and drag points in form create }
P1 := TPointI.Create(50, RenderBox1.Height div 2);
C1 := TPointI.Create(150, RenderBox1.Height div 2 - 100);
C2 := TPointI.Create(RenderBox1.Width - 150, RenderBox1.Height div 2 + 100);
P2 := TPointI.Create(RenderBox1.Width - 50, RenderBox1.Height div 2);
end;
procedure TForm1.RenderBox1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
R: TRectI;
begin
{ Start dragging points on mouse down }
R := TRectI.Create(15, 15);
{ Center the rectangular grab area over each point }
R.Center(P1);
{ And test if it contains the mouse down location }
if R.Contains(X, Y) then
{ If the test passes, set the drag reference }
Drag := @P1;
{ Repeat for all points }
R.Center(P2);
if R.Contains(X, Y) then
Drag := @P2;
R.Center(C1);
if R.Contains(X, Y) then
Drag := @C1;
R.Center(C2);
if R.Contains(X, Y) then
Drag := @C2;
end;
procedure TForm1.RenderBox1MouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
begin
{ If dragging, update the point and refresh the render area }
if Drag <> nil then
begin
Drag.X := X;
Drag.Y := Y;
RenderBox1.Invalidate;
end;
end;
procedure TForm1.RenderBox1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
{ Release the drag }
Drag := nil;
end;
procedure TForm1.RenderBox1Render(Sender: TObject; Surface: ISurface);
var
R: TRectI;
P: IPen;
begin
{ We're rendering in a TRenderBox control }
R := RenderBox1.ClientRect;
{ Start with a transparent checker board background }
Surface.FillRect(Brushes.Transparent, R);
{ Create paths for our points using a 15x15 rect }
R := TRectI.Create(15, 15);
{ TRectI can center itself on a point }
R.Center(P1);
{ We are drawing rectangle paths for the end points }
Surface.Rectangle(R);
R.Center(P2);
Surface.Rectangle(R);
R.Center(C1);
{ And circle paths for the control points }
Surface.Ellipse(R);
R.Center(C2);
Surface.Ellipse(R);
{ Connect the lines between end point and control points }
Surface.MoveTo(P1.X, P1.Y);
Surface.LineTo(C1.X, C1.Y);
Surface.MoveTo(P2.X, P2.Y);
Surface.LineTo(C2.X, C2.Y);
{ Now stroke all paths in a red pen, after this call all paths removed }
Surface.Stroke(NewPen(clRed, 2));
{ Now start the curve path }
Surface.MoveTo(P1.X, P1.Y);
{ Using the CurveTo command }
Surface.CurveTo(P2.X, P2.Y, C1, C2);
{ Let's use a fat black dashed pen }
P := NewPen(clBlack, 8);
P.LinePattern := pnDash;
P.LineCap := cpRound;
{ And stroke the curve }
Surface.Stroke(P);
end;Result: