From 0b2f84884b987cd53973c9416ba8ae5d77e5e714 Mon Sep 17 00:00:00 2001 From: hut Date: Wed, 24 Apr 2013 22:30:59 +0200 Subject: ext.img_display: more efficient way to draw images Instead of loading up w3mimgdisplay for each image and killing it afterwards, we just open it once for the first image and keep it open. It can receive any number of commands, so we can just keep writing them into stdin. Perhaps it even caches the images to save time, I didn't test that yet. --- ranger/core/fm.py | 3 + ranger/ext/img_display.py | 164 ++++++++++++++++++++++++-------------------- ranger/gui/widgets/pager.py | 18 ++--- 3 files changed, 101 insertions(+), 84 deletions(-) diff --git a/ranger/core/fm.py b/ranger/core/fm.py index 918d1de2..b0d34359 100644 --- a/ranger/core/fm.py +++ b/ranger/core/fm.py @@ -19,6 +19,7 @@ from ranger.container.tags import Tags from ranger.gui.ui import UI from ranger.container.bookmarks import Bookmarks from ranger.core.runner import Runner +from ranger.ext.img_display import ImageDisplayer from ranger.ext.rifle import Rifle from ranger.container.directory import Directory from ranger.ext.signals import SignalDispatcher @@ -47,6 +48,7 @@ class FM(Actions, SignalDispatcher): self.start_paths = paths self.directories = dict() self.log = deque(maxlen=20) + self.image_displayer = ImageDisplayer() self.bookmarks = bookmarks self.current_tab = 1 self.tabs = {} @@ -329,6 +331,7 @@ class FM(Actions, SignalDispatcher): raise SystemExit finally: + self.image_displayer.quit() if ranger.arg.choosedir and self.thisdir and self.thisdir.path: # XXX: UnicodeEncodeError: 'utf-8' codec can't encode character # '\udcf6' in position 42: surrogates not allowed diff --git a/ranger/ext/img_display.py b/ranger/ext/img_display.py index b6f59f4f..3d710837 100644 --- a/ranger/ext/img_display.py +++ b/ranger/ext/img_display.py @@ -10,7 +10,12 @@ framebuffer) or in a Xorg session. w3m need to be installed for this to work. """ -import termios, fcntl, struct, sys, os +import fcntl +import os +import select +import struct +import sys +import termios from subprocess import Popen, PIPE W3MIMGDISPLAY_PATH = '/usr/lib/w3m/w3mimgdisplay' @@ -19,6 +24,91 @@ W3MIMGDISPLAY_OPTIONS = [] class ImgDisplayUnsupportedException(Exception): pass + +class ImageDisplayer(object): + is_initialized = False + + def initialize(self): + """start w3mimgdisplay""" + self.is_initialized = True + self.binary_path = os.environ.get("W3MIMGDISPLAY_PATH", None) + if not self.binary_path: + self.binary_path = W3MIMGDISPLAY_PATH + self.process = Popen([self.binary_path] + W3MIMGDISPLAY_OPTIONS, + stdin=PIPE, stdout=PIPE, universal_newlines=True) + + def draw(self, path, start_x, start_y, width, height): + """Draw an image at the given coordinates.""" + if not self.is_initialized or self.process.poll() is not None: + self.initialize() + self.process.stdin.write(self._generate_w3m_input(path, start_x, + start_y, width, height)) + self.process.stdin.flush() + self.process.stdout.readline() + + def clear(self, start_x, start_y, width, height): + """Clear a part of terminal display.""" + if not self.is_initialized or self.process.poll() is not None: + self.initialize() + + fontw, fonth = _get_font_dimensions() + + cmd = "6;{x};{y};{w};{h}\n4;\n3;\n".format( + x = start_x * fontw, + y = start_y * fonth, + w = (width + 1) * fontw, + h = height * fonth) + + self.process.stdin.write(cmd) + self.process.stdin.flush() + self.process.stdout.readline() + + def _generate_w3m_input(self, path, start_x, start_y, max_width, max_height): + """Prepare the input string for w3mimgpreview + + start_x, start_y, max_height and max_width specify the drawing area. + They are expressed in number of characters. + """ + fontw, fonth = _get_font_dimensions() + if fontw == 0 or fonth == 0: + raise ImgDisplayUnsupportedException() + + max_width_pixels = max_width * fontw + max_height_pixels = max_height * fonth + + # get image size + cmd = "5;{}\n".format(path) + + self.process.stdin.write(cmd) + self.process.stdin.flush() + output = self.process.stdout.readline().split() + + if len(output) != 2: + raise Exception('Failed to execute w3mimgdisplay', output) + + width = int(output[0]) + height = int(output[1]) + + # get the maximum image size preserving ratio + if width > max_width_pixels: + height = (height * max_width_pixels) // width + width = max_width_pixels + if height > max_height_pixels: + width = (width * max_height_pixels) // height + height = max_height_pixels + + return "0;1;{x};{y};{w};{h};;;;;{filename}\n4;\n3;\n".format( + x = start_x * fontw, + y = start_y * fonth, + w = width, + h = height, + filename = path) + + def quit(self): + if self.is_initialized: + self.process.kill() + + def _get_font_dimensions(): # Get the height and width of a character displayed in the terminal in # pixels. @@ -28,75 +118,3 @@ def _get_font_dimensions(): rows, cols, xpixels, ypixels = struct.unpack("HHHH", x) return (xpixels // cols), (ypixels // rows) - - -def _w3mimgdisplay(commands): - """Invoke w3mimgdisplay and send commands on its standard input.""" - path = os.environ.get("W3MIMGDISPLAY_PATH", None) - if not path: - path = W3MIMGDISPLAY_PATH - process = Popen([path] + W3MIMGDISPLAY_OPTIONS, stdin=PIPE, - stdout=PIPE, universal_newlines=True) - - # wait for the external program to finish - output, _ = process.communicate(input=commands) - - return output - -def generate_w3m_input(path, start_x, start_y, max_width, max_height): - """Prepare the input string for w3mimgpreview - - start_x, start_y, max_height and max_width specify the drawing area. - They are expressed in number of characters. - """ - fontw, fonth = _get_font_dimensions() - if fontw == 0 or fonth == 0: - raise ImgDisplayUnsupportedException() - - max_width_pixels = max_width * fontw - max_height_pixels = max_height * fonth - - # get image size - cmd = "5;{}".format(path) - output = _w3mimgdisplay(cmd).split() - - if len(output) != 2: - raise Exception('Failed to execute w3mimgdisplay') - - width = int(output[0]) - height = int(output[1]) - - # get the maximum image size preserving ratio - if width > max_width_pixels: - height = (height * max_width_pixels) // width - width = max_width_pixels - if height > max_height_pixels: - width = (width * max_height_pixels) // height - height = max_height_pixels - - return "0;1;{x};{y};{w};{h};;;;;{filename}\n4;\n3;".format( - x = start_x * fontw, - y = start_y * fonth, - w = width, - h = height, - filename = path) - -def draw(path, start_x, start_y, max_width, max_height): - """Draw an image file in the terminal. - - start_x, start_y, max_height and max_width specify the drawing area. - They are expressed in number of characters. - """ - _w3mimgdisplay(generate_w3m_input(path, start_x, start_y, max_width, max_height)) - -def clear(start_x, start_y, width, height): - """Clear a part of terminal display.""" - fontw, fonth = _get_font_dimensions() - - cmd = "6;{x};{y};{w};{h}\n4;\n3;".format( - x = start_x * fontw, - y = start_y * fonth, - w = (width + 1) * fontw, - h = height * fonth) - - _w3mimgdisplay(cmd) diff --git a/ranger/gui/widgets/pager.py b/ranger/gui/widgets/pager.py index 4a98e083..0b9071d7 100644 --- a/ranger/gui/widgets/pager.py +++ b/ranger/gui/widgets/pager.py @@ -8,7 +8,7 @@ from . import Widget from ranger.core.loader import CommandLoader from ranger.gui import ansi from ranger.ext.direction import Direction -import ranger.ext.img_display as img_display +from ranger.ext.img_display import ImgDisplayUnsupportedException # TODO: Scrolling in embedded pager class Pager(Widget): @@ -40,7 +40,7 @@ class Pager(Widget): def clear_image(self, force=False): if (force or self.need_clear_image) and self.image_drawn: - img_display.clear(self.x, self.y, self.wid, self.hei) + self.fm.image_displayer.clear(self.x, self.y, self.wid, self.hei) self.need_clear_image = False self.image_drawn = False @@ -90,18 +90,14 @@ class Pager(Widget): self.source = None self.need_redraw_image = False try: - cmd = CommandLoader([img_display.W3MIMGDISPLAY_PATH] + - img_display.W3MIMGDISPLAY_OPTIONS, - input=img_display.generate_w3m_input(self.image, - self.x, self.y, self.wid, self.hei), - descr="loading preview image", - silent=True, kill_on_pause=True) - self.fm.loader.add(cmd) - self.image_drawn = True - except img_display.ImgDisplayUnsupportedException: + self.fm.image_displayer.draw(self.image, self.x, self.y, + self.wid, self.hei) + except ImgDisplayUnsupportedException: self.fm.settings.preview_images = False except Exception as e: self.fm.notify(e, bad=True) + else: + self.image_drawn = True def _draw_line(self, i, line): if self.markup is None: -- cgit 1.4.1-2-gfad0