Slicing an image into tiles to paste render result pixels into it

Hey there people,

Wrote a small utility script that can “paste” a render result into an image, to see if I could make sprite sheets directly in cycles or internal instead of fusing layers in GIMP. It works as intended, though a tad slower than I hoped.

Now, iterating over every pixel in the render result and then assigning the RGBA values to the corresponding pixel in the output image works in theory, but with a larger image size means millions of iterations. So I take a slice of pixels for an entire row and assign the values all at once for that specific row of pixels. This is better, but I wonder if the number of iterations needed to paste the image can be reduced some more.

Here’s code,

import bpy

sheet = bpy.data.images["sprite_sheet"] #image to paste render to
source = bpy.data.images["Viewer Node"] #dummy to grab render pixels from

#get sprite sheet size and render resolution
#use that to get the number of tiles
sheet_x, sheet_y = int(sheet.generated_width), int(sheet.generated_height)
source_x, source_y = int(bpy.data.scenes["Scene"].render.resolution_x), int(bpy.data.scenes["Scene"].render.resolution_y)
tiles_x, tiles_y = int(sheet_x/source_x), int(sheet_y/source_y)

#render first frame of animation
bpy.context.scene.frame_set(0)
bpy.ops.render.render()

for current_y_tile in range(1, tiles_y+1):    
    for current_x_tile in range(1, tiles_x+1):
        
        #get index of first pixel in tile
        source_pos = int((source_x*source_y*4)-source_x*4)
        sheet_pos = int((len(sheet.pixels)/current_y_tile)-(sheet_x*4/current_x_tile))
        
        for i in range(0, source_x):
            #copy this row of pixels up to tile width
            sheet.pixels[sheet_pos:sheet_pos+source_x*4] = source.pixels[source_pos:source_pos+source_x*4]

            #get index of first pixel in the next row for this tile
            source_pos -= source_x*4
            sheet_pos -= sheet_x*4
        
        #render next frame
        bpy.context.scene.frame_set(bpy.context.scene.frame_current+1)
        bpy.ops.render.render()

#set frame back to start
bpy.context.scene.frame_set(0)

I think ‘plot’ should work, in the video texture module, I wonder if there is an equivalent in bgl?

I’m inclined to believe bgl would come in handy with these types of operations, but frankly the number of functions one has to call just to do simple stuff is intimidating.

Managed to numpy my way around the issue, however c:

image.pixels is a float sequence; so convert it into an ndarray of shape(size_x, size_y, RGBA)
then I can take a slice in both dimensions. arr[0:size_y, 0:size_x] = tilez

think I figured out the math here, but I haven’t tried with non-square images; may need more testing.

import bpy, itertools
import numpy as np

bpy.context.scene.frame_set(0)
bpy.ops.render.render()

sheet = bpy.data.images["sheet"]
source = bpy.data.images["Viewer Node"]

sheet_x, sheet_y = int(sheet.generated_width), int(sheet.generated_height)
source_x, source_y = int(bpy.data.scenes["Scene"].render.resolution_x), int(bpy.data.scenes["Scene"].render.resolution_y)

tiles_x, tiles_y = int(sheet_x/source_x), int(sheet_y/source_y)

sheet_mat = np.ndarray((sheet_x,sheet_y,4), buffer=np.array(sheet.pixels))
source_mat = np.ndarray((source_x,source_y,4), buffer=np.array(source.pixels))

print(sheet_mat.shape, source_mat.shape)

for current_y_tile in range(1, tiles_y+1):
    
    for current_x_tile in range(0, tiles_x):
        
        pos_x = source_x*current_x_tile
        pos_y = sheet_y-(source_y*current_y_tile)
            
        sheet_mat[pos_y:pos_y+source_y, pos_x:pos_x+source_x] = source_mat[0:source_x, 0:source_y]

        print("Tile %d:%d --> x(%d) to x(%d) : y(%d) to y(%d)"%(current_x_tile+1, current_y_tile, pos_x,pos_x+source_x, pos_y,pos_y+source_y))
        
        bpy.context.scene.frame_set(bpy.context.scene.frame_current+1)
        bpy.ops.render.render()
        
        source_mat = np.ndarray((source_x,source_y,4), buffer=np.array(source.pixels))
        
final_mat = list(itertools.chain.from_iterable(sheet_mat.tolist()))
final_mat = list(itertools.chain.from_iterable(final_mat))

sheet.pixels = final_mat
bpy.context.scene.frame_set(0)

with
glDrawPixels(width, height, format, type, pixels)
I think you could write all the pixels from the image into a buffer in 1 shot