diff options
-rw-r--r-- | ranger/ext/img_display.py | 85 |
1 files 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('<HH', file_header[6:10]) + elif image_type == 'jpeg': + try: + file_handle.seek(0) + size = 2 + ftype = 0 + while not 0xc0 <= ftype <= 0xcf: + file_handle.seek(size, 1) + byte = file_handle.read(1) + while ord(byte) == 0xff: + byte = file_handle.read(1) + ftype = ord(byte) + size = 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. |