- 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>
331 lines
12 KiB
Markdown
331 lines
12 KiB
Markdown
# 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):
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
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 `.vue` files)
|
||
- `tailwindcss-language-server` for class intellisense
|
||
- `eslint-lsp` + `prettier` for format on save
|
||
- See `CLAUDE.md §7` for the full Neovim config checklist
|
||
|
||
**Browser DevTools:**
|
||
- [Vue DevTools for Chrome/Brave](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
|
||
- [Vue DevTools for Firefox](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
|
||
- 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).
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```bash
|
||
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:
|
||
```bash
|
||
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:**
|
||
```bash
|
||
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
|