How can I convert a PIL Image to a ui.Image?
Forum Archive
How can I convert a PIL Image to a ui.Image?
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)
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.
Thanks guys! :D
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
@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()
# ...
@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!
Have you tried using @tony's code, but replacing ip.format with 'PNG'?
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.
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)
@omz Bingo!!!
Thanks a lot!
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)
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.
@ccc changes done. Thanks.
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.
Helped me! I used your pil2ui(), thanks!
http://omz-forums.appspot.com/edit-post/5291307131994112
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
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?
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.
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?
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
@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.
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...
Oops, I feel stupid, JPEG doesn't support transparency XD
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. ;)