slash-ascii

Turn any image into text art, right in your terminal.

npx slash-ascii your-image.png

Install

You need Node.js 20 or newer. Nothing else. To run it once, without installing anything:

npx slash-ascii your-image.png

To keep it around:

npm install -g slash-ascii
slash-ascii your-image.png

Installing makes no network requests and runs no install scripts. Nothing is ever downloaded unless you explicitly ask for background removal.

Three ways to draw

The --charset option decides which characters get used. Every example below is real output from the tool, generated when this page was built — not a picture of it. Try selecting the text.

ascii — the default

Only characters you would find on a keyboard, so it pastes safely into a code comment, a commit message or a chat window.

slash-ascii logo.png -w 100
                                                                                           -*+++*-  
                                                                                         //-------#\
                                                                                         @=-------=@
                                                                                          \------=/ 
                                                                                           -------  
                       @@@                                  ===                                     
         -@@@@@@      /@@@                                  --=              --             /--\    
      @@@@@@@@@@@@    @@@                                  ---+            /    @          @/--=\   
     @@@@       -    /@@@                                  --=            @      @        /+----=\  
     @@@             @@@        @@@@@@@@@     @@@@@@@@    ---#@@@@@@              @      /+-------\\
     @@@@-          /@@@     /@@@@@@@@@@@   @@@@@@@@@@    --=@@@@@@@@   @@  -  -  @@    @#++++++++-@
      @@@@@@@       @@@@    @@@@     @@@@   @@@          ---+     @@@@                              
         @@@@@@    /@@@    @@@/      @@@    @@@@@-       --=      @@@@  @@  -  -  @@    /#---------@
            @@@@   @@@/   @@@/      @@@@      @@@@@@    =--+      @@@                /@  \=-------*/
            @@@@   @@@    @@@@      @@@          @@@@   --=      @@@@     |      @   -    \+----=/  
  @@-     @@@@@    @@@-   \@@@@   @@@@@   @-     @@@@  ---+      @@@       \    @  @    @  \*--=%   
 @@@@@@@@@@@@@     @@@@@   @@@@@@@@@@@@  @@@@@@@@@@@   --=      |@@@         -@   @      @@ \\+/    
     @@@@-                    -@@           -@@-                                 @         @        
                                                                                 @  -  -@ @@        
                                                                                                    
Live output, 100 columns

blocks — the best-looking one

Uses the half-block character ▀ with a separate colour above and below, so each character carries two pixels. Not really ASCII any more, which is why you have to ask for it — but it looks dramatically better.

slash-ascii logo.png --charset blocks -w 100
                                                                                          ▄▄▀▀▀▀▀▄▄ 
                                                                                         ▄▀▀▀▀▀▀▀▀▀▄
                                                                                         ▀▀▀▀▀▀▀▀▀▀▀
                                                                                         ▀▀▀▀▀▀▀▀▀▀▀
                                                                                           ▀▀▀▀▀▀▀  
                       ▄▄▄                                  ▄▄▄                                     
         ▄▄▄▄▄▄▄▄     ▄▀▀▀                                 ▄▀▀▀              ▀▀             ▄▀▀▀    
      ▄▀▀▀▀▀▀▀▀▀▀▀    ▀▀▀▀                                 ▀▀▀▀            ▄    ▀          ▄▀▀▀▀▀   
     ▀▀▀▀▀      ▀    ▄▀▀▀                                 ▄▀▀▀            ▄      ▄        ▀▀▀▀▀▀▀▀▄ 
     ▀▀▀             ▀▀▀▀       ▄▄▀▀▀▀▀▀▀    ▄▄▀▀▀▀▀▀▀▄   ▀▀▀▀▀▀▀▀▀▄     ▀▀       ▀      ▀▀▀▀▀▀▀▀▀▀▄
     ▀▀▀▀▄          ▄▀▀▀     ▄▀▀▀▀▀▀▀▀▀▀▀   ▀▀▀▀▀▀▀▀▀▀   ▄▀▀▀▀▀▀▀▀▀▀▀▄  ▀▀ ▄▄▄ ▄▄ ▄▀    ▀▀▀▀▀▀▀▀▀▀▀▀
      ▀▀▀▀▀▀▄       ▀▀▀▀    ▀▀▀▀▀    ▀▀▀▀   ▀▀▀          ▀▀▀▀     ▀▀▀▀                              
        ▀▀▀▀▀▀▀    ▄▀▀▀    ▀▀▀▀     ▄▀▀▀    ▀▀▀▀▄▄      ▄▀▀▀      ▀▀▀▀  ▄▀ ▀▀▀ ▀▀ ▀▀    ▄▀▀▀▀▀▀▀▀▀▀▀
            ▀▀▀▀   ▀▀▀▀   ▀▀▀▀      ▀▀▀▀     ▀▀▀▀▀▀▀    ▀▀▀▀     ▄▀▀▀    ▄▄       ▄  ▄▄  ▀▀▀▀▀▀▀▀▀▀▀
            ▀▀▀▀   ▀▀▀    ▀▀▀▀      ▀▀▀          ▀▀▀▀   ▀▀▀      ▀▀▀▀     ▀      ▀   ▀ ▀  ▀▀▀▀▀▀▀▀▀ 
  ▄▄▄    ▄▄▀▀▀▀    ▀▀▀▄   ▀▀▀▀▄   ▄▀▀▀▀   ▄▄    ▄▀▀▀▀  ▀▀▀▀      ▀▀▀       ▀▄   ▀  ▀▀   ▀  ▀▀▀▀▀▀   
 ▀▀▀▀▀▀▀▀▀▀▀▀▀     ▀▀▀▀▀   ▀▀▀▀▀▀▀▀▀▀▀▀  ▀▀▀▀▀▀▀▀▀▀▀   ▀▀▀      ▀▀▀▀         ▄▄▄  ▀      ▀▄ ▀▀▀▀    
    ▀▀▀▀▀▀            ▀       ▀▀▀▀         ▀▀▀▀▀▀                               ▄▄         ▄        
                                                                                 ▀  ▀▀ ▀▀ ▀▀        
                                                                                                    
Live output, 100 columns

braille — the most detailed

Packs 2×4 dots into every character, resolving far more detail than the other two. Your terminal font needs braille glyphs; if you see empty boxes, use blocks.

slash-ascii logo.png --charset braille -w 100
                                                                                           ⣴⠞⢝⠝⢝⠗⢦  
                                                                                         ⢠⠟⢅⠕⢅⠕⢅⠕⢅⢽⡄
                                                                                         ⢸⠕⢅⠕⢅⠕⢅⠕⢅⢝⡇
                                                                                          ⣷⠅⠕⢅⠕⢅⠕⢅⣽ 
                                                                                           ⠳⠷⣥⣵⣥⠵⠛  
                       ⣀⣤⣤                                   ⢀⠄                                     
         ⣀⣤⣤⣤⣤⣤⣄      ⢰⣿⣿⡟                                  ⠕⢅⢕              ⠟⠻             ⢠⢞⠝⢦    
      ⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿    ⣾⣿⣿                                  ⢄⠕⢅⠅            ⣴    ⣦          ⣰⠟⢅⠕⢝⢷   
     ⣾⣿⣿⡟       ⠙    ⢰⣿⣿⡟                                  ⢅⠕⢕            ⣤      ⢠        ⣴⢏⠕⢅⠕⢅⠝⢷  
     ⣿⣿⣿             ⣾⣿⣿        ⣠⣴⣶⣶⣾⣿⣷⣶⣶     ⣤⣶⣾⣿⣿⣿⣶⣶    ⠔⢅⠕⣵⣶⣾⣿⣷⣶⣤              ⠃      ⣼⠕⢅⠕⢅⠕⢅⠕⢅⢽⡄
     ⢿⣿⣿⣧⣀          ⢰⣿⣿⡟     ⣠⣾⣿⣿⡿⠟⠛⠛⢻⣿⣿⣿   ⣴⣿⣿⠿⠛⠛⠛⠛⠻⠟    ⠕⢅⢕⠟⠛⠛⠛⠿⣿⣿⣿   ⠸⣧  ⣤  ⣤  ⣤⠗    ⠸⣥⣕⣥⣕⣥⣕⣥⣕⣥⣵⡽
      ⠻⣿⣿⣿⣿⣶⣤       ⣾⣿⣿⠇    ⣾⣿⣿⠟     ⣼⣿⣿⡇   ⣿⣿⣿          ⢄⠕⢅⠅     ⢹⣿⣿⡇                              
         ⠛⠿⣿⣿⣿⣦    ⢰⣿⣿⡿    ⣾⣿⣿⠃      ⣿⣿⣿    ⢻⣿⣿⣷⣦⣄       ⢅⠕⢕      ⣼⣿⣿⡇  ⢰⡟  ⠛  ⠛  ⠻⡆    ⢰⢟⠟⢝⠟⢝⠟⢝⠟⢝⠟⣦
            ⢿⣿⣿⡇   ⣾⣿⣿⠇   ⢸⣿⣿⡏      ⣼⣿⣿⡇      ⠛⠿⣿⣿⣿⣦    ⠔⢅⠕⠅      ⣿⣿⣿                ⣠⣄  ⢳⠕⢅⠕⢅⠕⢅⠕⢅⣽⠋
            ⣸⣿⣿⡇   ⣿⣿⣿    ⢸⣿⣿⡇      ⣿⣿⣿          ⢹⣿⣿⡇   ⠕⢅⢕      ⣼⣿⣿⡇     ⠛      ⠚   ⠉    ⢻⣅⠕⢅⠕⢅⢕⡽  
  ⣶⣤⣀     ⣠⣴⣿⣿⡿    ⣿⣿⣿⣄   ⠸⣿⣿⣷⣄   ⣠⣾⣿⣿⣏   ⣤⣀     ⣼⣿⣿⠇  ⢄⠕⢅⠅      ⣿⣿⣿       ⠻    ⠟  ⠺    ⠻  ⠹⣕⢅⠕⢅⡽   
 ⠾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠋     ⠙⢿⣿⣿⡿   ⠙⢿⣿⣿⣿⣿⣿⣿⠟⣿⣿⣿  ⢾⣿⣿⣿⣿⣿⣿⣿⣿⠿⠋   ⢅⠕⢕      ⣼⣿⣿⡇         ⣦⣤   ⡶      ⠰⡆ ⠙⣥⣵⡟    
     ⠉⠉⠉⠉⠉                    ⠉⠉⠉           ⠉⠉⠉⠉                                 ⡄         ⣄        
                                                                                 ⠳  ⠶  ⠶⠆ ⠾⠋        
                                                                                                    
Live output, 100 columns

The same photograph, three ways

On a photograph rather than a logo, with the background removed:

A 3D-rendered astronaut drawn with ASCII characters in a terminal
--charset ascii --line-art -w 32 --remove-bg
The same astronaut drawn with coloured half-block characters
--charset blocks --line-art -w 32 --remove-bg
The same astronaut drawn with braille dot characters
--charset braille --line-art -w 32 --remove-bg

Logos and thin lines

A stroke narrower than a character cell is a coverage problem, not a resolution problem. A cell paints only once it is at least half covered, so a hairline crossing a cell covers perhaps a fifth of it, falls short, and the line arrives as speckle or not at all.

--line-art lowers that cutoff and turns off the median filter, which is a majority vote among neighbours that any stroke thinner than its 3×3 window loses.

slash-ascii logo.svg --charset blocks --line-art -w 124
                                                                                                                 ▄▀▀▀▀▀▀▄▄  
                                                                                                               ▄▀▀▀▀▀▀▀▀▀▀▀ 
                                                                                                               ▀▀▀▀▀▀▀▀▀▀▀▀▀
                                                                                                               ▀▀▀▀▀▀▀▀▀▀▀▀▀
                                                                                                               ▀▀▀▀▀▀▀▀▀▀▀▀ 
                                                                                                                ▀▀▀▀▀▀▀▀▀▀  
                                                                                                                    ▀▀▀     
                            ▀▀▀▀▀                                         ▀▀▀▀▀                ▄▄▄                 ▄▄▄      
          ▄▄▀▀▀▀▀▀▀▄▄▄      ▀▀▀▀                                          ▀▀▀▀                 ▀ ▀▀               ▄▀▀▀▀▄    
       ▄▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀    ▀▀▀▀▀                                         ▀▀▀▀▀               ▀▀    ▀▄            ▄▀▀▀▀▀▀▄   
      ▄▀▀▀▀▀       ▀▀      ▀▀▀▀                                          ▀▀▀▀               ▄        ▄          ▀▀▀▀▀▀▀▀▀▄  
      ▀▀▀▀                ▀▀▀▀▀          ▄▄▄▄▄▄▄▄▄▄       ▄▄▄▄▄▄▄▄▄     ▀▀▀▀▀▄▄▄▄▄▄▄       ▀▀        ▀         ▀▀▀▀▀▀▀▀▀▀▀▄ 
      ▀▀▀▀▄               ▀▀▀▀        ▄▀▀▀▀▀▀▀▀▀▀▀▀    ▄▀▀▀▀▀▀▀▀▀▀▀▀    ▀▀▀▀▀▀▀▀▀▀▀▀▀▄    ▄            ▄      ▀▀▀▀▀▀▀▀▀▀▀▀▀▀
      ▀▀▀▀▀▄▄            ▀▀▀▀▀      ▀▀▀▀▀▀▀▀  ▄▀▀▀▀   ▄▀▀▀▀▀     ▀▀    ▀▀▀▀▀▀   ▀▀▀▀▀▀    ▀▀  ▀▀  ▀▀  ▀▀      ▀▀▀▀▀▀▀▀▀▀▀▀▀▀
       ▀▀▀▀▀▀▀▀▄▄        ▀▀▀▀     ▄▀▀▀▀▀      ▀▀▀▀    ▀▀▀▀             ▀▀▀▀       ▀▀▀▀                                      
          ▀▀▀▀▀▀▀▀▄     ▀▀▀▀▀    ▄▀▀▀▀       ▄▀▀▀▀    ▀▀▀▀▀▄▄         ▀▀▀▀▀       ▀▀▀▀    ▀▀  ▀▀ ▀▀▀ ▀▀▄      ▀▀▀▀▀▀▀▀▀▀▀▀▀▄
              ▀▀▀▀▀     ▀▀▀▀     ▀▀▀▀        ▀▀▀▀      ▀▀▀▀▀▀▀▀▄      ▀▀▀▀       ▄▀▀▀▀    ▀            ▀      ▀▀▀▀▀▀▀▀▀▀▀▀▀▀
               ▀▀▀▀▀   ▀▀▀▀▀     ▀▀▀▀       ▄▀▀▀▀          ▀▀▀▀▀▀    ▀▀▀▀▀       ▀▀▀▀▀     ▀▄        ▀▀  ▀▀▀▄  ▀▀▀▀▀▀▀▀▀▀▀▀ 
               ▀▀▀▀    ▀▀▀▀      ▀▀▀▀       ▀▀▀▀             ▀▀▀▀    ▀▀▀▀        ▀▀▀▀        ▄          ▄    ▄  ▀▀▀▀▀▀▀▀▀▀  
  ▄▀▄▄▄    ▄▄▄▀▀▀▀▀    ▀▀▀▀▄▄    ▀▀▀▀▄    ▄▀▀▀▀▀    ▄▄▄     ▄▀▀▀▀   ▄▀▀▀▀       ▀▀▀▀▀        ▀▀    ▀▀  ▀▀    ▀▀  ▀▀▀▀▀▀▀▀   
 ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀       ▀▀▀▀▀▀    ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▄  ▀▀▀▀▀▀▀▀▀▀▀▀▀    ▀▀▀▀        ▀▀▀▀           ▄▄▄▄  ▄▀        ▀  ▀▀▀▀▀     
   ▀▀▀▀▀▀▀▀▀▀▀            ▀▀▀      ▀▀▀▀▀▀▀▀  ▀▀▀▀   ▀▀▀▀▀▀▀▀▀▀      ▀▀▀▀       ▀▀▀▀▀            ▀▀   ▀         ▀    ▀▀      
                                                                                                    ▀▄  ▄▄  ▄▄  ▄▀          
                                                                                                     ▀  ▀▀  ▀▀  ▀           
                                                                                                                            
Live output with --line-art, 124 columns

It only ever adds ink, never removes it. The cost is that a cutoff low enough to catch a stroke also catches the antialiased rim of a solid shape, so silhouettes grow by up to a cell.

There is a floor. A feature narrower than one cell can be detected but not drawn at its true width. Roughly, you want columns ≥ image width ÷ thinnest stroke width — so a 2px rule on a 318px logo wants about 160 columns.

Remove the background

--remove-bg runs a saliency model over the image, keeps the largest connected region, and crops to it.

slash-ascii portrait.jpg --remove-bg

This is the one feature that needs a model file, and it is not bundled. The first time you use it the tool tells you exactly what it wants to download, how big it is, where it comes from and where it will be saved — then asks. Pressing Enter declines, and declining is not an error.

TierModelSizeGood for
lite (default)u2netp4.6 MBGeneral subjects, fast
fullu2net176 MBFine edges, hair, complex outlines

Options

FlagDefaultWhat it does
-w, --width <n>terminal widthOutput width in columns
-h, --height <n>from the imageOutput height in rows
--char-aspect <n>0.5Cell width ÷ cell height
-c, --color <mode>autotrue, 256, mono
--charset <name>asciiascii, blocks, braille
--ramp <chars>" .:-=+*#%@"Ramp characters, darkest first
--invertoffFlip the ramp, for light backgrounds
--no-edgesedges onBrightness only, no line characters
--no-denoiseon for bitmapsSkip the median filter
--line-artoffKeep hairlines
--alpha-cutoff <n>0.5Coverage a cell needs to paint
--remove-bgoffKeep only the subject
--model <tier>litelite or full
--threshold <n>0.5Mask cutoff, 0 to 1
--format <fmt>ansiansi, txt, html, svg
-o, --output <file>stdoutWrite to a file
--offlineoffFail rather than fetch anything
-y, --yesoffApprove a model download without asking
--model-dir <path>cache directoryWhere models are stored

Recipes

Save it as a file

slash-ascii photo.jpg --format txt -o art.txt

Keep the colour, as a web page or an image

slash-ascii photo.jpg --format html -o art.html
slash-ascii photo.jpg --format svg  -o art.svg

Convert something from the internet, without saving it first

curl -s https://example.com/photo.jpg | slash-ascii - --format svg -o art.svg

On a light terminal

slash-ascii photo.jpg --invert

Use it from JavaScript

import { convert } from 'slash-ascii';
import { readFile } from 'node:fs/promises';

const art = await convert(await readFile('photo.jpg'), {
  width: 80,
  format: 'txt',
});

What it does on your network

  1. Installing the package makes no network requests. There is no postinstall script.
  2. Every feature except background removal works with the network unplugged.
  3. Nothing is downloaded implicitly, in the background, or on a schedule.
  4. The only thing ever fetched is a segmentation model, only when you ask for it, and only after you say yes.

Downloads are checked against a pinned size and SHA-256 before the file is moved into place, and the checksum is re-verified every time it is loaded. In CI or under cron, where there is nobody to ask, the tool does not hang and does not download — it explains what it needs and exits.