- Auto-convert on image load (0ms) and flag change (400ms debounce) - SizeControl: linked width/height sliders with aspect-ratio lock and 0.5 font correction factor - Full flag exposure for all 4 tools (chafa, jp2a, ascii-image-converter, img2txt) - ChafaOptions: symbols/fill dropdowns, dither controls, work/threshold/font-ratio sliders, format select, toggles - Jp2aOptions: color-depth, RGB weight sliders, edge controls, 8 toggles - ImgTxtOptions: dither select with valid libcaca values, gamma slider - OutputDisplay: ansi-to-html rendering for colored chafa output - ShellBridge: abort-previous pattern, conversion-start/end lifecycle events - Test API (ENABLE_TEST_API=true): /test/health, /test/convert, /test/flags/:tool, /test/imagemagick - buildArgs: space-separated args (not = format); full schemas in SCHEMAS export - runChafa: width/height destructured and combined into --size WxH - Port changed to 3050; Vite on 0.0.0.0 with allowedHosts for production domain - 98 unit tests passing across 12 test files Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> |
||
|---|---|---|
| .vscode | ||
| e2e | ||
| public | ||
| server | ||
| src | ||
| .editorconfig | ||
| .gitattributes | ||
| .gitignore | ||
| .oxlintrc.json | ||
| .prettierrc.json | ||
| CLAUDE.md | ||
| eslint.config.js | ||
| index.html | ||
| jsconfig.json | ||
| package-lock.json | ||
| package.json | ||
| playwright.config.js | ||
| postcss.config.js | ||
| README.md | ||
| tailwind.config.js | ||
| vite.config.js | ||
| vitest.config.js | ||
ASCIInator
█████╗ ███████╗ ██████╗██╗██╗███╗ ██╗ █████╗ ████████╗ ██████╗ ██████╗
██╔══██╗██╔════╝██╔════╝██║██║████╗ ██║██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗
███████║███████╗██║ ██║██║██╔██╗ ██║███████║ ██║ ██║ ██║██████╔╝
██╔══██║╚════██║██║ ██║██║██║╚██╗██║██╔══██║ ██║ ██║ ██║██╔══██╗
██║ ██║███████║╚██████╗██║██║██║ ╚████║██║ ██║ ██║ ╚██████╔╝██║ ██║
╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝
Turn any image into ASCII art. Headless Vue 3 front-end for your system's ASCII conversion binaries.
What It Does
ASCIInator is a local-first GUI wrapper around the ASCII conversion tools already on your system. Drop an image in, pick your tool, tune the flags, get plain-text ASCII out. No cloud, no SaaS, no telemetry — just your binaries, a thin Fastify bridge, and a Vue 3 interface.
Built as a test bed for Neovim IDE v2.1.x.
Supported Converters
| Tool | Notes |
|---|---|
| chafa | Default. Best quality, full flag exposure |
| jp2a | JPEG-focused, classic output |
| ascii-image-converter | Braille support, good for fine detail |
| img2txt | libcaca-based, multiple output formats |
All tools must be installed on your system. ASCIInator does not install them for you.
Stack
| Layer | Tech |
|---|---|
| Frontend | Vue 3 + Vite |
| Styling | Tailwind CSS (no component lib) |
| API server | Fastify |
| Shell invocation | execa |
| Image processing | ImageMagick v7 (magick) |
Architecture
┌─────────────────────────────────────────┐
│ Vue 3 Frontend │
│ │
│ ┌─────────────┐ ┌─────────────────┐ │
│ │ ImageInput │ │ ToolSelector │ │
│ │ │ │ │ │
│ │ • Upload │ │ • chafa │ │
│ │ • Paste │ │ • jp2a │ │
│ │ • Drag-drop │ │ • ascii-i-c │ │
│ └──────┬──────┘ │ • img2txt │ │
│ │ └────────┬────────┘ │
│ └────────┬──────────┘ │
│ ▼ │
│ ┌───────────────┐ │
│ │ ShellBridge │ (renderless) │
│ └───────┬───────┘ │
│ │ POST /convert │
└──────────────────┼──────────────────────┘
│
┌──────────────────┼──────────────────────┐
│ Fastify :3050 │ │
│ ▼ │
│ ImageMagick preprocessing │
│ ↓ │
│ execa → binary │
│ ↓ │
│ stdout → response │
└─────────────────────────────────────────┘
│
┌──────────────────┼──────────────────────┐
│ Vue 3 Frontend │
│ │
│ ┌────────────────────┐ ┌───────────┐ │
│ │ OutputDisplay │ │ ErrorLog │ │
│ │ │ │ │ │
│ │ <pre> ASCII art │ │ stderr │ │
│ │ copy / download │ │ warnings │ │
│ └────────────────────┘ └───────────┘ │
└─────────────────────────────────────────┘
Prerequisites
System binaries (install via your package manager):
# macOS
brew install chafa jp2a libcaca imagemagick
brew install ascii-image-converter
# Debian/Ubuntu
apt install chafa jp2a caca-utils imagemagick
# ascii-image-converter: see https://github.com/TheZoraiz/ascii-image-converter
# Arch
pacman -S chafa jp2a libcaca imagemagick
yay -S ascii-image-converter # AUR
Node — v20.19+ or v22.12+ required
Vite — installed via npm
Installation
git clone <repo-url>
cd asciinator
npm install
IDE Setup
Neovim (primary — this is a Neovim v2.1.x test bed):
- Volar LSP in Takeover mode (disable tsserver for
.vuefiles) tailwindcss-language-serverfor class intellisenseeslint-lsp+prettierfor format on save- See
CLAUDE.md §7for the full Neovim config checklist
Browser DevTools:
- Vue DevTools for Chrome/Brave
- Vue DevTools for Firefox
- Enable Custom Object Formatters in DevTools for readable Vue reactive state
Running
You need two terminals (or one split — you're using Neovim, you know the drill).
# Terminal 1 — Fastify API server
node server/index.js
# Terminal 2 — Vite dev server
npm run dev
| Service | URL |
|---|---|
| Vue app | http://localhost:5173 |
| Fastify API | http://localhost:3050 |
Project Structure
asciinator/
├── CLAUDE.md ← Full spec for Claude Code
├── README.md ← You are here
├── index.html
├── vite.config.js
├── tailwind.config.js
├── src/
│ ├── main.js
│ ├── App.vue
│ ├── assets/
│ │ └── main.css
│ └── components/
│ ├── ImageInput.vue ← Upload / paste / drag-drop
│ ├── ToolSelector.vue ← Tool picker + flag controls
│ ├── ShellBridge.vue ← GUI state → API call (renderless)
│ ├── OutputDisplay.vue ← Plain-text ASCII result
│ ├── ErrorLog.vue ← stderr / error surface
│ └── options/
│ ├── ChafaOptions.vue
│ ├── Jp2aOptions.vue
│ ├── AsciiOptions.vue
│ └── ImgTxtOptions.vue
└── server/
├── index.js
├── routes/
│ └── convert.js ← POST /convert handler
└── lib/
├── imagemagick.js ← Pre/post processing
└── converters.js ← Per-tool execa invocations
npm Scripts
npm run dev # Vite dev server (HMR)
npm run build # Production build
npm run preview # Preview production build
npm run server # Fastify API server
npm run server:dev # Fastify with --watch (auto-restart)
npm run lint # oxlint + ESLint (with autofix)
npm run format # Prettier
npm run test:unit # Vitest unit tests
npm run test:e2e # Playwright end-to-end tests
For e2e tests, build first if running on CI:
npm run build
npm run test:e2e
npm run test:e2e -- --project=chromium # Chromium only
npm run test:e2e -- --debug # Debug mode
API
POST /convert
Request — multipart/form-data
| Field | Type | Description |
|---|---|---|
image |
file | Image file to convert |
tool |
string | chafa | jp2a | ascii-image-converter | img2txt |
flags |
JSON string | Tool-specific flags object |
Response — text/plain — raw ASCII output on success, stderr on 422.
curl example:
curl -s -X POST http://localhost:3050/convert \
-F "image=@/path/to/photo.jpg" \
-F "tool=chafa" \
-F 'flags={"width":"80","colors":"256"}'
Flag Reference
All flags are optional. Omitting a flag lets the binary use its own default.
chafa
| Flag | Type | Values | Description |
|---|---|---|---|
width |
integer | e.g. 80 |
Output width in characters (combined into --size=WxH) |
height |
integer | e.g. 40 |
Output height in characters (combined into --size=WxH) |
colors |
enum | none 2 8 16/8 16 240 256 full |
Color depth |
symbols |
string | e.g. block+border+extra |
Symbol sets to use |
dither |
enum | none ordered diffusion noise |
Dithering algorithm |
threshold |
float | 0.0–1.0 |
Alpha threshold |
font-ratio |
float | e.g. 0.5 |
Terminal font width/height ratio |
jp2a
| Flag | Type | Values | Description |
|---|---|---|---|
width |
integer | e.g. 80 |
Output width in characters |
height |
integer | e.g. 40 |
Output height in characters |
background |
enum | light dark |
Assumed terminal background |
chars |
string | e.g. .:-=+*#%@ |
Character ramp to use |
ascii-image-converter
| Flag | Type | Values | Description |
|---|---|---|---|
width |
integer | e.g. 80 |
Output width in characters |
height |
integer | e.g. 40 |
Output height in characters |
color |
boolean | true |
Emit ANSI color codes |
braille |
boolean | true |
Use braille dot patterns |
threshold |
integer | 0–255 |
Brightness threshold |
img2txt
| Flag | Type | Values | Description |
|---|---|---|---|
width |
integer | e.g. 80 |
Output width in characters |
height |
integer | e.g. 40 |
Output height in characters |
format |
enum | ansi utf8 html |
Output format |
dither |
string | e.g. fstein |
Dithering mode |
gamma |
float | e.g. 1.0 |
Gamma correction |
Troubleshooting
command not found: chafa (or jp2a, img2txt, ascii-image-converter)
The binary isn't on PATH. Install it via your package manager — see Prerequisites above.
convert: command not found
ImageMagick isn't installed. It's required for all conversions regardless of tool.
CORS error in the browser console
The Fastify server isn't running, or it's on a different port. Start it with npm run server and confirm it logs Server listening at http://127.0.0.1:3050.
422 from the server
The binary returned a non-zero exit code. The full stderr is in the response body and shown in the ErrorLog panel.
Blank or truncated output
Some tools default to terminal width detection which may return 0 in a non-TTY context. Set an explicit --width flag.
Design
- Always dark — light mode not wired (planned)
- No component library — raw Tailwind only
- Monospace output: JetBrains Mono
- UI font: IBM Plex Sans
- Accent:
#39ff14(neon green — because ASCII)
Neovim v2 Test Bed
This project exists partly to stress-test the Neovim IDE v2.1.x setup across a real Vue 3 + JS project. See CLAUDE.md §7 for the full test checklist covering Volar, Tailwind LSP, ESLint, DAP, and more.
License
MIT