58

I'm making a simple API in Flask that accepts an image encoded in base64, then decodes it for further processing using Pillow.

I've looked at some examples (1, 2, 3), and I think I get the gist of the process, but I keep getting an error where Pillow can't read the string I gave it.

Here's what I've got so far:

import cStringIO
from PIL import Image
import base64

data = request.form
image_string = cStringIO.StringIO(base64.b64decode(data['img']))
image = Image.open(image_string)

which gives the error:

IOError: cannot identify image file <cStringIO.StringIO object at 0x10f84c7a0>
Community
  • 1
  • 1
Lazaro Gamio
  • 759
  • 1
  • 6
  • 11
  • Can you paste in an example of what you're getting in `data['img']`? Log that out, or print it out. – OregonTrail Sep 27 '14 at 03:57
  • Here's an example: http://jsfiddle.net/gn0x0wvc/. I put it in an `img` tag to make sure the image data wasn't corrupted. – Lazaro Gamio Sep 27 '14 at 04:21
  • What does the data look like after `b64decode()`? Is there any other encoding going on, e.g. urlencoding? Is it really an image supported by PIL? – mhawke Sep 27 '14 at 05:09
  • Just that fiddle doesn't tell us anything about how you are posting that string to your Flask server. What does `print repr(data['img'])` produce on your console for example? – Martijn Pieters Sep 27 '14 at 12:37
  • @mhawke Here's a screenshot of the output of `b64 decode`: http://imgur.com/7ZJNuPf I was trying to follow the example [here](http://stackoverflow.com/questions/19908975/loading-base64-string-into-python-image-library). @Martijn Pieters here's what `print repr(data['img'])` looks like: http://imgur.com/xmrvNmI – Lazaro Gamio Sep 27 '14 at 14:42

3 Answers3

124

You should try something like:

from PIL import Image
from io import BytesIO
import base64

data['img'] = '''R0lGODlhDwAPAKECAAAAzMzM/////wAAACwAAAAADwAPAAACIISPeQHsrZ5ModrLl
N48CXF8m2iQ3YmmKqVlRtW4MLwWACH+H09wdGltaXplZCBieSBVbGVhZCBTbWFydFNhdmVyIQAAOw==''' 

im = Image.open(BytesIO(base64.b64decode(data['img'])))

Your data['img'] string should not include the HTML tags or the parameters data:image/jpeg;base64 that are in the example JSFiddle.

I've changed the image string for an example I took from Google, just for readability purposes.

André Teixeira
  • 2,392
  • 4
  • 28
  • 41
32

There is a metadata prefix of data:image/jpeg;base64, being included in the img field. Normally this metadata is used in a CSS or HTML data URI when embedding image data into the document or stylesheet. It is there to provide the MIME type and encoding of the embedded data to the rendering browser.

You can strip off the prefix before the base64 decode and this should result in valid image data that PIL can load (see below), but you really need to question how the metadata is being submitted to your server as normally it should not.

import re
import cStringIO
from PIL import Image

image_data = re.sub('^data:image/.+;base64,', '', data['img']).decode('base64')
image = Image.open(cStringIO.StringIO(image_data))
mhawke
  • 84,695
  • 9
  • 117
  • 138
  • @LazaroGamio : the main point though is that the prefix should not be present in the data posted to your server. How is it being posted? – mhawke Sep 28 '14 at 14:32
  • I'm taking a user - uploaded image and sending it to the API via the jquery post method. I suppose I could strip the prefix off client side instead of removing it on the server. – Lazaro Gamio Sep 28 '14 at 15:46
13

Sorry for necromancy, but none of the answers worked completely for me. Here is code working on Python 3.6 and Flask 0.13.

Server:

from flask import Flask, jsonify, request
from io import BytesIO
from web import app
import base64
import re
import json
from PIL import Image

@app.route('/process_image', methods=['post'])
def process_image():
    image_data = re.sub('^data:image/.+;base64,', '', request.form['data'])
    im = Image.open(BytesIO(base64.b64decode(image_data)))
    return json.dumps({'result': 'success'}), 200, {'ContentType': 'application/json'}

Client JS:

// file comes from file input
var reader = new FileReader();
reader.onloadend = function () {
    var fileName = file.name;
    $.post('/process_image', { data: reader.result, name: fileName });
};
reader.readAsDataURL(file);
cyberj0g
  • 3,707
  • 1
  • 19
  • 34