Skip to content
Joe Lim edited this page Nov 25, 2023 · 43 revisions

Image Previews

With Überzug

Note

The original Überzug project is no longer maintained. You might want to use one of the alternative forks instead:

If you want to use Überzug for image previews, it will be necessary to start both lf and Überzug in a wrapper script. Place a file with the following contents in your PATH. You will be using this file to start lf with preview utilities:

#!/bin/sh
set -e

if [ -n "$DISPLAY" ]; then
  export FIFO_UEBERZUG="${TMPDIR:-/tmp}/lf-ueberzug-$$"

  cleanup() {
    exec 3>&-
    rm "$FIFO_UEBERZUG"
  }

  mkfifo "$FIFO_UEBERZUG"
  ueberzug layer -s <"$FIFO_UEBERZUG" &
  exec 3>"$FIFO_UEBERZUG"
  trap cleanup EXIT

  if ! [ -d "$HOME/.cache/lf" ]; then
    mkdir -p "$HOME/.cache/lf"
  fi

  lf "$@" 3>&-
else
  exec lf "$@"
fi

Make sure the file is executable. Using a preview cache in ~/.cache/lf is optional. You may remove those lines if you don't need them, but take note that the previewer script must also be modified so as not to use an image cache as well.

Now, make a file named draw_img.sh in the lf configuration folder with the following contents:

#!/bin/sh
if [ -n "$FIFO_UEBERZUG" ]; then
  path="$(printf '%s' "$1" | sed 's/\\/\\\\/g;s/"/\\"/g')"
  printf '{"action": "add", "identifier": "preview", "x": %d, "y": %d, "width": %d, "height": %d, "scaler": "contain", "scaling_position_x": 0.5, "scaling_position_y": 0.5, "path": "%s"}\n' \
    "$4" "$5" "$2" "$3" "$1" >"$FIFO_UEBERZUG"
fi

And also a file named clear_img.sh in the lf configuration folder:

#!/bin/sh
if [ -n "$FIFO_UEBERZUG" ]; then
  printf '{"action": "remove", "identifier": "preview"}\n' >"$FIFO_UEBERZUG"
fi

Next, modify your previewer script to call our draw_img.sh script. Make a file named "previewer.sh" under the lf configuration directory, and make sure it's executable. Your previewer script may look like this:

#!/bin/sh
draw() {
  ~/.config/lf/draw_img.sh "$@"
  exit 1
}

hash() {
  printf '%s/.cache/lf/%s' "$HOME" \
    "$(stat --printf '%n\0%i\0%F\0%s\0%W\0%Y' -- "$(readlink -f "$1")" | sha256sum | awk '{print $1}')"
}

cache() {
  if [ -f "$1" ]; then
    draw "$@"
  fi
}

file="$1"
shift

if [ -n "$FIFO_UEBERZUG" ]; then
  case "$(file -Lb --mime-type -- "$file")" in
    image/*)
      orientation="$(identify -format '%[EXIF:Orientation]\n' -- "$file")"
      if [ -n "$orientation" ] && [ "$orientation" != 1 ]; then
        cache="$(hash "$file").jpg"
        cache "$cache" "$@"
        convert -- "$file" -auto-orient "$cache"
        draw "$cache" "$@"
      else
        draw "$file" "$@"
      fi
      ;;
    video/*)
      cache="$(hash "$file").jpg"
      cache "$cache" "$@"
      ffmpegthumbnailer -i "$file" -o "$cache" -s 0
      draw "$cache" "$@"
      ;;
  esac
fi

file -Lb -- "$1" | fold -s -w "$width"
exit 0

Take note that the draw() function exits with code 1. This is to signal lf not to cache the result of the previewer script so that the next time the user selects the same file the previewer script will be executed again.

Now, place the following lines in your lfrc:

set previewer ~/.config/lf/previewer.sh
set cleaner ~/.config/lf/clear_img.sh

More extensive examples are available at the following repositories:

With w3mimgdisplay

Make a file named draw_img.sh in the lf configuration folder with the contents below:

#!/usr/bin/env bash

clear_screen() {
    printf '\e[%sH\e[9999C\e[1J%b\e[1;%sr' \
           "$((LINES-2))" "${TMUX:+\e[2J}" "$max_items"
}

# Get a file's mime_type.
mime_type=$(file -bi "$1")

# File isn't an image file, give warning.
if [[ $mime_type != image/* ]]; then
    lf -remote "send $id echoerr 'Not an image'"
    exit
fi

w3m_paths=(/usr/{local/,}{lib,libexec,lib64,libexec64}/w3m/w3mi*)
read -r w3m _ < <(type -p w3mimgdisplay "${w3m_paths[@]}")
read -r LINES COLUMNS < <(stty size)

# Get terminal window size in pixels and set it to WIDTH and HEIGHT.
export $(xdotool getactivewindow getwindowgeometry --shell)

# Get the image size in pixels.
read -r img_width img_height < <("$w3m" <<< "5;${CACHE:-$1}")

((img_width > WIDTH)) && {
    ((img_height=img_height*WIDTH/img_width))
    ((img_width=WIDTH))
}

((img_height > HEIGHT)) && {
    ((img_width=img_width*HEIGHT/img_height))
    ((img_height=HEIGHT))
}

# Variable needed for centering image.
HALF_HEIGHT=$(expr $HEIGHT / 2)
HALF_WIDTH=$(expr $WIDTH / 2)
HALF_IMG_HEIGHT=$(expr $img_height / 2)
HALF_IMG_WIDTH=$(expr $img_width / 2)
X_POS=$(expr $HALF_WIDTH - $HALF_IMG_WIDTH)
Y_POS=$(expr $HALF_HEIGHT - $HALF_IMG_HEIGHT)

clear_screen
# Hide the cursor.
printf '\e[?25l'

# Display the image.
printf '0;1;%s;%s;%s;%s;;;;;%s\n3;\n4\n' \
    ${X_POS:-0} \
    ${Y_POS:-0} \
    "$img_width" \
    "$img_height" \
    "${CACHE:-$1}" | "$w3m" &>/dev/null

# Wait for user input.
read -ern 1

# Clear the image.
printf '6;%s;%s;%s;%s\n3;' \
    "${X_POS:-0}" \
    "${Y_POS:-0}" \
    "$WIDTH" \
    "$HEIGHT" | "$w3m" &>/dev/null

clear_screen

Now add key mappings corresponding to the script.

As key mappings

If you only want to preview images on a case by case basis, you may map the draw_img.sh script to a key:

map - $~/.config/lf/draw_img.sh "$f"

And likewise for video previews:

cmd video_preview ${{
    cache="$(mktemp "${TMPDIR:-/tmp}/thumb_cache.XXXXX")"
    ffmpegthumbnailer -i "$f" -o "$cache" -s 0
    ~/.config/lf/draw_img.sh "$cache"
}}
map + :video_preview

With Kitty and Pistol

The following setup will use kitty to display images, and fall back to pistol for everything else. This also works with the wezterm or Konsole terminal.

As usual, we'll specify a previewer and a cleaner in ~/.config/lf/lfrc:

set previewer ~/.config/lf/lf_kitty_preview
set cleaner ~/.config/lf/lf_kitty_clean

The cleaner script is ~/.config/lf/lf_kitty_clean:

#!/usr/bin/env bash

kitty +kitten icat --clear --stdin no --silent --transfer-mode file < /dev/null > /dev/tty

And the previewer is at ~/.config/lf/lf_kitty_preview:

#!/usr/bin/env bash
file=$1
w=$2
h=$3
x=$4
y=$5

if [[ "$( file -Lb --mime-type "$file")" =~ ^image ]]; then
    kitty +kitten icat --silent --stdin no --transfer-mode file --place "${w}x${h}@${x}x${y}" "$file" < /dev/null > /dev/tty
    exit 1
fi

pistol "$file"

Don't forget to mark the files as executable: chmod +x ~/.config/lf/lf_kitty_{clean,preview}

As with the setups above, you can integrate ffmpegthumbnailer into the previewer to it to support videos. The following modification, for example, uses the vidthumb script:

#!/usr/bin/env bash
file=$1
w=$2
h=$3
x=$4
y=$5

filetype="$( file -Lb --mime-type "$file")"

if [[ "$filetype" =~ ^image ]]; then
    kitty +kitten icat --silent --stdin no --transfer-mode file --place "${w}x${h}@${x}x${y}" "$file" < /dev/null > /dev/tty
    exit 1
fi

if [[ "$filetype" =~ ^video ]]; then
    # vidthumb is from here:
    # https://raw.githubusercontent.com/duganchen/kitty-pistol-previewer/main/vidthumb
    kitty +kitten icat --silent --stdin no --transfer-mode file --place "${w}x${h}@${x}x${y}" "$(vidthumb "$file")" < /dev/null > /dev/tty
    exit 1
fi

pistol "$file"

With Sixel

If your terminal has Sixel support, you can display image previews in Sixel format:

  1. Install chafa or any similar program that can convert images to Sixel.

  2. Set the sixel and previewer options in your config file.

  3. Add the following previewer script:

    #!/bin/sh
    
    case "$(file -Lb --mime-type -- "$1")" in
        image/*)
            chafa -f sixel -s "$2x$3" --animate off --polite on "$1"
            exit 1
            ;;
        *)
            cat "$1"
            ;;
    esac
    

Note

This only works partially for KDE konsole (images can't be cleaned up automatically, only by redrawing the screen with ctrl+l). In the official Sixel manual, printing new characters over an image is undefined, but most other terminals will overwrite the image.

With stpv and ctpv

stpv and ctpv are previewer utilities made for integrating into lf. No wrapper scripts are needed. You only need to add 4 lines in lf config to make either one of them work.

stpv is a POSIX shell script, while ctpv is a rewrite of stpv written in C. ctpv is faster and has a few additional features.

stpv:

set previewer stpv
set cleaner stpvimgclr
&stpvimg --listen $id
cmd on-quit $stpvimg --end $id

ctpv:

set previewer ctpv
set cleaner ctpvclear
&ctpv -s $id
&ctpvquit $id

Sandboxing preview operations

While the default lf configuration only previews text files, using more complex preview parsers is somewhat dangerous. In case there is a vulnerability in a preview parser like pdftotext, it is possible to use this simple script to sandbox the previewer:

#!/bin/bash
## previewSandbox.sh at ~/.config/lf/

set -euo pipefail
(
    exec bwrap \
     --ro-bind /usr/bin /usr/bin \
     --ro-bind /usr/share/ /usr/share/ \
     --ro-bind /usr/lib /usr/lib \
     --ro-bind /usr/lib64 /usr/lib64 \
     --symlink /usr/bin /bin \
     --symlink /usr/bin /sbin \
     --symlink /usr/lib /lib \
     --symlink /usr/lib64 /lib64 \
     --proc /proc \
     --dev /dev  \
     --ro-bind /etc /etc \
     --ro-bind ~/.config ~/.config \
     --ro-bind ~/.cache ~/.cache \
     --ro-bind "$PWD" "$PWD" \
     --unshare-all \
     --new-session \
     bash ~/.config/lf/preview.sh "$@"
)

Set your previewer to the sandbox script and have your real preview script at ~/.config/lf/preview.sh

set previewer ~/.config/lf/previewSandbox.sh

Clone this wiki locally