[patch][RFC] display images *inside* urxvt

Raphaël Droz raphael.droz at gmail.com
Fri Feb 8 17:12:36 CET 2013


Hi all,

The attached patch intends to add support of image display inside urxvt.
Of course urxvt is already fully capable of displaying images as
backgrounds. But images "inside" the terminal window were not.

This screenshot (http://i.imgur.com/TdajM0c.jpg) gives an idea of some
of the possibilities: display one image or more, inlined or not, at full
width or as thumbnails. This can easily goes further to w3m|elinks -dump
(images attachments inside mutt, ...)
[ an (unsafe) POC patch for `w3m` -dump is also attached for your convenience ]



=== The patch *is* usable, but there are ===
* one issue in resizing (more specifically when terminal's height changes).
* one partial implementation: images wrapping when terminal's width decreases
* four missing parts:
  - handling of clear-screen (^L)
  - line renumbering when the scrollback circular buffer starts rolling
  - the ability to remove one given image by filename from term memory
  - automatic scrollback cleanup (though manual `reset` works too)

( I also considered grabbing the filename from X/mouse [drag & drop or
copy-filename-on-click] but didn't found a viable solution yet )


=== Implementation ===
* The code added by the patch is fully optional (--[en|dis]able-images)
* It relies on the gdk-pixbuf-xlib and depends on --enable-pixbuf
* The 21th XTerm Operating System Command was used (20 is bg pixmap setting)
  ie: printf "\33]21;filename.jpg;<flags>\007"
* <flags> define several aspects of the image: size, position, behavior
  and cursor movements following image registration and displayed.


Most of the grunt-work was about defining *where* and *when* display must
happens given the actual urxvt code and try to redraw the smallest
possible part of the area while still avoiding artifacts.
I've done some trade-off between efficiency and code complexity and I
believe knowledgeable people could do far better but I found the current
result to be reasonably responsive: once images are out of the visible
area, scrolling speed is not affected.

Thus, the most sensible part of the patch is about src/screen.C:
* ZERO_SCROLLBACK() macro
* scr_reset()
* scr_poweron()
* scr_scroll_text()
* scr_expose()
* scr_changeview()
* scr_refresh()
 are all concerned in terminal events which could imply images position
computation or pixbuf redraw.
These events are: click, mouse select, scrollbar, page-up/down, newline
and resizing.

Roughly: image redraw is always restricted to scr_refresh().
But the particularity is the need to refresh the whole screen using scr_touch().
Instead of hijacking the existing `want_refresh` variable, an
`pictures_need_expose` integer was used (2 = redraw all the visible screen)
Otherwise and if needed, only a limited area is redrawn using scr_expose().

pictures_set_next_expose() is about setting new position if the visible
area changed (but not line numbering).

image_recompute_pos() is when line numbering changes (right now, only
resizing seems to triggers this)

When more than 2 images are visible, partial area redraw isn't
practicable and a full screen redraw is always preferred.




=== Upper layers ===
To ease the use of this feature, and inspired by `tput` there's a `tputimg`
shell-script: it takes a simple set of options and a filename to wrap
the core printf syntax.
simple: $ tputimg <file>

On-top of `tputimg`: `ils` shell-script which intend to be a
kind of image-oriented /bin/ls. Long-format or table of images are
more or less supported.
$ ils -s 32 -7 .
# display images in . using ratio-perserved 32px wide thumbnails and 7
# images per line.




While this patch is still incomplete, but usable, I thought it was time
to request comments, mainly about 1) the feature, globally; and 2) the implementation.



thank you in advance.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: urxvt-9.16-image-display.patch
Type: text/x-diff
Size: 40969 bytes
Desc: not available
URL: <http://lists.schmorp.de/pipermail/rxvt-unicode/attachments/20130208/c1e45cff/attachment.patch>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: w3m-display-images.patch
Type: text/x-diff
Size: 1151 bytes
Desc: not available
URL: <http://lists.schmorp.de/pipermail/rxvt-unicode/attachments/20130208/c1e45cff/attachment-0001.patch>
-------------- next part --------------
#!/bin/bash

# Copyright (C) 2013, Raphaël . Droz + floss @ gmail DOT com

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# (urxvt) Relies upon the 21's "XTerm Operating System Command" in urxvt
# to display images inside the terminal window.
# This is a kind of wrapper to the tput utility intended to specifically
# wrap "display-image" urxvt code.

# It takes simple options and call the equivalent Xterm sequence.
# see doc/images.txt in the urxvt sources^Wpatch

param=
dim=32x-1
while getopts ":s:ieFW" opt; do
    case $opt in
	s)
	    [[ $OPTARG =~ ^[0-9-]+x[0-9-]+$ ]] && dim=$OPTARG
	    param+="dim:$dim;"
	    ;;
	i)
	    param+="pad:novert;"
	    ;;
	e)
	    param+="pos:eol;"
	    ;;
	W)
	    param+="wrap;"
	    ;;
	F)
	    # fake path for the "flush" flag
	    printf "\33]21;%s;%s\007" "/" "flush"
	    exit 0
	    ;;
    esac
done
shift $((OPTIND-1))

[[ ! -f "$1" ]] && echo "[$(basename $0)]can't find file $1" >&2 && exit 1

# path must be absolute
printf "\33]21;%s;%s\007" "$(realpath "$1")" "${param}"
-------------- next part --------------
#!/bin/bash

# Copyright (C) 2013, Raphaël . Droz + floss @ gmail DOT com

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.


# (urxvt) Relies upon the 21's "XTerm Operating System Command" in urxvt
# to display images inside the terminal window while wrapping /bin/ls.

# Use:
# ils [-l] [-s [<width>[x<height>]]] [-<N>] <files|dirs> ...

# -l: use the "long" format: images are displayed at the end of each line
# -s: set the size, default if 32px ratio-perserved. Height is also optionnal
# -<N>: with <N> being an integer, set the number of column to use when multiple
#	images are displayed inline (= number of images per line)

# Examples:
# $ ils image.png
# display image.png inside the terminal window without any transformation.
# Parts excessing terminal window dimensions will be hidden.

# $ ils -s 64 -10 ~/dir
# display, non-recursively, a table of all images from dir, using 10 thumbnails
# per line, each one being 64px wide and respecting image ratio.

# $ ils -l -s -1x90 *.jpg
# display using the `ls -l` format all *.jpg. Thumbnail will be 90 pixels high
# so all lines will be equally spaced from 90/<font-height> pixels.

dir=
name=
long=
help=
let colnum=5
let resize=0
size=32x-1

while [[ $1 ]]; do
    if [[ $1 == -s ]]; then
	shift; resize=1
	if [[ $1 =~ ^[0-9-]+x[0-9-]+$ ]]; then size="$1"; shift
	elif [[ $1 =~ ^[0-9-]+$ ]]; then size="$1x-1"; shift
	else continue
	fi
    elif [[ $1 =~ ^-[0-9]+$ ]]; then colnum=${1:1}; shift
    elif [[ $1 == -l ]]; then long='-l'; shift 
    elif [[ $1 == -h ]]; then help=1; shift
    else break
    fi
done

if [[ -n $help ]]; then
    cat<<EOF
usage: $(basename $0) [-l | -X] [-s [size]] [name] ...
Display images inside the (patched) urxvt window.
	 name: an image file or a directory ("." if omitted)
	 X: number of images per line
	 size: W[xH], default to 32x-1
EOF
exit 1
fi

dir=.
[[ -n "$1" ]] && dir="$1"


trap "tputimg -F" exit

if [[ $long ]]; then
    if [[ -n $LS_COLORS ]]; then
	cmd="ls -l --color=always"
    else
	cmd="ls -l"
    fi

    [[ -d "$dir" ]] && name="$dir" || name="$(dirname "$dir")"
    $cmd "$@" | \
	while read f; do
	    [[ $f =~ :$ ]] && name=${f:0:-1}
	    filename="$(printf "%s" "$f"|sed -e "s/\x1b\[0.;...//g" -e "s/\x1b\[0m//g" -e "s/\x1b\[K//g"|perl -lane 'print "@F[8..$#F]"')"
	    [[ -z $filename || ! "$filename" =~ \.(png|jpg|gif|tiff)$ ]] && printf "%s\n" "$f" && continue
	    if [[ -f $filename ]]; then
		sed "s@\$@ $(tputimg -s "$size" -eW "${filename}")@"<<<"$f"
	    else
		# $dir may help us to find the dirname from which ls extracted images
		sed "s@\$@ $(tputimg -s "$size" -eW "${name}/${filename}")@"<<<"$f"
	    fi
    done

elif [[ -d "$dir" ]]; then
    name="$dir" && shift
    let j=0
    for i in "$name/"*; do
	mime=$(file -b --mime-type "$i" 2>/dev/null|cut -d'/' -f1)
	[[ $mime != image ]] && continue;
	((j++))
	if (( $j == $colnum )); then
	    j=0
	    tputimg -s "$size" "$i"
	else
	    tputimg -s "$size" -i "$i"
	fi
    done
    tputimg -F

else
    (( $resize )) && args="-s $size" || args=
    files=( "$@" )
    let j=0
    for i in "${files[@]}"; do
	mime=$(file -b --mime-type "$i" 2>/dev/null|cut -d'/' -f1)
	[[ $mime != image ]] && continue;
	((j++))
	# default column number + no size specified, image go one
	# under this other
	if (( $j == $colnum || ( ! $resize && $colnum == 5 )  )) ; then
	    j=0
	    tputimg $args "$i"
	else
	    tputimg $args -i "$i"
	fi
    done
    tputimg -F    
fi


More information about the rxvt-unicode mailing list