File: pixelaccess.py

package info (click to toggle)
pysdl2 0.9.9%2Bdfsg1-6
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 3,276 kB
  • sloc: python: 18,592; makefile: 148; sh: 40
file content (174 lines) | stat: -rw-r--r-- 6,458 bytes parent folder | download
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