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