Show More
@@ -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 |
|
|
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. |
@@ -19,10 +19,10 b' Authors' | |||
|
19 | 19 | # Imports |
|
20 | 20 | #----------------------------------------------------------------------------- |
|
21 | 21 | |
|
22 | import struct | |
|
23 | 22 | import sys |
|
24 | 23 | from io import BytesIO |
|
25 | 24 | |
|
25 | from IPython.core.display import _pngxy | |
|
26 | 26 | from IPython.utils.decorators import flag_calls |
|
27 | 27 | |
|
28 | 28 | # If user specifies a GUI, that dictates the backend, otherwise we read the |
@@ -103,22 +103,16 b" def print_figure(fig, fmt='png'):" | |||
|
103 | 103 | dpi = rcParams['savefig.dpi'] |
|
104 | 104 | if fmt == 'retina': |
|
105 | 105 | dpi = dpi * 2 |
|
106 | fmt = 'png' | |
|
106 | 107 | fig.canvas.print_figure(bytes_io, format=fmt, bbox_inches='tight', |
|
107 | 108 | facecolor=fc, edgecolor=ec, dpi=dpi) |
|
108 | 109 | data = bytes_io.getvalue() |
|
109 | 110 | return data |
|
110 | 111 | |
|
111 | def pngxy(data): | |
|
112 | """read the width/height from a PNG header""" | |
|
113 | ihdr = data.index(b'IHDR') | |
|
114 | # next 8 bytes are width/height | |
|
115 | w4h4 = data[ihdr+4:ihdr+12] | |
|
116 | return struct.unpack('>ii', w4h4) | |
|
117 | ||
|
118 | 112 | def retina_figure(fig): |
|
119 | 113 | """format a figure as a pixel-doubled (retina) PNG""" |
|
120 | 114 | pngdata = print_figure(fig, fmt='retina') |
|
121 | w, h = pngxy(pngdata) | |
|
115 | w, h = _pngxy(pngdata) | |
|
122 | 116 | metadata = dict(width=w//2, height=h//2) |
|
123 | 117 | return pngdata, metadata |
|
124 | 118 |
General Comments 0
You need to be logged in to leave comments.
Login now