python3 memoryview 对象到 C 函数 char* 参数
python3 memoryview object to C function char* parameter
框架如下:PySide2 QImage 让我可以通过 bits()
- 调用访问内部像素数据。调用 returns memoryview - 我想传递对图像像素数据进行处理的 C 库函数的对象(举个例子,假设它执行超级花式模糊)。
问题是我还没有找到一种方法来获取 memoryview 底层数据作为 ctypes 调用参数。
即:我如何从 memoryview 对象 ctypes.c_void_p
(或类似对象)中获取——而不复制缓冲区。 memoryview tobytes()
returns 字节,但是它是从内存中复制过来的,所以修改不会修改原图(像素数据)。
现在最小的例子python silly.py
边是:
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 );
}
编译和测试:
% 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
memoryview
is not a void * but a structure (Py_buffer
) 其成员内部是缓冲区,因此转换不是直接的。
在这些情况下,我更喜欢使用 Cython(python -m pip install cython
),它允许以简单的方式为库创建包装器。考虑到以上情况,我创建了一个项目:
├── 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_()
编译和测试:
python setup.py build_ext --inplace
python main.py
输出:
注意:编译项目时,它是作为psilly.c的中间元素生成的,你会看到memoryview和unsigned char *
之间使用Py_buffer结构的转换。
A memoryview
支持 Python 缓冲区协议,因此使用其 from_buffer
方法创建一个适当大小的 ctypes
数组,该方法不会进行复制但引用相同的内存。
稍微修改您的示例以查看缓冲区变化:
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])
输出:
original: 80
00000213CCF3C080 500 100
final: 123
为了完整起见,人们从 google 中找到此页面,但另一种可行的替代方法是根据 docs 中所述使用 C/C++ 制作 python 模块.
它需要一些样板,但实际访问缓冲区很容易:
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;
}
框架如下:PySide2 QImage 让我可以通过 bits()
- 调用访问内部像素数据。调用 returns memoryview - 我想传递对图像像素数据进行处理的 C 库函数的对象(举个例子,假设它执行超级花式模糊)。
问题是我还没有找到一种方法来获取 memoryview 底层数据作为 ctypes 调用参数。
即:我如何从 memoryview 对象 ctypes.c_void_p
(或类似对象)中获取——而不复制缓冲区。 memoryview tobytes()
returns 字节,但是它是从内存中复制过来的,所以修改不会修改原图(像素数据)。
现在最小的例子python silly.py
边是:
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 );
}
编译和测试:
% 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
memoryview
is not a void * but a structure (Py_buffer
) 其成员内部是缓冲区,因此转换不是直接的。
在这些情况下,我更喜欢使用 Cython(python -m pip install cython
),它允许以简单的方式为库创建包装器。考虑到以上情况,我创建了一个项目:
├── 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_()
编译和测试:
python setup.py build_ext --inplace
python main.py
输出:
注意:编译项目时,它是作为psilly.c的中间元素生成的,你会看到memoryview和unsigned char *
之间使用Py_buffer结构的转换。
A memoryview
支持 Python 缓冲区协议,因此使用其 from_buffer
方法创建一个适当大小的 ctypes
数组,该方法不会进行复制但引用相同的内存。
稍微修改您的示例以查看缓冲区变化:
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])
输出:
original: 80
00000213CCF3C080 500 100
final: 123
为了完整起见,人们从 google 中找到此页面,但另一种可行的替代方法是根据 docs 中所述使用 C/C++ 制作 python 模块.
它需要一些样板,但实际访问缓冲区很容易:
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;
}