5

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.

Zebulon
  • 53
  • 1
  • 1
  • 4
  • In Python 3, plain strings are for (Unicode) text, don't try to use them for binary data. Instead, you should be using `bytearray`, or possibly `bytes` objects. Is Numpy an option for you? How come your RGB values range from 0-254 instead of 0-255? – PM 2Ring Jan 16 '18 at 11:10
  • Thanks PM 2Ring. Yes, they are up to 255 indeed, will correct this. I can use Numpy. How would you encode the RGB channel data then to use it with Pillow please? – Zebulon Jan 16 '18 at 11:17

3 Answers3

6

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

PM 2Ring
  • 54,345
  • 6
  • 82
  • 182
3

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)
Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
1

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
Dalen
  • 4,128
  • 1
  • 17
  • 35