summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorDelisa Mason <iskanamagus@gmail.com>2015-03-08 16:09:23 -0700
committerDelisa Mason <iskanamagus@gmail.com>2015-03-08 16:39:25 -0700
commit0d16709f82a2f15c43eb875c3f4c92a110600541 (patch)
tree996ecc9c05401187be2e8607025fbb45747f84a4
parent6b53c5d75bfa4ba3fb43d591ddd51527c1845555 (diff)
downloadranger-0d16709f82a2f15c43eb875c3f4c92a110600541.tar.gz
Scale iTerm2 image previews to fit within “best guess” dimensions
* Assume a character size of 8x11 as a good average which should fit most
scenarios
-rw-r--r--ranger/ext/img_display.py85
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.