download
program
freehand drawing

Introduction

This article describes a Delphi-7 project for freehand drawing.
Pictured below is an example: (reduced size)
The drawing is generated by mouse-movements over a paintbox,
connecting the (x,y) mouse coordinates by lines.

For color- and penwidth selection, my own components are used.
These components can be found [here]
(Delphi-7, but will fit in any Delphi version).

Descriptions of the component may be found
[here] for the colorpicker and
[here] for the arraybutton.

Drawing is not too difficult, but we want to store the drawing as well to be repainted later.

The program allows for storage of 9 drawings.
To verify this storage, bitbuttons “clear” and “repaint” are added.
“Clear” erases the paintbox and “repaint” draws all recorded drawings again.
The “backspace” button removes only the last added drawing.

Coding a drawing

I choosed to code a drawing by means of a step-by-step route description.
See figure below:
Pictured are 3 * 3 pixels. From center (x,y) a code (0..7) points to the next pixel.
A freehand drawing can be defined by a list of these codes, given the coördinates of the starting point.

The code requires 3 bits for each step.
This is far from ideal, because multiples of 3 bits do not fit bytes or words.
A 4 bit code would be better.
The extra 4th bit provides an extra possibility, see figure:
Two successive codes of “0” may be combined to one code of “8” , two codes of “1” to one code of “9” etcetera.
A 4 bits -movement- code I call a “stroke” for the moment.

The list of strokes

At the start of a drawing, memory space is reserved for the storage of maximal 5000 strokes.
This is accomplished by the statement
     getmem( P,number_of_bytes)
which reserves number_of_bytes bytes on the heap.
(note: the heap is memory space outside the program area and is managed by the operating system)
P is a pointer to this space.
The strokes are stored in an array of words.
   Type TA = array[0..strokewords] of word;
        PA = ^TA;
Type PA points to the start of an array of (strokewords + 1) words.
The elements in this array are now addressable by statements like
var p : PA;
................
begin
 P^[index]:= .................
p^[0] contains the number of strokes in the array. See figure , showing the data format.
To define the complete drawing, also the start coordinates must be remembered together
with the pen-width and pen-color.
This is done in array “props” , saving this data for drawings 1..9.
But this data could as well be saved on the heap.

Array “buffer” contains the pointers to the stroke arrays for each drawing.

Generating the stroke list

This is less simple then expected for the following reason:
when the mouse pointer moves slowly over the screen, the generated (x,y) coordinates will constitute adjacent pixels.
However, for faster mouse movements the generated (x,y) points will have empty spaces between them
which must be filled by the stroke codes.

We look at the program source code:
Drawing starts by a mouse-down event in paintbox1.
Funtion “requestbufferspace” is called to reserve space for the strokes.
If reservation was successfull, “true” is returned.
Then a flag “drawing” is set true, clearing the way for coming mouse-move events.
The starting point is stored in array props, together with the selected pen-color and pen-width.
(x,y) coördinates are stored in variables (x1,y1) for start and (x2,y2) for the end of a line.
BufNr is the number of the current drawing.
(x1,y1) and (x2,y2) are outside procedures so they can be addressed by all procedures in unit1.

The line (x1,y1)----> (x2,y2) is painted on the screen and function “recordstrokes” is called to encode the strokes.

Converting a line into strokes

“Recordstrokes” translates a line into a sequence of strokes valued 0..7 (not 0..15).
For each stroke generated, procedure “savestroke” is called to add this stroke to the list.
Savestroke looks at the previously stored stroke and in case of equality the new stroke is not stored
but the old one is increased by 8.

To encode a line , it must be analysed point by point.
In this process it is convenient to distinguish “horizontally”- and “vertically”- oriented lines, see figure:
In “horizontally” oriented lines, where dx > dy is true ,
dx is the number of steps and for each step the y-value changes by an amount of dy/dx.

In case of vertical orientation, dy is the step count and for each step, X changes by an amount of dx/dy.

Note, that in the program at first dx = (x2 – x1) , dy = (y2 – y1) but later dx and dy are converted
to the step values.(-1 or +1 , dx/dy or dy/dx)

For two adjacent points (px1,py1)..(px2,py2) the stroke code must be calculated.
An interim variable called “dir” is used.
Dir is reset to zero and then increased by
    2  if px2 > px1            binary 00000010 
    3  if px2 < px1                   00000011
    8  if py2 > py1                   00001000
    12 if py2 < py1                   00001100
     
Array “dir2stroke” translates the “dir” code into the stroke code.

Cleaning up trash

Recenty I have added some extra code to cleanup the drawing.
What is the case?
Look at the pictures below, which show a diagonal line
In most cases, the mouse coordinates will advance x or y, but not both at the same time.
This results in sloppy lines with excess pixels painted.
Extra coding takes care of removing these erroneous pixels.
Not when drawing, but when recording the strokes.
So, to see the result of the cleanup, the picture has to be repainted.

Changes were made to procedure savestroke.
A new variable (outside the procedure) newstrdir is true when the
previous 2 strokes had different directions.
If this is the case, the new stroke is compared to the previous stroke,
which may result in replacing the previous stroke in the buffer.
Note: In the stroke sequence 0 - 0 - 2 , we do not want to replace strokes 0 - 2 by 1
The newstrdir variable prevents this removal.

Housekeeping

Windows has granted us memory space and keeps this space reserved far after we have closed our drawing application.
It is our responsibility the free the memory space for use by others.
At “formcreate” the 9 pointers in he buffer are set to “nil” , no space is reserved yet.
At “formdestroy” the statement freemem(pointer) returns the reserved memory space to Windows.
Possibly, not all pace was needed for our drawing. So, following a mouse-up event at the end of the drawing, the statement
    reAllocMem(p,((p^[0]+1) shr 1)+2)
returns unused memory space to the operating system and does not affect the data stored already.

The general format is reallocmem(pointer, bytesize) .

The expression for the bytesize : (p^[0]+1) shr 1)+2 evolved by
    p^[0]  : the number of strokes stored
    (P^[0]+1) shr 1 : the number of bytes needed {rounded upwards}
    +2 : bytes needed for p^[0] itself.
    
To see the complete Delphi-7 project, click [here]

Please refer to the program listing for more details.