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.
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.pymanaged image datashapes.pyhandled 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] = colorThis 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.