Reputation: 727
The frame is following: PySide2 QImage gives me access to the internal pixel data with the bits()
- call. The call returns memoryview - object that i would like to pass C-library function that does processing on the image pixel data (say it does super-fancy-blur for an example).
Problem is that i have not found a way to get memoryview underlaying data as ctypes call parameter.
That is: how can i get from memoryview object ctypes.c_void_p
(or similar) -- without making copy of the buffer. The memoryview tobytes()
returns bytes, but its copy from the memory, and so modifying that will not modify the original image (pixel data).
Now the minimal example python silly.py
side is:
import ctypes
from PySide2.QtGui import QImage
img = QImage(500,100, QImage.Format.Format_RGB32 )
data = img.bits()
print(data)
dll = ctypes.CDLL( "./silly.o" )
dll_set_image = getattr( dll, "set_image" )
dll_set_image.argtypes = [ ctypes.c_void_p, ctypes.c_uint32, ctypes.c_uint32 ]
# Next line gives: ctypes.ArgumentError: argument 1: <class 'TypeError'>: wrong type
dll_set_image( data, img.width(), img.height() )
C-"dll": silly.c
#include <stdint.h>
#include <stdio.h>
void set_image( void* data, uint32_t xsize, uint32_t ysize )
{
printf("Data set %lx %d %d", (uint64_t)data, xsize, ysize );
}
Compiling and testing:
% gcc -shared -o silly.o silly.c
% python3 silly.py
<memory at 0x7f2f9806a700>
Traceback (most recent call last):
File "silly.py", line 13, in <module>
dll_set_image( data, img.width(), img.height() )
ctypes.ArgumentError: argument 1: <class 'TypeError'>: wrong type
Upvotes: 2
Views: 1111
Reputation: 727
For completeness sake ppl finding this page from google, yet another alternative that works is based on making python module with C/C++ as described on docs.
It requires some boilerplate, but the actual access to buffer is easy:
PyObject* set_image(PyObject *self, PyObject *args)
{
Py_buffer buffer;
long xsize;
long ysize;
int ok = PyArg_ParseTuple(args, "y*ll", &buffer, &xsize, &ysize);
if ( ok != 1 )
return NULL;
printf("Data set: %d %d - %lx\n", (int)xsize, (int)ysize, (unsigned long)buffer.buf );
Py_RETURN_NONE;
}
Upvotes: 1
Reputation: 177481
A memoryview
supports the Python buffer protocol, so create a ctypes
array of the appropriate size using its from_buffer
method, which won't make a copy but reference the same memory.
Modifying your example slightly to see the buffer change:
silly.c:
#include <stdint.h>
#include <stdio.h>
#ifdef _WIN32
# define API __declspec(dllexport)
#else
# define API
#endif
API void set_image(unsigned char* data, uint32_t xsize, uint32_t ysize)
{
printf("%p %u %u\n",data,xsize,ysize);
*data = 123;
}
test.py:
import ctypes
from PySide2.QtGui import QImage
img = QImage(500,100, QImage.Format.Format_RGB32 )
data = img.bits()
print('original:',data[0])
dll = ctypes.CDLL('./silly')
dll_set_image = dll.set_image
dll_set_image.argtypes = ctypes.c_void_p, ctypes.c_uint32, ctypes.c_uint32
buffer = (ctypes.c_ubyte * len(data)).from_buffer(data)
dll_set_image(buffer, img.width(), img.height())
print('final:',data[0])
Output:
original: 80
00000213CCF3C080 500 100
final: 123
Upvotes: 4
Reputation: 243907
memoryview
is not a void * but a structure (Py_buffer
) that within its members is the buffer, so the conversion is not direct.
In these cases I prefer to use Cython(python -m pip install cython
) that allows creating wrappers to libraries in a simple way. Considering the above I have created a project:
├── main.py
├── psilly.c
├── psilly.pyx
├── setup.py
├── silly.c
├── silly.h
└── silly.pxd
silly.h
extern void set_image( unsigned char* data, uint32_t xsize, uint32_t ysize, uint32_t channels);
silly.c
#include <stdint.h>
#include "silly.h"
void set_image( unsigned char* data, uint32_t xsize, uint32_t ysize, uint32_t channels)
{
// fill white color
for (uint32_t i = 0; i < (xsize * ysize * channels); i++)
{
data[i] = 255;
}
}
silly.pxd
from libc.stdint cimport uint32_t
cdef extern from "silly.h":
void set_image(unsigned char* data, uint32_t xsize, uint32_t ysize, uint32_t channels)
psilly.pyx
cimport silly
cpdef set_image(unsigned char[:] data, int width, int height, int channels):
silly.set_image(&data[0], width, height, channels)
setup.py
from distutils.core import Extension, setup
from Cython.Build import cythonize
ext = Extension(
name="psilly",
sources=["psilly.pyx", "silly.c"],
)
setup(ext_modules=cythonize(ext))
main.py
import psilly
from PySide2.QtWidgets import QApplication, QHBoxLayout, QLabel, QWidget
from PySide2.QtGui import QImage, QPixmap
app = QApplication()
img = QImage(500, 100, QImage.Format.Format_RGB32)
data = img.bits()
label1 = QLabel()
label1.setPixmap(QPixmap.fromImage(img.copy()))
psilly.set_image(data, img.width(), img.height(), 4)
label2 = QLabel()
label2.setPixmap(QPixmap.fromImage(img))
widget = QWidget()
lay = QHBoxLayout(widget)
lay.addWidget(label1)
lay.addWidget(label2)
widget.show()
app.exec_()
Compiling and testing:
python setup.py build_ext --inplace
python main.py
Output:
Note: When the project is compiled, it is generated as an intermediate element to psilly.c where you will see the conversion between memoryview and unsigned char *
using the Py_buffer structure.
Upvotes: 0