Creating a simple image
In this article, we will create a very simple vector image using generativepy. The image looks like this:
generativepy can also be used to create bitmap images, NumPy images, 3D images, and videos. These will be covered in later tutorials, but the basic process is similar in all cases.
Vector images
A computer image is often stored as a 2-dimensional array of pixels, where each pixel can be any colour. This is called a bitmap image.
However, when we use vector graphics we don't usually specify the colours of individual pixels. Instead, we work at a higher level, defining shapes in the image. These can be simple shapes, such as rectangles, or more complex shapes, such as text characters.
When we use generativepy in vector mode, our program simply specifies a shape and specifies how it should be filled and outlined. The generativepy library is responsible for deciding how individual pixels should be filled.
The end result is still a bitmap image file, in PNG format. However, the way your code describes the image works at the level of shapes and text, which is more useful for mathematical images.
The code
Here is the code to draw the image above:
from generativepy.drawing import make_image, setup
from generativepy.color import Color
from generativepy.geometry import Rectangle
def draw(ctx, pixel_width, pixel_height, frame_no, frame_count):
setup(ctx, pixel_width, pixel_height, background=Color("grey"))
Rectangle(ctx).of_corner_size((100, 150), 250, 200).fill(Color("orange"))
make_image("rectangle.png", draw, 500, 400)
This code is available on github.
We will now look at this code in more detail.
Basic structure
The basic structure of the code is:
- A user-defined function
draw
that does the drawing. - A call to
make_image
. This function controls the process of creating an image and saving it to a file. We pass in thedraw
function to control the image content.
This basic structure is used throughout generativepy. We use a draw
function to create the drawing, and a make_XXX
function to create the image. The same pattern is used when creating bitmap images, NumPy-based images, and 3D images. The same pattern is used whether we are creating still images, animated GIFs, image sequences, or videos.
In all cases, images are stored in memory in the form of NumPy arrays. This makes the library very flexible as images from different sources can be combined using simple Python and NumPy functions.
For vector images, like this one, generativepy uses the Pycairo library. As we will see, ctx
is a Pycairo drawing context.
make_image
The make_image
function, from the drawing module, is the main function that handles creating vector images.
The basic parameters are:
- The filename of the output PNG file.
- A
draw
function that does the actual drawing. - The width and height of the image in pixels.
The draw
function is a function that we define ourselves to perform the drawing. It doesn't have to be called draw
- you can call it whatever you like, provided you pass its name into make_image
. It must have the correct parameters.
draw function
draw
accepts 5 parameters:
ctx
is a Pycairo context. This is like a virtual drawing surface that you can draw on in code. Whatever you draw will appear on the final image.pixel_width
andpixel_height
are the width and height of the image in pixels. These are the values that we passed intomake_image
.frame_no
andframe_count
are only used when you want to create image sequences (for videos), so we can ignore them here.
generativepy uses Pycairo as its drawing library. generativepy provides a rich set of high-level drawing functions for creating shapes, text, markers, graphs, and formulas that can be added to the image within the draw function
The generativepy drawing functions are built on top of the normal Pycairo functions. If you are familiar with Pycairo, it is also possible to use the Pycairo functions directly, or even to use a mixture of generativepy and Pycairo calls. Generally, though, it is usually better to stick with the generativepy functions as they operate at a higher level and are designed specifically for maths and generative art.
Our draw
function calls the setup
function to set up the drawing area, then draws a rectangle.
setup function
The setup
function isn't mandatory, but it does some useful things, so you will often want to call it.
setup
accepts 3 required parameters - the ctx
, pixel_width
and pixel_height
that were passed into the draw
function. It has some optional parameters that do various things.
In this case, we are setting the background
parameter to Color("grey")
which sets the background colour to a mid-grey. We define the colour using the color module of generativepy. In this example, we are using colour names to select colours. These names are based on the CSS named colours.
Drawing a rectangle
We use a Rectangle
object to draw a rectangle, like this:
Rectangle(ctx).of_corner_size((100, 150), 250, 200).fill(Color("orange"))
All shapes are drawn using the same pattern:
- Declare a shape object, for example
Rectangle(ctx)
. The context is passed in at this stage. - An of_xxx function is used to define the shape. In this case,
of_corner_size
defines a rectangle from the position of its top, left-hand corner, and its width and height. The corner is specified using an(x, y)
tuple. - A drawing function is then used to draw the shape. In this case, we use
fill
which fills the shape with an orange colour.
Coordinate system
All coordinates and sizes are specified in user space coordinates.
By default, user space measured in pixels, based on the size of the image we are creating.
- The origin of the user space coordinate system (0, 0) is at the top left of the image.
- x values increase from left to right.
- y values increase as you move down the image.
In our case:
- Our image is 500 by 400 pixels.
- The orange rectangle is 250 by 200 pixels.
- The position of the rectangle (that is, the position of its top-left corner) is at pixel position (100, 150).
This diagram illustrates the coordinates of the rectangle:
User space can be transformed, so that shapes drown in user space can be scaled, rotated, etc when they are drawn, This will covered in a later tutorial.
See also
Join the GraphicMaths Newletter
Sign up using this form to receive an email when new content is added: