Commits

Lenard Lindstrom committed 9ce6270

Add pygame.bufferproxy.Bufferproxy compatibility option to pygame.Surface.get_view()

The Surface.get_view() option '&' returns a pygame._view.BufferProxy
instance exposing the surface data as raw bytes. The _view.BufferProxy
still needs to be updated to properly expose those bytes.

The docs for Surface.get_view are updated to describe the completed
method before is replaces Surface.get_buffer.

  • Participants
  • Parent commits 1f6a9a7

Comments (0)

Files changed (5)

docs/reST/ref/surface.rst

 
    .. method:: get_view
 
-      | :sl:`return a view of a surface's pixel data.`
-      | :sg:`get_view(kind='2') -> <view>`
+      | :sl:`return a buffer view of the Surface's pixels.`
+      | :sg:`get_buffer(<kind>='&') -> BufferProxy`
 
-      Return an object which exposes a surface's internal pixel buffer to a
-      NumPy array. For now a custom object with an array struct interface is
-      returned. A Python memoryview may be returned in the future. The buffer
-      is writeable.
+      To be renamed, and therefore replace, get_buffer.
 
-      The kind argument is the length 1 string '2', '3', 'r', 'g', 'b', or 'a'.
-      The letters are case insensitive; 'A' will work as well. The argument can
-      be either a Unicode or byte (char) string. The default is '2'.
+      Return an object which exposes a surface's internal pixel buffer as
+      an array interface or a buffer interface. The buffer is writeable. For
+      Python 2.x, only the classic buffer view is only available,
+      for the '&' kind.
+      For Python 3.x the new buffer interface is exposed, and is available
+      for all buffer kinds.
 
-      A kind '2' view is a (surface-width, surface-height) array of raw pixels.
+      The kind argument is the length 1 string '&', '0', '1', '2', '3',
+      'r', 'g', 'b', or 'a'. The letters are case insensitive;
+      'A' will work as well. The argument can be either a Unicode or byte (char)
+      string. The default is '&'.
+
+      A kind '&' view is unstructured bytes. It is for backwards compatibility
+      with Pygame 1.9.1. Consider it deprecated, with '0' becoming the
+      default method argument value in a future Pygame release.
+      
+      '0' returns a continguous unstructured bytes view.
+      A ValueError is raised if the surface's pixels are discontinuous.
+      
+      '1' returns a (surface-width * surface-height) array of continguous pixels.
+      A ValueError is raised if the surface pixels are discontinuous.
+      
+      '2' returns a (surface-width, surface-height) array of raw pixels.
       The pixels are surface bytesized unsigned integers. The pixel format is
       surface specific. The 3 byte unsigned integers of 24 bit surfaces are
       unlikely accepted by anything other than other Pygame functions.
 
-      '3' returns a (surface-width, surface-height, 3) view of ``RGB`` color
+      '3' returns a (surface-width, surface-height, 3) array of ``RGB`` color
       components. Each of the red, green, and blue components are unsigned
       bytes. Only 24-bit and 32-bit surfaces are supported. The color
       components must be in either ``RGB`` or ``BGR`` order within the pixel.
       and 32-bit surfaces support 'r', 'g', and 'b'. Only 32-bit surfaces with
       ``SRCALPHA`` support 'a'.
 
-      This method implicitly locks the Surface. The lock will be released, once
-      the returned view object is deleted.
-
-      New in pygame 1.9.2.
-
-      .. ## Surface.get_view ##
-
-   .. method:: get_buffer
-
-      | :sl:`acquires a buffer object for the pixels of the Surface.`
-      | :sg:`get_buffer() -> BufferProxy`
-
-      Return a buffer object for the pixels of the Surface. The buffer can be
-      used for direct pixel access and manipulation.
-
-      This method implicitly locks the Surface. The lock will be released, once
-      the returned BufferProxy object is deleted.
+      For kind '&', the method call also locks the surface. The lock is released
+      when the BufferProxy object is deleted. With all other kinds, the surface
+      is locked only when an exposed interface is accessed. For new buffer
+      interace accesses, the surface is unlocked once the last buffer view is
+      released. For array interface accesses, the surface remains locked until the
+      BufferProxy object is released.
 
       New in pygame 1.8.
+      Extended in pygame 1.9.2.
 
       .. ## Surface.get_buffer ##
 
     return 0;
 }
 
+static int
+PgBufproxy_Trip(PyObject *obj)
+{
+    PgBufproxyObject *proxy = (PgBufproxyObject *)obj;
+
+    if (!PyObject_IsInstance(obj, (PyObject *)&PgBufproxy_Type)) {
+        PyErr_Format(PyExc_TypeError,
+                     "Expected a BufferProxy instance: got type %s",
+                     Py_TYPE(obj)->tp_name);
+        return -1;
+    }
+    if (!proxy->global_release) {
+        if (proxy->before(obj)) {
+            return -1;
+        }
+        proxy->global_release = 1;
+    }
+    return 0;
+}
+
 static PyObject *
 Pg_ArrayStructAsDict(PyArrayInterface *inter_p)
 {
         DECREF_MOD(module);
         MODINIT_ERROR;
     }
-#if PYGAMEAPI_VIEW_NUMSLOTS != 5
+#if PYGAMEAPI_VIEW_NUMSLOTS != 6
 #error export slot count mismatch
 #endif
     c_api[0] = &PgBufproxy_Type;
     c_api[2] = PgBufproxy_GetParent;
     c_api[3] = Pg_GetArrayInterface;
     c_api[4] = Pg_ArrayStructAsDict;
+    c_api[5] = PgBufproxy_Trip;
     apiobj = encapsulate_api(c_api, "_view");
     if (apiobj == NULL) {
         DECREF_MOD(module);
 #define BUFPROXY_C_ORDER       2
 #define BUFPROXY_F_ORDER       4
 
-#define PYGAMEAPI_VIEW_NUMSLOTS 5
+#define PYGAMEAPI_VIEW_NUMSLOTS 6
 #define PYGAMEAPI_VIEW_FIRSTSLOT 0
 
 #if !(defined(PYGAMEAPI_VIEW_INTERNAL) || defined(NO_PYGAME_C_API))
                                    PgBufproxy_CallbackBefore,
                                    PgBufproxy_CallbackAfter);
 typedef PyObject *(*_pgbufproxy_get_obj_t)(PyObject *);
-typedef int *(*_pg_getarrayinterface_t)(PyObject *,
-                                        PyObject **,
-                                        PyArrayInterface **);
+typedef int (*_pgbufproxy_trip_t)(PyObject *);
+typedef int (*_pg_getarrayinterface_t)(PyObject *,
+                                       PyObject **,
+                                       PyArrayInterface **);
 typedef PyObject *(*_pg_arraystructasdict_t)(PyArrayInterface *inter_p);
 
 #define PgBufproxy_Type (*(PyTypeObject*)PgBUFPROXY_C_API[0])
     (*(_pgbufproxy_get_obj_t)PgBUFPROXY_C_API[2])
 #define Pg_GetArrayInterface (*(_pg_getarrayinterface_t)PgBUFPROXY_C_API[3])
 #define Pg_ArrayStructAsDict (*(_pg_arraystructasdict_t)PgBUFPROXY_C_API[4])
+#define PgBufproxy_Trip (*(_pgbufproxy_trip_t)PgBUFPROXY_C_API[5])
 #define PgBufproxy_Check(x) ((x)->ob_type == (PgBufproxy_Type))
 #define import_pygame_view() \
     _IMPORT_PYGAME_MODULE(_view, VIEW, PgBUFPROXY_C_API)
     VIEWKIND_GREEN,
     VIEWKIND_BLUE,
     VIEWKIND_ALPHA,
+    VIEWKIND_RAW
 } SurfViewKind;
 
 int
 
 static PyObject *
 _raise_get_view_ndim_error(int bitsize, SurfViewKind kind) {
-    const char *kind_names[] = {"2D", "3D", "red", "green", "blue", "alpha"};
     const char *name;
 
     switch (kind) {
 
+    case VIEWKIND_RAW:
+        name = "raw";
+        break;
     case VIEWKIND_2D:
-        name = kind_names[0];
+        name = "2D";
         break;
     case VIEWKIND_3D:
-        name = kind_names[1];
+        name = "3D";
         break;
     case VIEWKIND_RED:
-        name = kind_names[2];
+        name = "red";
         break;
     case VIEWKIND_GREEN:
-        name = kind_names[3];
+        name = "green";
         break;
     case VIEWKIND_BLUE:
-        name = kind_names[4];
+        name = "blue";
         break;
     case VIEWKIND_ALPHA:
-        name = kind_names[5];
+        name = "alpha";
         break;
     default:
         PyErr_Format(PyExc_SystemError,
     char format[] = {'B', '\0', '\0'};
     Py_buffer view;
 #   undef SURF_GET_VIEW_MAXDIM
-    SurfViewKind view_kind = VIEWKIND_2D;
+    SurfViewKind view_kind = VIEWKIND_RAW;
     int ndim = maxdim;
     SDL_Surface *surface = PySurface_AsSurface (self);
     int pixelsize;
     Uint32 mask = 0;
     int pixelstep;
     int flags = 0;
+    PyObject *proxy_obj;
 
     char *keywords[] = {"kind", 0};
 
     case VIEWKIND_ALPHA:
         mask = surface->format->Amask;
         break;
+    case VIEWKIND_RAW:
+        ndim = 0;
+        itemsize = 1;
+        break;
     default:
         PyErr_Format (PyExc_SystemError,
                       "pygame bug in surf_get_view:"
                       (int)itemsize);
         return 0;
     }
-    view.len = itemsize * shape[0] * shape[1];
-    if (ndim == 3) {
-        view.len *= shape[2];
+    switch (ndim) {
+
+    case 0:
+        view.len = pixelsize * shape[0] * shape[1];
+        break;
+    case 2:
+        view.len = itemsize * shape[0] * shape[1];
+        break;
+    case 3:
+        view.len = itemsize * shape[0] * shape[1] * shape[2];
+        break;
+    default:
+        /* Bug! Should not get here. */
+        PyErr_Format (PyExc_SystemError,
+                      "Pygame bug: Surface.get_buffer: "
+                      "unrecognized array dimension %d",
+                      (int)ndim);
+        return 0;
     }
     view.obj = self;
     Py_INCREF (self);
 
-    return PgBufproxy_New (&view, flags, _view_before, _view_after);
+    proxy_obj = PgBufproxy_New (&view, flags, _view_before, _view_after);
+    if (proxy_obj && view_kind == VIEWKIND_RAW) {
+        if (PgBufproxy_Trip (proxy_obj)) {
+            Py_DECREF (proxy_obj);
+            proxy_obj = 0;
+        }
+    }
+    return proxy_obj;
 }
 
 static int
     case '3':
         *view_kind_ptr = VIEWKIND_3D;
         break;
+    case '&':
+        *view_kind_ptr = VIEWKIND_RAW;
+        break;
     default:
         PyErr_Format (PyExc_TypeError,
                       "unrecognized view kind '%c' for argument 1", (int)ch);

test/surface_test.py

         self.assert_(isinstance(v, BufferProxy))
         v = s.get_view('b')
 
+        s = pygame.Surface((5, 7), 0, 24)
+        length = s.get_width() * s.get_height() * s.get_bytesize()
+        v = s.get_view('&')
+        self.assert_(isinstance(v, BufferProxy))
+        self.assertEqual(v.length, length)
+        v = s.get_view()
+        self.assert_(isinstance(v, BufferProxy))
+        self.assertEqual(v.length, length)
+        
+
         # Check argument defaults.
         s = pygame.Surface((5, 7), 0, 16)
         v = s.get_view()
         # Check locking.
         s = pygame.Surface((2, 4), 0, 32)
         self.assert_(not s.get_locked())
-        v = s.get_view()
+        v = s.get_view('2')
         self.assert_(not s.get_locked())
         c = v.__array_interface__
         self.assert_(s.get_locked())
         gc.collect()
         self.assert_(not s.get_locked())
 
+        v = s.get_view('&')
+        self.assert_(s.get_locked())
+        v = None
+        gc.collect()
+        self.assert_(not s.get_locked())
+
         # Check invalid view kind values.
         s = pygame.Surface((2, 4), pygame.SRCALPHA, 32)
         self.assertRaises(TypeError, s.get_view, '')