Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 97e4378

Browse files
authored
Convert PIL image objects to data uri strings in JSON serialization. (plotly#1991)
This conversion is already done by the layout.image.source validator, but this way it will also happen when serializing from a dict without validation, and for images that show up elsewhere in the figure (as mapbox layers for example)
1 parent de05925 commit 97e4378

File tree

3 files changed

+43
-9
lines changed

3 files changed

+43
-9
lines changed

packages/python/plotly/_plotly_utils/basevalidators.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2347,20 +2347,25 @@ def validate_coerce(self, v):
23472347
pass
23482348
elif self._PIL and isinstance(v, self._PIL.Image.Image):
23492349
# Convert PIL image to png data uri string
2350-
in_mem_file = io.BytesIO()
2351-
v.save(in_mem_file, format="PNG")
2352-
in_mem_file.seek(0)
2353-
img_bytes = in_mem_file.read()
2354-
base64_encoded_result_bytes = base64.b64encode(img_bytes)
2355-
base64_encoded_result_str = base64_encoded_result_bytes.decode("ascii")
2356-
v = "data:image/png;base64,{base64_encoded_result_str}".format(
2357-
base64_encoded_result_str=base64_encoded_result_str
2358-
)
2350+
v = self.pil_image_to_uri(v)
23592351
else:
23602352
self.raise_invalid_val(v)
23612353

23622354
return v
23632355

2356+
@staticmethod
2357+
def pil_image_to_uri(v):
2358+
in_mem_file = io.BytesIO()
2359+
v.save(in_mem_file, format="PNG")
2360+
in_mem_file.seek(0)
2361+
img_bytes = in_mem_file.read()
2362+
base64_encoded_result_bytes = base64.b64encode(img_bytes)
2363+
base64_encoded_result_str = base64_encoded_result_bytes.decode("ascii")
2364+
v = "data:image/png;base64,{base64_encoded_result_str}".format(
2365+
base64_encoded_result_str=base64_encoded_result_str
2366+
)
2367+
return v
2368+
23642369

23652370
class CompoundValidator(BaseValidator):
23662371
def __init__(self, plotly_name, parent_name, data_class_str, data_docs, **kwargs):

packages/python/plotly/_plotly_utils/utils.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import re
55

66
from _plotly_utils.optional_imports import get_module
7+
from _plotly_utils.basevalidators import ImageUriValidator
78

89

910
PY36_OR_LATER = sys.version_info.major == 3 and sys.version_info.minor >= 6
@@ -104,6 +105,7 @@ def default(self, obj):
104105
self.encode_as_date,
105106
self.encode_as_list, # because some values have `tolist` do last.
106107
self.encode_as_decimal,
108+
self.encode_as_pil,
107109
)
108110
for encoding_method in encoding_methods:
109111
try:
@@ -192,6 +194,15 @@ def encode_as_decimal(obj):
192194
else:
193195
raise NotEncodable
194196

197+
@staticmethod
198+
def encode_as_pil(obj):
199+
"""Attempt to convert PIL.Image.Image to base64 data uri"""
200+
pil = get_module("PIL")
201+
if isinstance(obj, pil.Image.Image):
202+
return ImageUriValidator.pil_image_to_uri(obj)
203+
else:
204+
raise NotEncodable
205+
195206

196207
class NotEncodable(Exception):
197208
pass

packages/python/plotly/plotly/tests/test_optional/test_utils/test_utils.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@
1616
from nose.plugins.attrib import attr
1717
from pandas.util.testing import assert_series_equal
1818
import json as _json
19+
import os
20+
import base64
1921

2022
from plotly import optional_imports, utils
2123
from plotly.graph_objs import Scatter, Scatter3d, Figure, Data
24+
from PIL import Image
2225

2326

2427
matplotlylib = optional_imports.get_module("plotly.matplotlylib")
@@ -274,6 +277,21 @@ def test_datetime_dot_date(self):
274277
j1 = _json.dumps(a, cls=utils.PlotlyJSONEncoder)
275278
assert j1 == '["2014-01-01", "2014-01-02"]'
276279

280+
def test_pil_image_encoding(self):
281+
import _plotly_utils
282+
283+
img_path = os.path.join(
284+
_plotly_utils.__path__[0], "tests", "resources", "1x1-black.png"
285+
)
286+
287+
with open(img_path, "rb") as f:
288+
hex_bytes = base64.b64encode(f.read()).decode("ascii")
289+
expected_uri = "data:image/png;base64," + hex_bytes
290+
291+
img = Image.open(img_path)
292+
j1 = _json.dumps({"source": img}, cls=utils.PlotlyJSONEncoder)
293+
assert j1 == '{"source": "%s"}' % expected_uri
294+
277295

278296
if matplotlylib:
279297

0 commit comments

Comments
 (0)