All posts

Drawing Engine in Python

A small Python project for generating images programmatically with NumPy arrays and object-oriented shape primitives — a peek under the hood of how images are stored and drawn.

#python#numpy#graphics#image-processing

Introduction

Recently, I built a small Python project that lets users generate images programmatically using NumPy arrays and object-oriented programming. The goal was to better understand how rendering systems and image generation work internally.

The project included a canvas system, shape generation, image manipulation, and image export.

Creating the Canvas

The canvas was built using NumPy arrays. I created RGB image data with the shape (height, width, 3), where the dimensions represent image height, image width, and the three RGB colour channels:

import numpy as np
 
class Canvas:
    def __init__(self, width: int, height: int, background=(255, 255, 255)):
        self.data = np.full((height, width, 3), background, dtype=np.uint8)

Working directly with arrays helped me understand how images are stored and manipulated at a lower level — every pixel is just three bytes in a flat memory region, and "drawing" is really just writing numbers into the right cells.

Object-Oriented Design

To keep the project organized, I separated functionality into different files and classes:

  • canvas.py managed image data
  • shapes.py handled shape creation
  • individual classes represented rectangles and squares

This made the project easier to extend — adding a new shape meant adding a new class with a draw() method, not modifying a giant if/elif block.

Drawing Shapes

The main drawing logic worked by modifying sections of the NumPy array directly. For example, filling a rectangle is just slicing into the array and assigning a colour:

canvas.data[row:row + height, col:col + width] = color

This allowed shapes to be drawn efficiently by updating pixel ranges inside the image array in a single operation — no per-pixel loop required.

Using array slicing introduced me to coordinate systems, image rendering, and low-level graphics manipulation.

Saving Images

After generating the image data, the project exported the final result as image files. This helped me better understand file generation, image encoding, and rendering workflows — the gap between "pixels in memory" and "valid PNG on disk" is bigger than you'd guess until you try.

What I Learned

This project improved my understanding of NumPy, object-oriented programming, image generation, rendering systems, and array manipulation.

It also helped me understand how graphical applications and drawing systems work internally — and made me appreciate the engineering layered into the high-level drawing libraries I usually take for granted.