##// 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
1 NO CONTENT: new file 100644, binary diff hidden
@@ -20,6 +20,7 b' Authors:'
20 20 from __future__ import print_function
21 21
22 22 import os
23 import struct
23 24
24 25 from IPython.utils.py3compat import string_types
25 26
@@ -471,6 +472,32 b' class Javascript(DisplayObject):'
471 472 _PNG = b'\x89PNG\r\n\x1a\n'
472 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 501 class Image(DisplayObject):
475 502
476 503 _read_flags = 'rb'
@@ -478,7 +505,7 b' class Image(DisplayObject):'
478 505 _FMT_PNG = u'png'
479 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 509 """Create a display an PNG/JPEG image given raw data.
483 510
484 511 When this object is returned by an expression or passed to the
@@ -512,6 +539,13 b' class Image(DisplayObject):'
512 539 Width to which to constrain the image in html
513 540 height : int
514 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 550 Examples
517 551 --------
@@ -561,12 +595,32 b' class Image(DisplayObject):'
561 595 raise ValueError("Cannot embed the '%s' image format" % (self.format))
562 596 self.width = width
563 597 self.height = height
598 self.retina = retina
564 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 618 def reload(self):
567 619 """Reload the raw data from file or URL."""
568 620 if self.embed:
569 621 super(Image,self).reload()
622 if self.retina:
623 self._retina_shape()
570 624
571 625 def _repr_html_(self):
572 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 14 # Distributed under the terms of the BSD License. The full license is in
15 15 # the file COPYING, distributed as part of this software.
@@ -22,6 +22,7 b' Authors'
22 22 import sys
23 23 from io import BytesIO
24 24
25 from IPython.core.display import _pngxy
25 26 from IPython.utils.decorators import flag_calls
26 27
27 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 92 def print_figure(fig, fmt='png'):
92 93 """Convert a figure to svg or png for inline display."""
94 from matplotlib import rcParams
93 95 # When there's an empty figure, we shouldn't return anything, otherwise we
94 96 # get big blank areas in the qt console.
95 97 if not fig.axes and not fig.lines:
@@ -98,11 +100,21 b" def print_figure(fig, fmt='png'):"
98 100 fc = fig.get_facecolor()
99 101 ec = fig.get_edgecolor()
100 102 bytes_io = BytesIO()
103 dpi = rcParams['savefig.dpi']
104 if fmt == 'retina':
105 dpi = dpi * 2
106 fmt = 'png'
101 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 109 data = bytes_io.getvalue()
104 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 119 # We need a little factory function here to create the closure where
108 120 # safe_execfile can live.
@@ -147,7 +159,7 b' def mpl_runner(safe_execfile):'
147 159
148 160
149 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 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 169 svg_formatter = shell.display_formatter.formatters['image/svg+xml']
158 170 png_formatter = shell.display_formatter.formatters['image/png']
159 171
160 if fmt=='png':
172 if fmt == 'png':
161 173 svg_formatter.type_printers.pop(Figure, None)
162 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 179 png_formatter.type_printers.pop(Figure, None)
165 180 svg_formatter.for_type(Figure, lambda fig: print_figure(fig, 'svg'))
166 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 184 # set the format to be used in the backend()
170 185 backend_inline._figure_format = fmt
@@ -22,6 +22,24 b' def test_image_size():'
22 22 img = display.Image(url=thisurl)
23 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 43 def test_image_filename_defaults():
26 44 '''test format constraint, and validity of jpeg and png'''
27 45 tpath = ipath.get_ipython_package_dir()
@@ -18,7 +18,7 b' from IPython.config.configurable import SingletonConfigurable'
18 18 from IPython.core.display import display
19 19 from IPython.core.displaypub import publish_display_data
20 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 22 from IPython.utils.warn import warn
23 23
24 24 #-----------------------------------------------------------------------------
@@ -56,7 +56,7 b' class InlineBackend(InlineBackendConfig):'
56 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 60 help="The image format for figures with the inline backend.")
61 61
62 62 def _figure_format_changed(self, name, old, new):
@@ -65,7 +65,7 b' class InlineBackend(InlineBackendConfig):'
65 65 else:
66 66 select_figure_format(self.shell, new)
67 67
68 close_figures = CBool(True, config=True,
68 close_figures = Bool(True, config=True,
69 69 help="""Close all figures at the end of each cell.
70 70
71 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 147 package_data = {
148 148 'IPython.config.profile' : ['README*', '*/*.py'],
149 'IPython.core.tests' : ['*.png', '*.jpg'],
149 150 'IPython.testing' : ['*.txt'],
150 151 'IPython.testing.plugin' : ['*.txt'],
151 152 'IPython.html' : ['templates/*'] + static_data,
General Comments 0
You need to be logged in to leave comments. Login now