Forum Archive

How to load a custom scene Texture?

Moe

Hi, I am trying to build a scene with custom textures, which I pre-process using PIL. From PIL I then convert them to ui.Image and then I want to load them as a Texture. Unfortunately, I cannot get them to load at all. Included is a minimum (non-) working example with a normal png in my project directory.

import ui, scene

image = ui.Image('sprite.png')
image.show()    # this works
texture = scene.Texture(image) # this fails
'''
Traceback (most recent call last):
  File "/private/var/mobile/Containers/Shared/AppGroup/73760516-2464-4322-8DCC-0525D46DFFC3/Pythonista3/Documents/smw/minimal_example.py", line 5, in <module>
    texture = scene.Texture(image)
ValueError: Could not load image
'''

texture = scene.Texture('sprite.png') # this fails as well
'''
Traceback (most recent call last):
  File "/private/var/mobile/Containers/Shared/AppGroup/73760516-2464-4322-8DCC-0525D46DFFC3/Pythonista3/Documents/smw/minimal_example.py", line 6, in <module>
    texture = scene.Texture('sprite.png')
ValueError: Image not found
'''

The sprite.png is a 32x32 PNG with a size of 981 bytes. I had no luck getting this to work with other images so far.
Any help is appreciated.

cvp

@Moe sure that sprite.png in the same folder as your script? For me, it is ok

import ui, scene
image = ui.Image('sprite.png')
image.show()                                                # this works
texture = scene.Texture(image)              # this works
texture = scene.Texture('sprite.png') # this works
Moe

Thanks for the suggestion, but the image is in the same directory. It is really odd that it does work for you, but not for me.

I am on iOS 13.5 (17F75) with an iPad 7 and Pythonista 3.3 (330025)

cvp

@Moe same iOS, Pythonista, iPad mini 4, but I did not test with a 32x32 png, I'll do it

cvp

@Moe works with a 32x32

cvp

Try to restart Pythonista

Moe

After testing with a bigger image (overworld_tileset.png, 565x564 px, 44kb) I got the same result.
Restarting Pythonista did not help, restarting the iPad did not help.

cvp

@Moe sure you test with the little script, not with a bigger including the little

Moe

I now imported a photo I took with the iPad (JPG) via the little plus in the bottom left -> Import... -> Photo Library and it worked fine!

Maybe there is a problem using PNGs?

cvp

@Moe I'had tried with a png. Could you rename your .jpg as .png and retry

Moe

I found the problem: The images I used For testing where saved in P / Indexed Color mode. This can be checked with PIL

from PIL import Image

img = Image.open('sprite.png')
print(img.mode)
#> 'P'

I converted them with img = img.convert('RGB') and saved them again. Now it works. It seems that the Texture class cannot handle files in mode P and does not fail gracefully. This is probably an oversight.
Thank you again @cvp for taking your time to help! Much appreciated.

cvp

@Moe Nice job! 🍾

My tested ping mode was RGBA

stephen

@Moe @cvp

You could use io. BytesIO in a Context manager soyou dont need to save and open the images. and when you use ui.Image.from_data(image_data[, scale]) and set scale to your IOS device pixel/point ratio. most comonly 2. (1:1, 2:1, 3:1 ...) this will scale your image properly to the screen. you can get this by calling scene.get_screen_scale()

heres an example:

import scene
import Image
import io

cache = dict({
    "img1":"./image1.png",
    "img2":"./image2.png",
})

for k,v in cache.items():
    with Image.open(v) as img:
        resized_img=img.resize(
            (int(img.size[0]/2), int(img.size[1]/2)), Image.ANTIALIAS)

        with io.BytesIO() as byteStream:
            resized_img.save(byteStream, "tiff")    
            ns.cache[k]=scene.Texture(ui.Image.from_data(byteStream.getvalue(), scene.get_screen_scale()))

stephen

@cvp said:

@Moe sure that sprite.png in the same folder as your script? For me, it is ok
import ui, scene image = ui.Image('sprite.png') image.show() # this works texture = scene.Texture(image) # this works texture = scene.Texture('sprite.png') # this works

from my understanding shoudn't it be ui.Image.named('sprite.png')

cvp

@stephen said:

from my understanding shoudn't it be ui.Image.named('sprite.png')

You're right but try it, it works also without .named

stephen

@cvp

well look at that... i think its all beena lie... lol jk but i do wonder what the method named might be doing that may be a benefit?

cvp

@stephen said:

what the method named might be doing that may be a benefit?

No idea

Moe

My current approach is to load the tileset.png which contains all the tiles. I then use Image.crop() for every tile I want to extract. I then upscale them using Image.resize() by some arbitrary factor, because if I would let the scene upscale the 8x8 textures to something like 64x64, the performance drops hard. Using BytesIO I convert them to ui.Image without saving them on disk and from there I can load them as a scene.Texture.

But thank you for the hint that I can load the image and scale them in one operation!

All I had to do to fix the original problem was converting the tileset.png to RGB mode.