We will approach graphics programming initially through the TBitmap object:
uses
SysUtils, graphics;
var bmp:TBitmap;
One benefit of using the TBitmap object is that a graphic can be saved and opened for viewing in a graphics programme such as Paint or Photoshop. The TBitmap object has many methods and properties, for example:
bmp:=TBitmap.Create;
bmp.Width:=500;
bmp.Height:=500;
bmp.Canvas.Pixels[i,250]:=clBlack;
bmp.SaveToFile('line.bmp');
Methods and properties are revealed when you type the '.' - pause briefly while the list appears; either scroll down the list or type the letters of the method or property you want.
A bitmap has two axes, X and Y and numbering begins in the top left corner at point (0,0). The bitmap defined above would run from (0,0) to (499,499).
This line of code will turn the colour of a pixel at location [i,250] black:
bmp.Canvas.Pixels[i,250]:=clBlack;
The first simple problem is to draw a continuous line across the bitmap. The line will be at a fixed Y value of, say, 250. To complete the line the X position will have to run from 0 to 499.
When you have completed the horizontal line add a vertical line at X=250.
Now try producing diagonal lines from opposite corners. Try varying the width and colour of the lines with
bmp.Canvas.pen.color:=clRed;
bmp.Canvas.pen.width:=4;
Now you have drawn a line in code you are qualified to use the short cut:
bmp.Canvas.MoveTo(x,y);
bmp.Canvas.LineTo(x,y);
Doing things from first principles is good for programming practice and comes in handy when there are no pre-defined shapes.
How can a line be drawn between any two points? The equation for a straignt line is y=mx+c, where m is the slope and c is a constant (the starting point on the y axis). Given two points, how might they be joined? If the points are [0,0] and [499,499] this is a diagonal line from one corner to another of a 500 pixel grid. In this case y=x+0.

For a line with equation y=x/2 we need to calculate a value for y:
How do we find the value for each y? How might we do this for any line?
One way is to treat the line to be drawn as the hypotenuse of a right-angle triangle. We will need to know (input) the values of x1, x2, y1 and y2, that is the coordinates of the two points given by [x1, y1][x2,y2]. In the first example above the points are [0,0] and [499,499] while in the second example the points are [0,0] and [499,499].
We can use (y1-y2)/(x1-x2) to calculate the slope.
In the first example this gives (0-499)/(0-499)=-499/-499=1 (ignore sign) so y=x+0.
In the second example this gives (0-249)/(0-499)=-249/-499= 0.5 (approx., ignore sign) so y = x/2+0.
The difference between the two y values forms the length of the 'opposite' side of the triangle while the difference between the two x values forms the length of the 'adjacent' side of the triangle. Now, opposite/adjacent = tan(θ); if the sides are equal tan(θ) = 1; the angle made by the adjacent and the hypotenuse can be found from the inverse tangent function (arctan); arctan(1)=0.785398; this is in radians so multiply by 180/pi to get the result in degrees: 45, just what we would expect for a right-angled isosceles triangle (two equal sides). (Verify this in Excel if you wish.)
Use the abs() function is Pascal to discard the sign in the calculations of x1-x2 and y1-y2.
Calculate the slope from the y coordinates.
Use y1 as the constant in y = mx + c.
Draw the line from x1 to x2.
As y is a function of x you will need a separate variable to operate on the equation inside the loop.
Consideration will have to be given to the case where y2 is less than y1, i.e. the slope is negative:

(Remember 0,0 is at the top left in Pascal graphics.)
Parametric equations provide a way of drawing a curve: details. We need parametric equations for graphics because we plot the curves as values of x and y.
One example is drawing a parabola for y=x2:
x=t
y=t2
For given values of x (e.g. 1 to 500 or -250 to 249) plot the corresponding value of y. Do it now!

In Excel enter these numbers in the A column: 0.1, 0.2..6.3.In the next column enter =sin(a1) and in the next column =cos(a1). (Why numbers up to 6.3?) Experiment with graphs: A+B columns, A+C columns, A+B+C columns (all X-Y).
To create a sine or cosine graph in Delphi you will need to vary the Y value in line with the function while the X value increments by 1 across the graphic.
Problems to solve:
If x = sin(i) what type of variable is x?
The bmp.Canvas.Pixels[ ] instruction requires two integers for the values of x and y. How are you going to make x into an integer?
Look at the values of the sin function in Excel. How will you scale these so you can see the wave on a bitmap of 500 x 500?
The x axis value starts at 0 and runs to 499. How will you change this value and use it to draw the sine wave?
When you have finished plot the cosine function on the same bitmap.

As you saw in Excel you can use the sine and cosine functions to draw a circle.
The solution here is similar to that for the separate sine and cosine functions but this time both the sine and cosine values will have to be scaled and converted to integers for the pixels plot routine.
Assign the sin and cos values to variables. Scale these values so they can be used in the pixels[ ] instruction. How will you draw circles at different points on the canvas? How will you increment the values of the sin and cos functions?
How can you distort the circle to form an ellipse (OK, a circle is an ellipse with equal X and Y radii).
When you have completed an ellipse and a circle from first principles you are ready to use the pre-defined routine:
bmp.Canvas.MoveTo(250,250);
bmp.canvas.Ellipse(100,100,200,200);
(A candidate was once asked during an interview at an Oxford college: How would you draw a circle on a computer screen? You just saw how!)

Here is an animation that explains how a cardioid is produced. The method of drawing a cardioid is similar to that of the circle but the formulae for calculating the position of the X and Y coordinates are different:
r:=1-(cos(i)* c);
y:=sin(i)*r;
x:=cos(i) * r;
Given this it is a matter of calculating suitable integer values (by truncating scaled values) for the plots on the bitmap. You could produce the same effects using an X-Y plot in Excel.


Here is an example that creates flower shapes:
x:=((sin(i * c)) * cos(i));
y:=(sin(i *c)) * sin(i);
Set c to 2 or other integer values to see the results. Remember that you will need to scale x and y to integer values on the bitmap. Once again you could produce the same effects using an X-Y plot in Excel.

This code draws a spiral:
r1:=5; r2:=2; h:=3;
i:=0.01;
repeat
x:=(r1-r2)* cos(i) + h*cos(((r1-r2)/r2)*i);
y:=(r1-r2)* sin(i) + h*sin(((r1-r2)/r2)*i)
These are more complex curves drawn from parametric equations. They formed the basis of the Spirograph toy.
Check out these references: Wolfram Mathworld; Wikipedia.
This code will draw a hypotrochoid:
r1:=10; //radius larger outer circle
r2:=3; //radius smaller inner circle
d:=2; //distance of drawing point from centre
of inner circle
i:=0.01;
repeat
x:=((r1-r2)* cos(i)) + (d*cos(((r1-r2)/r2)*i));
y:=((r1-r2)* sin(i)) - (d*sin(((r1-r2)/r2)*i));
a:=trunc(x*10)+250; //use x*n to scale the drawing
b:=trunc(y*10)+250;


This code draws an epitrochoid:
r1:=12; //radius larger outer circle
r2:=1; //radius smaller inner circle
h:=3; //distance of drawing point from centre
of inner circle
i:=0.001; //smaller values give finer detail in outputs
repeat
x:=((r1+r2)* cos(i)) - (h*cos(((r1+r2)/r2)*i));
y:=((r1+r2)* sin(i)) - (h*sin(((r1+r2)/r2)*i));
a:=trunc(x*10)+250; //use x*n to scale the drawing
b:=trunc(y*10)+250;


It is often necessary to scale the output as well as the input data.
Working out what affects the curves produced takes some time; not all combinations of values appear to work.
As well as using ClBlack, ClRed, ClBlue and so on Delphi will accept a number. The range for these numbers is quite wide and we would like to see what it is. Possibilities for the range of colours are:
2^8=256 (one byte);
2^16=65,536 (two bytes)
2^24=16,777,216 (three bytes, one byte for each of R, G and B).
Write a program that will set the pixels to a range of colours. Set up loops and use the counters to set the colour in the bmp.Canvas.Pixels[ ] instruction.
Simple manipulations can produce extraordinary outputs!


It appears that there are 16.7 million colours available. Write a program that displays every colour from 0 to 16,777215 in single pixels. (16.7 million pixels is roughly the size of the image taken by a top quality digital SLR camera e.g. Canon 50D.) How many pixels square is this?
The issue in this program is the loop. Take a smaller example of a 5x5 bitmap. The values we want are as follows:
| outer loop counter | inner loop counter | output |
| 1 | 1 | 1 |
| 1 | 2 | 2 |
| 1 | 3 | 3 |
| 1 | 4 | 4 |
| 1 | 5 | 5 |
| 2 | 1 | 6 |
| 2 | 2 | 7 |
| 2 | 3 | 8 |
| 2 | 4 | 9 |
| 2 | 5 | 10 |
| 3 | 1 | 11 |
| 3 | 2 | 12 |
| 3 | 3 | 13 |
| 3 | 4 | 14 |
| 3 | 5 | 15 |
How can we get the third column of values from the first two? Hint: do something to the sequence 1,2,3,4,5. You could use a spreadsheet to figure out how to derive the values.
Here is some code that was made up fairly quickly just to see what happens:
for i:= 1 to 500 do
begin
for j:= 1 to 500 do
begin
if (i mod j) < 50 then bmp.Canvas.Pixels[i, j]:=clRed;
if (j mod i) < 50 then bmp.Canvas.Pixels[i, j]:=clblue;
end;
end;
Try this out and view the results. What can you produce on the graphics canvas? The challenge is to produce the most interesting pattern in a small number of lines of code. Simple manipulations can produce amazing outputs!

Plotting the Mandelbrot set is something of a leap from made up patterns to what some consider to be the most complicated mathematical object known to humankind. The formula for plotting the Mandelbrot set is both advanced and simple: advanced in that it involves complex numbers but simple in that you can treat the formula as a series of operations and assignments in a programming language. To learn more about the Mandelbrot set start here; there are many implementations of Mandelbrot drawing programs: FractInt is one the most well known.
The Mandelbrot set is the set of numbers inside the shape in the centre of the image. Pixels in the Mandelbrot set are often coloured black. The colour of a pixel is determined by the behaviour of the equation for the Mandelbrot set:
zn+1=zn2 + c
In this equation the new value of z is computed by squaring the previous value and adding a constant c. So far so simple but z happens to be a complex number. A complex number has the form a + bi where a is the x value in the complex plane, b is the y value and i is an imaginary part such that i2 = -1. Complex numbers can be added and multiplied. You may not follow this but you can still follow the code as it is a matter of assignments and arithmetic operations.
Members of the Mandelbrot set are found at points in the complex plane where the equation produces convergence, that is the value of z does not increase beyond a certain limit.
Convergence is tested by iterating (repeating) the calculation z=zn+12 + c until a counter reaches a value such as 255: the output from the equation has not exceeded the limit despite a large number of iterations.
Divergence is tested by testing the output value of the equation and stopping the loop when it passes the pre-defined limit. Pixels where convergence takes place are coloured black, those where divergence takes place are coloured with the current value of the loop counter.
Here is some code:
const offset=500; //set to half number of
iterations in for loop
var
bmp:TBitmap;
x, y, a, b, c, d, color :integer;
cx, cy, scale: real;
//or single; centre of viewpoint and scale of view
a1, b1, a2, b2, ax, ay :real; //components of
Mandelbrot equation
lc, limit: word; //test values for convergence/divergence
begin
//change these values to move the centre of view, the
scale and the amount of divergence
cx:=0; cy:=0; scale:=0.01; limit:=4;
a:=-500; b:=499; c:=-500; d:=499; //loop counters linked
to bmp size
begin
bmp:= TBitmap.Create;
bmp.Width:=1000; //note a, b, c and d are -500,-499;
offset is 500 for this bitmap
bmp.Height:=1000;
for x:= a to b do //top left of bitmap is mapped to
-500,-500; this simulates the complex plane
for y:= c to d do //bottom right of bitmap is
mapped to 499,499; an offset is added later for plotting on the bitmap
begin
ax:=cx+x*scale; //set starting value of z
ay:=cy+y*scale; //set starting value of pixel
a1:=ax; b1:=ay; lp:=0; //set up values for
loop
repeat
lc:=lc+1; //loop counter and pixel
colour
a2:=a1*a1-b1*b1+ax; //Square of a+bi, a
complex number
b2:=2*a1*b1+ay;
a1:=a2; b1:=b2;
until (lc>255) or ((a1*a1)+(b1*b1)>limit);
//does the point converge or diverge?
//If the point
converges, it is part of the M-set and we draw it with colour black
if lc>=255 then bmp.canvas.Pixels[x+offset,y+offset]:=clblack
//note use of offset to map points to bitmap
else
bmp.canvas.Pixels[x+offset,y+offset]:= lc*40000;
end;
bmp.SaveToFile('mandelbrot.bmp');
end.
Rather than figure out how to draw the set from the start we will start with the code and see how it works.
The scale of the output can be changed by setting the scale to e.g. 0.009, 0.008, 0.001, etc. This has the effect of 'zooming' in on the Mandelbrot set. The edge of the set is fractal, that is it has no definite boundary so it is always possible to magnify it and see it in more detail.
The colours of the graphic can be changed in the lines:
if lc>=255 then bmp.canvas.Pixels[x+offset,y+offset]:=clblack
and
bmp.canvas.Pixels[x+offset,y+offset]:= lc*40000;
Notice that the colour of pixels where convergence takes place is given by the loop counter. To produce a better spread of colours the value of lc is scaled to the range of colours. Experiment with the colour values.
It appears that there are 16.7 million colours available. Scaling the value of lc, the loop counter, produces a different range of colours.
Try changing the limit of the loop counter, for example to 1000 or 10000. What two things will this affect?.
Change the values of cx and cy to move the centre of view.
You could ask a user to enter values for these variables.


So far we have output our graphics to bitmap files but we can also output graphics direct to the screen. To do this we will switch to programming in the Windows environment. This is a different style of programming but with your knowledge of Pascal you will soon pick it up.
Start with a new file of the Win32 type. Drag the form borders to the maximum possible for the screen. Add a TImage object from the Addtional group and size it to cover most of the form, leaving an area along the bottom for buttons to perform various actions. We will focus on what we can do with a simple geometric shape, a square. Things that we can do with a square include: scale (make bigger and smaller), move around (translation: left, right, up and down), shear and stretch and rotate. Add some TButton objects from the Standard group, placing them under the TImage.
In the programming model we have used so far we would probably draw a square and then write some procedures to call from a menu that would change the appearance of the square. One problem here is that console applications do not provide easy access to graphical techniques while Windows does.
An alternative model of programming is the object oriented style that is found in languages like Java, C++ and Delphi Pascal and it is the latter that we shall use. In this programming style we define classes, which combine the properties of Pascal records (data items or properties) with procedures and function that operate on the data (methods). Delphi Pascal leads us naturally into this style of programming without having to learn the details of the technique, which we can pick up later when we need to.
From what we have done so far Delphi has built the following class for us:
type
TForm1 = class(TForm)
Image1: TImage;
Button1: TButton;
Button2: TButton;
Button3: TButton;
Button4: TButton;
Button5: TButton;
Button6: TButton;
procedure Button6Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button5Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
The form is the basic class of a Delphi Windows application while the other objects are based on other classes that Delphi makes available to programmers. The procedure headings are built automatically when we double click the buttons. Double click the right-most button and enter the code 'halt' in between begin and end:
procedure TForm1.Button5Click(Sender: TObject);
begin
halt;
end;
Change the Caption property of the button to 'Exit'.
To create and manipulate a square we need 8 points. How might we store them? We could use individual (scalar) variables such as a, b, c... or x1, y1, x2, y2... or we could use an array.
We should initialise the values that we will use to draw a basic square when the program runs. To do this double click on the form (not the TImage or a button) and enter some code such as this:
procedure TForm1.FormCreate(Sender: TObject);
begin
points[1]:=200;
points[2]:=100;
points[3]:=300;
points[4]:=100;
points[5]:=300;
points[6]:=200;
points[7]:=200;
points[8]:=200;
end;
We can now program another button to draw a square using these points:
procedure TForm1.Button1Click(Sender: TObject);
begin
image1.Canvas.MoveTo(trunc(points[1]),trunc(points[2]));
image1.canvas.LineTo(trunc(points[3]),trunc(points[4]));
image1.canvas.LineTo(trunc(points[5]),trunc(points[6]));
image1.canvas.LineTo(trunc(points[7]),trunc(points[8]));
image1.canvas.LineTo(trunc(points[1]),trunc(points[2]));
end;
Notice how Delphi provides a list of properties and methods as you write the code. Image1 is an object that Delphi defines as a class. The TImage object has a property called 'canvas', which in turn has a number of methods and properties such as MoveTo and LineTo.
Remember that the Delphi canvas has point [0,0] in the top left corner so you have to get the changes to the coordinates worked out in advance. To enlarge the square draw over the current one and then change the coordinates:
procedure TForm1.Button2Click(Sender: TObject);
begin
image1.Canvas.Pen.Color:=clwhite;
image1.Canvas.MoveTo(trunc(points[1]),trunc(points[2]));
image1.canvas.LineTo(trunc(points[3]),trunc(points[4]));
image1.canvas.LineTo(trunc(points[5]),trunc(points[6]));
image1.canvas.LineTo(trunc(points[7]),trunc(points[8]));
image1.canvas.LineTo(trunc(points[1]),trunc(points[2]));
points[1]:=points[1]-5;
points[2]:=points[2]-5;
points[3]:=points[3]+5;
points[4]:=points[4]-5;
points[5]:=points[5]+5;
points[6]:=points[6]+5;
points[7]:=points[7]-5;
points[8]:=points[8]+5;
image1.Canvas.Pen.Color:=clblack;
image1.Canvas.MoveTo(trunc(points[1]),trunc(points[2]));
image1.canvas.LineTo(trunc(points[3]),trunc(points[4]));
image1.canvas.LineTo(trunc(points[5]),trunc(points[6]));
image1.canvas.LineTo(trunc(points[7]),trunc(points[8]));
image1.canvas.LineTo(trunc(points[1]),trunc(points[2]));
end;
For shear we change just two values:
procedure TForm1.Button3Click(Sender: TObject);
begin
image1.Canvas.Pen.Color:=clwhite;
image1.Canvas.MoveTo(trunc(points[1]),trunc(points[2]));
image1.canvas.LineTo(trunc(points[3]),trunc(points[4]));
image1.canvas.LineTo(trunc(points[5]),trunc(points[6]));
image1.canvas.LineTo(trunc(points[7]),trunc(points[8]));
image1.canvas.LineTo(trunc(points[1]),trunc(points[2]));
points[1]:=points[1]+5;
points[3]:=points[3]+5;
image1.Canvas.Pen.Color:=clblack;
image1.Canvas.MoveTo(trunc(points[1]),trunc(points[2]));
image1.canvas.LineTo(trunc(points[3]),trunc(points[4]));
image1.canvas.LineTo(trunc(points[5]),trunc(points[6]));
image1.canvas.LineTo(trunc(points[7]),trunc(points[8]));
image1.canvas.LineTo(trunc(points[1]),trunc(points[2]));
end;
Note that shear can also be performed using x + y tan (angle).
For rotation the operation is more complex:
procedure TForm1.Button4Click(Sender: TObject);
begin
image1.Canvas.Pen.Color:=clwhite;
image1.Canvas.MoveTo(trunc(points[1]),trunc(points[2]));
image1.canvas.LineTo(trunc(points[3]),trunc(points[4]));
image1.canvas.LineTo(trunc(points[5]),trunc(points[6]));
image1.canvas.LineTo(trunc(points[7]),trunc(points[8]));
image1.canvas.LineTo(trunc(points[1]),trunc(points[2]));
centrex:= 250;
//points[1]+((points[3]-points[1])/2);
centrey:= 150;
//points[2]+((points[6]-points[4])/2);
points[1]:=points[1]*cos(pi/12)-points[2]*sin(pi/12)+centrex*(1-cos(pi/12))+centrey*sin(pi/12);
points[2]:=points[1]*sin(pi/12)+points[2]*cos(pi/12)+centrey*(1-cos(pi/12))-centrex*sin(pi/12);
points[3]:=points[3]*cos(pi/12)-points[4]*sin(pi/12)+centrex*(1-cos(pi/12))+centrey*sin(pi/12);
points[4]:=points[3]*sin(pi/12)+points[4]*cos(pi/12)+centrey*(1-cos(pi/12))-centrex*sin(pi/12);
points[5]:=points[5]*cos(pi/12)-points[6]*sin(pi/12)+centrex*(1-cos(pi/12))+centrey*sin(pi/12);
points[6]:=points[5]*sin(pi/12)+points[6]*cos(pi/12)+centrey*(1-cos(pi/12))-centrex*sin(pi/12);
points[7]:=points[7]*cos(pi/12)-points[8]*sin(pi/12)+centrex*(1-cos(pi/12))+centrey*sin(pi/12);
points[8]:=points[7]*sin(pi/12)+points[8]*cos(pi/12)+centrey*(1-cos(pi/12))-centrex*sin(pi/12);
image1.Canvas.Pen.Color:=clblack;
image1.Canvas.MoveTo(trunc(points[1]),trunc(points[2]));
image1.canvas.LineTo(trunc(points[3]),trunc(points[4]));
image1.canvas.LineTo(trunc(points[5]),trunc(points[6]));
image1.canvas.LineTo(trunc(points[7]),trunc(points[8]));
image1.canvas.LineTo(trunc(points[1]),trunc(points[2]));
end;
The corners of the square rotate in a circle about the centre of the rotation. The centre of rotation might be in the centre of the square or it might be at some other point such as any corner of the square or any other point. The code shown here performs one successful rotation but subsequent rotations are less accurate. Anyone care to fix this?
This is pretty similar to the earlier version:
procedure TForm1.FormCreate(Sender: TObject);
const offset=500; //set to half number of iterations in
for loop
var
x, y, a, b, c, d :integer;
cx, cy, scale, a1,b1,a2,b2,ax,ay :real;
limit, lp: integer;
begin
cx:=0; cy:=0; scale:=0.0075; limit:=10;
a:=-500; b:=499; c:=-500; d:=499; //loop counters
linked to bmp size
for x:= a to b do
for y:= c to d do
begin
ax:=cx+x*scale;
ay:=cy+y*scale;
a1:=ax; b1:=ay; lp:=0;
repeat
lp:=lp+1;
a2:=a1*a1-b1*b1+ax; //Square of a+bi
b2:=2*a1*b1+ay;
a1:=a2; b1:=b2;
until (lp>256) or ((a1*a1)+(b1*b1)>limit); //does
the point converge or diverge?
if lp>=256 then image1.canvas.Pixels[x+offset,y+offset]:=clblack
else
image1.canvas.Pixels[x+offset,y+offset]:= (lp);
{If the point converges, it is part of the M-set
and we
draw it with colour 0, or black.}
end;