Copying image from image editor to clipboard?

Hey all! So I’ve got some working code that copies an image from the image editor to the windows clipboard, unfortunately it’s extremely slow. Copying a 2048x4096 texture takes 30 seconds. I suppose I can live with that, as it works flawlessly otherwise, but I want to know if anybody has any ideas on how to make this thing run faster.

I’m writing it as part of a larger set of plugins, and I’ve pasted the relevant portion below, hopefully that’s everything.

import bpyimport struct
from binascii import hexlify
from ctypes import *
import math

def set_clipboard_from_string(data):
    kernel32 = windll.kernel32
    user32 = windll.user32
    memcpy = cdll.msvcrt.memcpy
    
    user32.OpenClipboard(None)
    user32.EmptyClipboard()
    
    CF_DIB = 8  # constant value for predefined clipboard format BITMAPINFO
    GPTR = 0x0042 # Flags to use when allocating global memory
    
    hexlify(b'helloworld!') #ESSENTIAL CODE, DON'T KNOW WHY!!!  (Seriously...)
    
    length = len(data)
    memory_handle = kernel32.GlobalAlloc(GPTR, length)
    
    hexlify(b'helloworld!') #ESSENTIAL CODE, DON'T KNOW WHY!!!  (Seriously...)
    
    data_point = kernel32.GlobalLock(memory_handle)
    
    hexlify(b'helloworld!') #ESSENTIAL CODE, DON'T KNOW WHY!!!  (Seriously...)
    
    memcpy(data_point, bytes(data), length)
    
    kernel32.GlobalUnlock(memory_handle)
    
    hexlify(b'helloworld!') #ESSENTIAL CODE, DON'T KNOW WHY!!!  (Seriously...)
    
    user32.SetClipboardData(CF_DIB, memory_handle)
    
    hexlify(b'helloworld!') #ESSENTIAL CODE, DON'T KNOW WHY!!!  (Seriously...)
    
    user32.CloseClipboard()
    return True


def get_image_as_binary_data(img):
    image_width = img.size[0]
    image_height = img.size[1]
    image_channels = img.channels
    px = img.pixels[:]
    width_bytes = image_width * 4  # we'll write an alpha channel even if there isn't one in the blender image.
    size = width_bytes * image_height + 40 # x bytes wide per row for y rows
    b = bytearray(size)
    channel_order = (2,1,0,3)  # BGRA
    
    b[0:4] = struct.pack("<L",40)  # header length
    b[4:8] = struct.pack("<L",image_width)  # x size in pixels
    b[8:12] = struct.pack("<L",image_height)  # y size in pixels
    b[12:14] = struct.pack("<H", 1)  # image planes
    b[14:16] = struct.pack("<H", 32)  # bits per pixel
    b[16:20] = struct.pack("<L", 0)  # compression style
    b[20:24] = struct.pack("<L", 0)  # unused for RGB ('size image'?)
    b[24:28] = struct.pack("<L", 2835)  # roughly 72 pixels per inch (pixels/meter)
    b[28:32] = struct.pack("<L", 2835)  # same as above for y
    b[32:36] = struct.pack("<L", 0)  # unused for RGB (color used)
    b[36:40] = struct.pack("<L", 0)  # unused for RGB (color needed)
    
    pos = 40  # position of next byte to be written
    
    offset = 0
    if image_channels == 4:  # write image as usual
        for y in range(image_height):
            for x in range(image_width):
                for c in range(4):
                    if c == 3:
                        val = min(max(int((px[offset]*255.0)+0.5),0),255)
                    else:
                        val = min(max(int((px[offset + (2 - 2*c)]*255.0)+0.5),0),255)
                    b[pos] = val
                    pos += 1
                    offset += 1
                
        return b
    elif image_channels == 3:  # write image, plus an alpha of 255
        for y in range(image_height):
            for x in range(image_width):
                for c in range(3):
                    val = min(max(int((px[offset + (2 - 2*c)]*255.0)+0.5),0),255)
                    b[pos] = val
                    pos += 1
                    offset += 1
                b[pos] = 255
                pos += 1
        return b
    else:
        return None

class CopyImageToClipboard(bpy.types.Operator):
    bl_idname = "spark.copy_clipboard"
    bl_label = "Copy Image to Clipboard"
    bl_description = "Copies the currently visible image to the clipboard"
    
    def execute(self, context):
        img = context.edit_image
        if img is None:
            self.report({'ERROR'}, "No image in image editor.")
            return {'CANCELLED'}
        b = get_image_as_binary_data(img)
        if b is None:
            self.report({'ERROR'}, "Invalid image in image editor.")
            return {'CANCELLED'}
        set_clipboard_from_string(b)
        self.report({'INFO'}, "Image successfully copied to clipboard.")
        
        return {'FINISHED'}

Yes… those hexlify lines are necessary. You get access violations if you comment them out.

You should probably save to file with built-in methods, and copy from there to clipboard.

Mmmm was afraid you’d say that. I just have an extreme aversion to file clutter. If the only purpose of a file is to be saved before being opened right back up in Photoshop, then I really hate having to make that file in the first place.

It could always the same file in temp directory I guess.