Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python `ctypes` - How to copy buffer returned by C function into a bytearray

Tags:

python

ctypes

A pointer to buffer of type POINTER(c_ubyte) is returned by the C function (the image_data variable in the following code). I want this data to be managed by Python, so I want to copy it into a bytearray. Here's the function call

image_data = stb_image.stbi_load(filename_cstr, byref(width),
                                 byref(height), byref(num_channels), 
                                 c_int(expected_num_channels))

We get to know the width and height of the image only after that call, so can't pre-allocate a bytearray.

I would have used

 array_type = c.c_ubyte * (num_channels.value * width.value * height.value)

 image_data_bytearray = bytearray(cast(image_data, array_type))

But the type to cast to must be a pointer, not array, so I get an error.

TypeError: cast() argument 2 must be a pointer type, not c_ubyte_Array_262144

What should I do?

like image 381
soumik Avatar asked Sep 01 '25 10:09

soumik


1 Answers

OK, reading the answer to the question linked to in the comments (thanks, @"John Zwinck" and @"eryksun"), there are two ways of storing the data, either in a bytearray or a numpy.array. In all these snippets, image_data is of type POINTER(c_ubyte), and we have array_type defined as -

array_type = c_ubyte * num_channels * width * height

We can create a bytearray first and then loop over and set the bytes

arr_bytes = bytearray(array_size)
for i in range(array_size):
    arr_bytes[i] = image_data[i]

Or a better way is to create a C array instance using from_address and then initialize a bytearray with it -

image_data_carray = array_type.from_address(addressof(image_data.contents))

# Copy into bytearray
image_data_bytearray = bytearray(image_data_carray)

And during writing the image (didn't ask this question, just sharing for completeness), we can obtain pointer to the bytearray data like this and give it to stbi_write_png

image_data_carray = array_type.from_buffer(image_data_bytearray)
image_data = cast(image_data_carray, POINTER(c_ubyte))

The numpy based way of doing it is as answered in the linked question

address = addressof(image_data.contents)
image_data_ptr = np.ctypeslib.as_array(array_type.from_address(address))

This alone however only points to the memory returned by the C function, doesn't copy into a Python-managed array object. We can copy by creating a numpy array as

image_data = np.array(image_data_ptr)

To confirm I have done an assert all(arr_np == arr_bytes) there. And arr_np.dtype is uint8.

And during writing the image, we can obtain a pointer to the numpy array's data like this

image_data = image_data_numpy.ctypes.data_as(POINTER(c_ubyte))
like image 74
soumik Avatar answered Sep 04 '25 06:09

soumik