I'm trying to put together a function that takes in a grayscale PIL Image
and returns a bpy.types.Image
(to be used a diffuse texture) but it feels slow:
First I've tried a simpler non python version:
def pil_to_image(pil_image, name='NewImage'):
'''
PIL image pixels is 2D array of byte tuple (when mode is 'RGB', 'RGBA') or byte (when mode is 'L')
bpy image pixels is flat array of normalized values in RGBA order
'''
now = time.time()
# setup PIL image reading
width = pil_image.width
height = pil_image.height
pil_pixels = pil_image.load()
byte_to_normalized = 1.0 / 255.0
num_pixels = width * height
# setup bpy image
channels = 4
bpy_image = bpy.data.images.new(name, width=width, height=height)
# bpy image has a flat RGBA array (similar to JS Canvas)
bpy_pixels = [None] * width * height * channels
for index in range(num_pixels):
x = index % width
y = index // width
# read x,y int or tuple flip Y
pixel = pil_pixels[x,height - y - 1]
# convert to 1D index, taking channels(4) into account = red index
r_index = index * 4
# handle gray
normalized_pixel = pixel * byte_to_normalized
bpy_pixels[r_index] = normalized_pixel
bpy_pixels[r_index + 1] = normalized_pixel
bpy_pixels[r_index + 2] = normalized_pixel
bpy_pixels[r_index + 3] = 1.0
# update pixels
bpy_image.pixels = bpy_pixels
print("pil_to_image completed in",time.time() - now,"s")
return bpy_image
Which prints pil_to_image completed in 4.9107561111450195 s
for a 4096 x 2160 image
I've tried using numpy, however the it's similarly slow:
def pil_to_image(pil_image, name='NewImage'):
'''
PIL image pixels is 2D array of byte tuple (when mode is 'RGB', 'RGBA') or byte (when mode is 'L')
bpy image pixels is flat array of normalized values in RGBA order
'''
now = time.time()
# setup PIL image reading
width = pil_image.width
height = pil_image.height
pil_pixels = pil_image.load()
byte_to_normalized = 1.0 / 255.0
num_pixels = width * height
# setup bpy image
channels = 4
bpy_image = bpy.data.images.new(name, width=width, height=height)
# bpy image has a flat RGBA array (similar to JS Canvas)
bpy_image.pixels = (np.asarray(pil_image.convert('RGBA'),dtype=np.float32) * byte_to_normalized).ravel()
print("pil_to_image completed in",time.time() - now,"s")
return bpy_image
this prints pil_to_image completed in 5.018976926803589 s
Am I missing something? Is there a more efficient way of turning a PIL image into a Blender image to be used as a DiffuseBSDF texture?
Update
Thanks to @batFINGER link I could speed up the function a tad using slice notation:
def pil_to_image(pil_image, name='NewImage'):
'''
PIL image pixels is 2D array of byte tuple (when mode is 'RGB', 'RGBA') or byte (when mode is 'L')
bpy image pixels is flat array of normalized values in RGBA order
'''
now = time.time()
# setup PIL image conversion
width = pil_image.width
height = pil_image.height
byte_to_normalized = 1.0 / 255.0
# create new image
bpy_image = bpy.data.images.new(name, width=width, height=height)
# convert Image 'L' to 'RGBA', normalize then flatten
bpy_image.pixels[:] = (np.asarray(pil_image.convert('RGBA'),dtype=np.float32) * byte_to_normalized).ravel()
print("pil_to_image completed in",time.time() - now,"s")
return bpy_image
which now prints: pil_to_image completed in 3.4869320392608643 s
Any tips on speeding it up further more than welcome :)
pil_to_image completed in 0.006505489349365234 s
$\endgroup$converted to RGBA in 0.022275209426879883 converted to numpy array in 0.12366914749145508 numpy array normalized in 0.0631706714630127 ravel in 0.0 assigned pixels in 3.236783742904663
$\endgroup$