Forum Archive

How can I convert a PIL Image to a ui.Image?

Sebastian

How can I convert a PIL Image to a ui.Image?

dgelessus

Taken more or less directly from filenav:

try:
    import cStringIO as StringIO
except ImportError:
    import StringIO
import ui

def pil_to_ui(img):
    strio = StringIO.StringIO()
    img.save(strio, img.format)
    data = strio.getvalue()
    strio.close()
    return ui.Image.from_data(data)
[deleted]

Or...

import ui, io
from PIL import Image as ImageP

ip = ImageP.open('ionicons-ios7-reload-32')

with io.BytesIO() as bIO:
    ip.save(bIO, ip.format)
    ui.Button(image = ui.Image.from_data(bIO.getvalue())).present()

Python Tutorial 6.2.1
It is good practice to use the with keyword when dealing with file objects. This has the advantage that the file is properly closed after its suite finishes, even if an exception is raised on the way.

Sebastian

Thanks guys! :D

jmv38

this code works with the above image, but not with 'Test_Lenna' image.
How can convert a pil image like Lenna into ui.image? Thanks

omz

@jmv38 If you want a ui.Image from a built-in image, the easiest/fastest would be to just not create a PIL image to start with:

import ui

img = ui.Image.named('Test_Lenna')
img.show()
# ...
jmv38

@omz thanks, but i know this way, it is not what i want to achieve. Ok let me be more detailed:

A/ i must have an array from numpy that contains an image. i must have this because of fft functions.
B/ i must have a ui image because i want to display my array A and touch it and do actions from the touch. I could use a scene image, but i also want buttons, so i thought i'll have to go through ui anyway, which supports images, and i dont need 60 hz updates, hence my choice not to add another lib on top of it. (maybe wrong choice?).
C/ i want to draw a mask on top of my image, and i assume this mask will be a PIL image, because all drawing functions are there. But i'll have to convert this mask to array to multiply it to A. Note It is a gray scale mask, not binary one.

A,B and C will be updated all the time, so they are not built in images, but computed images.
And i will have 2, maybe 3 such sets of images in the same display.

Due to A,B and C, i will have to convert from one type to the other, and rather efficiently. I though i should not save the Array on disk each time i want to update the ui image, because i assume it will be too slow, but maybe i am wrong.

So to summarize, i expect to use:
PIL -> ui.
ui -> PIL.
array -> ui.
ui -> array.
array -> PIL. (these i found in the docs how to do).
PIL -> array.

If i could avoid all these conversions, i'd love to, but it doesnt seem possible at first sight, if i want to use the built-in libraries. I did not expect that each lib module had its own image format, not compatible with the other ones...
I guess i am not the only one to face that type of question..?

Now if you can suggest a better way to achieve what i want, using only one library module, please let me know, i have no experience in Python. Btw, Pythonista is really fantastic, the possibilty + quality of the whole thing is just .. AWSOME! That is a incredible good job you have done here!

Thanks for your help!

omz

Have you tried using @tony's code, but replacing ip.format with 'PNG'?

jmv38

i just tried

import ui, io
from PIL import Image as ImageP

def test(ip):
    with io.BytesIO() as bIO:
        ip.save(bIO, 'PNG')
        ui.Button(image = ui.Image.from_data(bIO.getvalue())).present()


ip = ImageP.open('Test_Lenna')
#ip = ImageP.open('ionicons-ios7-reload-32')
test(ip)

but the result is a big blue square, not lenna color image...
Thanks.

omz

I see... The image is probably alright, it's just that it doesn't work well as a button image. By default, a button only uses the alpha component of an image (which is completely opaque in this case) and tints that with its tint_color (which is blue by default). This works well with icons, but not so much with photos. To create a button with a full-color image, you have to convert it to an image with "original" rendering mode:

import ui, io
from PIL import Image as ImageP

def test(ip):
    with io.BytesIO() as bIO:
        ip.save(bIO, 'PNG')
        img = ui.Image.from_data(bIO.getvalue())
        img = img.with_rendering_mode(ui.RENDERING_MODE_ORIGINAL)
        ui.Button(image=img).present()

ip = ImageP.open('Test_Lenna')
test(ip)
jmv38

@omz Bingo!!!
Thanks a lot!

jmv38

here are the various conversions i have set. Tell me if there is some better code. Thanks.
[edit] modified according to @ccc comment below.
[edit] modified to add functions and tests.


#coding: utf-8
import ui
import io
from PIL import ImageOps, ImageDraw
from PIL import Image 
import numpy as np
import StringIO

import console

# numpy <=> pil
def np2pil(arrayIn):
        imgOut = Image.fromarray(arrayIn)
        return imgOut

def pil2np(imgIn,arrayOut=None):
    if arrayOut == None:
        arrayOut = np.array(imgIn)
        return arrayOut
    else:
        arrayOut[:] = np.array(imgIn)
        return None


# pil <=> ui
def pil2ui(imgIn):
    with io.BytesIO() as bIO:
        imgIn.save(bIO, 'PNG')
        imgOut = ui.Image.from_data(bIO.getvalue())
    del bIO
    return imgOut

def ui2pil(imgIn):
    # create a fake png file in memory
    memoryFile = StringIO.StringIO( imgIn.to_png() )
    # this creates the pil image, but does not read the data
    imgOut = Image.open(memoryFile)
    # this force the data to be read
    imgOut.load()
    # this releases the memory from the png file
    memoryFile.close()
    return imgOut

# numpy <=> ui
def np2ui(arrayIn):
    # this is a lazy implementation, maybe could be more efficient?
    return pil2ui( np2pil(arrayIn) )

def ui2np(imgIn):
    # this is a lazy implementation, maybe could be more efficient?
    return pil2np( ui2pil(imgIn) )


if __name__ == "__main__":
    # testing the functions above

    img = Image.open('Test_Lenna')
    s = 256
    img = img.resize((s, s), Image.BILINEAR)
    img = ImageOps.grayscale(img)

    console.clear()

    print( 'test: open a pil image:')
    img.show()

    print('- ')
    print( 'test: pil image => return a new np array')
    print(' ')
    arr = pil2np(img)
    print(arr)

    print('- ')
    print( 'test: pil image => write into existing np array')
    print(' ')
    pil2np(img.rotate(90), arr)
    print(arr)

    print('- ')
    print( 'test:  np array => return a new pil image')
    img1 = np2pil(arr)
    img1.show()

    # test: pil2ui verification is done via a popover
    iv = ui.ImageView(frame=(0,0,256,256))
    iv.name = 'pil2ui'
    iv.image = pil2ui(img)
    iv.present('popover', popover_location=(600,100))

    print('- ')
    print( 'test:  ui image => return a new pil image (rotated by -90° to prove pil type)')
    img2 = ui2pil(iv.image).rotate(-90)
    print( type(img2))
    img2.show()

    # test: np2ui verification is done via a popover
    iv2 = ui.ImageView(frame=(0,0,256,256))
    iv2.name = 'np2ui'
    iv2.image = np2ui(arr)
    iv2.present('popover', popover_location=(300,100))

    print('- ')
    print( 'test:  ui image => return a new np array')
    arr2 = ui2np(iv2.image)
    print( arr2)

ccc

Should np2pil() always return imgOut?

Should pil2np() always return arrayOut?

If the else clauses are supposed to return None then you should do that explicitly.

jmv38

@ccc changes done. Thanks.

jmv38

I have updated my code above:
- corrected for a bug.
- added ui -> pil and np conversions.
- Added tests for all functions.
This is certainly not the best code possible,
but i hope it hepls someone.
Thanks.

Tizzy

Helped me! I used your pil2ui(), thanks!

http://omz-forums.appspot.com/edit-post/5291307131994112

Webmaster4o

Is there a faster way? I have a user take a picture with the camera, then have it displayed in an ImageView, but it takes a while

ccc

Which function above are you using? Can you try wrapping the call to the conversion function in a with timer(): block (or similar) and tell us how long the conversion function takes to execute?

Webmaster4o

Using the first method with a photos.capture_image(). Strange behavior:

x = Image.open('test.jpg')
a = time.time()
pil_to_ui(x)
b = time.time()
print b-a

prints 0.97... but

x = photos.capture_image()
a = time.time()
pil_to_ui(x)
b = time.time()
print b-a

prints 6.22...
I don't think this is a resolution difference, they're both about the same size.

Webmaster4o

Wait, I had changed the img.format to PNG to fix an error, JPG makes it take 0.2 seconds. Related to my PNG crash?

JonB

one thing that helps, not with speed, but with the problem of capture_image returning, yet the conversion is not yet complete, is to add an ActivityIndicator which starts animating before starting conversion, and stops animation (and removes the indicator ) afterwards. the ActivityIndicator can be added on top of your imageview, or else it can be added to your root view, and sized to the root view, so that it essentially blocks anything else from happening while the processing happens.

on my ipad3, pil2ui takes a second or two for an image captured by the camera... an indicator at least doesn't leave the user wondering if something didn't work right, and prevents you from hitting the capture button again

Gerzer

@Webmaster4o: You should start a blog dedicated to problems with PNG images in Pythonista 1.5...except you would have no way to maintain it when 1.6 comes out. Still, it might gain some good traffic until that happens.

@omz: Did you know about these bugs while developing 1.6, or were the fixes just accidents? All this seems quite strange to me.

Webmaster4o

How do I make this support alpha? Using

def pil_to_ui(img):
    b = BytesIO()
    img.save(b, "JPEG")
    data = b.getvalue()
    b.close()
    return ui.Image.from_data(data)

Clear turns white...

Webmaster4o

Oops, I feel stupid, JPEG doesn't support transparency XD

dgelessus

Why would you use JPEG, you heretic. JPEG has three use cases: photos (the kind you make with your digital camera or smartphone), situations where something else requires you to use JPEG, and memes about lossy image compression. ;)