284 lines
8.2 KiB
Bash
Executable File
284 lines
8.2 KiB
Bash
Executable File
#!/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
|