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.