Zebulon
Zebulon

Reputation: 53

Create image from RGB list with Pillow and Python 3

I have a list of RGB data:

cdata=[R1, G1, B1, R2, G2, B2,..., Rn, Gn, Bn]

where each value is comprised between 0 to 255.

I am trying to rebuild this array as an image using Pillow 5.0.0. Under Python 2, I was able to convert the list of values into a byte string this way:

        cdata2 = []
        gidx = len(cdata)//3
        bidx = len(cdata)//3*2
        for i in range(len(cdata)//3):
            cdata2.append(cdata[i])
            cdata2.append(cdata[i+gidx])
            cdata2.append(cdata[i+bidx])

        data = ""
        for c in cdata2:
            data += chr(c)

        im = Image.frombytes("RGB", (420, 560), data)

and then reincode 'im' in base64 and display it as a PNG in an HTML template.

Unfortunately this does not work in Python 3, I am having errors like:

UnicodeEncodeError: 'charmap' codec can't encode characters in position 42099-42101: character maps to <undefined>

Furthermore, Pillow 5 documentation now suggests using

im = Image.open(StringIO(data))

but cannot make it work with my string built above. Is there any more clever way to do this? Thanks a lot in advance for your help.

Upvotes: 5

Views: 12273

Answers (3)

Dalen
Dalen

Reputation: 4236

If you want to have nice Python2 and Python3 compatible code you can use struct or array module:

# Works from Python 2.5 (maybe earlier) to Python 3.x
import struct
cdata = [...]
bindata = struct.pack("<%dB" % len(cdata), *cdata)
# And then use PIL's Image.frombytes() to construct the Image() from bindata

Or:

import array
cdata = [...]
a = array.array("B", cdata)
bindata = a.tostring()
# And then use PIL's Image.frombytes() to construct the Image() from bindata
# This should be faster than struct, but I didn't test it for speed

Upvotes: 1

PM 2Ring
PM 2Ring

Reputation: 55469

Here's an example using frombytes. This is just using pure Python, no Numpy. If you're using Numpy to create the RGB values then you can use the Image.fromarray method to convert the Numpy data into a PIL Image.

The important step here is to convert the list of RGB values into a bytes object, which is easily done by passing it to the bytes constructor.

from colorsys import hsv_to_rgb
from PIL import Image

# Make some RGB values. 
# Cycle through hue vertically & saturation horizontally
colors = []
for hue in range(360):
    for sat in range(100):
        # Convert color from HSV to RGB
        rgb = hsv_to_rgb(hue/360, sat/100, 1)
        rgb = [int(0.5 + 255*u) for u in rgb]
        colors.extend(rgb)

# Convert list to bytes
colors = bytes(colors)
img = Image.frombytes('RGB', (100, 360), colors)
img.show()
img.save('hues.png')

output

hue & saturation demo image

Upvotes: 6

Aran-Fey
Aran-Fey

Reputation: 43126

Use Image.frombytes. Image.open is intended for opening encoded images (like jpg or png), not raw RGB data.


Constructing the required byte data is trivial with the bytes constructor:

img_bytes = bytes([R1, G1, B1, R2, G2, B2,..., Rn, Gn, Bn])

And then we can create an Image like so:

im = Image.frombytes("RGB", (width, height), img_bytes)

Upvotes: 4

Related Questions