From 0d16709f82a2f15c43eb875c3f4c92a110600541 Mon Sep 17 00:00:00 2001 From: Delisa Mason Date: Sun, 8 Mar 2015 16:09:23 -0700 Subject: Scale iTerm2 image previews to fit within “best guess” dimensions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Assume a character size of 8x11 as a good average which should fit most scenarios --- ranger/ext/img_display.py | 85 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 78 insertions(+), 7 deletions(-) diff --git a/ranger/ext/img_display.py b/ranger/ext/img_display.py index 7ff611ca..1f5be2be 100644 --- a/ranger/ext/img_display.py +++ b/ranger/ext/img_display.py @@ -8,6 +8,7 @@ implementations, which are currently w3m and iTerm2. import base64 import curses import fcntl +import imghdr import os import select import struct @@ -130,6 +131,9 @@ class ITerm2ImageDisplayer(ImageDisplayer, FileManagerAware): Ranger must be running in iTerm2 for this to work. """ + _minimum_font_width = 8 + _minimum_font_height = 11 + def draw(self, path, start_x, start_y, width, height): curses.putp(curses.tigetstr("sc")) sys.stdout.write(curses.tparm(curses.tigetstr("cup"), start_y, start_x)) @@ -141,19 +145,47 @@ class ITerm2ImageDisplayer(ImageDisplayer, FileManagerAware): self.fm.ui.win.redrawwin() self.fm.ui.win.refresh() - def _generate_iterm2_input(self, path, width, height): + def _generate_iterm2_input(self, path, max_cols, max_rows): """Prepare the image content of path for image display in iTerm2""" + image_width, image_height = self._get_image_dimensions(path) + if max_cols == 0 or max_rows == 0 or image_width == 0 or image_height == 0: + return "" + image_width = self._fit_width( + image_width, image_height, max_cols, max_rows) content = self._encode_image_content(path) - text = "\033]1337;File=inline=1;preserveAspectRatio=0" - text += ";size=" + str(len(content)) - text += ";width=" + str(width) - text += ":" + content - text += "\a\n" + text = "\033]1337;File=inline=1;preserveAspectRatio=0;" + text += "size={0};width={1}px:{2}\a\n".format( + str(len(content)), + str(int(image_width)), + content) return text + def _fit_width(self, width, height, max_cols, max_rows): + max_width = self._minimum_font_width * max_cols + max_height = self._minimum_font_height * max_rows + if height > max_height: + if width > max_width: + width_scale = max_width/float(width) + height_scale = max_height/float(height) + min_scale = min(width_scale, height_scale) + max_scale = max(width_scale, height_scale) + if width * max_scale <= max_width and height * max_scale <= max_height: + return (width * max_scale) + else: + return (width * min_scale) + else: + scale = max_height/float(height) + return (width * scale) + elif width > max_width: + scale = max_width/float(width) + return (width * scale) + else: + return width + + def _encode_image_content(self, path): """Read and encode the contents of path""" - file = open(path, "r") + file = open(path, 'rb') try: return base64.b64encode(file.read()) except: @@ -161,6 +193,45 @@ class ITerm2ImageDisplayer(ImageDisplayer, FileManagerAware): finally: file.close() + def _get_image_dimensions(self, path): + """Determine image size using imghdr""" + file_handle = open(path, 'rb') + file_header = file_handle.read(24) + image_type = imghdr.what(path) + if len(file_header) != 24: + file_handle.close() + return 0, 0 + if image_type == 'png': + check = struct.unpack('>i', file_header[4:8])[0] + if check != 0x0d0a1a0a: + file_handle.close() + return 0, 0 + width, height = struct.unpack('>ii', file_header[16:24]) + elif image_type == 'gif': + width, height = struct.unpack('H', file_handle.read(2))[0] - 2 + file_handle.seek(1, 1) + height, width = struct.unpack('>HH', file_handle.read(4)) + except: + file_handle.close() + return 0, 0 + else: + file_handle.close() + return 0, 0 + file_handle.close() + return width, height + def _get_font_dimensions(): # Get the height and width of a character displayed in the terminal in # pixels. -- cgit 1.4.1-2-gfad0