Python Library

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.

Python 3.8+ PNG Export HTML/CSS Export Tkinter Editor MIT License

Installation

Install from PyPI. Pillow is included automatically — no extra steps needed.

PyPI pip install pixelart-py

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
Pillow is the only pip dependency. Tkinter is part of Python's standard library and ships with the installer on macOS and Windows. Only Linux users need to install it separately.

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.

You write
Your script
Editor GUI
Core
Canvas
flat pixel list
(r,g,b,a) tuples
Helpers
palette.py
color parsing
drawing.py
algorithms
Output
PNG
HTML/CSS
Python code

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().

FormatExampleNotes
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.
💡 Transparent pixels (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)
ParameterTypeDefaultDescription
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_pixel (x, y, color) → None

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
get_pixel (x, y) → (r, g, b, a)

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 (color) → None

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
flood_fill (x, y, color) → None

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
clear () → None

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_rect (x, y, w, h, color, *, filled=True) → None

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_line (x1, y1, x2, y2, color) → None

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_circle (cx, cy, radius, color, *, filled=False) → None

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

copy () → Canvas

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
paste (other, x, y) → None

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

black
#000000
white
#ffffff
red
#ff0000
lime
#00ff00
blue
#0000ff
yellow
#ffff00
cyan
#00ffff
magenta
#ff00ff
silver
#c0c0c0
gray
#808080
maroon
#800000
green
#008000
navy
#000080
purple
#800080
teal
#008080
olive
#808000

PASTEL — soft tones

blush
#ffb3ba
peach
#ffdfba
butter
#ffffba
mint
#baffc9
powder
#bae1ff
lavender
#e8baff
lilac
#c9baff
rose
#ffbae8
cream
#fff9f0
charcoal
#333333

EARTHY — natural tones

soil
#5c3a1e
bark
#7b4f2e
sand
#c2a06e
wheat
#e8d08a
grass
#4a7c3f
forest
#2d5a27
sky
#87ceeb
dusk
#e07b54
stone
#9e9e8e
snow
#f5f5f5

PNG Export

Exports a pixel-perfect PNG image scaled by pixel_size. Requires Pillow, which is installed automatically with the library.

canvas.save (path) → None

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)
canvas.to_image () → PIL.Image

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
canvas.preview () → None

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>
💡 Re-theming tip: open the 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.

canvas.to_code (var_name="canvas") → str

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())
The editor supports canvases up to 128×128 pixels. For larger canvases, the editor will raise a ValueError.

Tools

IconToolWhat it does
DrawPaint pixels with the current color. Supports brush sizes 1–16px.
🧹EraseMake pixels transparent. Also supports brush sizes.
🪣FillFlood fill all connected same-color pixels from where you click.
💉EyedropClick any pixel to set it as the current color, then switches back to Draw.

Keyboard Shortcuts

DSwitch to Draw tool
ESwitch to Erase tool
FSwitch to Fill tool
ISwitch to Eyedrop tool
GToggle grid overlay
[Decrease brush size
]Increase brush size
Ctrl + ZUndo (50 levels)
Ctrl + SOpen Save & Export dialog
Right-clickInstant eyedrop (pick color)
Ctrl + ScrollZoom in / out

Save & Export Dialog

Clicking ▶ Save & Export opens a modal dialog. You can export multiple formats at once — tick whichever you want:

OptionDescription
PNGExport a PNG with a custom pixel_size. The spinner controls the final image dimensions.
HTML — CSS GridSelf-contained HTML using CSS Grid layout.
HTML — TableSelf-contained HTML using an HTML table.
HTML — CSS VariablesSelf-contained HTML with one CSS variable per unique color.
📋 Copy codeCopy 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)
💡 Test on TestPyPI first with twine upload --repository testpypi dist/* before publishing to the real PyPI.

After publishing, anyone can install it with:

pip install pixelart-py

File Reference

FilePurpose
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.