Difference between revisions of "BGRABitmap tutorial 3"
m (wiki lang) |
(→Code: Avoid memory leak in Tutorial 3) |
||
(8 intermediate revisions by 7 users not shown) | |||
Line 1: | Line 1: | ||
{{BGRABitmap_tutorial_3}} | {{BGRABitmap_tutorial_3}} | ||
+ | |||
+ | {{BGRABitmap_tutorial_index}} | ||
This tutorial shows you how to draw on a bitmap with the mouse. | This tutorial shows you how to draw on a bitmap with the mouse. | ||
Line 9: | Line 11: | ||
=== Create a new image === | === Create a new image === | ||
− | Add a private variable to the main form to store the image : | + | Add a private variable to the main form to store the image: |
− | < | + | |
+ | <syntaxhighlight lang="pascal"> | ||
+ | TForm1 = class(TForm) | ||
private | private | ||
{ private declarations } | { private declarations } | ||
Line 16: | Line 20: | ||
public | public | ||
{ public declarations } | { public declarations } | ||
− | end; </ | + | end; |
+ | </syntaxhighlight> | ||
+ | |||
+ | Create the image when the form is created. To do this, double-click on the form, a procedure should appear in the code editor. Add the create instruction: | ||
− | + | <syntaxhighlight lang="pascal">procedure TForm1.FormCreate(Sender: TObject); | |
− | < | ||
begin | begin | ||
image := TBGRABitmap.Create(640,480,BGRAWhite); //create a 640x480 image | image := TBGRABitmap.Create(640,480,BGRAWhite); //create a 640x480 image | ||
− | end; </ | + | end; |
+ | </syntaxhighlight> | ||
=== Draw the bitmap === | === Draw the bitmap === | ||
− | Add an OnPaint handler. To do this, select the main form, then go to the object inspector, in the event tab, and double-click on the OnPaint line. Then, add the drawing code : | + | Add an OnPaint handler. To do this, select the main form, then go to the object inspector, in the event tab, and double-click on the OnPaint line. Then, add the drawing code: |
− | < | + | |
+ | <syntaxhighlight lang="pascal"> | ||
+ | procedure TForm1.FormPaint(Sender: TObject); | ||
begin | begin | ||
PaintImage; | PaintImage; | ||
− | end; </ | + | end; |
+ | </syntaxhighlight> | ||
+ | |||
+ | Add the PaintImage procedure: | ||
− | + | <syntaxhighlight lang="pascal"> | |
− | < | + | procedure TForm1.PaintImage; |
begin | begin | ||
image.Draw(Canvas,0,0,True); | image.Draw(Canvas,0,0,True); | ||
end; | end; | ||
− | </ | + | </syntaxhighlight> |
After writing this, put the cursor on PaintImage and press Ctrl-Shift-C to add the declaration to the interface. | After writing this, put the cursor on PaintImage and press Ctrl-Shift-C to add the declaration to the interface. | ||
Line 43: | Line 55: | ||
=== Handle mouse === | === Handle mouse === | ||
− | With the object inspector, add handlers for MouseDown and MouseMove events : | + | With the object inspector, add handlers for MouseDown and MouseMove events: |
− | < | + | |
+ | <syntaxhighlight lang="pascal"> | ||
+ | procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; | ||
Shift: TShiftState; X, Y: Integer); | Shift: TShiftState; X, Y: Integer); | ||
begin | begin | ||
Line 54: | Line 68: | ||
begin | begin | ||
if ssLeft in Shift then DrawBrush(X,Y); | if ssLeft in Shift then DrawBrush(X,Y); | ||
− | end;</ | + | end; |
+ | </syntaxhighlight> | ||
+ | |||
+ | Add the DrawBrush procedure: | ||
− | + | <syntaxhighlight lang="pascal"> | |
− | < | + | procedure TForm1.DrawBrush(X, Y: Integer); |
const radius = 5; | const radius = 5; | ||
begin | begin | ||
Line 65: | Line 82: | ||
PaintImage; | PaintImage; | ||
− | end;</ | + | end; |
+ | </syntaxhighlight> | ||
After writing this, put the cursor on DrawBrush and press Ctrl-Shift-C to add the declaration to the interface. | After writing this, put the cursor on DrawBrush and press Ctrl-Shift-C to add the declaration to the interface. | ||
− | This procedure draws as radial gradient (gtRadial) : | + | This procedure draws as radial gradient (gtRadial): |
+ | |||
* the bounding rectangle is (X-radius,Y-radius, X+radius,Y+radius). | * the bounding rectangle is (X-radius,Y-radius, X+radius,Y+radius). | ||
* the center is black, the border is transparent | * the center is black, the border is transparent | ||
Line 76: | Line 95: | ||
=== Code === | === Code === | ||
− | < | + | <syntaxhighlight lang="pascal"> |
+ | unit UMain; | ||
{$mode objfpc}{$H+} | {$mode objfpc}{$H+} | ||
Line 91: | Line 111: | ||
TForm1 = class(TForm) | TForm1 = class(TForm) | ||
procedure FormCreate(Sender: TObject); | procedure FormCreate(Sender: TObject); | ||
+ | procedure FormDestroy(Sender: TObject); | ||
procedure FormMouseDown(Sender: TObject; Button: TMouseButton; | procedure FormMouseDown(Sender: TObject; Button: TMouseButton; | ||
Shift: TShiftState; X, Y: Integer); | Shift: TShiftState; X, Y: Integer); | ||
Line 114: | Line 135: | ||
begin | begin | ||
image := TBGRABitmap.Create(640,480,BGRAWhite); | image := TBGRABitmap.Create(640,480,BGRAWhite); | ||
+ | end; | ||
+ | |||
+ | procedure TForm1.FormDestroy(Sender: TObject); | ||
+ | begin | ||
+ | image.Free; | ||
end; | end; | ||
Line 151: | Line 177: | ||
{$I UMain.lrs} | {$I UMain.lrs} | ||
− | end.</ | + | end. |
+ | </syntaxhighlight> | ||
=== Run the program === | === Run the program === | ||
Line 158: | Line 185: | ||
[[Image:BGRATutorial3.png]] | [[Image:BGRATutorial3.png]] | ||
+ | |||
+ | === Continuous drawing === | ||
+ | |||
+ | To have a continuous drawing, we need additionnal variables: | ||
+ | |||
+ | <syntaxhighlight lang="pascal"> | ||
+ | TForm1 = class(TForm) | ||
+ | ... | ||
+ | private | ||
+ | { private declarations } | ||
+ | image: TBGRABitmap; | ||
+ | mouseDrawing: boolean; | ||
+ | mouseOrigin: TPoint; | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | mouseDrawing will be True during the drawing (with left button pressed) and mouseOrigin will be the starting point of the segment being drawn. | ||
+ | |||
+ | The clicking handler becomes a little bit more complicated: | ||
+ | |||
+ | <syntaxhighlight lang="pascal"> | ||
+ | procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; | ||
+ | Shift: TShiftState; X, Y: Integer); | ||
+ | begin | ||
+ | if Button = mbLeft then | ||
+ | begin | ||
+ | mouseDrawing := True; | ||
+ | mouseOrigin := Point(X,Y); | ||
+ | DrawBrush(X,Y,True); // or DrawBrush(X,Y); | ||
+ | end; | ||
+ | end; | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | It initialises the drawing with the current position. Then, it draws as closed segment (note the new parameter for DrawBrush). Indeed, at the beginning, the segment is closed and has a length of zero, which gives a disk: | ||
+ | |||
+ | [[Image:BGRATutorial3b.png]] | ||
+ | |||
+ | Little by little, we add a new part, which is an opened segment: | ||
+ | |||
+ | [[Image:BGRATutorial3c.png]] | ||
+ | |||
+ | That's why we need a new parameter for the DrawBrush function, which becomes: | ||
+ | |||
+ | <syntaxhighlight lang="pascal"> | ||
+ | procedure TForm1.DrawBrush(X, Y: Integer; Closed: Boolean); | ||
+ | const brushRadius = 20; | ||
+ | begin | ||
+ | image.DrawLineAntialias(X,Y,mouseOrigin.X,mouseOrigin.Y,BGRA(0,0,0,128),brushRadius,Closed); | ||
+ | mouseOrigin := Point(X,Y); | ||
+ | |||
+ | PaintImage; | ||
+ | end; | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | We transmit the parameter Closed to DrawLineAntialias, to indicate whether the segment is closed or not. Note coordinates order. The start position and the end position are swapped. Indeed, for DrawLineAntialias, it's the end that is opened, whereas in this case, we want that the beginning be opened. | ||
+ | |||
+ | DrawBrush definition must be updated in the interface. | ||
+ | |||
+ | The MouseMove handler becomes: | ||
+ | |||
+ | <syntaxhighlight lang="pascal"> | ||
+ | procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, | ||
+ | Y: Integer); | ||
+ | begin | ||
+ | if mouseDrawing then DrawBrush(X,Y,False); | ||
+ | end; | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Finally, we need to add a MouseUp handler to update mouseDrawing: | ||
+ | |||
+ | <syntaxhighlight lang="pascal"> | ||
+ | procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton; | ||
+ | Shift: TShiftState; X, Y: Integer); | ||
+ | begin | ||
+ | if Button = mbLeft then | ||
+ | mouseDrawing := False; | ||
+ | end; | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | === Code === | ||
+ | |||
+ | <syntaxhighlight lang="pascal"> | ||
+ | unit UMain; | ||
+ | |||
+ | {$mode objfpc}{$H+} | ||
+ | |||
+ | interface | ||
+ | |||
+ | uses | ||
+ | Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs, | ||
+ | BGRABitmap, BGRABitmapTypes; | ||
+ | |||
+ | type | ||
+ | { TForm1 } | ||
+ | |||
+ | TForm1 = class(TForm) | ||
+ | procedure FormCreate(Sender: TObject); | ||
+ | procedure FormMouseDown(Sender: TObject; Button: TMouseButton; | ||
+ | Shift: TShiftState; X, Y: Integer); | ||
+ | procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); | ||
+ | procedure FormMouseUp(Sender: TObject; Button: TMouseButton; | ||
+ | Shift: TShiftState; X, Y: Integer); | ||
+ | procedure FormPaint(Sender: TObject); | ||
+ | private | ||
+ | { private declarations } | ||
+ | image: TBGRABitmap; | ||
+ | mouseDrawing: boolean; | ||
+ | mouseOrigin: TPoint; | ||
+ | procedure DrawBrush(X, Y: Integer; Closed: boolean); | ||
+ | procedure PaintImage; | ||
+ | public | ||
+ | { public declarations } | ||
+ | end; | ||
+ | |||
+ | var | ||
+ | Form1: TForm1; | ||
+ | |||
+ | implementation | ||
+ | |||
+ | { TForm1 } | ||
+ | |||
+ | procedure TForm1.FormCreate(Sender: TObject); | ||
+ | begin | ||
+ | image := TBGRABitmap.Create(640,480,BGRAWhite); | ||
+ | end; | ||
+ | |||
+ | procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; | ||
+ | Shift: TShiftState; X, Y: Integer); | ||
+ | begin | ||
+ | if Button = mbLeft then | ||
+ | begin | ||
+ | mouseDrawing := True; | ||
+ | mouseOrigin := Point(X,Y); | ||
+ | DrawBrush(X,Y,True); | ||
+ | end; | ||
+ | end; | ||
+ | |||
+ | procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, | ||
+ | Y: Integer); | ||
+ | begin | ||
+ | if mouseDrawing then DrawBrush(X,Y,False); | ||
+ | end; | ||
+ | |||
+ | procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton; | ||
+ | Shift: TShiftState; X, Y: Integer); | ||
+ | begin | ||
+ | if Button = mbLeft then | ||
+ | mouseDrawing := False; | ||
+ | end; | ||
+ | |||
+ | procedure TForm1.FormPaint(Sender: TObject); | ||
+ | begin | ||
+ | PaintImage; | ||
+ | end; | ||
+ | |||
+ | procedure TForm1.DrawBrush(X, Y: Integer; Closed: Boolean); | ||
+ | const brushRadius = 20; | ||
+ | begin | ||
+ | image.DrawLineAntialias(X,Y,mouseOrigin.X,mouseOrigin.Y,BGRA(0,0,0,128),brushRadius,Closed); | ||
+ | mouseOrigin := Point(X,Y); | ||
+ | |||
+ | PaintImage; | ||
+ | end; | ||
+ | |||
+ | procedure TForm1.PaintImage; | ||
+ | begin | ||
+ | image.Draw(Canvas,0,0,True); | ||
+ | end; | ||
+ | |||
+ | initialization | ||
+ | {$I UMain.lrs} | ||
+ | |||
+ | end. | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | === Run the program === | ||
+ | |||
+ | Now the drawing is almost continous: | ||
+ | |||
+ | [[Image:BGRATutorial3d.png]] | ||
[[BGRABitmap tutorial 2|Previous tutorial (image loading)]] | [[BGRABitmap tutorial 4|Next tutorial (direct pixel access)]] | [[BGRABitmap tutorial 2|Previous tutorial (image loading)]] | [[BGRABitmap tutorial 4|Next tutorial (direct pixel access)]] | ||
[[Category:Graphics]] | [[Category:Graphics]] | ||
+ | [[Category: BGRABitmap]] |
Latest revision as of 20:24, 13 February 2023
│ Deutsch (de) │ English (en) │ español (es) │ français (fr) │
Home | Tutorial 1 | Tutorial 2 | Tutorial 3 | Tutorial 4 | Tutorial 5 | Tutorial 6 | Tutorial 7 | Tutorial 8 | Tutorial 9 | Tutorial 10 | Tutorial 11 | Tutorial 12 | Tutorial 13 | Tutorial 14 | Tutorial 15 | Tutorial 16 | Edit
This tutorial shows you how to draw on a bitmap with the mouse.
Create a new project
Create a new project and add a reference to BGRABitmap, the same way as in the first tutorial.
Create a new image
Add a private variable to the main form to store the image:
TForm1 = class(TForm)
private
{ private declarations }
image: TBGRABitmap;
public
{ public declarations }
end;
Create the image when the form is created. To do this, double-click on the form, a procedure should appear in the code editor. Add the create instruction:
procedure TForm1.FormCreate(Sender: TObject);
begin
image := TBGRABitmap.Create(640,480,BGRAWhite); //create a 640x480 image
end;
Draw the bitmap
Add an OnPaint handler. To do this, select the main form, then go to the object inspector, in the event tab, and double-click on the OnPaint line. Then, add the drawing code:
procedure TForm1.FormPaint(Sender: TObject);
begin
PaintImage;
end;
Add the PaintImage procedure:
procedure TForm1.PaintImage;
begin
image.Draw(Canvas,0,0,True);
end;
After writing this, put the cursor on PaintImage and press Ctrl-Shift-C to add the declaration to the interface.
Handle mouse
With the object inspector, add handlers for MouseDown and MouseMove events:
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if Button = mbLeft then DrawBrush(X,Y);
end;
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
begin
if ssLeft in Shift then DrawBrush(X,Y);
end;
Add the DrawBrush procedure:
procedure TForm1.DrawBrush(X, Y: Integer);
const radius = 5;
begin
image.GradientFill(X-radius,Y-radius, X+radius,Y+radius,
BGRABlack,BGRAPixelTransparent, gtRadial,
PointF(X,Y), PointF(X+radius,Y), dmDrawWithTransparency);
PaintImage;
end;
After writing this, put the cursor on DrawBrush and press Ctrl-Shift-C to add the declaration to the interface.
This procedure draws as radial gradient (gtRadial):
- the bounding rectangle is (X-radius,Y-radius, X+radius,Y+radius).
- the center is black, the border is transparent
- the center is at (X,Y) and the border at (X+radius,Y)
Code
unit UMain;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
BGRABitmap, BGRABitmapTypes;
type
{ TForm1 }
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
procedure FormPaint(Sender: TObject);
private
{ private declarations }
image: TBGRABitmap;
procedure DrawBrush(X, Y: Integer);
procedure PaintImage;
public
{ public declarations }
end;
var
Form1: TForm1;
implementation
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
image := TBGRABitmap.Create(640,480,BGRAWhite);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
image.Free;
end;
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if Button = mbLeft then DrawBrush(X,Y);
end;
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
begin
if ssLeft in Shift then DrawBrush(X,Y);
end;
procedure TForm1.FormPaint(Sender: TObject);
begin
PaintImage;
end;
procedure TForm1.DrawBrush(X, Y: Integer);
const radius = 20;
begin
image.GradientFill(X-radius,Y-radius, X+radius,Y+radius,
BGRABlack,BGRAPixelTransparent,gtRadial,
PointF(X,Y), PointF(X+radius,Y), dmDrawWithTransparency);
PaintImage;
end;
procedure TForm1.PaintImage;
begin
image.Draw(Canvas,0,0,True);
end;
initialization
{$I UMain.lrs}
end.
Run the program
You should be able to draw on the form.
Continuous drawing
To have a continuous drawing, we need additionnal variables:
TForm1 = class(TForm)
...
private
{ private declarations }
image: TBGRABitmap;
mouseDrawing: boolean;
mouseOrigin: TPoint;
mouseDrawing will be True during the drawing (with left button pressed) and mouseOrigin will be the starting point of the segment being drawn.
The clicking handler becomes a little bit more complicated:
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if Button = mbLeft then
begin
mouseDrawing := True;
mouseOrigin := Point(X,Y);
DrawBrush(X,Y,True); // or DrawBrush(X,Y);
end;
end;
It initialises the drawing with the current position. Then, it draws as closed segment (note the new parameter for DrawBrush). Indeed, at the beginning, the segment is closed and has a length of zero, which gives a disk:
Little by little, we add a new part, which is an opened segment:
That's why we need a new parameter for the DrawBrush function, which becomes:
procedure TForm1.DrawBrush(X, Y: Integer; Closed: Boolean);
const brushRadius = 20;
begin
image.DrawLineAntialias(X,Y,mouseOrigin.X,mouseOrigin.Y,BGRA(0,0,0,128),brushRadius,Closed);
mouseOrigin := Point(X,Y);
PaintImage;
end;
We transmit the parameter Closed to DrawLineAntialias, to indicate whether the segment is closed or not. Note coordinates order. The start position and the end position are swapped. Indeed, for DrawLineAntialias, it's the end that is opened, whereas in this case, we want that the beginning be opened.
DrawBrush definition must be updated in the interface.
The MouseMove handler becomes:
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
begin
if mouseDrawing then DrawBrush(X,Y,False);
end;
Finally, we need to add a MouseUp handler to update mouseDrawing:
procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if Button = mbLeft then
mouseDrawing := False;
end;
Code
unit UMain;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs,
BGRABitmap, BGRABitmapTypes;
type
{ TForm1 }
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
procedure FormMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure FormPaint(Sender: TObject);
private
{ private declarations }
image: TBGRABitmap;
mouseDrawing: boolean;
mouseOrigin: TPoint;
procedure DrawBrush(X, Y: Integer; Closed: boolean);
procedure PaintImage;
public
{ public declarations }
end;
var
Form1: TForm1;
implementation
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
image := TBGRABitmap.Create(640,480,BGRAWhite);
end;
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if Button = mbLeft then
begin
mouseDrawing := True;
mouseOrigin := Point(X,Y);
DrawBrush(X,Y,True);
end;
end;
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
begin
if mouseDrawing then DrawBrush(X,Y,False);
end;
procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if Button = mbLeft then
mouseDrawing := False;
end;
procedure TForm1.FormPaint(Sender: TObject);
begin
PaintImage;
end;
procedure TForm1.DrawBrush(X, Y: Integer; Closed: Boolean);
const brushRadius = 20;
begin
image.DrawLineAntialias(X,Y,mouseOrigin.X,mouseOrigin.Y,BGRA(0,0,0,128),brushRadius,Closed);
mouseOrigin := Point(X,Y);
PaintImage;
end;
procedure TForm1.PaintImage;
begin
image.Draw(Canvas,0,0,True);
end;
initialization
{$I UMain.lrs}
end.
Run the program
Now the drawing is almost continous:
Previous tutorial (image loading) | Next tutorial (direct pixel access)