4

I'm trying to send json dict that should contain Pillow image as one of his fields, to do that I have to convert the image to string. I tried to use pillow function: image.toString() but still got it as bytes, so I tried to encode it:

buff = BytesIO()
image.save(buff, format="JPEG")
img_str = base64.b64encode(buff.getvalue())

but still got it as bytes. How can I convert Pillow images to format that can be saved in json file?

j.her
  • 157
  • 1
  • 2
  • 11
  • 2
    I'm not sure I understand the question. You now have your data as a string, and you want to save your data in a json file, and json files are capable of saving strings. So what's the problem? Save your string to the file. – Kevin Jun 03 '19 at 13:07
  • Maybe this `jstr = json.dumps({"image": base64.b64encode(buff.getvalue()).decode('ascii')})` – Mark Setchell Jun 03 '19 at 13:20

2 Answers2

4

In the comments, Mark Setchell suggests calling .decode('ascii') on the result of your b64encode call. I agree that this will work, but I think base64encoding to begin with is introducing an unnecessary extra step that complicates your code.*

Instead, I suggest directly decoding the bytes returned by image.tostring. The only complication is that the bytes object can contain values larger than 128, so you can't decode it with ascii. Try using an encoding that can handle values up to 256, such as latin1.

from PIL import Image
import json

#create sample file. You don't have to do this in your real code.
img = Image.new("RGB", (10,10), "red")

#decode.
s = img.tobytes().decode("latin1")

#serialize.
with open("outputfile.json", "w") as file:
    json.dump(s, file)

(*but, to my surprise, the resulting json file is still smaller than one made with a latin1 encoding, at least for my sample file. Use your own judgement to determine whether file size or program clarity is more important.)

Kevin
  • 72,202
  • 12
  • 116
  • 152
  • I'm not wishing to argue or disagree with you, as I am here to learn as much as the next man. I think that the JPEG encoding is maybe a far more critical factor in reducing size than any concerns about base64 efficiency. A colour image of 3000x4000px is 36MB, but maybe only 300kB JPEG encoded where an extra 30% of base64 inefficiency making 400kB is dwarfed by omitting the JPEG step. As I said, I am here to learn and may be wrong - I'm pretty new to Python and JSON and happy to see the other side of solutions and learn. – Mark Setchell Jun 03 '19 at 14:01
  • @MarkSetchell Good observation. I agree that saving as JPEG could result in considerable improvements in memory. My sample code uses `tobytes()` instead of `image.save(buff, format="JPEG")` because it's slightly more concise. But it's not essential to my answer; the OP should feel free to continue using their BytesIO strategy, regardless of whether they go with a b64-and-ascii encoding or a latin1 encoding. – Kevin Jun 03 '19 at 14:09
  • Cool - thank you for taking time to clarify. You already have my vote. – Mark Setchell Jun 03 '19 at 14:12
3

I use the following to exchange Pillow images via json.

import json
from PIL import Image
import numpy as np

filename = "filename.jpeg"
image = Image.open(filename)
json_data = json.dumps(np.array(image).tolist())
new_image = Image.fromarray(np.array(json.loads(json_data), dtype='uint8'))