Creating a simple image

By Martin McBride, 2023-12-07

In this article, we will create a very simple vector image using generativepy. The image looks like this:

Simple rectangle

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 the draw 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 and pixel_height are the width and height of the image in pixels. These are the values that we passed into make_image.
  • frame_no and frame_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:

Coordinates

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:

Popular tags