BGRABitmap tutorial 3: what changed
Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.
Title: BGRABitmap tutorial 3
| Old | New | Line | |
|---|---|---|---|
| 1 | + | This tutorial shows you how to draw on a bitmap with the mouse. | |
| 2 | + | ||
| 3 | + | ### Create a new project | |
| 4 | + | ||
| 5 | + | Create a new project and add a reference to [[BGRABitmap]], the same way as in [[BGRABitmap tutorial|the first tutorial]]. | |
| 6 | + | ||
| 7 | + | ### Create a new image | |
| 8 | + | ||
| 9 | + | Add a private variable to the main form to store the image: | |
| 10 | + | ||
| 11 | + | ``` pascal | |
| 12 | + | TForm1 = class(TForm) | |
| 13 | + | private | |
| 14 | + | { private declarations } | |
| 15 | + | image: TBGRABitmap; | |
| 16 | + | public | |
| 17 | + | { public declarations } | |
| 18 | + | end; | |
| 19 | + | ``` | |
| 20 | + | ||
| 21 | + | 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: | |
| 22 | + | ||
| 23 | + | ``` pascal | |
| 24 | + | procedure TForm1.FormCreate(Sender: TObject); | |
| 25 | + | begin | |
| 26 | + | image := TBGRABitmap.Create(640,480,BGRAWhite); //create a 640x480 image | |
| 27 | + | end; | |
| 28 | + | ``` | |
| 29 | + | ||
| 30 | + | ### Draw the bitmap | |
| 31 | + | ||
| 32 | + | 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: | |
| 33 | + | ||
| 34 | + | ``` pascal | |
| 35 | + | procedure TForm1.FormPaint(Sender: TObject); | |
| 36 | + | begin | |
| 37 | + | PaintImage; | |
| 38 | + | end; | |
| 39 | + | ``` | |
| 40 | + | ||
| 41 | + | Add the PaintImage procedure: | |
| 42 | + | ||
| 43 | + | ``` pascal | |
| 44 | + | procedure TForm1.PaintImage; | |
| 45 | + | begin | |
| 46 | + | image.Draw(Canvas,0,0,True); | |
| 47 | + | end; | |
| 48 | + | ``` | |
| 49 | + | ||
| 50 | + | After writing this, put the cursor on PaintImage and press Ctrl-Shift-C to add the declaration to the interface. | |
| 51 | + | ||
| 52 | + | ### Handle mouse | |
| 53 | + | ||
| 54 | + | With the object inspector, add handlers for MouseDown and MouseMove events: | |
| 55 | + | ||
| 56 | + | ``` pascal | |
| 57 | + | procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; | |
| 58 | + | Shift: TShiftState; X, Y: Integer); | |
| 59 | + | begin | |
| 60 | + | if Button = mbLeft then DrawBrush(X,Y); | |
| 61 | + | end; | |
| 62 | + | ||
| 63 | + | procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, | |
| 64 | + | Y: Integer); | |
| 65 | + | begin | |
| 66 | + | if ssLeft in Shift then DrawBrush(X,Y); | |
| 67 | + | end; | |
| 68 | + | ``` | |
| 69 | + | ||
| 70 | + | Add the DrawBrush procedure: | |
| 71 | + | ||
| 72 | + | ``` pascal | |
| 73 | + | procedure TForm1.DrawBrush(X, Y: Integer); | |
| 74 | + | const radius = 5; | |
| 75 | + | begin | |
| 76 | + | image.GradientFill(X-radius,Y-radius, X+radius,Y+radius, | |
| 77 | + | BGRABlack,BGRAPixelTransparent, gtRadial, | |
| 78 | + | PointF(X,Y), PointF(X+radius,Y), dmDrawWithTransparency); | |
| 79 | + | ||
| 80 | + | PaintImage; | |
| 81 | + | end; | |
| 82 | + | ``` | |
| 83 | + | ||
| 84 | + | After writing this, put the cursor on DrawBrush and press Ctrl-Shift-C to add the declaration to the interface. | |
| 85 | + | ||
| 86 | + | This procedure draws as radial gradient (gtRadial): | |
| 87 | + | ||
| 88 | + | - the bounding rectangle is (X-radius,Y-radius, X+radius,Y+radius). | |
| 89 | + | - the center is black, the border is transparent | |
| 90 | + | - the center is at (X,Y) and the border at (X+radius,Y) | |
| 91 | + | ||
| 92 | + | ### Code | |
| 93 | + | ||
| 94 | + | ``` pascal | |
| 95 | + | unit UMain; | |
| 96 | + | ||
| 97 | + | {$mode objfpc}{$H+} | |
| 98 | + | ||
| 99 | + | interface | |
| 100 | + | ||
| 101 | + | uses | |
| 102 | + | Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs, | |
| 103 | + | BGRABitmap, BGRABitmapTypes; | |
| 104 | + | ||
| 105 | + | type | |
| 106 | + | { TForm1 } | |
| 107 | + | ||
| 108 | + | TForm1 = class(TForm) | |
| 109 | + | procedure FormCreate(Sender: TObject); | |
| 110 | + | procedure FormDestroy(Sender: TObject); | |
| 111 | + | procedure FormMouseDown(Sender: TObject; Button: TMouseButton; | |
| 112 | + | Shift: TShiftState; X, Y: Integer); | |
| 113 | + | procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); | |
| 114 | + | procedure FormPaint(Sender: TObject); | |
| 115 | + | private | |
| 116 | + | { private declarations } | |
| 117 | + | image: TBGRABitmap; | |
| 118 | + | procedure DrawBrush(X, Y: Integer); | |
| 119 | + | procedure PaintImage; | |
| 120 | + | public | |
| 121 | + | { public declarations } | |
| 122 | + | end; | |
| 123 | + | ||
| 124 | + | var | |
| 125 | + | Form1: TForm1; | |
| 126 | + | ||
| 127 | + | implementation | |
| 128 | + | ||
| 129 | + | { TForm1 } | |
| 130 | + | ||
| 131 | + | procedure TForm1.FormCreate(Sender: TObject); | |
| 132 | + | begin | |
| 133 | + | image := TBGRABitmap.Create(640,480,BGRAWhite); | |
| 134 | + | end; | |
| 135 | + | ||
| 136 | + | procedure TForm1.FormDestroy(Sender: TObject); | |
| 137 | + | begin | |
| 138 | + | image.Free; | |
| 139 | + | end; | |
| 140 | + | ||
| 141 | + | procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; | |
| 142 | + | Shift: TShiftState; X, Y: Integer); | |
| 143 | + | begin | |
| 144 | + | if Button = mbLeft then DrawBrush(X,Y); | |
| 145 | + | end; | |
| 146 | + | ||
| 147 | + | procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, | |
| 148 | + | Y: Integer); | |
| 149 | + | begin | |
| 150 | + | if ssLeft in Shift then DrawBrush(X,Y); | |
| 151 | + | end; | |
| 152 | + | ||
| 153 | + | procedure TForm1.FormPaint(Sender: TObject); | |
| 154 | + | begin | |
| 155 | + | PaintImage; | |
| 156 | + | end; | |
| 157 | + | ||
| 158 | + | procedure TForm1.DrawBrush(X, Y: Integer); | |
| 159 | + | const radius = 20; | |
| 160 | + | begin | |
| 161 | + | image.GradientFill(X-radius,Y-radius, X+radius,Y+radius, | |
| 162 | + | BGRABlack,BGRAPixelTransparent,gtRadial, | |
| 163 | + | PointF(X,Y), PointF(X+radius,Y), dmDrawWithTransparency); | |
| 164 | + | ||
| 165 | + | PaintImage; | |
| 166 | + | end; | |
| 167 | + | ||
| 168 | + | procedure TForm1.PaintImage; | |
| 169 | + | begin | |
| 170 | + | image.Draw(Canvas,0,0,True); | |
| 171 | + | end; | |
| 172 | + | ||
| 173 | + | initialization | |
| 174 | + | {$I UMain.lrs} | |
| 175 | + | ||
| 176 | + | end. | |
| 177 | + | ``` | |
| 178 | + | ||
| 179 | + | ### Run the program | |
| 180 | + | ||
| 181 | + | You should be able to draw on the form. | |
| 182 | + | ||
| 183 | + | <figure> | |
| 184 | + | <img src="BGRATutorial3.png" title="BGRATutorial3.png" /> | |
| 185 | + | <figcaption>BGRATutorial3.png</figcaption> | |
| 186 | + | </figure> | |
| 187 | + | ||
| 188 | + | ### Continuous drawing | |
| 189 | + | ||
| 190 | + | To have a continuous drawing, we need additionnal variables: | |
| 191 | + | ||
| 192 | + | ``` pascal | |
| 193 | + | ||
| 194 | + | TForm1 = class(TForm) | |
| 195 | + | ... | |
| 196 | + | private | |
| 197 | + | { private declarations } | |
| 198 | + | image: TBGRABitmap; | |
| 199 | + | mouseDrawing: boolean; | |
| 200 | + | mouseOrigin: TPoint; | |
| 201 | + | ``` | |
| 202 | + | ||
| 203 | + | mouseDrawing will be True during the drawing (with left button pressed) and mouseOrigin will be the starting point of the segment being drawn. | |
| 204 | + | ||
| 205 | + | The clicking handler becomes a little bit more complicated: | |
| 206 | + | ||
| 207 | + | ``` pascal | |
| 208 | + | procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; | |
| 209 | + | Shift: TShiftState; X, Y: Integer); | |
| 210 | + | begin | |
| 211 | + | if Button = mbLeft then | |
| 212 | + | begin | |
| 213 | + | mouseDrawing := True; | |
| 214 | + | mouseOrigin := Point(X,Y); | |
| 215 | + | DrawBrush(X,Y,True); // or DrawBrush(X,Y); | |
| 216 | + | end; | |
| 217 | + | end; | |
| 218 | + | ``` | |
| 219 | + | ||
| 220 | + | 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: | |
| 221 | + | ||
| 222 | + | <figure> | |
| 223 | + | <img src="BGRATutorial3b.png" title="BGRATutorial3b.png" /> | |
| 224 | + | <figcaption>BGRATutorial3b.png</figcaption> | |
| 225 | + | </figure> | |
| 226 | + | ||
| 227 | + | Little by little, we add a new part, which is an opened segment: | |
| 228 | + | ||
| 229 | + | <figure> | |
| 230 | + | <img src="BGRATutorial3c.png" title="BGRATutorial3c.png" /> | |
| 231 | + | <figcaption>BGRATutorial3c.png</figcaption> | |
| 232 | + | </figure> | |
| 233 | + | ||
| 234 | + | That's why we need a new parameter for the DrawBrush function, which becomes: | |
| 235 | + | ||
| 236 | + | ``` pascal | |
| 237 | + | procedure TForm1.DrawBrush(X, Y: Integer; Closed: Boolean); | |
| 238 | + | const brushRadius = 20; | |
| 239 | + | begin | |
| 240 | + | image.DrawLineAntialias(X,Y,mouseOrigin.X,mouseOrigin.Y,BGRA(0,0,0,128),brushRadius,Closed); | |
| 241 | + | mouseOrigin := Point(X,Y); | |
| 242 | + | ||
| 243 | + | PaintImage; | |
| 244 | + | end; | |
| 245 | + | ``` | |
| 246 | + | ||
| 247 | + | 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. | |
| 248 | + | ||
| 249 | + | DrawBrush definition must be updated in the interface. | |
| 250 | + | ||
| 251 | + | The MouseMove handler becomes: | |
| 252 | + | ||
| 253 | + | ``` pascal | |
| 254 | + | procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, | |
| 255 | + | Y: Integer); | |
| 256 | + | begin | |
| 257 | + | if mouseDrawing then DrawBrush(X,Y,False); | |
| 258 | + | end; | |
| 259 | + | ``` | |
| 260 | + | ||
| 261 | + | Finally, we need to add a MouseUp handler to update mouseDrawing: | |
| 262 | + | ||
| 263 | + | ``` pascal | |
| 264 | + | procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton; | |
| 265 | + | Shift: TShiftState; X, Y: Integer); | |
| 266 | + | begin | |
| 267 | + | if Button = mbLeft then | |
| 268 | + | mouseDrawing := False; | |
| 269 | + | end; | |
| 270 | + | ``` | |
| 271 | + | ||
| 272 | + | ### Code | |
| 273 | + | ||
| 274 | + | ``` pascal | |
| 275 | + | unit UMain; | |
| 276 | + | ||
| 277 | + | {$mode objfpc}{$H+} | |
| 278 | + | ||
| 279 | + | interface | |
| 280 | + | ||
| 281 | + | uses | |
| 282 | + | Classes, SysUtils, FileUtil, LResources, Forms, Controls, Graphics, Dialogs, | |
| 283 | + | BGRABitmap, BGRABitmapTypes; | |
| 284 | + | ||
| 285 | + | type | |
| 286 | + | { TForm1 } | |
| 287 | + | ||
| 288 | + | TForm1 = class(TForm) | |
| 289 | + | procedure FormCreate(Sender: TObject); | |
| 290 | + | procedure FormMouseDown(Sender: TObject; Button: TMouseButton; | |
| 291 | + | Shift: TShiftState; X, Y: Integer); | |
| 292 | + | procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); | |
| 293 | + | procedure FormMouseUp(Sender: TObject; Button: TMouseButton; | |
| 294 | + | Shift: TShiftState; X, Y: Integer); | |
| 295 | + | procedure FormPaint(Sender: TObject); | |
| 296 | + | private | |
| 297 | + | { private declarations } | |
| 298 | + | image: TBGRABitmap; | |
| 299 | + | mouseDrawing: boolean; | |
| 300 | + | mouseOrigin: TPoint; | |
| 301 | + | procedure DrawBrush(X, Y: Integer; Closed: boolean); | |
| 302 | + | procedure PaintImage; | |
| 303 | + | public | |
| 304 | + | { public declarations } | |
| 305 | + | end; | |
| 306 | + | ||
| 307 | + | var | |
| 308 | + | Form1: TForm1; | |
| 309 | + | ||
| 310 | + | implementation | |
| 311 | + | ||
| 312 | + | { TForm1 } | |
| 313 | + | ||
| 314 | + | procedure TForm1.FormCreate(Sender: TObject); | |
| 315 | + | begin | |
| 316 | + | image := TBGRABitmap.Create(640,480,BGRAWhite); | |
| 317 | + | end; | |
| 318 | + | ||
| 319 | + | procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; | |
| 320 | + | Shift: TShiftState; X, Y: Integer); | |
| 321 | + | begin | |
| 322 | + | if Button = mbLeft then | |
| 323 | + | begin | |
| 324 | + | mouseDrawing := True; | |
| 325 | + | mouseOrigin := Point(X,Y); | |
| 326 | + | DrawBrush(X,Y,True); | |
| 327 | + | end; | |
| 328 | + | end; | |
| 329 | + | ||
| 330 | + | procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, | |
| 331 | + | Y: Integer); | |
| 332 | + | begin | |
| 333 | + | if mouseDrawing then DrawBrush(X,Y,False); | |
| 334 | + | end; | |
| 335 | + | ||
| 336 | + | procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton; | |
| 337 | + | Shift: TShiftState; X, Y: Integer); | |
| 338 | + | begin | |
| 339 | + | if Button = mbLeft then | |
| 340 | + | mouseDrawing := False; | |
| 341 | + | end; | |
| 342 | + | ||
| 343 | + | procedure TForm1.FormPaint(Sender: TObject); | |
| 344 | + | begin | |
| 345 | + | PaintImage; | |
| 346 | + | end; | |
| 347 | + | ||
| 348 | + | procedure TForm1.DrawBrush(X, Y: Integer; Closed: Boolean); | |
| 349 | + | const brushRadius = 20; | |
| 350 | + | begin | |
| 351 | + | image.DrawLineAntialias(X,Y,mouseOrigin.X,mouseOrigin.Y,BGRA(0,0,0,128),brushRadius,Closed); | |
| 352 | + | mouseOrigin := Point(X,Y); | |
| 353 | + | ||
| 354 | + | PaintImage; | |
| 355 | + | end; | |
| 356 | + | ||
| 357 | + | procedure TForm1.PaintImage; | |
| 358 | + | begin | |
| 359 | + | image.Draw(Canvas,0,0,True); | |
| 360 | + | end; | |
| 361 | + | ||
| 362 | + | initialization | |
| 363 | + | {$I UMain.lrs} | |
| 364 | + | ||
| 365 | + | end. | |
| 366 | + | ``` | |
| 367 | + | ||
| 368 | + | ### Run the program | |
| 369 | + | ||
| 370 | + | Now the drawing is almost continous: | |
| 371 | + | ||
| 372 | + | <figure> | |
| 373 | + | <img src="BGRATutorial3d.png" title="BGRATutorial3d.png" /> | |
| 374 | + | <figcaption>BGRATutorial3d.png</figcaption> | |
| 375 | + | </figure> | |
| 376 | + | ||
| 377 | + | [[BGRABitmap tutorial 2|Previous tutorial (image loading)]] \| [[BGRABitmap tutorial 4|Next tutorial (direct pixel access)]] | |
| 378 | + | ||
| 379 | + | --- | |
| 380 | + | *From the Free Pascal wiki, page "BGRABitmap tutorial 3", as it stood on 2023-02-13 after a change by Wp, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* | |
| 381 | + | Category: BGRABitmap | |
| 382 | + | Category: Graphics |
