Skip to content

Commit 0d68281

Browse files
committed
Add image resizing option
1 parent 12d7a74 commit 0d68281

File tree

3 files changed

+86
-6
lines changed

3 files changed

+86
-6
lines changed

src/jupyter_contrib_nbextensions/nbconvert_support/pre_embedimages.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Nbconvert preprocessor for the python-markdown nbextension."""
22

33
from nbconvert.preprocessors import Preprocessor
4-
from traitlets import Bool, Float
4+
from traitlets import Bool, Float, Unicode
55
import re
66
import os
77
import base64
@@ -38,17 +38,23 @@ class EmbedImagesPreprocessor(Preprocessor):
3838
to additionally embeds all images referenced by an url (e.g. http://jupyter.org/assets/nav_logo.svg) instead
3939
of a local file name. Also
4040
41-
EmbedImagesPreprocessor.dpi_scaling
41+
EmbedImagesPreprocessor.resize=small
4242
43-
Let's you scale the size of an image. This is interesting if you want to save space by not embedding large
43+
Let's you scale-down the size of an image. This is useful if you want to save space by not embedding large
4444
images and instead use a smaller (scaled) version. Works only for raster images (i.e. png, jpg).
45+
Valid resize settings are: small = 500px, mid = 1000px, large = 2000px for maximum size in length or width
46+
No upscaling of small images will be performed.
47+
48+
Example::
49+
50+
$ jupyter nbconvert --to html --EmbedImagesPreprocessor.embed_images=True --EmbedImagesPreprocessor.scale=large mynotebook.ipynb
4551
4652
*Note:* To embed images after conversion to HTML you can also use the `html_embed` exporter
4753
"""
4854

4955
embed_images = Bool(False, help="Embed images as attachment").tag(config=True)
5056
embed_remote_images = Bool(False, help="Embed images referenced by an url as attachment").tag(config=True)
51-
dpi_scaling = Float(0, help="Resize images to a certain DPI number (reduce size)").tag(config=True)
57+
resize = Unicode('', help="Resize images to save space (reduce size)").tag(config=True)
5258

5359
def preprocess(self, nb, resources):
5460
"""Skip preprocessor if not enabled"""
@@ -59,7 +65,7 @@ def preprocess(self, nb, resources):
5965
def replfunc_md(self, match):
6066
"""Read image and store as base64 encoded attachment"""
6167
url = match.group(2)
62-
imgformat = url.split('.')[-1]
68+
imgformat = url.split('.')[-1].lower()
6369
if url.startswith('http'):
6470
if self.embed_remote_images:
6571
data = urlopen(url).read()
@@ -72,6 +78,30 @@ def replfunc_md(self, match):
7278
with open(filename, 'rb') as f:
7379
data = f.read()
7480

81+
# resize settings: small -> 500px, mid -> 1000px, large -> 200px
82+
imgsizes = {'small': 500, 'mid': 1000, 'large': 2000}
83+
if self.resize in imgsizes.keys() and imgformat in ['png', 'jpg']:
84+
from io import BytesIO
85+
try:
86+
from PIL import Image
87+
except ImportError:
88+
self.log.info("Pillow library not available to resize images")
89+
Image = None
90+
if Image:
91+
# Only make images smaller when rescaling
92+
im = Image.open(BytesIO(data))
93+
size = im.size
94+
factor = imgsizes[self.resize]/max(size)
95+
if factor < 1.0:
96+
newsize = ( int(size[0]*factor), int(size[1]*factor))
97+
newim = im.resize(newsize)
98+
fp = BytesIO()
99+
newim.save(fp, format=imgformat.replace('jpg', 'jpeg')) # PIL requires JPEG instead of JPG
100+
data = fp.getvalue()
101+
fp.close()
102+
self.log.debug("Resized %d x %d image %s to size %d x %d pixels" %
103+
(size[0], size[1], url, newsize[0], newsize[1]) )
104+
75105
self.log.debug("embedding url: %s, format: %s" % (url, imgformat))
76106
b64_data = base64.b64encode(data).decode("utf-8")
77107
self.attachments[url] = { 'image/'+imgformat : b64_data }

tests/data/large_image.png

88.1 KB
Loading

tests/test_preprocessors.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def test_preprocessor_svg2pdf():
9292
'exported pdf should be referenced in exported notebook')
9393

9494

95-
def test_preprocessor_embedigmages():
95+
def test_preprocessor_embedimages():
9696
"""Test python embedimages preprocessor."""
9797
# check import shortcut
9898
from jupyter_contrib_nbextensions.nbconvert_support import EmbedImagesPreprocessor # noqa E501
@@ -109,3 +109,53 @@ def test_preprocessor_embedigmages():
109109

110110
expected = 'image/png'
111111
assert_in(expected, body, 'Attachment {} is missing'.format(expected))
112+
113+
def test_preprocessor_embedimages_resize():
114+
"""Test python embedimages preprocessor."""
115+
# check import shortcut
116+
from jupyter_contrib_nbextensions.nbconvert_support import EmbedImagesPreprocessor # noqa E501
117+
118+
try:
119+
from PIL import Image
120+
except ImportError:
121+
raise SkipTest('PIL not found')
122+
123+
notebook_node = nbf.new_notebook(cells=[
124+
nbf.new_code_cell(source="a = 'world'"),
125+
nbf.new_markdown_cell(
126+
source="![testimage]({})".format(path_in_data('large_image.png'))
127+
),
128+
])
129+
body, resources = export_through_preprocessor(
130+
notebook_node, EmbedImagesPreprocessor, NotebookExporter, 'ipynb')
131+
len_noembed = len(body)
132+
133+
customconfig = Config(EmbedImagesPreprocessor={'embed_images': True, 'resize': 'small'})
134+
body, resources = export_through_preprocessor(
135+
notebook_node, EmbedImagesPreprocessor, NotebookExporter, 'ipynb',
136+
customconfig)
137+
len_small = len(body)
138+
139+
customconfig = Config(EmbedImagesPreprocessor={'embed_images': True, 'resize': 'mid'})
140+
body, resources = export_through_preprocessor(
141+
notebook_node, EmbedImagesPreprocessor, NotebookExporter, 'ipynb',
142+
customconfig)
143+
len_mid = len(body)
144+
145+
customconfig = Config(EmbedImagesPreprocessor={'embed_images': True, 'resize': 'large'})
146+
body, resources = export_through_preprocessor(
147+
notebook_node, EmbedImagesPreprocessor, NotebookExporter, 'ipynb',
148+
customconfig)
149+
len_large = len(body)
150+
151+
152+
customconfig = Config(EmbedImagesPreprocessor={'embed_images': True})
153+
body, resources = export_through_preprocessor(
154+
notebook_node, EmbedImagesPreprocessor, NotebookExporter, 'ipynb',
155+
customconfig)
156+
len_noresize = len(body)
157+
158+
assert(len_noembed < len_small)
159+
assert(len_small < len_mid)
160+
assert(len_mid < len_large)
161+
assert(len_large < len_noresize)

0 commit comments

Comments
 (0)