HOME - - - - - Lazarus Tutorials TOC - - - - - - Other material for programmers
Delicious.Com Bookmark this on Delicious     StumbleUpon.Com Recommend to StumbleUpon

Drawing in Lazarus or Delphi

Getting it Exactly right

file: PixelProblem.htm

I've been doing tutorials on drawing pixels, lines, etc for more than thirty years. And often getting it slightly wrong.

In this, I will try again to get it (exactly) right. Sigh.

Summary....

How would you draw....?

-

You don't have to draw the yellow lines. Inside each yellow square is a single pixel. You don't have to draw the gray squares... they represent the form on which your graphic displayed. The white squares (pixels) ARE part of what you have to draw.

Being only 5 pixels across, it will be very tiny on most screens today. You can use the standard Windows "Magnifier" to magnify your creation, to make sure it is what has been requested. (XP: The "Magnifier" application is in the start menu under All Programs/ Accessories/ Accessibility. Win 7: All Programs/ Accessories/ Ease Of Access.)

Good luck! I've been programming with moveto, lineto, rectangle, fillrect since before Windows... and still managed to have trouble while writing this. But it isn't unreliable. I'm just, perhaps, easily confused? Maybe you too?

Below, I show you the heart of the code to do the diagram with Lazarus. I believe that the same code would work with Delphi. Then I discuss the code.

(Don't try to use the following just yet. There are a few things you need before it will work... but, fear not, they are given to you just before the next bit of code.)

iImageWidth:=5; //Not 4, not 6
iImageHeight:=5;

Image1.width:=iImageWidth;
Image1.height:=iImageHeight;

//Next two determine background color. Delphi bitmaps default to white, Lazarus to black.

Bitmap.canvas.pen.color:=clWhite;
Bitmap.canvas.Rectangle
  (0,0,iImageWidth,iImageHeight);
//Note rectangle dimensions: "5,5" fills TO, (excluding
//  the cell identified by "5,5". If the image was
//  set up with WIDTH 5, the IDENTIFIER of the
//  lower right hand cell is "4,4", because the
//  upper left hand cell is "0,0"

Image1.Picture.Graphic:=Bitmap; //Assign the bitmap
  //to the image component The size of the image
  //seems to adjust to the size of the bitmap assigned
  //to it, at least if you use the default properties
  //for Image1, which include Align= alNone,
  //AutoSize= false, Stretch= false.
  //I'm not quite sure why Autosize doesn't need
  //to be "true"... it may relate to something else.

//So far, we've created a white drawing surface, 5x5
//Now test, reinforce what we've seen...
//Remember: iImageHeight and Width are 5.

//N.B.: Once the bitmap has been assigned to
//  Image1, you can no longer draw directly to
//  the bitmap, as we did to establish the background
//  color, but must use Image1.Picture.Bitmap.canvas....

//Draw a filled rectangle in the center of the
//  Drawing area... leaving a 1 pixel border of
//  white pixels.
//Note we have changed to using FillRect
//  and setting the BRUSH (not pen) color...
Image1.Picture.Bitmap.canvas.brush.color:=clLime;
Image1.Picture.Bitmap.canvas.FillRect
    (1,1,iImageWidth-1,iImageHeight-1);

//Prove that the pixel we THINK is 0,0 IS 0,0
Image1.Picture.Bitmap.canvas.pixels[0,0]:=clRed;

//Draw a line, in the central part
//  of the drawing area. Note we are back to
//  using the pen property. First: choose color...
Image1.Picture.Bitmap.canvas.pen.color:=clBlack;

//Continue with drawing line...
//A diagonal from NEAR upper right to
//   NEAR lower left. Remember that lineTO
//   does not color in the last pixel, but
//   DOES color in the pixel the line starts
//   from. (Our line will leave one white
//   pixel unchanged at the edge of the image.

Image1.Picture.Bitmap.canvas.moveto
    (iImageWidth-2,1);

Image1.Picture.Bitmap.canvas.lineto
    (0,iImageHeight-1);

I have been using MoveTo and LineTo for perhaps 30 years... and using them incorrectly, as often as not. They were part of Borland's Turbo Pascal, in the good old days "BW" (Before Windows)

I have been writing Delphi programs and tutorials for a long time. First in my teaching in the 1980s, and for quite some time on the internet.

And now the latest descendant of Turbo Pascal also has MoveTo and LineTo, And, despite trying VERY HARD to, finally, get it right... there were still errors in the early drafts of a new tutorial, May 2014.

So in this essay, I am going to try to give you some Basic Facts. With them, I hope you will be able to avoid making the mistakes I so often fall into. And I must crave the indulgence of people using all of my tutorials... please take the contents of any tutorial with a grain of salt. Please apply what you can know from the examples I am about to present, and use them to correct errors in the use of MoveTo and LineTo you may come across in my tutorials. (If you are feeling kind, alert me to errors which are still in them? Spare the next reader the hassles?)

The errors are almost always of the "off by one" variety. I may, for instance, say such and such will make a line 5 pixels long... when it fact it makes a line six pixels long. Or maybe 4.

In many cases, it "doesn't matter"... in any immediately obvious way. But, I promise you, being sloppy, accepting "close enough", will very, very often come back to give you far more grief in the long run than just getting something RIGHT in the first place would ever have cost you.


So.

Moving on.

Before I go into "full lecture mode", here's a little diagram which, I hope, mostly "explains itself"....

-

If you understand various other things, you'll know that the green "line" (all two pixels of it!) in the above could be drawn with....

moveto(1,1);
lineto(3,1);

Note the coordinates carefully. The white squares represent the bitmap we are drawing on. The green dots around the edge of pixel 3,1 are meant to draw your attention to the fact that the "lineto(3,1) command did NOT change the color of pixel 3,1.

I haven't really "explained" all of that. It was a "summary" statement of some useful information. If questions persist, read on!....



First some "paper" to "draw" on...

In Lazarus, which is very similar to Delphi in this regard, here's what I do when I want to plot points, draw lines, etc.

FIRST....

1) Put a TImage type component on the application's from,
2) Create a bitmap,
3) Assign the bitmap to the TImage component.

THEN.... you will be able to use the following code.

In many cases, you do these things once, during the handler for the FormCreate event. Viz, in a crude form....

   Image1.width:=30;
   Image1.height:=20;

   Bitmap:=TBitmap.create;
   Bitmap.width:=30;
   Bitmap.height:=20;
   Image1.Picture.Graphic:=Bitmap;

The above will work if you have....

a) Used the IDE to place a TImage component on the application's main form, and called the component Image1, and

b) Declared a variable called Bitmap of type TBitmap. (This is "unremarkable". You do it the same way that you will declare variables called iImageWidth and iImageHeight, type integer, in a little bit.... but I draw attention to it, as you may not have used a TBitmap variable previously, or you might think that the "TBitmap.create" somehow takes care of the variable declaration.)

Up in the type declaration for your form's class, in "public" or "private"....

   Bitmap:TBitmap;

-----
The code shown will work, but is isn't very clever to do the job so crudely.

Doing it better

Declare two further variables, iImageWidth, iImageHeight, and revise our previous code to....

   iImagewidth:=30;
   iImageHeight:=20;

   Image1.width:=iImagewidth;
   Image1.height:=iImageHeight;

   Bitmap:=TBitmap.create;

   Bitmap.width:=iImagewidth;
   Bitmap.height:=iImageHeight;

   Bitmap.canvas.pen.color:=clWhite;//1 of 2
   Bitmap.canvas.Rectangle
       (0,0,iImagewidth,iImageHeight);//2 of 2

   Image1.Picture.Graphic:=Bitmap;

Besides using variables for the things we previously hardcoded as 30 and 20, I've added the "of 2" lines. More on them in a moment.

Doing things thus... putting things you'll refer to over and over into variables... is enormously useful if you decide to change a dimension. Even if you don't ever want to change a dimesion, it makes generating correct sourcecode easier. For one thing, un-noticed typo will get caught. Type 40 where you wanted 30, and the program will run, but the results probably won't be what you wanted. Type iImgeHeight for iImageHeight and the compiler will help you by rejecting it.

Now a word about the lines marked "1 of 2" and "2 of 2": If you want to draw on a white background, these two lines are not needed in Delphi. They are required in Lazarus. (It defaults to a black drawing surface.) The parameters of Rectangle specify where it is to be drawn as follows: The first two numbers are the X and Y of the upper left corner of the rectangle. The second pair of numbers give... almost... the coordinates of the diagonally opposite corner.

===

The right values for the parameters of the Rectangle command bring us to the area where I've made mistakes many times.

I have not made a mistake is in saying that the upper, left hand, pixel is at position 0,0, as far as Turbo Pascal, Delphi, and Lazarus are concerned.

It is at the other end of both axes that things get tricky.

We're going to look at a somewhat smaller Image1 for a bit, to settle, once and for all, I hope, some of the details.

We're going to be using...

   iImagewidth:=5;
   iImageHeight:=5;

   Image1.width:=iImagewidth;
   Image1.height:=iImageHeight;

   Bitmap:=TBitmap.create;

   Bitmap.width:=iImagewidth;
   Bitmap.height:=iImageHeight;

   Bitmap.canvas.pen.color:=clGreen;//1 of 2
   Bitmap.canvas.Rectangle
       (0,0,iImagewidth,iImageHeight);//2 of 2

   Image1.Picture.Graphic:=Bitmap;

Run that, and you should see a very small green square, with its upper left corner wherever you had the upper left corner of the TImage component you placed on the form.

This makes a certain amount of sense, I hope.

What happens if, using Lazarus, you make the second two numbers of the Rectangle statement one smaller?....

  Bitmap.canvas.Rectangle
    (0,0,iImagewidth-1,iImageHeight-1);//2 of 2

The above gives rise to a 5x5 drawing area, too.. but this time the right hand edge and the bottom edge, to a depth of one pixel, have remained black if you're using Lazarus, white if you're using Delphi. They weren't under the green rectangle. The green only went out to the 4th pixel.

So we can see, if I have, for once, interpreted that correctly, that on a drawing area which is 5x5 pixels, invoking Rectangle 0,0,5,5 is just right to change the color of all the pixels.

If, still on a 5x5 bitmap, we do Rectangle(0,0,4,4). we have two edges with not-colored pixels: confirmation of what I said a moment ago about 0,0,5,5 being "just right".

(Remember the standard Windows helper application called "Magnifier" which I mentioned earlier. It can be a big help in checking, in detail, what your code does after whatever tweaks you make.)

What happens with (0,0,5,5) and (0,0,4,4) may seem "obvious", but things aren't as simple as they seem. Consider the results of the next experiments....

What happens if we use numbers that are too big? If we draw "outside" the bitmap?

I tried (0,0,6,6) to find out. No problem. Even (-1,-1,6,6) "worked".... there doesn't seem to be a problem if you try to draw "outside" the bitmap.

=== Cast your mind back to the "just right" numbers.

For a 5x5 bitmap, the "just right" numbers are (0,0,5,5).

But wait a moment... look at this diagram....


-

The upper left hand corner is "0,0". Do you see what's "wrong" with 0,0,5,5 being the "right" numbers to cover the drawing area? Pixel 5,5 is NOT the pixel at the lower right. The "name" for the pixel at the lower right is 4,4... because we started counting from 0, not from the 1 that we use in everyday things.

So when we say "do a rectangle", "from 0,0 ", "to 5,5", we actually mean "to" quite literally. "To BUT NOT INCLUDING". But the for the first corner of the rectangle (0,0) the pixel we have specified will be colored. Sigh. First corner included. Second corner (and row and column it is part of excluded.

There's probably a reason for this, but I admit that it escapes me. There ARE reasons for "counting from zero". I understand them.

The "include first specified pixel, exclude last" rule also applies to the moveto/ lineto procedures.

If you say....

moveto(0,0);
lineto(3,3);

... then you will get a very short line indeed. Only the following pixels will have been colored in...

0,0
1,1
2,2

Note... Especially: Pixel 3,3 will NOT be colored. When we said lineto(3,3), we meant color in the pixel along that line UP TO, but NOT INCLUDING, the pixel at 3,3.

Happily, this is at least consistent with what we saw in the matter of the Rectangle procedure earlier.


So... as I said... I hope I have these things right finally. Sometimes, though it is not wise, you can get away with being a little bit sloppy in these matters... but I'd advise against that.

Even in writing this very simple note, while concentrating only on the issues raised in it, I made mistakes about which cells would colored, which would not.

Sigh.

Finally...

The "pixels" command should probably get a mention here before we end. This code...

Image1.canvas.pixels[0,0]:=clBlue;

... will make the pixel in the upper left hand corner of the drawing area blue.

Image1.canvas.pixels[0,2]:=clBlue;

... will make the pixel on the left hand edge of the drawing surface blue. The blue pixel will be the THIRD one down from the top, because we count from zero. Or, to put it another way, the "addresses" of the pixels start at zero.





Search across all my sites with the Google search...

Custom Search
            powered by FreeFind
  Site search Web search
Site Map    What's New    Search This search merely looks for the words you enter. It won't answer "Where can I download InpOut32?"
Ad from page's editor: Yes.. I do enjoy compiling these things for you. I hope they are helpful. However... this doesn't pay my bills!!! Sheepdog Software (tm) is supposed to help do that, so if you found this stuff useful, (and you run a Windows or MS-DOS PC) please visit my freeware and shareware page, download something, and circulate it for me? Links on your page to this page would also be appreciated!
Click here to visit editor's freeware, shareware page.

Link to Lazarus Tutorials main page
How to contact the editor of this page, Tom Boyd


Please consider contributing to the author of this site... and if you don't want to do that, at least check out his introduction to the new micro-donations system Flattr.htm....



Valid HTML 4.01 Transitional Page tested for compliance with INDUSTRY (not MS-only) standards, using the free, publicly accessible validator at validator.w3.org. Mostly passes. There were two "unknown attributes" in Google+ button code. Sigh.


If this page causes a script to run, why? Because of things like Google panels, and the code for the search button. Why do I mention scripts? Be sure you know all you need to about spyware.

....... P a g e . . . E n d s .....