在使用ctypes数组时,从缓冲区的期望数据长度。

时间:2021-01-20 00:25:12

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.

我使用Python、PIL和ctypes来进行图像处理。当我在一起攻击时,我使用了PIL的fromstring函数来从ctypes中获取像素缓冲区,并将其转换为一个PIL对象。我只是遍历了数组,构建了python字符串。

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 performance issues with this code block. Not surprising, I guess.

它不漂亮,但它起作用了。带着TODO的评论,继续前进。在获得最初的管道之后,概要分析显示了这个代码块的显著性能问题。并不奇怪,我猜。

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:

类似于这个问题:像素操作与PIL。图像和ctypes,我尝试使用frombuffer()将像素数据更有效地获取到PIL对象:

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:

尽管fromstring工作,但使用frombuffer会导致以下异常:

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:

在C中缓冲是malloc'ed:

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.
  • 对于每个RGBA,缓冲区每个像素有4个字节。
  • Mac OS X 10.7, python2.7.1, PIL 1.1.7
  • 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:

基于eryksun的评论和下面的回答,我将C库中malloc的缓冲区分配改为Python,并将指针传递到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!

这是预期。它仍然不能解释为什么PIL对c分配的缓冲区不满意,但是这是内存管理的更好的结构。谢谢你的红日和海莉!

2 个解决方案

#1


3  

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.

当您设置tx.restype =指针(c_ubyte)时,产生的ctypes指针对象为图像缓冲区地址的4或8字节的缓冲区。您需要从这个地址创建一个ctypes数组。

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.

如果您提前知道大小,可以设置size = WIDTH * HEIGHT * 4;restype =指针(c_ubyte * SIZE)。否则设置tx.restype =指针(c_ubyte),并将其转换为动态大小,即size = w * h * 4;pbuf = cast(结果,指针(c_ubyte * size))。无论哪种方式,都需要取消指向数组的指针。使用buf = pbuf[0]或buf = pbuf.contents。然后可以将buf传递给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.

也就是说,使用ctypes分配内存通常更简单。这将为您提供引用计数的内存管理,而不是手动释放内存。下面是一个玩具示例。

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.

假设一个动态大小,调用者需要获得图像维度来分配数组,所以我添加了一个结构,它具有图像的宽度、高度和深度,由C函数get_image_info填充。函数get_image接收一个指向图像数组的指针,以复制数据。

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:

C声明:

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);

#2


3  

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.

buf是一个ubyte数组,pbuf是一个指向ubyte的指针,pbuf2是一个指向ubyte[400]的指针。img1是由buf直接创建的,img2是由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:

您的程序从一个指向ubyte的指针创建图像,您必须将它转换为指向数组的指针,并使用contents属性来获取缓冲区。因此,请使用以下代码转换指向数组的指针:

tmp = cast(result, POINTER(c_ubyte*4*w*h)).contents
Image.frombuffer('RGBA', (w, h), tmp, 'raw', 'RGBA', 0, 1)

#1


3  

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.

当您设置tx.restype =指针(c_ubyte)时,产生的ctypes指针对象为图像缓冲区地址的4或8字节的缓冲区。您需要从这个地址创建一个ctypes数组。

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.

如果您提前知道大小,可以设置size = WIDTH * HEIGHT * 4;restype =指针(c_ubyte * SIZE)。否则设置tx.restype =指针(c_ubyte),并将其转换为动态大小,即size = w * h * 4;pbuf = cast(结果,指针(c_ubyte * size))。无论哪种方式,都需要取消指向数组的指针。使用buf = pbuf[0]或buf = pbuf.contents。然后可以将buf传递给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.

也就是说,使用ctypes分配内存通常更简单。这将为您提供引用计数的内存管理,而不是手动释放内存。下面是一个玩具示例。

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.

假设一个动态大小,调用者需要获得图像维度来分配数组,所以我添加了一个结构,它具有图像的宽度、高度和深度,由C函数get_image_info填充。函数get_image接收一个指向图像数组的指针,以复制数据。

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:

C声明:

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);

#2


3  

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.

buf是一个ubyte数组,pbuf是一个指向ubyte的指针,pbuf2是一个指向ubyte[400]的指针。img1是由buf直接创建的,img2是由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:

您的程序从一个指向ubyte的指针创建图像,您必须将它转换为指向数组的指针,并使用contents属性来获取缓冲区。因此,请使用以下代码转换指向数组的指针:

tmp = cast(result, POINTER(c_ubyte*4*w*h)).contents
Image.frombuffer('RGBA', (w, h), tmp, 'raw', 'RGBA', 0, 1)