2

I'm working on Python script to apply textures to objects. I need to load an image that was created using PIL via img_from_array = Image.fromarray(ARRAY). Is it possible?

Marty Fouts
  • 33,070
  • 10
  • 35
  • 79
wooh
  • 23
  • 4
  • 1
    If you want to load from PNG/JPEG/etc data, see this. If you want to load from a pixel array, use img.pixels.foreach_set(data) (and do img.pack() afterwards). – scurest Mar 23 '22 at 08:08

1 Answers1

4

EDIT: From a comment on the answer, it is clear that one more step is needed. EDIT 2: From another comment, there's apparently a better pil_to_image function.

Since you are starting from a PIL image, you need to convert the image to a Blender image. To do that, see this answer from which I take this function and modified it according to comments on this answer.

def pil_to_image(pil_image, name='NewImage', alpha=False):
    '''
    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, alpha=alpha)
# convert Image 'L' to 'RGBA', normalize then flatten 
bpy_image.pixels.foreach_set((np.asarray(pil_image.convert('RGBA'),
                                         dtype=np.float32)
                              * byte_to_normalized).ravel())
bpy_image.pack()
print("pil_to_image completed in",time.time() - now,"s")
return bpy_image

Once you've done that, the key is that images are stored in bpy.data.images and that you use them in texture images by assigning the image to a field in the texture.

Given your comment and the above function you need something like

bpy_image = pil_to_image(img_from_array, name=WHATEVER_NAME_YOU_WANT)

to perform that conversion

Here's an example that adds the image created above to the active object. It's a bit of overkill, as it creates the material. It assumes the object has no materials yet, so the new material will go into slot 0 and be applied to the entire object by default. It also assumes you want to be able to map UV coordinates for the texture.

If you have an existing material you need to add an image texture and link it to the shader.

object = bpy.context.active_object

material = bpy.data.materials.new(name=MATERIAL_NAME) object.data.materials.append(material) material.use_nodes = True tree = material.node_tree nodes = tree.nodes

texcord = nodes.new('ShaderNodeTexCoord') mapping = nodes.new('ShaderNodeMapping') teximage = nodes.new("ShaderNodeTexImage")

bsdf = nodes["Principled BSDF"]

tree.links.new(texcord.outputs[2], mapping.inputs[0]) tree.links.new(mapping.outputs[0], teximage.inputs[0]) tree.links.new(teximage.outputs[0], bsdf.inputs[0])

teximage.image = bpy_image

Marty Fouts
  • 33,070
  • 10
  • 35
  • 79
  • Thanks for your explanation. Unfortunately I still don't get it. – wooh Mar 23 '22 at 18:36
  • In my scenario image is generated using Python PIL: img_from_array = Image.fromarray(ARRAY).

    And I want to assign this. Originally my script was:

    face = "FRONT" mat = bpy.data.materials.new(name=face) mat.use_nodes = True bsdf = mat.node_tree.nodes["Principled BSDF"] texImage = mat.node_tree.nodes.new('ShaderNodeTexImage') texImage.image = bpy.data.images.load(PNG_FILE)

    but now I need instead of loading PNG_FILE assign img_from_array

    – wooh Mar 23 '22 at 18:43
  • I've updated your question and my answer to account for PIL – Marty Fouts Mar 23 '22 at 19:31
  • 1
    pixels.foreach_set(data) was added in 2.83 and is faster than the pixels[:] = data. You also need to pack the image with .pack() afterwards, otherwise the image is not "saved" (see what happens if you close and reopen Blender). – scurest Mar 23 '22 at 20:56
  • 1
    Also you need to pass the correct alpha= argument to images.new. This doesn't seem like it does anything, but it also matters for save/reload. – scurest Mar 23 '22 at 21:19
  • @scurest thanks. I don't have PIL installed, so I'd appreciate it if you'd review my changes to the pil_to_image function. Feel free to edit if I've botched addressing your comments. – Marty Fouts Mar 23 '22 at 23:44
  • Getting through script without errors but converted textures in Blender look weird. Is there a simple way to draw pby_image on screen (using Jupiter) – wooh Mar 25 '22 at 00:36
  • @MartyFouts Thanks for support so far. By "weird" look I mean bpy_image definitely does not match original at least on Blender object. Thus I'm asking if there is a possibility to "see" this bpy_image in Jupiter UI somehow? – wooh Mar 25 '22 at 18:04
  • It is possible that the pil_to_image code is broken. I don't have PIL so I can't test it. But the easiest way to look at the image is to plug the image texture into an emission shader and place that material on a plane with the same aspect ratio as the image. (Effectively doing an "Image as plane" kind of thing.) – Marty Fouts Mar 25 '22 at 19:11
  • @martyFouts Here is screenshot where you can see both images. bpy_image looks like a cropped version from very center of the pil_image:https://www.dropbox.com/s/yevq92tr4ywlvoc/Screenshot%202022-03-26%20at%2001.21.58.png?dl=0 – wooh Mar 25 '22 at 23:27
  • Sorry, seems cropped even not from centre but shifted to right and down – wooh Mar 25 '22 at 23:29
  • I'm pretty sure that's the conversion routine. Unfortunately, that's not my area. Perhaps you can ask @scurest to comment on my modifications to the routine. If it's not the conversion routine is it possible that you're supplying different coordinates to the two versions of the texture? – Marty Fouts Mar 25 '22 at 23:33
  • @scurest Could you please take a look at this issue? – wooh Mar 26 '22 at 06:52
  • 1
    Solved. Thank you for help. Issue was not not related with conversion but with vertice definition in my script. – wooh Mar 26 '22 at 22:46
  • @scurest It appears converted images are flipped vertically. Of course I can fix this by flipping PIL image prior to conversion but what would be correct syntax to solve this? – wooh Apr 11 '22 at 23:35