Created
June 3, 2017 03:36
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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