Create pixel art
with code.
pixelart-py is a pure-Python library for generating pixel art programmatically. Draw with code, export to PNG or HTML, or open the desktop editor and draw by hand — then get the Python code that recreates your drawing automatically.
Installation
Install from PyPI. Pillow is included automatically — no extra steps needed.
For the desktop drawing editor, you also need Tkinter:
# Linux
sudo apt-get install python3-tk
# macOS / Windows — already included with Python from python.org
Quick Start
Create a canvas, draw on it, export it. That's the whole loop.
from pixelart_py import Canvas
# 1. Create a 16×16 canvas — each pixel = 20×20 px in the output image
canvas = Canvas(16, 16, pixel_size=20)
# 2. Paint
canvas.fill("#1a1a2e") # dark navy background
canvas.draw_circle(8, 8, 6, "#e63946", filled=True) # filled red circle
canvas.draw_rect(2, 2, 4, 4, "#ffd60a") # yellow square
canvas.set_pixel(0, 0, "#ffffff") # single white pixel
# 3. Export
canvas.save("art.png") # PNG file
canvas.save_html("art.html", style="grid") # HTML/CSS file
print(canvas.to_code()) # Python source code
Or draw by hand in the desktop editor:
from pixelart_py.editor import open_editor
canvas = open_editor(width=16, height=16)
# A desktop window opens. Draw, then click Save & Export.
# The canvas is returned here and Python code is printed to the terminal.
canvas.save("my_drawing.png")
print(canvas.to_code())
How It Works
pixelart-py is built around one central object — the Canvas — which acts as the source of truth for all pixel data. Everything else reads from or writes to it.
flat pixel list
(r,g,b,a) tuples
color parsing
algorithms
Internally, a canvas is just a flat Python list of (r, g, b, a) tuples — one per pixel, row by row from top-left. drawing.py runs algorithms on this list directly, and exporter.py reads it when saving. This keeps things fast and simple with no external state.
File map
pixelart_py/
__init__.py ← public imports (Canvas, Palette, CLASSIC, PASTEL, EARTHY…)
canvas.py ← Canvas class — create, draw, fill, paste, export
palette.py ← Palette class + color parsing + 3 built-in palettes
drawing.py ← algorithms: Bresenham line, midpoint circle, BFS flood fill
exporter.py ← PNG via Pillow, 3 HTML styles, Python code generator
editor.py ← Tkinter desktop drawing editor
Color Formats
Every method that accepts a color will take any of these formats interchangeably. You never need to convert — the library handles it internally via parse_color().
| Format | Example | Notes |
|---|---|---|
| 6-digit hex string | "#ff0000" |
Most common. Alpha assumed 255 (fully opaque). |
| 3-digit hex shorthand | "#f00" |
Expanded to #ff0000 automatically. |
| 8-digit hex with alpha | "#ff000080" |
Last two digits = alpha. 80 = ~50% transparent. |
| RGB tuple | (255, 0, 0) |
Integer values 0–255. Alpha assumed 255. |
| RGBA tuple | (255, 0, 0, 128) |
Explicit alpha. 0 = transparent, 255 = opaque. |
alpha = 0) show as a checkerboard in the editor. In PNG output they are truly transparent. In HTML output they are rendered as the page background color.
Canvas
The central object. Every piece of pixel art starts with a canvas.
Creating a Canvas
canvas = Canvas(width, height, pixel_size=1, background=None)
| Parameter | Type | Default | Description |
|---|---|---|---|
| width | int | required | Number of pixel columns in the grid. |
| height | int | required | Number of pixel rows in the grid. |
| pixel_size | int | 1 | Scale factor for export. Each logical pixel becomes pixel_size × pixel_size screen pixels. A 16×16 canvas with pixel_size=20 exports as a 320×320 image. |
| background | ColorLike | transparent | Starting fill color. If omitted, all pixels start as fully transparent (0, 0, 0, 0). |
# 16×16 grid, each pixel = 20px in output → 320×320 final image
canvas = Canvas(16, 16, pixel_size=20)
# White background from the start
canvas = Canvas(32, 32, pixel_size=10, background="#ffffff")
# Large pixel art canvas
canvas = Canvas(128, 128, pixel_size=4)
Pixel Manipulation
Set the color of a single pixel. Out-of-bounds coordinates are silently ignored — no exception is raised if you paint near or past the edge.
canvas.set_pixel(4, 4, "#ff0000")
canvas.set_pixel(4, 4, (255, 0, 0)) # same result
Return the color at (x, y) as an (r, g, b, a) tuple. Raises IndexError if coordinates are out of bounds.
rgba = canvas.get_pixel(4, 4)
# → (255, 0, 0, 255)
Fill the entire canvas with one color. Replaces every pixel. Typically the first call you make to set a background.
canvas.fill("#1a1a2e") # dark navy background
canvas.fill("#ffffff") # white background
Replace all pixels connected to (x, y) that share its exact color. Works like the paint-bucket tool in image editors. Uses an iterative BFS so it's safe on large canvases with no recursion limit issues.
canvas.draw_rect(2, 2, 10, 10, "#0000ff", filled=False) # outline box
canvas.flood_fill(5, 5, "#ff0000") # fill inside of box with red
Reset every pixel to fully transparent (0, 0, 0, 0). Unlike fill(), this does not set a color — the canvas becomes blank.
canvas.clear()
Drawing Primitives
All drawing calls use the same coordinate system: (0, 0) is the top-left corner, x increases rightward, y increases downward.
Draw a rectangle. Top-left corner at (x, y), size w × h. Filled by default; pass filled=False for a 1-pixel outline.
canvas.draw_rect(2, 2, 8, 6, "#ff0000") # filled red box
canvas.draw_rect(2, 2, 8, 6, "#00ff00", filled=False) # green outline only
Draw a straight line from (x1, y1) to (x2, y2) using Bresenham's algorithm — pixel-perfect, no anti-aliasing.
canvas.draw_line(0, 0, 15, 15, "#ffffff") # diagonal
canvas.draw_line(0, 8, 15, 8, "#ff0000") # horizontal
Draw a circle centered at (cx, cy). Outline by default; pass filled=True for a solid disk. Uses the midpoint circle algorithm for the outline, scanline fill for the disk.
canvas.draw_circle(8, 8, 6, "#0000ff") # blue outline ring
canvas.draw_circle(8, 8, 6, "#ffd60a", filled=True) # filled yellow disk
Canvas Utilities
Return a deep copy of the canvas. Useful for saving checkpoints before a destructive operation like flood fill.
checkpoint = canvas.copy()
canvas.flood_fill(0, 0, "#ff0000")
# oops — restore from checkpoint
canvas = checkpoint
Stamp another canvas onto this one with its top-left at (x, y). Pixels from other with alpha = 0 are skipped, so transparency works correctly for sprite composition.
scene = Canvas(64, 64, pixel_size=8)
sprite = Canvas(8, 8)
# ... draw on sprite ...
scene.paste(sprite, x=28, y=28) # stamp sprite at center
Palette
A Palette is a named dictionary of colors. Define your colors once and reference them by name throughout your code — much cleaner than scattering hex strings everywhere.
from pixelart_py import Canvas, Palette
p = Palette({
"sky": "#87ceeb",
"ground": "#4a7c3f",
"outline": "#000000",
})
canvas.fill(p["sky"])
canvas.draw_rect(0, 28, 64, 4, p["ground"])
# Add colors after creation
p.add("water", "#1a6b9a")
# Check if a name exists
if "sky" in p:
canvas.fill(p["sky"])
# List all names
p.names() # → ["sky", "ground", "outline", "water"]
Built-in Palettes
Three palettes are included. Import them directly and use them like any other palette.
from pixelart_py import CLASSIC, PASTEL, EARTHY
canvas.fill(CLASSIC["black"])
canvas.draw_rect(2, 2, 4, 4, PASTEL["mint"])
canvas.draw_circle(8, 8, 4, EARTHY["dusk"], filled=True)
CLASSIC — 16 standard colors
PASTEL — soft tones
EARTHY — natural tones
PNG Export
Exports a pixel-perfect PNG image scaled by pixel_size. Requires Pillow, which is installed automatically with the library.
Save the canvas as a PNG file. The output dimensions are width × pixel_size by height × pixel_size.
canvas = Canvas(16, 16, pixel_size=20)
canvas.save("output.png")
# Saves a 320×320 PNG (16 × 20 = 320)
Return the canvas as a PIL.Image object in RGBA mode, without saving to disk. Useful for further post-processing with Pillow.
img = canvas.to_image()
img = img.convert("RGB") # strip alpha if needed
img.save("output.jpg") # or save as JPEG via Pillow directly
Open the canvas in your OS image viewer. Handy for a quick look during development. Requires a display environment — won't work on headless servers.
canvas.preview()
HTML / CSS Export
Export your pixel art as a fully self-contained HTML file — no external dependencies, no images. Just pure HTML and CSS. Three styles are available, each producing different markup.
canvas.save_html("output.html") # default: grid style
canvas.save_html("output.html", style="grid")
canvas.save_html("output.html", style="table")
canvas.save_html("output.html", style="custom-properties")
# Override the HTML cell size (doesn't affect PNG pixel_size)
canvas.save_html("output.html", pixel_size=16)
# Set the browser tab title
canvas.save_html("output.html", title="My Pixel Art")
# Get the HTML as a string (no file saved)
html = canvas.to_html(style="grid")
Style: "grid" — CSS Grid
One <div> per pixel laid out with CSS Grid. Modern, compact, recommended for most use cases.
<!-- Each pixel is a div with an inline background color -->
<div class="pixel-grid">
<div class="p" style="background:#1a1a2e"></div>
<div class="p" style="background:#e63946"></div>
...
</div>
Style: "table" — HTML Table
One <td> per pixel inside a proper HTML table. Maximum browser compatibility — works even in older browsers and email clients.
<table class="pixel-table">
<tr>
<td style="background:#1a1a2e"></td>
<td style="background:#e63946"></td>
</tr>
</table>
Style: "custom-properties" — CSS Variables
Every unique color in your art becomes a CSS custom property. To re-theme the entire artwork, just edit the variable values at the top of the file — no digging through thousands of inline styles.
<style>
:root {
--c0: #1a1a2e; /* dark background */
--c1: #e63946; /* accent red */
--c2: #ffd60a; /* yellow */
}
</style>
<div class="pixel-grid">
<div class="p" style="background:var(--c0)"></div>
...
</div>
custom-properties HTML in any text editor, change the hex values in the :root {} block, and refresh your browser — the entire color scheme updates instantly.
Code Generator
to_code() converts a canvas — however it was created — back into the Python source code that would recreate it exactly. This is especially powerful after using the drawing editor: draw by hand, then get clean Python code out.
Returns a runnable Python string. Uses a greedy rectangle-packing algorithm — solid regions of the same color become draw_rect() calls instead of thousands of individual set_pixel() calls, keeping the output compact and readable.
code = canvas.to_code()
print(code)
# Example output:
# from pixelart_py import Canvas
#
# canvas = Canvas(16, 16, pixel_size=20)
#
# canvas.draw_rect(0, 0, 16, 8, "#1a1a2e")
# canvas.draw_rect(0, 8, 16, 8, "#e63946")
# canvas.set_pixel(4, 4, "#ffd60a")
# canvas.save("output.png")
# Custom variable name
code = canvas.to_code(var_name="my_canvas")
Drawing Editor
A full desktop GUI built with Tkinter. Draw pixel art by hand, then get the Python code to recreate it — or export directly to PNG and HTML.
from pixelart_py.editor import open_editor
# Opens a desktop window. Blocks until you save or close.
canvas = open_editor(
width=16, # grid width (1–128)
height=16, # grid height (1–128)
pixel_size=16, # initial cell size in the editor view
initial_canvas=None # pass an existing Canvas to pre-load it
)
# After closing the editor:
canvas.save("my_drawing.png")
canvas.save_html("my_drawing.html", style="grid")
print(canvas.to_code())
ValueError.
Tools
| Icon | Tool | What it does |
|---|---|---|
| ✏ | Draw | Paint pixels with the current color. Supports brush sizes 1–16px. |
| 🧹 | Erase | Make pixels transparent. Also supports brush sizes. |
| 🪣 | Fill | Flood fill all connected same-color pixels from where you click. |
| 💉 | Eyedrop | Click any pixel to set it as the current color, then switches back to Draw. |
Keyboard Shortcuts
Save & Export Dialog
Clicking ▶ Save & Export opens a modal dialog. You can export multiple formats at once — tick whichever you want:
| Option | Description |
|---|---|
| PNG | Export a PNG with a custom pixel_size. The spinner controls the final image dimensions. |
| HTML — CSS Grid | Self-contained HTML using CSS Grid layout. |
| HTML — Table | Self-contained HTML using an HTML table. |
| HTML — CSS Variables | Self-contained HTML with one CSS variable per unique color. |
| 📋 Copy code | Copy the Python source code to the clipboard. |
Common Patterns
Paint back to front
Always paint background elements first, then work forward. Later calls overwrite earlier ones.
canvas.fill("#87ceeb") # 1. sky fills everything
canvas.draw_rect(...) # 2. distant mountains
canvas.draw_circle(...) # 3. trees in midground
canvas.set_pixel(...) # 4. fine details last
Sprites from text grids
Define your sprite as a string grid — easy to visualize and edit in code.
SPRITE = [
"..HHH..",
".SSSSS.",
".SESESE.",
]
COLOR_MAP = {"H": "#333", "S": "#f2c27e", "E": "#111", ".": None}
for y, row in enumerate(SPRITE):
for x, ch in enumerate(row):
if COLOR_MAP[ch]:
canvas.set_pixel(x, y, COLOR_MAP[ch])
Sprite composition with paste()
Build scene elements on small canvases and stamp them onto a larger scene.
# Build a tree sprite
tree = Canvas(8, 12)
tree.draw_rect(3, 8, 2, 4, "#5c3a1e") # trunk
tree.draw_circle(4, 5, 4, "#2d5a27", filled=True) # leaves
# Stamp it multiple times on the scene
scene.paste(tree, x=5, y=20)
scene.paste(tree, x=18, y=22)
scene.paste(tree, x=32, y=19)
Export all HTML styles at once
for style in ["grid", "table", "custom-properties"]:
canvas.save_html(f"output/art_{style}.html", style=style, pixel_size=12)
Draw → get code → paste into project
# Open the editor, draw something, close it
canvas = open_editor(width=16, height=16)
# The code is printed automatically, but you can also get it as a string
code = canvas.to_code()
print(code) # copy this into your project
Publishing to PyPI
# Install build tools
pip install build twine
# Build distribution packages
python -m build
# Creates: dist/pixelart_py-1.0.0.tar.gz
# dist/pixelart_py-1.0.0-py3-none-any.whl
# Upload to PyPI
twine upload dist/*
# Username: __token__
# Password: your PyPI API token (from pypi.org → Account Settings)
twine upload --repository testpypi dist/* before publishing to the real PyPI.
After publishing, anyone can install it with:
pip install pixelart-py
File Reference
| File | Purpose |
|---|---|
| pixelart_py/__init__.py | Public API surface. Everything you import from pixelart_py is wired up here. |
| pixelart_py/canvas.py | The Canvas class. Stores pixels as a flat list of (r,g,b,a) tuples. All drawing and export methods live here and delegate to the other modules. |
| pixelart_py/palette.py | The Palette class, the parse_color() and to_hex() utility functions, and the three built-in palettes (CLASSIC, PASTEL, EARTHY). |
| pixelart_py/drawing.py | Pure drawing algorithms: Bresenham's line, midpoint circle, scanline circle fill, iterative BFS flood fill. Called by Canvas methods — you don't normally import this directly. |
| pixelart_py/exporter.py | All export logic: PNG via Pillow, three HTML/CSS styles, and the Python code generator with greedy rectangle packing. |
| pixelart_py/editor.py | Full Tkinter desktop drawing editor and the Save & Export dialog. Only imported when you call open_editor(). |
| examples/heart.py | Pixel heart drawn from a text grid. Demonstrates Palette + manual pixel placement. |
| examples/showcase.py | Full night scene using all drawing primitives. Exports PNG + all three HTML styles. |
| pyproject.toml | PyPI packaging metadata. Used by python -m build. |