Last active
July 14, 2022 23:49
-
-
Save ayosec/4c9675752bcae2cfaef9160cf64490e4 to your computer and use it in GitHub Desktop.
Show image thumbnails 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 | |
# | |
# Usage: | |
# | |
# $ list-images [files]* | |
# | |
# This script is a demo to show how to use hyperlinks with images in Alacritty. | |
# It can be used on any terminal emulator with Sixel support, but it is not | |
# intended to be used for regular use, mostly because it lacks a proper way to | |
# handle errors (it just dies). | |
# | |
# Dependencies: | |
# | |
# - A relatively recent version of Bash. | |
# - identify(1), from ImageMagick 6. | |
# - img2sixel(1), from libsixel. | |
# - nproc(1), from coreutils. | |
# | |
# img2sixel(1) is used instead of ImageMagick's convert(1) because the | |
# conversion to Sixel is much faster with it. | |
# | |
# There are two environment variables that can be used to change the behaviour | |
# of the script: | |
# | |
# THUMBNAIL_SIZE Number of rows to determine the size of the thumbnail. | |
# | |
# BORDER_COLOR Color of the highlight border. It is expected in | |
# hexadecimal form (i.e., "FF0000" is red). | |
set -euo pipefail | |
: "${THUMBNAIL_SIZE:=5}" | |
: "${BORDER_COLOR:=FF7700}" | |
printf -v BORDER \ | |
'\e[38;2;%d;%d;%dm' \ | |
$(("0x${BORDER_COLOR:0:2}")) \ | |
$(("0x${BORDER_COLOR:2:2}")) \ | |
$(("0x${BORDER_COLOR:4:2}")) | |
# Terminal dimensions. | |
IFS=';' read -p $'\e[14t' -rs -dt _ WIN_HEIGHT WIN_WIDTH | |
IFS=';' read -p $'\e[18t' -rs -dt _ ROWS COLUMNS | |
CELL_WIDTH=$((WIN_WIDTH / COLUMNS)) | |
CELL_HEIGHT=$((WIN_HEIGHT / ROWS)) | |
IMAGE_WIDTH=$((CELL_WIDTH * THUMBNAIL_SIZE * 2)) | |
IMAGE_HEIGHT=$((CELL_HEIGHT * THUMBNAIL_SIZE)) | |
NPROC=$(nproc) | |
# Main program. | |
declare -a workers=() | |
declare -a tmpfiles=() | |
trap cleanup EXIT | |
cleanup() { | |
if [ "${#tmpfiles[@]}" -gt 0 ] | |
then | |
rm -f "${tmpfiles[@]}" | |
tmpfiles=() | |
fi | |
} | |
declare -i CURRENT_ROW_OFFSET_X="$WIN_WIDTH" | |
declare -i CURRENT_ROW_HEIGHT=0 | |
# Reserve vertical space and save the cursor at the beginning. | |
start_row() { | |
if [ "$CURRENT_ROW_HEIGHT" -gt 0 ] | |
then | |
printf '\e8\e[%dB' $((CURRENT_ROW_HEIGHT / CELL_HEIGHT + 1)) | |
fi | |
for (( n = 0 ; n <= THUMBNAIL_SIZE ; n += 1 )) | |
do | |
printf '\n' | |
done | |
printf '\e[%dA\e7' "$THUMBNAIL_SIZE" | |
CURRENT_ROW_OFFSET_X=0 | |
CURRENT_ROW_HEIGHT=0 | |
} | |
# Draw a single image from the workers array. | |
draw() { | |
if [ "${#workers[@]}" -eq 0 ] | |
then | |
return | |
fi | |
# Extract first worker from the array. | |
local worker="${workers[0]}" | |
workers=("${workers[@]:1}") | |
read -rs width height < "$worker.size" | |
local next_offset_x=$((CURRENT_ROW_OFFSET_X + width)) | |
if [ "$next_offset_x" -ge "$WIN_WIDTH" ] | |
then | |
start_row | |
fi | |
if [ "${height}" -ge "$CURRENT_ROW_HEIGHT" ] | |
then | |
CURRENT_ROW_HEIGHT=${height} | |
fi | |
if [ "$CURRENT_ROW_OFFSET_X" -gt 0 ] | |
then | |
printf '\e8\e[%dC' $((CURRENT_ROW_OFFSET_X / CELL_WIDTH + 1)) | |
fi | |
printf '%s\e]8;;%s\a' "$BORDER" "$(cat "$worker.image")" | |
cat "$worker" | |
printf '\e]8;;\e[m\a' | |
CURRENT_ROW_OFFSET_X+=$((width + CELL_WIDTH * 2)) | |
} | |
for image in "$@" | |
do | |
if [ "${#workers[@]}" -ge "$NPROC" ] | |
then | |
# We have to wait for the process before calling draw() because the | |
# subshell created for the function can't wait for a process created | |
# in this scope. | |
wait "$(cat "${workers[0]}.pid")" | |
draw | |
fi | |
image=$(realpath "$image") | |
# The “worker” is a background shell where the image is rescaled | |
# and converted to Sixel. | |
worker=$(mktemp) | |
printf "%s" "$image" > "$worker.image" | |
( | |
identify -format '%w %h\n' "$image" > "$worker" | |
read -rs width height < "$worker" | |
if [ "$width" -gt "$height" ] | |
then | |
scale=(-w "$IMAGE_WIDTH" -h auto) | |
else | |
scale=(-h "$IMAGE_HEIGHT" -w auto) | |
fi | |
img2sixel \ | |
"${scale[@]}" \ | |
-r nearest \ | |
-q low \ | |
-o "$worker" \ | |
"$image" | |
identify -format '%w %h\n' sixel:"$worker" > "$worker.size" | |
) & | |
printf "%s" "$!" > "$worker.pid" | |
workers+=("$worker") | |
tmpfiles+=("$worker" "$worker.image" "$worker.pid" "$worker.size") | |
done | |
# Show pending images. | |
wait | |
while [ "${#workers[@]}" -gt 0 ] | |
do | |
draw | |
done | |
echo |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment