2
$\begingroup$

I'm trying to merge set of images together with script in Blender 2.8. At the moment there is no library like PIL available in Blender, but there is imbuf which one I can use to read the images from disk to list. There is not much documentation about this imbuf... Anyway I want to do this merge process with blender own libraries, so users not need to get PIL or similar library...

How I can merge multiple image files from disk together with Python in Blender? Is there pythonic way to do this without libraries, or can I do it with imbuf, or using imbuf and numpy, or how to do this in blender script?

I'm lost how can I make this, so If someone knows how to do this in Blender 2.8, please show me.

import imbuf

def merge_image_parts(image_paths):

    images = []

    # Load images to list with imbuf
    for img in image_paths:
        img_path = os.path.realpath(bpy.path.abspath(img))
        images.append(imbuf.load(filepath))

    # MERGE TO NEW IMAGE BUT HOW?

enter image description here

$\endgroup$
4
  • $\begingroup$ Can't you subdivide a plane into 4 faces and give each face an individual material? $\endgroup$ Feb 8, 2020 at 16:11
  • $\begingroup$ @Nate_Sycro27 yes, but trying to do this in script. My plan is render multiple images and then combine them together with script. All under the hood in script. This is new plugin where trying to create this process automatically that user do not need to take those images to other program to merge rendered images in there e.g. Gimp or PS. $\endgroup$ Feb 8, 2020 at 16:44
  • $\begingroup$ It certainly seems like pasting has been implemented in imbuf but I can't tell if it has been committed: developer.blender.org/D6226 $\endgroup$ Feb 8, 2020 at 17:35
  • $\begingroup$ @batFINGER Would be interested to hear your input/answer to this if you get a chance. $\endgroup$ Feb 12, 2020 at 16:17

3 Answers 3

1
$\begingroup$

Finally, I managed to combine image parts together with looping nested arrays. Although the script itself is not the most beautiful... I mark this answer to correct now, but if somebody is doing better that works in this context, I'll change it.

So here is "monster" that I've used in this case:

# First I need to order rendered part names correctly
rendered_images_ordered = []            
for y in range(parts_count, 0, -1):
    for x in range(parts_count):
        rendered_images_ordered.append(rendered_images[(y-1)+(x*parts_count)])

# Then I grab pixel data from all parts
part_pixels = []  
for image in rendered_images_ordered:
    filepath = os.path.join(scene.render_settings.render_folder, image)
    filepath = os.path.realpath(bpy.path.abspath(filepath))
    loaded_pixels = list(bpy.data.images.load(filepath, check_existing=False).pixels)
    part_pixels.append([loaded_pixels[ipx:ipx+4] for ipx in range(0, len(loaded_pixels), 4)])

# And finally loop all pixels together to final image
final_image_pixels = []
for i in range(parts_count, 0, -1):
    for row in range(part_height):    
        part_switch = -1           
        px_counter = -1            
        for col in range(final_image_width):
            if col % part_width == 0:
                part_switch += 1
                px_counter = 0
            if i-1 == 0:
                px_arr = i-1 + part_switch
            else:
                px_arr = ((i-1) * parts_count) + part_switch
            target_pixel = row * part_width + px_counter
            final_image_pixels.append(part_pixels[px_arr][target_pixel])
            px_counter += 1 

# And then save output image
output_image = bpy.data.images.new(final_image_name, alpha=True, width=final_image_width, height=final_image_height)
output_image.alpha_mode = 'STRAIGHT'          
output_image.pixels = final_image_pixels
output_image.filepath_raw = final_image_filepath
output_image.file_format = scene.render.image_settings.file_format
output_image.save()     

Result:

Result

If you want to investigate more about where I use this loop check here. This is part of RenderFart blender addon. I hope my answer or addon is helpful for someone...

$\endgroup$
0
$\begingroup$

OK. This answer doesn't necessarily use python, but it achieves the same result.

Say we have 4 pictures, and want to combine it into one image in Blender. There are many ways to do it. I'll give a few examples:

1) Individual faces for individual materials

You can assign the images to individual faces in Blender. I'll use the example you provided.

I have here 4 faces with individual materials:

enter image description here

Now how to make multiple materials like this combine into 1 face?

Add a new face, and go to edit mode. CTRL+R twice to loop cut once along the X and Y axes of the plane. Should look like this:

enter image description here

Now make sure "select faces" is selected in edit mode. Select one face, and go to the materials tab. Add a new material slot, and find one of the other color materials (or image), and select "assign". Do the same for the different faces, and the other materials. Make sure you add a new material slot for the object only for each face.

Here is the result:

enter image description here

Now, of course, to merge the materials into one image:

I do this lazy Blenderer's way.

CTRL+S will bring you a snap menu. Select "cursor to selected". Make sure you have the new plane selected.

Now add a camera, and delete the old one. Set this new camera's resolution to 1200x1200, or some value that is identical to the other value.

Move the camera up on the Z axis until through it you see just about the entire plane. Now render.

Here's the final combined image:

enter image description here

Of course you can edit the image in the compositor to achieve a nice image texture. If you want to mess around with it, here is the .blend:

$\endgroup$
1
  • 1
    $\begingroup$ Thanks for answer. But I try to avoid all manual work in this script and do everything with python. But that is great answer if wanted to create it using blender UI. $\endgroup$ Feb 8, 2020 at 16:47
0
$\begingroup$

Finally after investigations I got something to work. Here is example blender file where is script that exports merged image. It works properly when using images that are rendered with "full final size" where is transparent section included and other with "cropped borders". Look below for a better understanding.

enter image description here

And here is script that is also included in example Blender (zip) file.

# Example script to show how to merge external images in Blender
# using numpy. In this example we use 4 images (2x2) that should
# be merged to one actual final image. 
# Regular (not cropped render borders) seems to work fine but
# how to merge cropped images properly???
#
# Usage: Just run script and it will export image named "MERGED_IMAGE"
# to root of this project folder and you'll see what's the problem.

import bpy, os
import numpy as np

ctx = bpy.context
scn = ctx.scene

print('START')

# Get all image files
def get_files_in_folder(path):
    path = bpy.path.abspath(path)
    render_files = []
    for root, dirs, files in os.walk(path):
        for file in files:
            if (file.lower().endswith(('.png', '.jpg', '.jpeg', '.tiff', '.bmp', '.gif'))):
                render_files.append(file)
    return render_files

def merge_images(image_files, image_cropped = True):

    image_pixels = []
    final_image_pixels = 0

    print(image_files)

    for file in image_files:
        if image_cropped is True:
            filepath = bpy.path.abspath('//Cropped\\' + file)
        else:
            filepath = bpy.path.abspath('//Regular\\' + file)
        loaded_pixels = bpy.data.images.load(filepath, check_existing=True).pixels
        image_pixels.append(loaded_pixels)

    np_array = np.array(image_pixels)

    # Merge images
    if image_cropped:
        final_image_pixels = np_array
        # HOW MERGE PROPERLY WHEN USING CROPPED IMAGES???
    else:
        for arr in np_array:
            # This works only with 4 parts properly.
            final_image_pixels += arr

    # Save output image
    output_image = bpy.data.images.new('MERGED_IMAGE', alpha=True, width=256, height=256)
    output_image.file_format = 'PNG'
    output_image.alpha_mode = 'STRAIGHT'
    output_image.pixels = final_image_pixels.ravel()
    output_image.filepath_raw = bpy.path.abspath("//MERGED_IMAGE.png")
    output_image.save()   

images_cropped = get_files_in_folder("//Cropped")
images_regular = get_files_in_folder('//Regular')

# Change between these to get different example
merge_images(images_cropped)
#merge_images(images_regular, False)

print('END')

Here is project folder in zip file that contains working test script example, where you can test how this works. https://drive.google.com/file/d/1R4G_fubEzFWbHZMLtAAES-QsRhKyLKWb/view?usp=sharing

Update about findings (13.2.2020)

I'm trying to sort all pixels one by one with loops and see how that will work. So I don't care performance at this point, just want to get output right. This whole merge feature is part of add-on that I'm working now called RenderFarts https://github.com/Creatide/RenderFarts :) So here you can see the function where I'm going to use this.

$\endgroup$
0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .