以下是该方案:
作为我的2D渲染cython库的重构过程的一部分,我正在移除无关的" getter / setter"功能使其更加用户友好。我的渲染器执行的任务之一是在屏幕上显示图像。目前,我加载图像并使用一些SDL2_image
函数提取其像素数据。然后,每当通过" setter"修改某些图像像素数据时函数,我会通知OpenGL需要将更新的数据从CPU复制到GPU。我的目标是使这个"簿记"通过将图像的像素暴露为可直接修改的属性并且具有CPU< =>来更隐蔽用户。 GPU同步发生在幕后。
当前代码:
以下是我的Image
类的相关cython代码(仅包含实现.pyx文件的完整性且仅与切线相关):
image.pxd
from libs.sdl2 cimport *
from libc.stdint cimport uint8_t, uint32_t, int64_t
from libc.stdlib cimport malloc, free
from cpython cimport bool
from cython.view cimport array as cvarray
cdef class Image:
cdef unsigned int[:, :, :] pixels
cdef readonly int width
cdef readonly int height
image.pyx
from libs.sdl2 cimport *
from libc.stdint cimport uint8_t, uint32_t, int64_t
from libc.stdlib cimport malloc, free
from cpython cimport bool
from cython.view cimport array as cvarray
cdef int rgba_size = 4
cdef class Image:
def __cinit__(self, int width, int height, unsigned int[:, :, :] pixels=None):
self.width = width
self.height = height
self.pixels = pixels
def __dealloc__(Image self):
pass
@property
def pixels(self):
return self.pixels
@pixels.setter
def pixels(self, unsigned int[:, :, :] new_pixels):
self.pixels = new_pixels
print("updated")
@staticmethod
def from_url(str image_url):
"""
1. Load in an image to an SDL_Surface using SDL_Image module
2. Convert the image to the SDL_PIXELFORMAT_ABGR8888 format (right channels for OpenGL's RGBA)
3. Flip the image as OpenGL spec states texture's BOTTOM-left = (0, 0)!
"""
cdef Image out
cdef char * err
cdef bytes b_image_url = bytes(image_url, "utf-8")
cdef SDL_Surface *surf = IMG_Load(b_image_url)
if <int>surf == 0:
err = IMG_GetError()
raise ValueError(err)
cdef SDL_Surface *rgba_surf = SDL_ConvertSurfaceFormat(surf, SDL_PIXELFORMAT_ABGR8888, 0)
cdef int width = rgba_surf.w
cdef int height = rgba_surf.h
cdef uint32_t *pixel_data = <uint32_t *>rgba_surf.pixels
cdef unsigned int[:, :, :] pixels
pixels = cvarray(shape=(width, height, rgba_size), itemsize=sizeof(int), format="I")
cdef int x, y, flipped = 0
cdef uint32_t abgr = 0
for y in xrange(height):
for x in xrange(width):
flipped = (height - 1 - y) * width + x
abgr = pixel_data[flipped]
pixels[x, y, 0] = abgr >> 0 & 0xFF
pixels[x, y, 1] = abgr >> 8 & 0xFF
pixels[x, y, 2] = abgr >> 16 & 0xFF
pixels[x, y, 3] = abgr >> 24 & 0xFF
SDL_FreeSurface(surf)
SDL_FreeSurface(rgba_surf)
out = Image(width, height, pixels=pixels)
return out
这就是我尝试使用我的课程的方式:
from graphics.image import Image
a = Image.from_url("./images/crate.png")
a.pixels[0, 0, 0] = 255#please trigger something that says that pixels has been partially updated!
问题当然是因为只修改了pixels
的单个值,所以不会触发@pixels.setter
装饰器。触发它的唯一方法似乎是替换整个内存视图。
TL; DR:给定一个暴露的,可公开修改的类属性,它是一个list / array / np.arrray / memoryview /具有多个子值的东西,有没有办法透明地检测是否或不是说在该课程中属性已经部分更新了吗?
答案 0 :(得分:2)
我认为你可以通过属性和上下文管理器的组合(对于with语句)获得合理有效的东西
首先保持你的getter和setter在问题中。修改你的getter,使它返回一个只读的numpy数组:
def writeable_pixels(self):
class WriteablePixelsCtx:
def __enter__(self2):
return self._pixels # get the underlying (writeable) memoryview
def __exit__(self2,*args):
# update changes
# handle any exceptions appropriately (see the main Python documentation on __exit__ for this)
return WriteablePixelsCtx()
因此不应该有任何“不受监控”的变化。设置器保持不变,只允许完全替换像素。
现在定义一个返回上下文管理器对象的函数:
with im.writeable_pixels() as pixels:
pixels[i,j] = something
然后你可以这样做:
cdef
并且在阻止结束时,您可以通过退出一次有效地处理所有更改。
我喜欢这个:
pixels
一个局部变量作为内存视图)我不喜欢的是: