Repository: ipython/ipython

Issue:
UnicodeDecodeError when using Image(data="some.jpeg")
For all JPEGs I have tried, I get the following error:

```
from IPython.display import Image
Image(data=file("myimage.jpeg").read())

---------------------------------------------------------------------------
UnicodeDecodeError                        Traceback (most recent call last)
<ipython-input-89-c318d6052b14> in <module>()
----> 1 Image(data=file("/home/luke/images/marvin1.jpg").read())

/usr/lib/python2.7/dist-packages/IPython/core/displayhook.pyc in __call__(self, result)
    240             self.update_user_ns(result)
    241             self.log_output(format_dict)
--> 242             self.finish_displayhook()
    243 
    244     def flush(self):

/usr/lib/python2.7/dist-packages/IPython/zmq/displayhook.pyc in finish_displayhook(self)
     59         sys.stdout.flush()
     60         sys.stderr.flush()
---> 61         self.session.send(self.pub_socket, self.msg, ident=self.topic)
     62         self.msg = None
     63 

/usr/lib/python2.7/dist-packages/IPython/zmq/session.pyc in send(self, stream, msg_or_type, content, parent, ident, buffers, subheader, track, header)
    557 
    558         buffers = [] if buffers is None else buffers
--> 559         to_send = self.serialize(msg, ident)
    560         flag = 0
    561         if buffers:

/usr/lib/python2.7/dist-packages/IPython/zmq/session.pyc in serialize(self, msg, ident)
    461             content = self.none
    462         elif isinstance(content, dict):
--> 463             content = self.pack(content)
    464         elif isinstance(content, bytes):
    465             # content is already packed, as in a relayed message

/usr/lib/python2.7/dist-packages/IPython/zmq/session.pyc in <lambda>(obj)
     76 
     77 # ISO8601-ify datetime objects
---> 78 json_packer = lambda obj: jsonapi.dumps(obj, default=date_default)
     79 json_unpacker = lambda s: extract_dates(jsonapi.loads(s))
     80 

/usr/lib/python2.7/dist-packages/zmq/utils/jsonapi.pyc in dumps(o, **kwargs)
     70         kwargs['separators'] = (',', ':')
     71 
---> 72     return _squash_unicode(jsonmod.dumps(o, **kwargs))
     73 
     74 def loads(s, **kwargs):

/usr/lib/python2.7/dist-packages/simplejson/__init__.pyc in dumps(obj, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, encoding, default, use_decimal, namedtuple_as_object, tuple_as_array, bigint_as_string, sort_keys, item_sort_key, **kw)
    332         sort_keys=sort_keys,
    333         item_sort_key=item_sort_key,
--> 334         **kw).encode(obj)
    335 
    336 

/usr/lib/python2.7/dist-packages/simplejson/encoder.pyc in encode(self, o)
    235         # exceptions aren't as detailed.  The list call should be roughly
    236         # equivalent to the PySequence_Fast that ''.join() would do.
--> 237         chunks = self.iterencode(o, _one_shot=True)
    238         if not isinstance(chunks, (list, tuple)):
    239             chunks = list(chunks)

/usr/lib/python2.7/dist-packages/simplejson/encoder.pyc in iterencode(self, o, _one_shot)
    309                 Decimal=Decimal)
    310         try:
--> 311             return _iterencode(o, 0)
    312         finally:
    313             key_memo.clear()

UnicodeDecodeError: 'utf8' codec can't decode byte 0xff in position 0: invalid start byte
```

If I do:

```
Image(filename="myimage.jpeg")
```

it works fine. Using 'data=' also works fine for PNGs



Discussion:

Resolving patch:
diff --git a/IPython/core/display.py b/IPython/core/display.py
--- a/IPython/core/display.py
+++ b/IPython/core/display.py
@@ -7,7 +7,7 @@
 """
 
 #-----------------------------------------------------------------------------
-#       Copyright (C) 2008-2011 The IPython Development Team
+#       Copyright (C) 2013 The IPython Development Team
 #
 #  Distributed under the terms of the BSD License.  The full license is in
 #  the file COPYING, distributed as part of this software.
@@ -19,6 +19,8 @@
 
 from __future__ import print_function
 
+import os
+
 from .displaypub import (
     publish_pretty, publish_html,
     publish_latex, publish_svg,
@@ -28,6 +30,17 @@
 
 from IPython.utils.py3compat import string_types
 
+#-----------------------------------------------------------------------------
+# utility functions
+#-----------------------------------------------------------------------------
+
+def _safe_exists(path):
+    """check path, but don't let exceptions raise"""
+    try:
+        return os.path.exists(path)
+    except Exception:
+        return False
+
 #-----------------------------------------------------------------------------
 # Main functions
 #-----------------------------------------------------------------------------
@@ -248,20 +261,26 @@ def __init__(self, data=None, url=None, filename=None):
         Parameters
         ----------
         data : unicode, str or bytes
-            The raw data or a URL to download the data from.
+            The raw data or a URL or file to load the data from
         url : unicode
             A URL to download the data from.
         filename : unicode
             Path to a local file to load the data from.
         """
-        if data is not None and isinstance(data, string_types) and data.startswith('http'):
-            self.url = data
-            self.filename = None
-            self.data = None
-        else:
-            self.data = data
-            self.url = url
-            self.filename = None if filename is None else unicode(filename)
+        if data is not None and isinstance(data, string_types):
+            if data.startswith('http') and url is None:
+                url = data
+                filename = None
+                data = None
+            elif _safe_exists(data) and filename is None:
+                url = None
+                filename = data
+                data = None
+        
+        self.data = data
+        self.url = url
+        self.filename = None if filename is None else unicode(filename)
+        
         self.reload()
 
     def reload(self):
@@ -420,6 +439,9 @@ def _repr_javascript_(self):
         r += lib_t2*len(self.lib)
         return r
 
+# constants for identifying png/jpeg data
+_PNG = b'\x89PNG\r\n\x1a\n'
+_JPEG = b'\xff\xd8'
 
 class Image(DisplayObject):
 
@@ -476,7 +498,9 @@ def __init__(self, data=None, url=None, filename=None, format=u'png', embed=None
             ext = self._find_ext(url)
         elif data is None:
             raise ValueError("No image data found. Expecting filename, url, or data.")
-        elif isinstance(data, string_types) and data.startswith('http'):
+        elif isinstance(data, string_types) and (
+            data.startswith('http') or _safe_exists(data)
+        ):
             ext = self._find_ext(data)
         else:
             ext = None
@@ -487,6 +511,11 @@ def __init__(self, data=None, url=None, filename=None, format=u'png', embed=None
                 format = self._FMT_JPEG
             if ext == u'png':
                 format = self._FMT_PNG
+        elif isinstance(data, bytes) and format == 'png':
+            # infer image type from image data header,
+            # only if format might not have been specified.
+            if data[:2] == _JPEG:
+                format = 'jpeg'
 
         self.format = unicode(format).lower()
         self.embed = embed if embed is not None else (url is None)
diff --git a/IPython/utils/jsonutil.py b/IPython/utils/jsonutil.py
--- a/IPython/utils/jsonutil.py
+++ b/IPython/utils/jsonutil.py
@@ -97,7 +97,11 @@ def date_default(obj):
 
 # constants for identifying png/jpeg data
 PNG = b'\x89PNG\r\n\x1a\n'
+# front of PNG base64-encoded
+PNG64 = b'iVBORw0KG'
 JPEG = b'\xff\xd8'
+# front of JPEG base64-encoded
+JPEG64 = b'/9'
 
 def encode_images(format_dict):
     """b64-encodes images in a displaypub format dict
@@ -120,12 +124,21 @@ def encode_images(format_dict):
 
     """
     encoded = format_dict.copy()
+
     pngdata = format_dict.get('image/png')
-    if isinstance(pngdata, bytes) and pngdata[:8] == PNG:
-        encoded['image/png'] = encodebytes(pngdata).decode('ascii')
+    if isinstance(pngdata, bytes):
+        # make sure we don't double-encode
+        if not pngdata.startswith(PNG64):
+            pngdata = encodebytes(pngdata)
+        encoded['image/png'] = pngdata.decode('ascii')
+
     jpegdata = format_dict.get('image/jpeg')
-    if isinstance(jpegdata, bytes) and jpegdata[:2] == JPEG:
-        encoded['image/jpeg'] = encodebytes(jpegdata).decode('ascii')
+    if isinstance(jpegdata, bytes):
+        # make sure we don't double-encode
+        if not jpegdata.startswith(JPEG64):
+            jpegdata = encodebytes(jpegdata)
+        encoded['image/jpeg'] = jpegdata.decode('ascii')
+
     return encoded
 
 


Test patch: