1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
|
"""Pixel-wise access routines."""
import ctypes
from .compat import UnsupportedError, experimental
from .array import MemoryView
from ..surface import SDL_MUSTLOCK, SDL_LockSurface, SDL_UnlockSurface, \
SDL_Surface
from ..stdinc import Uint8
from .sprite import SoftwareSprite
from .draw import _get_target_surface, prepare_color
__all__ = ["PixelView", "pixels2d", "pixels3d"]
class PixelView(MemoryView):
"""2D memory view for Sprite and SDL_Surface pixel access.
The PixelView uses a y/x-layout. Accessing view[N] will operate on the
Nth row of the underlying surface. To access a specific column within
that row, view[N][C] has to be used.
NOTE: The PixelView is implemented on top of the MemoryView class. As such
it makes heavy use of recursion to access rows and columns and can be
considered as slow in contrast to optimised ndim-array solutions such as
numpy.
"""
def __init__(self, source):
"""Creates a new PixelView from a Sprite or SDL_Surface.
If necessary, the surface will be locked for accessing its pixel data.
The lock will be removed once the PixelView is garbage-collected or
deleted.
"""
if isinstance(source, SoftwareSprite):
self._surface = source.surface
# keep a reference, so the Sprite's not GC'd
self._sprite = source
elif isinstance(source, SDL_Surface):
self._surface = source
elif "SDL_Surface" in str(type(source)):
self._surface = source.contents
else:
raise TypeError("source must be a Sprite or SDL_Surface")
if SDL_MUSTLOCK(self._surface):
SDL_LockSurface(self._surface)
pxbuf = ctypes.cast(self._surface.pixels, ctypes.POINTER(Uint8))
itemsize = self._surface.format.contents.BytesPerPixel
strides = (self._surface.h, self._surface.w)
srcsize = self._surface.h * self._surface.pitch
super(PixelView, self).__init__(pxbuf, itemsize, strides,
getfunc=self._getitem,
setfunc=self._setitem,
srcsize=srcsize)
def _getitem(self, start, end):
if self.itemsize == 1:
# byte-wise access
return self.source[start:end]
# move the pointer to the correct location
src = ctypes.byref(self.source.contents, start)
casttype = ctypes.c_ubyte
if self.itemsize == 2:
casttype = ctypes.c_ushort
elif self.itemsize == 3:
# TODO
raise NotImplementedError("unsupported bpp")
elif self.itemsize == 4:
casttype = ctypes.c_uint
return ctypes.cast(src, ctypes.POINTER(casttype)).contents.value
def _setitem(self, start, end, value):
target = None
if self.itemsize == 1:
target = ctypes.cast(self.source, ctypes.POINTER(ctypes.c_ubyte))
elif self.itemsize == 2:
target = ctypes.cast(self.source, ctypes.POINTER(ctypes.c_ushort))
elif self.itemsize == 3:
# TODO
raise NotImplementedError("unsupported bpp")
elif self.itemsize == 4:
target = ctypes.cast(self.source, ctypes.POINTER(ctypes.c_uint))
value = prepare_color(value, self._surface)
target[start // self.itemsize] = value
def __del__(self):
if self._surface:
if SDL_MUSTLOCK(self._surface):
SDL_UnlockSurface(self._surface)
_HASNUMPY = True
try:
import numpy
class SurfaceArray(numpy.ndarray):
"""Wrapper class around numpy.ndarray.
Used to keep track of the original source object for pixels2d()
and pixels3d() to avoid the deletion of the source object.
"""
def __new__(cls, shape, dtype=float, buffer_=None, offset=0,
strides=None, order=None, source=None, surface=None):
sfarray = numpy.ndarray.__new__(cls, shape, dtype, buffer_,
offset, strides, order)
sfarray._source = source
sfarray._surface = surface
return sfarray
def __array_finalize__(self, sfarray):
if sfarray is None:
return
self._source = getattr(sfarray, '_source', None)
self._surface = getattr(sfarray, '_surface', None)
def __del__(self):
if self._surface:
if SDL_MUSTLOCK(self._surface):
SDL_UnlockSurface(self._surface)
except ImportError:
_HASNUMPY = False
@experimental
def pixels2d(source, transpose=True):
"""Creates a 2D pixel array from the passed source."""
if not _HASNUMPY:
raise UnsupportedError(pixels2d, "numpy module could not be loaded")
psurface = _get_target_surface(source, argname="source")
bpp = psurface.format.contents.BytesPerPixel
if bpp < 1 or bpp > 4:
raise ValueError("unsupported bpp")
strides = (psurface.pitch, bpp)
sz = psurface.h * psurface.pitch
shape = psurface.h, psurface.w # surface.pitch // bpp
dtypes = {
1: numpy.uint8,
2: numpy.uint16,
3: numpy.uint32,
4: numpy.uint32
}
if SDL_MUSTLOCK(psurface):
SDL_LockSurface(psurface)
pxbuf = ctypes.cast(psurface.pixels, ctypes.POINTER(ctypes.c_ubyte * sz))
arr = SurfaceArray(shape, dtypes[bpp], pxbuf.contents, 0, strides, "C",
source, psurface)
return arr.transpose() if transpose else arr
@experimental
def pixels3d(source, transpose=True):
"""Creates a 3D pixel array from the passed source.
"""
if not _HASNUMPY:
raise UnsupportedError(pixels3d, "numpy module could not be loaded")
psurface = _get_target_surface(source, argname="source")
bpp = psurface.format.contents.BytesPerPixel
if bpp < 1 or bpp > 4:
raise ValueError("unsupported bpp")
strides = (psurface.pitch, bpp, 1)
sz = psurface.h * psurface.pitch
shape = psurface.h, psurface.w, bpp
if SDL_MUSTLOCK(psurface):
SDL_LockSurface(psurface)
pxbuf = ctypes.cast(psurface.pixels, ctypes.POINTER(ctypes.c_ubyte * sz))
arr = SurfaceArray(shape, numpy.uint8, pxbuf.contents, 0, strides, "C",
source, psurface)
return arr.transpose(1, 0, 2) if transpose else arr
|