PIL's Image.frombuffer expected data length when using ctypes array
I am using Python, PIL and ctypes to do image manipulation. As I hacked stuff together, I used PIL's fromstring
function to get the pixel buffer from ctypes into a PIL object. I simply iterated over the array, building the python string.
This works
tx = foo.tx
tx.restype = POINTER(c_ubyte)
result = tx(...args...)
#TODO there must also be a better way to do this
pystr = ""
for i in xrange(w*h*4):
pystr += result[i]
i = Image.fromstring("RGBA", (w, h), pystr)
i.save("out.png")
It wasn't pretty, but it worked. Commented with a TODO and moved on. After getting the initial plumbing in place, profiling showed significant performa开发者_开发问答nce issues with this code block. Not surprising, I guess.
This does not work
Similar to this question: Pixel manipulation with PIL.Image and ctypes, I am trying to use frombuffer()
to get the pixel data into the PIL object more efficiently:
tx = foo.tx
tx.restype = POINTER(c_ubyte)
result = tx(...args...)
i = Image.frombuffer('RGBA', (w, h), result, 'raw', 'RGBA', 0, 1)
i.save("out.png")
Despite fromstring
working, using frombuffer
results in the following exception:
Traceback (most recent call last):
File "test.py", line 53, in <module>
i = Image.frombuffer('RGBA', (w, h), res, 'raw', 'RGBA', 0, 1)
File "/Library/Python/2.7/site-packages/PIL/Image.py", line 1853, in frombuffer
core.map_buffer(data, size, decoder_name, None, 0, args)
ValueError: buffer is not large enough
Environment
The buffer is malloc
'ed in C as:
unsigned char *pixels_color = (unsigned char*)malloc((WIDTH*HEIGHT*4)*sizeof(unsigned char*));
- The buffer has 4 bytes per pixel for each of the bands RGBA.
- Mac OS X 10.7, python2.7.1, PIL 1.1.7
Edit
Based on eryksun's comment and answer below, I moved the buffer allocation from the malloc
in the C library into Python and passed the pointer into C:
tx = foo.tx
tx.restype = POINTER(c_ubyte)
pix_clr = (c_ubyte*(w*h*4))()
tx(...args..., pix_clr)
i = Image.frombuffer('RGBA', (w, h), pix_clr, 'raw', 'RGBA', 0, 1)
i.save("out.png")
This works as expected. It still doesn't explain why PIL was unhappy with the C-allocated buffer, but this is the better structure for memory management anyway. Thanks to both ErykSun and HYRY!
When you set tx.restype = POINTER(c_ubyte)
, the resulting ctypes pointer object is a buffer of either 4 or 8 bytes for the address of the image buffer. You need to create a ctypes array from this address.
If you know the size ahead of time, you can set SIZE = WIDTH * HEIGHT * 4; tx.restype = POINTER(c_ubyte * SIZE)
. Otherwise set tx.restype = POINTER(c_ubyte)
, and cast to the dynamic size, i.e. size = w * h * 4; pbuf = cast(result, POINTER(c_ubyte * size))
. Either way, you need to dereference the pointer to get at the array. Use either buf = pbuf[0]
or buf = pbuf.contents
. Then you can pass buf
to Image.frombuffer
.
That said, it's usually simpler to allocate memory using ctypes. This gives you reference-counted memory management instead of having to manually free memory. The following is a toy example.
Assuming a dynamic size, the caller needs to get the image dimensions in order to allocate the array, so I've added a struct that has the width, height, and depth of the image, which gets filled in by the C function get_image_info
. The function get_image
receives a pointer to the image array to copy in data.
import ctypes
lib = ctypes.CDLL('imgtest.dll')
class ImageInfo(ctypes.Structure):
_fields_ = (
('width', ctypes.c_int),
('height', ctypes.c_int),
('depth', ctypes.c_int),
)
pImageInfo = ctypes.POINTER(ImageInfo)
pImage = ctypes.POINTER(ctypes.c_ubyte)
lib.get_image_info.argtypes = [pImageInfo]
lib.get_image_info.restype = ctypes.c_int
lib.get_image.argtypes = [pImage]
lib.get_image.restype = ctypes.c_int
imgnfo = ImageInfo()
lib.get_image_info(ctypes.byref(imgnfo))
w, h, d = imgnfo.width, imgnfo.height, imgnfo.depth
imgdata = (w * h * d * ctypes.c_ubyte)()
lib.get_image(imgdata)
from PIL import Image
img = Image.frombuffer('RGBA', (w, h), imgdata, 'raw', 'RGBA', 0, 1)
C declarations:
typedef struct _ImageInfo {
int width;
int height;
int depth;
} ImageInfo, *pImageInfo;
typedef unsigned char *pImage;
int get_image_info(pImageInfo imgnfo);
int get_image(pImage img);
try the following code:
import Image
from ctypes import c_ubyte, cast, POINTER
buf = (c_ubyte * 400)()
pbuf = cast(buf, POINTER(c_ubyte))
pbuf2 = cast(pbuf, POINTER(c_ubyte*400))
img1 = Image.frombuffer("RGBA", (10,10), buf, "raw", "RGBA", 0, 1)
img2 = Image.frombuffer("RGBA", (10,10), pbuf2.contents, "raw", "RGBA", 0, 1)
buf is an ubyte array, pbuf is a pointer to ubyte, pbuf2 is a pointer to ubyte[400]. img1 is created from buf directly, img2 is created from pubf2.contents.
your program create image from an pointer to ubyte, you must cast it to pointer to array, and use contents attribute to get the buffer. So use the following code to convert pointer to array:
tmp = cast(result, POINTER(c_ubyte*4*w*h)).contents
Image.frombuffer('RGBA', (w, h), tmp, 'raw', 'RGBA', 0, 1)
精彩评论