Skip to content

Instantly share code, notes, and snippets.

@hackerb9
Created June 3, 2017 03:36
  • Star 12 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save hackerb9/a96cea91e6122d09a6c97f5eb797d5fa to your computer and use it in GitHub Desktop.
sixcat: Like 'cat' but show images directly in the terminal using SIXEL graphics
#!/bin/bash
# sixcat: Use sixel graphics to show an image inside a terminal.
# sixgif: Use sixel graphics to play an animation inside a terminal.
# Version 1.0
# hackerb9, June 2017
# Sixel graphics are supported by terminals such as the DEC VT340 and
# emulators, such as 'xterm -ti vt340'.
# ImageMagick is required. (apt-get install imagemagick)
# The heart of this program is really just one line:
#
# convert foo.jpg sixel:-
#
# All the other lines are bells and whistles, mostly to make the image
# look better by increasing the number of colors available.
######################################################################
# Configuration
######################################################################
# Number of colors in the terminal's sixel palette.
# (But also see the -x and -n options).
default_numcolors=16 # Genuine vt340's had a 16-color palette.
# Maximum image size is terminal dependent, so we have to guess.
geometry="-geometry 800x480>" # this was the actual vt340 resolution
#geometry="-geometry 800x800>" # allows for portrait orientation
#geometry="-geometry 1000x1000>" # maximum allowed in xterm322
# Should ImageMagick dither the image to make it look better?
# (Use dither="" to disable.)
dither="-dither floyd-steinberg"
# Maximum number of seconds to wait for a response from the terminal
# after a color escape sequence request. Usually, terminals respond
# much faster than 0.1 seconds, but I could imagine it needing to be
# increased for slow links (e.g., RS232C).
timeout=0.1
######################################################################
# Helper functions
######################################################################
usage() {
cat <<EOF
Usage: $(basename $0) [-x|-X|-n INTEGER] <imagefiles...>
-x: Force the use of xterm escape sequences to detect and
change the palette size instead of defaulting to $default_numcolors.
If the palette is smaller than 1024, it is set to 1024.
Note that -x is the default if \$TERM is "xterm".
-X: Just assume palette size is $default_numcolors.
Does not use xterm escape sequences regardless of \$TERM.
-n INTEGER:
Assume pallete size is INTEGER.
Does not use xterm escape sequences regardless of \$TERM.
Note: if this program is called sixgif, it will play GIF animations
in the top left instead of scrolling all the frames as multiple images.
EOF
}
# Some functions to deal with checking and changing the number of
# colors the terminal can show. Likely xterm specific, but YMMV.
getnumcolors() {
# Query xterm for the palette size and print it.
# (Negative numbers indicate errors.)
colors 1 0
}
resetnumcolors() {
# Reset xterm back to its default number of colors.
# Print the new number of colors (negative if an error occurred.)
colors 2 0
}
setnumcolors() {
# Request xterm to change the number of colors in its palette.
# Print the new number of colors (negative if an error occurred.)
colors 3 $1
}
colors() {
# Send an xterm escape code request in the form: ^[[?1;3;1024S
# Print the response value (or a negative number if an error occurs)
csi=$'\e['
action=$1 # Action. 1 to get, 2 to reset, 3 to set
value=$2 # Number of colors to set
returnvalue=-1
# Raw response is in the same form as request: ^[[?1;0;1024S
# Let's parse it to be more manageable.
IFS=";" \
read -a REPLY -s -t $timeout -d "S" \
-p "${csi}?1;${action};${value}S"
if [[ $? != 0 || ${#REPLY} == 0 ]]; then
# No response to VT escape sequence. Maybe you are not an xterm?
# Or, perhaps timeout is too short?
returnvalue=-2
elif [[ "${REPLY[1]}" == "3" ]]; then
# Response code 3 means error. Likely too many colors requested.
returnvalue=-3
elif [[ "${REPLY[1]}" == "0" ]] ; then
# 0 means okay. Yay!
returnvalue=${REPLY[2]}
else
# If we get here, response was not in the format we expected.
# Perhaps user typed during xterm response? Or cosmic rays?
returnvalue=-4
fi
echo $returnvalue
}
waitforterminal() {
# Send an escape sequence and wait for a response from the terminal.
# This routine will let us know when an image transfer has finished
# and it's okay to send escape sequences that request results.
# (E.g., if we want to run resetnumcolors at the end of the program.)
# WARNING. While this should work with any terminal, if it fails,
# it'll end up waiting for approximately *forever* (i.e., 60 seconds).
read -s -t 60 -d "c" -p $'\e[c'
}
debugeta() {
# Given a number of bytes, estimate the amount of time its going
# to take to send at the current TTY baud rate.
# Mostly for larks as this will be completely bogus on terminal emulators.
len=$1
# Static variables to store TTY type and STTY speed.
[[ "$TTY" ]] || TTY=$(tty)
[[ "$SPEED" ]] || SPEED=$(stty speed) || SPEED=0
echo "sixel command length is $len bytes"
if [[ "$SPEED" -gt 0 ]]; then
if [[ "$TTY" =~ /pt || "$TTY" =~ ttyp ]]; then
echo "pseudoterminal detected (speed estimates only valid for real hardware)"
fi
echo "terminal speed is $SPEED bps"
echo "estimated time to send: $((len*8/SPEED)) seconds"
fi
}
# If the user hits ^C, we don't want them stuck in SIXEL mode
cleanup() {
echo -n $'\e[?80h' # Reenable sixel scrolling
echo -n $'\e\\' # Escape sequence to stop SIXEL
exit 0
}
trap cleanup SIGINT SIGHUP SIGABRT
######################################################################
# MAIN BEGINS HERE
######################################################################
if [ $# -eq 0 ]; then
usage
exit 1
else
case "$1" in
-n|--num-colors) shift
nflag="$1"
shift
# Just assume the palette is n colors. Do not bother checking or changing.
# (Might be needed for some terminal emulators. I do not know.)
if [[ "$nflag" -gt 0 ]]; then
default_numcolors=$nflag
else
usage
exit 1
fi
;;
-x|--force-xterm-colors) shift
# Force xterm palette manipulation, regardless of $TERM
# (Might be needed for some terminal emulators. I do not know.)
xflag=yup
;;
-X|--disable-xterm-colors) shift
# Disable xterm palette manipulation, regardless of $TERM
# (Might be needed for some terminal emulators. I do not know.)
Xflag=yup
;;
-*)
usage
exit 1
;;
esac
fi
numcolors=$default_numcolors
# Optionally detect and set the number of palette colors
if [[ "$xflag" || -z "$Xflag" && -z "$nflag" && "$TERM" =~ ^xterm ]]; then
# Since the VT340 only had a palette of 16 colors, we can vastly
# improve the image if we tell xterm to increase it to 1024.
# This would fail on a real VT340.
palettechanged=yup
oldnumcolors=$(getnumcolors)
if [[ $oldnumcolors -lt 1024 ]]; then
numcolors=$(setnumcolors 1024)
if [[ $numcolors -le 0 ]]; then
echo -e "\rTerminal did not accept escape sequence to increase color palette. Are you really an xterm?" >&2
numcolors=$default_numcolors
fi
else
# At least 1024 colors, so do not change anything
numcolors=$oldnumcolors
palettechanged=
fi
fi
# Small optimization (saves half a second)
if [[ $numcolors == 1024 ]]; then
# Imagemagick already defaults to 1024 colors for sixel
colors=""
else
colors="-colors $numcolors"
fi
# Print image filename after showing it? Yes, if multiple files.
if [[ $# -gt 1 ]]; then
showname=yup
else
showname=""
fi
# PAST IS PROLOGUE... NOW TO THE MEAT.
for f; do
x=$(convert "$f" $geometry $colors sixel:- 2>/dev/null)
[[ ${#x} != 0 ]] || continue # The sixel command length better be >0
: debugeta ${#x}
case $0 in
*sixgif)
# If this program is named "sixgif" play files as animations,
# instead of displaying each frame and scrolling down.
echo -n $'\e[?80l' # Disable sixel scrolling
for t in {1..3}; do
echo -n "$x" # sixgif plays repeatedly
done
echo -n $'\e[?80h' # Reenable sixel scrolling
;;
*)
echo -n "$x" # sixcat just shows once
[[ "$showname" ]] && echo "$f"
;;
esac
done
# TIDY UP
waitforterminal
if [[ "$palettechanged" ]]; then
# Reset color palette
numcolors=$(resetnumcolors)
: echo Number of colors reset to $numcolors >&2
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment