---
title: Meet PaTUI: MS Paint for the Terminal, with Vim Controls
author: George Mandis <george@mand.is>
date: 2026-05-06
description: A terminal-based image editor with Vim-style modal controls, retro palette filters, and Floyd-Steinberg dithering. Built with Bun, React, and Ink.
tags: post, programming, cli, recurse-center
---

Every once in a while, a revolutionary product comes along that changes
everything. One is very fortunate if they get to work on even _one_ of these in
their career.

Today I'm proud to introduce three revolutionary products of this caliber. The
first is a state-of-the-art image editor channeling the elegance of MS Paint.
The second is an unparalleled user-interface experience continuing the
long-standing, intuitive traditions of Vim. And the third is software leveraging
the blazingly performant, brilliantly designed programming language that is
JavaScript.

So that's three things: an image editor in the class of MS Paint. An
unimaginably intuitive user-interface paradigm rivaling Vim. A software product
finally realizing the raw horsepower and sensible typing decisions of
JavaScript.

_MS Paint. Vim. JavaScript._

Are you getting it?

![The PaTUI screen with an image of the author's face loaded](https://georgemandis.s3-us-west-1.amazonaws.com/patui/patui-screenshot-me.png)

These are not three separate products. This is _one_ product, and we're calling
it **PaTUI**.

![The PaTUI screen with the word PaTUI drawn in a pixel-art-meets-Comic-Sans style logo](https://georgemandis.s3-us-west-1.amazonaws.com/patui/patui-splash.png)

## Welcome to PaTUI

Homage to
[one of the most iconic product launches of this century](https://www.youtube.com/watch?v=5J-47F8Hrdw)
aside, what is PaTUI?

PaTUI is a terminal-based image editor. Load an image (PNG or JPEG, locally or
via URL), and it renders as colored block characters in your terminal. Then
paint on it in ~~visual~~ "paint" mode, erase, fill, type text or apply retro
filters. When your masterpiece is complete you can export your work as a JPEG,
PNG or ANSI art. In all exports, WYSIWYG.

No, we don't dare impose the dogma of higher-fidelities on your artistic vision.
What are we, Photoshop? _Please_.

This isn't ~~an Arby's~~ Photoshop. This is _PaTUI_.

## "Zoom, Enhance"

Each pixel in the image is represented by block characters wrapped with ANSI
escape codes to render colors. We're using "True" color with RGB escape codes
(e.g. `echo -e "\e[48;2;255;0;255m \e[0m"`) so the vibrance of your original
image always shines through.

And, because the original image really is kept around in memory, you can use the
"zoom and enhance" feature to zero in on as much or little detail as you need.

![The PaTUI screen with the zoom feature engaged on the loaded image](https://georgemandis.s3-us-west-1.amazonaws.com/patui/patui-zoom.png)

## What pixels want: Vim-based controls

It has Vim-style modal controls (`i` for paint mode, `hjkl` to move, `dd` to
delete a "row" of pixels, `yy` to yank, `u` to undo), a 16-color palette you can
select with `!@#$%^&*()`, an extended CSS-compatible color palette you can
access with `:set color <cornflowerblue|salmon|rebeccapurple,etc>`, and commands
like `:w mona-lisa.png` and `:wq`.

Now you can draw shapes and touch up pixels with the most intuitive controls
ever invented in computing: arbitrary keys with a generous amount of `Shift`
thrown in.

You can always use `:help` for a more exhaustive list.

![The PaTUI help screen shown when you type `:help`](https://georgemandis.s3-us-west-1.amazonaws.com/patui/patui-help-screen.png)

**Vim motions on pixels.** `5j` moves down 5 rows. `dd` clears a row. `yy` yanks
it, `p` pastes. `W` jumps to the next color boundary. `dG` deletes from cursor
to bottom. If you know Vim, you already know how to navigate. If you don't know
Vim, well, you're going to learn!

**Retro palette filters.** `:palette gameboy` limits your image to the original
Game Boy's four shades of green. `:palette cga` gives you the CGA palette.
`:dither` applies Floyd-Steinberg error diffusion dithering. Combine them:
`:palette gameboy` then `:dither` and suddenly your photo looks like it belongs
on a 1989 handheld.

**Find-and-replace for colors.** `:%s/blue/red/g` replaces all blue pixels with
red. `:%s/~blue/red/g` does a fuzzy match -- anything in the blue family. The
Vim regex muscle memory just... works here.

**Text rasterization.** Press `t` to enter text mode and type characters
directly onto the image in the current foreground color. Font size scales with
brush size. It's exactly as janky and charming as it sounds.

**Export to ANSI art.** `:w painting.ans` exports your work as ANSI escape
codes. `:wc` copies the ANSI art to your clipboard. Paste it into a terminal and
it renders in color. Paste it into Slack and confuse your coworkers.

## Wh...Why? Why the terminal? Why any of this?

There's something satisfying about creative tools that work in environments
designed for text. The terminal gives you a grid of cells, each of which can
display a colored block character. That's your canvas. Each cell is a pixel. The
constraints are the point.

It's also just funny. The idea of bringing Vim motions to pixel art, of typing
`:wq` to save a painting, of having a tool sidebar in a terminal -- it's absurd
in a way that makes me happy to work on it.

## How it works

PaTUI is a [Bun](https://bun.sh) app built with
[Ink](https://github.com/vadimdemedes/ink), which is React for terminal UIs.
Image loading and manipulation use [sharp](https://sharp.pixelplumbing.com/).
State management is [zustand](https://github.com/pmndrs/zustand).

The rendering pipeline: load an image with sharp, downscale it to fit the
terminal viewport (accounting for the 2:1 aspect ratio of terminal characters),
map each pixel to a 256-color ANSI escape code, and render it as a grid of `▀`
(upper half block) characters. Each character encodes two vertical pixels using
foreground and background colors.

Edits modify the source image buffer. Undo/redo is a stack of image snapshots.
Filters (grayscale, palette limiting, dithering) are applied at render time and
included in exports.

## How do you pronounce PaTUI?

It sounds like "patooey," because that's what your images will look like.

## Try It

```bash
# Homebrew (macOS / Linux)
brew install georgemandis/tap/patui

# Or from source
bun install && bun src/index.tsx mona.png
```

Is it practical? Absolutely not. Is it fun? Easily the most fun 5 minutes you'll
procrastinate with today.

Built during my time at the [Recurse Center](https://www.recurse.com/) on a
lark. View the source and open a PR if you think you can help improve it.

- [github.com/georgemandis/patui](https://github.com/georgemandis/patui)