##// END OF EJS Templates
Merge pull request #3381 from minrk/retina...
Paul Ivanov -
r11049:d820363e merge
parent child Browse files
Show More
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
@@ -20,6 +20,7 b' Authors:'
20 from __future__ import print_function
20 from __future__ import print_function
21
21
22 import os
22 import os
23 import struct
23
24
24 from IPython.utils.py3compat import string_types
25 from IPython.utils.py3compat import string_types
25
26
@@ -471,6 +472,32 b' class Javascript(DisplayObject):'
471 _PNG = b'\x89PNG\r\n\x1a\n'
472 _PNG = b'\x89PNG\r\n\x1a\n'
472 _JPEG = b'\xff\xd8'
473 _JPEG = b'\xff\xd8'
473
474
475 def _pngxy(data):
476 """read the (width, height) from a PNG header"""
477 ihdr = data.index(b'IHDR')
478 # next 8 bytes are width/height
479 w4h4 = data[ihdr+4:ihdr+12]
480 return struct.unpack('>ii', w4h4)
481
482 def _jpegxy(data):
483 """read the (width, height) from a JPEG header"""
484 # adapted from http://www.64lines.com/jpeg-width-height
485
486 idx = 4
487 while True:
488 block_size = struct.unpack('>H', data[idx:idx+2])[0]
489 idx = idx + block_size
490 if data[idx:idx+2] == b'\xFF\xC0':
491 # found Start of Frame
492 iSOF = idx
493 break
494 else:
495 # read another block
496 idx += 2
497
498 h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
499 return w, h
500
474 class Image(DisplayObject):
501 class Image(DisplayObject):
475
502
476 _read_flags = 'rb'
503 _read_flags = 'rb'
@@ -478,7 +505,7 b' class Image(DisplayObject):'
478 _FMT_PNG = u'png'
505 _FMT_PNG = u'png'
479 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG]
506 _ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG]
480
507
481 def __init__(self, data=None, url=None, filename=None, format=u'png', embed=None, width=None, height=None):
508 def __init__(self, data=None, url=None, filename=None, format=u'png', embed=None, width=None, height=None, retina=False):
482 """Create a display an PNG/JPEG image given raw data.
509 """Create a display an PNG/JPEG image given raw data.
483
510
484 When this object is returned by an expression or passed to the
511 When this object is returned by an expression or passed to the
@@ -512,6 +539,13 b' class Image(DisplayObject):'
512 Width to which to constrain the image in html
539 Width to which to constrain the image in html
513 height : int
540 height : int
514 Height to which to constrain the image in html
541 Height to which to constrain the image in html
542 retina : bool
543 Automatically set the width and height to half of the measured
544 width and height.
545 This only works for embedded images because it reads the width/height
546 from image data.
547 For non-embedded images, you can just set the desired display width
548 and height directly.
515
549
516 Examples
550 Examples
517 --------
551 --------
@@ -561,12 +595,32 b' class Image(DisplayObject):'
561 raise ValueError("Cannot embed the '%s' image format" % (self.format))
595 raise ValueError("Cannot embed the '%s' image format" % (self.format))
562 self.width = width
596 self.width = width
563 self.height = height
597 self.height = height
598 self.retina = retina
564 super(Image, self).__init__(data=data, url=url, filename=filename)
599 super(Image, self).__init__(data=data, url=url, filename=filename)
600
601 if retina:
602 self._retina_shape()
603
604 def _retina_shape(self):
605 """load pixel-doubled width and height from image data"""
606 if not self.embed:
607 return
608 if self.format == 'png':
609 w, h = _pngxy(self.data)
610 elif self.format == 'jpeg':
611 w, h = _jpegxy(self.data)
612 else:
613 # retina only supports png
614 return
615 self.width = w // 2
616 self.height = h // 2
565
617
566 def reload(self):
618 def reload(self):
567 """Reload the raw data from file or URL."""
619 """Reload the raw data from file or URL."""
568 if self.embed:
620 if self.embed:
569 super(Image,self).reload()
621 super(Image,self).reload()
622 if self.retina:
623 self._retina_shape()
570
624
571 def _repr_html_(self):
625 def _repr_html_(self):
572 if not self.embed:
626 if not self.embed:
@@ -9,7 +9,7 b' Authors'
9 """
9 """
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2009-2011 The IPython Development Team
12 # Copyright (C) 2009 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is in
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
@@ -22,6 +22,7 b' Authors'
22 import sys
22 import sys
23 from io import BytesIO
23 from io import BytesIO
24
24
25 from IPython.core.display import _pngxy
25 from IPython.utils.decorators import flag_calls
26 from IPython.utils.decorators import flag_calls
26
27
27 # If user specifies a GUI, that dictates the backend, otherwise we read the
28 # If user specifies a GUI, that dictates the backend, otherwise we read the
@@ -90,6 +91,7 b' def figsize(sizex, sizey):'
90
91
91 def print_figure(fig, fmt='png'):
92 def print_figure(fig, fmt='png'):
92 """Convert a figure to svg or png for inline display."""
93 """Convert a figure to svg or png for inline display."""
94 from matplotlib import rcParams
93 # When there's an empty figure, we shouldn't return anything, otherwise we
95 # When there's an empty figure, we shouldn't return anything, otherwise we
94 # get big blank areas in the qt console.
96 # get big blank areas in the qt console.
95 if not fig.axes and not fig.lines:
97 if not fig.axes and not fig.lines:
@@ -98,11 +100,21 b" def print_figure(fig, fmt='png'):"
98 fc = fig.get_facecolor()
100 fc = fig.get_facecolor()
99 ec = fig.get_edgecolor()
101 ec = fig.get_edgecolor()
100 bytes_io = BytesIO()
102 bytes_io = BytesIO()
103 dpi = rcParams['savefig.dpi']
104 if fmt == 'retina':
105 dpi = dpi * 2
106 fmt = 'png'
101 fig.canvas.print_figure(bytes_io, format=fmt, bbox_inches='tight',
107 fig.canvas.print_figure(bytes_io, format=fmt, bbox_inches='tight',
102 facecolor=fc, edgecolor=ec)
108 facecolor=fc, edgecolor=ec, dpi=dpi)
103 data = bytes_io.getvalue()
109 data = bytes_io.getvalue()
104 return data
110 return data
105
111
112 def retina_figure(fig):
113 """format a figure as a pixel-doubled (retina) PNG"""
114 pngdata = print_figure(fig, fmt='retina')
115 w, h = _pngxy(pngdata)
116 metadata = dict(width=w//2, height=h//2)
117 return pngdata, metadata
106
118
107 # We need a little factory function here to create the closure where
119 # We need a little factory function here to create the closure where
108 # safe_execfile can live.
120 # safe_execfile can live.
@@ -147,7 +159,7 b' def mpl_runner(safe_execfile):'
147
159
148
160
149 def select_figure_format(shell, fmt):
161 def select_figure_format(shell, fmt):
150 """Select figure format for inline backend, either 'png' or 'svg'.
162 """Select figure format for inline backend, can be 'png', 'retina', or 'svg'.
151
163
152 Using this method ensures only one figure format is active at a time.
164 Using this method ensures only one figure format is active at a time.
153 """
165 """
@@ -157,14 +169,17 b' def select_figure_format(shell, fmt):'
157 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
169 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
158 png_formatter = shell.display_formatter.formatters['image/png']
170 png_formatter = shell.display_formatter.formatters['image/png']
159
171
160 if fmt=='png':
172 if fmt == 'png':
161 svg_formatter.type_printers.pop(Figure, None)
173 svg_formatter.type_printers.pop(Figure, None)
162 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png'))
174 png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png'))
163 elif fmt=='svg':
175 elif fmt in ('png2x', 'retina'):
176 svg_formatter.type_printers.pop(Figure, None)
177 png_formatter.for_type(Figure, retina_figure)
178 elif fmt == 'svg':
164 png_formatter.type_printers.pop(Figure, None)
179 png_formatter.type_printers.pop(Figure, None)
165 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg'))
180 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg'))
166 else:
181 else:
167 raise ValueError("supported formats are: 'png', 'svg', not %r"%fmt)
182 raise ValueError("supported formats are: 'png', 'retina', 'svg', not %r" % fmt)
168
183
169 # set the format to be used in the backend()
184 # set the format to be used in the backend()
170 backend_inline._figure_format = fmt
185 backend_inline._figure_format = fmt
@@ -22,6 +22,24 b' def test_image_size():'
22 img = display.Image(url=thisurl)
22 img = display.Image(url=thisurl)
23 nt.assert_equal(u'<img src="%s"/>' % (thisurl), img._repr_html_())
23 nt.assert_equal(u'<img src="%s"/>' % (thisurl), img._repr_html_())
24
24
25 def test_retina_png():
26 here = os.path.dirname(__file__)
27 img = display.Image(os.path.join(here, "2x2.png"), retina=True)
28 nt.assert_equal(img.height, 1)
29 nt.assert_equal(img.width, 1)
30 data, md = img._repr_png_()
31 nt.assert_equal(md['width'], 1)
32 nt.assert_equal(md['height'], 1)
33
34 def test_retina_jpeg():
35 here = os.path.dirname(__file__)
36 img = display.Image(os.path.join(here, "2x2.jpg"), retina=True)
37 nt.assert_equal(img.height, 1)
38 nt.assert_equal(img.width, 1)
39 data, md = img._repr_jpeg_()
40 nt.assert_equal(md['width'], 1)
41 nt.assert_equal(md['height'], 1)
42
25 def test_image_filename_defaults():
43 def test_image_filename_defaults():
26 '''test format constraint, and validity of jpeg and png'''
44 '''test format constraint, and validity of jpeg and png'''
27 tpath = ipath.get_ipython_package_dir()
45 tpath = ipath.get_ipython_package_dir()
@@ -18,7 +18,7 b' from IPython.config.configurable import SingletonConfigurable'
18 from IPython.core.display import display
18 from IPython.core.display import display
19 from IPython.core.displaypub import publish_display_data
19 from IPython.core.displaypub import publish_display_data
20 from IPython.core.pylabtools import print_figure, select_figure_format
20 from IPython.core.pylabtools import print_figure, select_figure_format
21 from IPython.utils.traitlets import Dict, Instance, CaselessStrEnum, CBool
21 from IPython.utils.traitlets import Dict, Instance, CaselessStrEnum, Bool
22 from IPython.utils.warn import warn
22 from IPython.utils.warn import warn
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
@@ -56,7 +56,7 b' class InlineBackend(InlineBackendConfig):'
56 inline backend."""
56 inline backend."""
57 )
57 )
58
58
59 figure_format = CaselessStrEnum(['svg', 'png'], default_value='png', config=True,
59 figure_format = CaselessStrEnum(['svg', 'png', 'retina'], default_value='png', config=True,
60 help="The image format for figures with the inline backend.")
60 help="The image format for figures with the inline backend.")
61
61
62 def _figure_format_changed(self, name, old, new):
62 def _figure_format_changed(self, name, old, new):
@@ -65,7 +65,7 b' class InlineBackend(InlineBackendConfig):'
65 else:
65 else:
66 select_figure_format(self.shell, new)
66 select_figure_format(self.shell, new)
67
67
68 close_figures = CBool(True, config=True,
68 close_figures = Bool(True, config=True,
69 help="""Close all figures at the end of each cell.
69 help="""Close all figures at the end of each cell.
70
70
71 When True, ensures that each cell starts with no active figures, but it
71 When True, ensures that each cell starts with no active figures, but it
@@ -146,6 +146,7 b' def find_package_data():'
146
146
147 package_data = {
147 package_data = {
148 'IPython.config.profile' : ['README*', '*/*.py'],
148 'IPython.config.profile' : ['README*', '*/*.py'],
149 'IPython.core.tests' : ['*.png', '*.jpg'],
149 'IPython.testing' : ['*.txt'],
150 'IPython.testing' : ['*.txt'],
150 'IPython.testing.plugin' : ['*.txt'],
151 'IPython.testing.plugin' : ['*.txt'],
151 'IPython.html' : ['templates/*'] + static_data,
152 'IPython.html' : ['templates/*'] + static_data,
General Comments 0
You need to be logged in to leave comments. Login now