From 18098181e35fb8ae7d366fc5f32e2a9981171b3d Mon Sep 17 00:00:00 2001 From: Robin Dunn Date: Sat, 28 Apr 2012 04:05:08 +0000 Subject: [PATCH] Add buffer related methods and helpers for wxBitmap git-svn-id: https://svn.wxwidgets.org/svn/wx/wxPython/Phoenix/trunk@71293 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- TODO.txt | 3 - etg/bitmap.py | 266 +++++++++++++++++++++++++++++++- etg/defs.py | 1 + etg/image.py | 105 ++++++++----- src/bitmap_ex.cpp | 319 +++++++++++++++++++++++++++++++++++++++ src/bitmap_ex.h | 46 ++++++ src/wxpy_api.h | 2 + unittests/test_bitmap.py | 108 ++++++++++++- unittests/test_image.py | 5 - 9 files changed, 806 insertions(+), 49 deletions(-) create mode 100644 src/bitmap_ex.cpp create mode 100644 src/bitmap_ex.h diff --git a/TODO.txt b/TODO.txt index d262f470..0b56675a 100644 --- a/TODO.txt +++ b/TODO.txt @@ -107,9 +107,6 @@ WAF Build other dev stuff --------------- - * port all of the wx.Bitmap copy-to and copy-from buffer methods, and - raw bitmap access routines from Classic. - * Come up with some way to implement the MustHaveApp check that Classic does. It should raise an exception when something is created/used that should not be done before there is an application diff --git a/etg/bitmap.py b/etg/bitmap.py index 5b740735..aef52fce 100644 --- a/etg/bitmap.py +++ b/etg/bitmap.py @@ -18,8 +18,14 @@ DOCSTRING = "" # The classes and/or the basename of the Doxygen XML files to be processed by # this script. -ITEMS = [ 'wxBitmap', 'wxBitmapHandler', 'wxMask' ] +ITEMS = [ 'wxBitmap', + 'wxBitmapHandler', + 'wxMask' ] +OTHERDEPS = [ 'src/bitmap_ex.h', + 'src/bitmap_ex.cpp', ] + + #--------------------------------------------------------------------------- def run(): @@ -52,9 +58,36 @@ def run(): return self->IsOk(); """) - # On MSW the handler classes are different than what is documented, and - # this causes compile errors. Nobody has needed them from Python thus far, - # so just ignore them all for now. + c.addCppMethod('long', 'GetHandle', '()', + doc='MSW-only method to fetch the windows handle for the bitmap.', + body="""\ + #ifdef __WXMSW__ + return self->GetHandle(); + #else + return 0; + #endif + """) + + c.addCppMethod('void', 'SetHandle', '(long handle)', + doc='MSW-only method to set the windows handle for the bitmap.', + body="""\ + #ifdef __WXMSW__ + self->SetHandle((WXHANDLE)handle); + #endif + """) + + c.addCppMethod('void', 'SetSize', '(const wxSize& size)', + doc='Set the bitmap size (does not affect the existing bitmap data).', + body="""\ + self->SetWidth(size->x); + self->SetHeight(size->y); + """) + + + + # On MSW the bitmap handler classes are different than what is + # documented, and this causes compile errors. Nobody has needed them from + # Python thus far, so just ignore them all for now. for m in c.find('FindHandler').all(): m.ignore() c.find('AddHandler').ignore() @@ -70,9 +103,230 @@ def run(): module.find('wxBitmapHandler').ignore() #module.addItem(tools.wxListWrapperTemplate('wxList', 'wxBitmapHandler', module)) - # TODO: The ctors and methods from Classic for converting to/from - # buffer objects with raw bitmap access. + #----------------------------------------------------------------------- + # Declarations, helpers and methods for converting to/from buffer objects + # with raw bitmap access. + + from etgtools import EnumDef, EnumValueDef + e = EnumDef(name='wxBitmapBufferFormat') + e.items.extend([ EnumValueDef(name='wxBitmapBufferFormat_RGB'), + EnumValueDef(name='wxBitmapBufferFormat_RGBA'), + EnumValueDef(name='wxBitmapBufferFormat_RGB32'), + EnumValueDef(name='wxBitmapBufferFormat_ARGB32'), + ]) + module.insertItem(0, e) + + c.includeCppCode('src/bitmap_ex.cpp') + module.addHeaderCode('#include "bitmap_ex.h"') + + + c.addCppMethod('void', 'CopyFromBuffer', + '(wxPyBuffer* data, wxBitmapBufferFormat format=wxBitmapBufferFormat_RGB, int stride=-1)', + doc="""\ + Copy data from a buffer object to replace the bitmap pixel data. + Default format is plain RGB, but other formats are now supported as + well. The following symbols are used to specify the format of the + bytes in the buffer: + + ============================= ================================ + wx.BitmapBufferFormat_RGB A simple sequence of RGB bytes + wx.BitmapBufferFormat_RGBA A simple sequence of RGBA bytes + wx.BitmapBufferFormat_ARGB32 A sequence of 32-bit values in native + endian order, with alpha in the upper + 8 bits, followed by red, green, and + blue. + wx.BitmapBufferFormat_RGB32 Same as above but the alpha byte + is ignored. + ============================= ================================""", + body="""\ + wxPyCopyBitmapFromBuffer(self, (byte*)data->m_ptr, data->m_len, format, stride); + """) + + + c.addCppMethod('void', 'CopyToBuffer', + '(wxPyBuffer* data, wxBitmapBufferFormat format=wxBitmapBufferFormat_RGB, int stride=-1)', + doc="""\ + Copy pixel data to a buffer object. See `CopyFromBuffer` for buffer + format details.""", + body="""\ + wxPyCopyBitmapToBuffer(self, (byte*)data->m_ptr, data->m_len, format, stride); + """) + + + # Some bitmap factories added as static methods + + c.addCppMethod('wxBitmap*', 'FromBufferAndAlpha', + '(int width, int height, wxPyBuffer* data, wxPyBuffer* alpha)', + isStatic=True, + factory=True, + doc="""\ + Creates a `wx.Bitmap` from in-memory data. The data and alpha + parameters must be a Python object that implements the buffer + interface, such as a string, bytearray, etc. The data object + is expected to contain a series of RGB bytes and be at least + width*height*3 bytes long, while the alpha object is expected + to be width*height bytes long and represents the image's alpha + channel. On Windows and Mac the RGB values will be + 'premultiplied' by the alpha values. (The other platforms do + the multiplication themselves.) + + Unlike `wx.ImageFromBuffer` the bitmap created with this function + does not share the memory block with the buffer object. This is + because the native pixel buffer format varies on different + platforms, and so instead an efficient as possible copy of the + data is made from the buffer object to the bitmap's native pixel + buffer. + """, + body="""\ + if (!data->checkSize(width*height*3) || !alpha->checkSize(width*height)) + return NULL; + + byte* ddata = (byte*)data->m_ptr; + byte* adata = (byte*)alpha->m_ptr; + wxBitmap* bmp = new wxBitmap(width, height, 32); + + wxAlphaPixelData pixData(*bmp, wxPoint(0,0), wxSize(width,height)); + if (! pixData) { + wxPyErr_SetString(PyExc_RuntimeError, "Failed to gain raw access to bitmap data."); + return NULL; + } + + wxAlphaPixelData::Iterator p(pixData); + for (int y=0; ym_ptr, data->m_len, wxBitmapBufferFormat_RGB); + if (PyErr_Occurred()) { + delete bmp; + bmp = NULL; + } + return bmp; + """) + + module.addPyFunction('BitmapFromBuffer', '(width, height, dataBuffer, alphaBuffer=None)', + deprecated=True, + doc='A compatibility wrapper for Bitmap.FromBuffer and Bitmap.FromBufferAndAlpha', + body="""\ + if alphaBuffer is not None: + return Bitmap.FromBufferAndAlpha(width, height, dataBuffer, alphaBuffer) + else: + return Bitmap.FromBuffer(width, height, dataBuffer) + """) + + + + + c.addCppMethod('wxBitmap*', 'FromBufferRGBA', '(int width, int height, wxPyBuffer* data)', + isStatic=True, + factory=True, + doc="""\ + Creates a `wx.Bitmap` from in-memory data. The data parameter + must be a Python object that implements the buffer interface, such + as a string, bytearray, etc. The data object is expected to contain + a series of RGBA bytes and be at least width*height*4 bytes long. + On Windows and Mac the RGB values will be 'premultiplied' by the + alpha values. (The other platforms do the multiplication themselves.) + + Unlike `wx.ImageFromBuffer` the bitmap created with this function + does not share the memory block with the buffer object. This is + because the native pixel buffer format varies on different + platforms, and so instead an efficient as possible copy of the + data is made from the buffer object to the bitmap's native pixel + buffer. + """, + body="""\ + wxBitmap* bmp = new wxBitmap(width, height, 32); + wxPyCopyBitmapFromBuffer(bmp, (byte*)data->m_ptr, data->m_len, wxBitmapBufferFormat_RGBA); + if (PyErr_Occurred()) { + delete bmp; + bmp = NULL; + } + return bmp; + """) + + module.addPyFunction('BitmapFromBufferRGBA', '(width, height, dataBuffer)', + deprecated=True, + doc='A compatibility wrapper for Bitmap.FromBufferRGBA', + body='return Bitmap.FromBufferRGBA(width, height, dataBuffer)') + + + + + c.addCppMethod('wxBitmap*', 'FromRGBA', + '(int width, int height, byte red=0, byte green=0, byte blue=0, byte alpha=0)', + isStatic=True, + factory=True, + doc="""\ + Creates a new empty 32-bit `wx.Bitmap` where every pixel has been + initialized with the given RGBA values. + """, + body="""\ + if ( !(width > 0 && height > 0) ) { + wxPyErr_SetString(PyExc_ValueError, "Width and height must be greater than zero"); + return NULL; + } + + wxBitmap* bmp = new wxBitmap(width, height, 32); + wxAlphaPixelData pixData(*bmp, wxPoint(0,0), wxSize(width,height)); + if (! pixData) { + wxPyErr_SetString(PyExc_RuntimeError, "Failed to gain raw access to bitmap data."); + return NULL; + } + + wxAlphaPixelData::Iterator p(pixData); + for (int y=0; yCreate(width, height, (unsigned char*)copy); + sipCpp->Create(width, height, (byte*)copy); """) c.addCppCtor_sip('(int width, int height, wxPyBuffer* data, wxPyBuffer* alpha)', @@ -70,7 +72,7 @@ def run(): if ((dcopy = data->copy()) == NULL || (acopy = alpha->copy()) == NULL) return NULL; sipCpp = new sipwxImage; - sipCpp->Create(width, height, (unsigned char*)dcopy, (unsigned char*)acopy, false); + sipCpp->Create(width, height, (byte*)dcopy, (byte*)acopy, false); """) c.addCppCtor_sip('(const wxSize& size, wxPyBuffer* data)', @@ -82,7 +84,7 @@ def run(): if (! copy) return NULL; sipCpp = new sipwxImage; - sipCpp->Create(size->x, size->y, (unsigned char*)copy, false); + sipCpp->Create(size->x, size->y, (byte*)copy, false); """) c.addCppCtor_sip('(const wxSize& size, wxPyBuffer* data, wxPyBuffer* alpha)', @@ -94,7 +96,7 @@ def run(): if ((dcopy = data->copy()) == NULL || (acopy = alpha->copy()) == NULL) return NULL; sipCpp = new sipwxImage; - sipCpp->Create(size->x, size->y, (unsigned char*)dcopy, (unsigned char*)acopy, false); + sipCpp->Create(size->x, size->y, (byte*)dcopy, (byte*)acopy, false); """) @@ -112,7 +114,7 @@ def run(): void* copy = data->copy(); if (! copy) return false; - return self->Create(width, height, (unsigned char*)copy); + return self->Create(width, height, (byte*)copy); """) c.addCppMethod('bool', 'Create', '(int width, int height, wxPyBuffer* data, wxPyBuffer* alpha)', @@ -123,7 +125,7 @@ def run(): return false; if ((dcopy = data->copy()) == NULL || (acopy = alpha->copy()) == NULL) return false; - return self->Create(width, height, (unsigned char*)dcopy, (unsigned char*)acopy); + return self->Create(width, height, (byte*)dcopy, (byte*)acopy); """) c.addCppMethod('bool', 'Create', '(const wxSize& size, wxPyBuffer* data)', @@ -134,7 +136,7 @@ def run(): void* copy = data->copy(); if (! copy) return false; - return self->Create(size->x, size->y, (unsigned char*)copy); + return self->Create(size->x, size->y, (byte*)copy); """) c.addCppMethod('bool', 'Create', '(const wxSize& size, wxPyBuffer* data, wxPyBuffer* alpha)', @@ -145,7 +147,7 @@ def run(): return false; if ((dcopy = data->copy()) == NULL || (acopy = alpha->copy()) == NULL) return false; - return self->Create(size->x, size->y, (unsigned char*)dcopy, (unsigned char*)acopy); + return self->Create(size->x, size->y, (byte*)dcopy, (byte*)acopy); """) @@ -160,7 +162,7 @@ def run(): void* copy = data->copy(); if (!copy) return; - self->SetData((unsigned char*)copy, false); + self->SetData((byte*)copy, false); """, briefDoc=bd, detailedDoc=dd) c.find('SetData').findOverload('int new_width').ignore() @@ -171,7 +173,7 @@ def run(): void* copy = data->copy(); if (!copy) return; - self->SetData((unsigned char*)copy, new_width, new_height, false); + self->SetData((byte*)copy, new_width, new_height, false); """) m = c.find('SetAlpha').findOverload('unsigned char *alpha') @@ -184,7 +186,7 @@ def run(): void* copy = alpha->copy(); if (!copy) return; - self->SetAlpha((unsigned char*)copy, false); + self->SetAlpha((byte*)copy, false); """) @@ -195,7 +197,7 @@ def run(): c.addCppMethod('PyObject*', 'GetData', '()', doc="Returns a copy of the RGB bytes of the image.", body="""\ - unsigned char* data = self->GetData(); + byte* data = self->GetData(); Py_ssize_t len = self->GetWidth() * self->GetHeight() * 3; PyObject* rv = NULL; wxPyBLOCK_THREADS( rv = PyByteArray_FromStringAndSize((const char*)data, len)); @@ -206,7 +208,7 @@ def run(): c.addCppMethod('PyObject*', 'GetAlpha', '()', doc="Returns a copy of the Alpha bytes of the image.", body="""\ - unsigned char* data = self->GetAlpha(); + byte* data = self->GetAlpha(); Py_ssize_t len = self->GetWidth() * self->GetHeight(); PyObject* rv = NULL; wxPyBLOCK_THREADS( rv = PyByteArray_FromStringAndSize((const char*)data, len)); @@ -223,7 +225,7 @@ def run(): image data buffer inside the wx.Image. You need to ensure that you do not use this buffer object after the image has been destroyed.""", body="""\ - unsigned char* data = self->GetData(); + byte* data = self->GetData(); Py_ssize_t len = self->GetWidth() * self->GetHeight() * 3; PyObject* rv; Py_buffer view; @@ -240,7 +242,7 @@ def run(): data buffer inside the wx.Image. You need to ensure that you do not use this buffer object after the image has been destroyed.""", body="""\ - unsigned char* data = self->GetAlpha(); + byte* data = self->GetAlpha(); Py_ssize_t len = self->GetWidth() * self->GetHeight(); PyObject* rv; Py_buffer view; @@ -263,7 +265,7 @@ def run(): if (!data->checkSize(self->GetWidth() * self->GetHeight() * 3)) return; // True means don't free() the pointer - self->SetData((unsigned char*)data->m_ptr, true); + self->SetData((byte*)data->m_ptr, true); """) c.addCppMethod('void', 'SetDataBuffer', '(wxPyBuffer* data, int new_width, int new_height)', doc="""\ @@ -275,7 +277,7 @@ def run(): if (!data->checkSize(new_width * new_height * 3)) return; // True means don't free() the pointer - self->SetData((unsigned char*)data->m_ptr, new_width, new_height, true); + self->SetData((byte*)data->m_ptr, new_width, new_height, true); """) @@ -289,7 +291,7 @@ def run(): if (!alpha->checkSize(self->GetWidth() * self->GetHeight())) return; // True means don't free() the pointer - self->SetAlpha((unsigned char*)alpha->m_ptr, true); + self->SetAlpha((byte*)alpha->m_ptr, true); """) @@ -387,10 +389,10 @@ def run(): unsigned rgblen = 3 * self->GetWidth() * self->GetHeight(); unsigned alphalen = self->GetWidth() * self->GetHeight(); - unsigned char* src_data = self->GetData(); - unsigned char* src_alpha = self->GetAlpha(); - unsigned char* dst_data = dest->GetData(); - unsigned char* dst_alpha = NULL; + byte* src_data = self->GetData(); + byte* src_alpha = self->GetAlpha(); + byte* dst_data = dest->GetData(); + byte* dst_alpha = NULL; // adjust rgb if ( factor_red == 1.0 && factor_green == 1.0 && factor_blue == 1.0) @@ -403,18 +405,18 @@ def run(): // rgb pixel for pixel for ( unsigned i = 0; i < rgblen; i= i + 3 ) { - dst_data[i] = (unsigned char) wxMin( 255, (int) (factor_red * src_data[i]) ); - dst_data[i + 1] = (unsigned char) wxMin( 255, (int) (factor_green * src_data[i + 1]) ); - dst_data[i + 2] = (unsigned char) wxMin( 255, (int) (factor_blue * src_data[i + 2]) ); + dst_data[i] = (byte) wxMin( 255, (int) (factor_red * src_data[i]) ); + dst_data[i + 1] = (byte) wxMin( 255, (int) (factor_green * src_data[i + 1]) ); + dst_data[i + 2] = (byte) wxMin( 255, (int) (factor_blue * src_data[i + 2]) ); } } // adjust the mask colour if ( self->HasMask() ) { - dest->SetMaskColour((unsigned char) wxMin( 255, (int) (factor_red * self->GetMaskRed() ) ), - (unsigned char) wxMin( 255, (int) (factor_green * self->GetMaskGreen() ) ), - (unsigned char) wxMin( 255, (int) (factor_blue * self->GetMaskBlue() ) ) ); + dest->SetMaskColour((byte) wxMin( 255, (int) (factor_red * self->GetMaskRed() ) ), + (byte) wxMin( 255, (int) (factor_green * self->GetMaskGreen() ) ), + (byte) wxMin( 255, (int) (factor_blue * self->GetMaskBlue() ) ) ); } // adjust the alpha channel @@ -436,7 +438,7 @@ def run(): // alpha value for alpha value for ( unsigned i = 0; i < alphalen; ++i ) { - dst_alpha[i] = (unsigned char) wxMin( 255, (int) (factor_alpha * src_alpha[i]) ); + dst_alpha[i] = (byte) wxMin( 255, (int) (factor_alpha * src_alpha[i]) ); } } } @@ -450,7 +452,7 @@ def run(): for ( unsigned i = 0; i < alphalen; ++i ) { - dst_alpha[i] = (unsigned char) wxMin( 255, (int) (factor_alpha * 255) ); + dst_alpha[i] = (byte) wxMin( 255, (int) (factor_alpha * 255) ); } } @@ -458,9 +460,9 @@ def run(): if ( dst_alpha && dest->HasMask() ) { // make the mask transparent honoring the alpha channel - const unsigned char mr = dest->GetMaskRed(); - const unsigned char mg = dest->GetMaskGreen(); - const unsigned char mb = dest->GetMaskBlue(); + const byte mr = dest->GetMaskRed(); + const byte mg = dest->GetMaskGreen(); + const byte mb = dest->GetMaskBlue(); for ( unsigned i = 0; i < alphalen; ++i ) { @@ -506,7 +508,42 @@ def run(): doc='Compatibility wrapper for creating an image from RGB and Alpha data', body='return Image(width, height, data, alpha)') - + + + module.addPyFunction('ImageFromBuffer', '(width, height, dataBuffer, alphaBuffer=None)', + doc="""\ + Creates a `wx.Image` from the data in dataBuffer. The dataBuffer + parameter must be a Python object that implements the buffer interface, + such as a string, array, etc. The dataBuffer object is expected to + contain a series of RGB bytes and be width*height*3 bytes long. A buffer + object can optionally be supplied for the image's alpha channel data, and + it is expected to be width*height bytes long. + + The wx.Image will be created with its data and alpha pointers initialized + to the memory address pointed to by the buffer objects, thus saving the + time needed to copy the image data from the buffer object to the wx.Image. + While this has advantages, it also has the shoot-yourself-in-the-foot + risks associated with sharing a C pointer between two objects. + + To help alleviate the risk a reference to the data and alpha buffer + objects are kept with the wx.Image, so that they won't get deleted until + after the wx.Image is deleted. However please be aware that it is not + guaranteed that an object won't move its memory buffer to a new location + when it needs to resize its contents. If that happens then the wx.Image + will end up referring to an invalid memory location and could cause the + application to crash. Therefore care should be taken to not manipulate + the objects used for the data and alpha buffers in a way that would cause + them to change size. + """, + body="""\ + img = Image(width, height) + img.SetDataBuffer(dataBuffer) + if alphaBuffer: + img.SetAlphaBuffer(alphaBuffer) + img._buffer = dataBuffer + img._alpha = alphaBuffer + return img + """) #------------------------------------------------------- c = module.find('wxImageHistogram') diff --git a/src/bitmap_ex.cpp b/src/bitmap_ex.cpp new file mode 100644 index 00000000..8e08d9d9 --- /dev/null +++ b/src/bitmap_ex.cpp @@ -0,0 +1,319 @@ +//-------------------------------------------------------------------------- +// Name: src/bitmap_ex.h +// Purpose: Helper functions and etc. for copying bitmap data to/from +// buffer objects. This file is included in etg/bitmap.py and +// used in the wxBitmap wrapper. +// +// Author: Robin Dunn +// +// Created: 27-Apr-2012 +// Copyright: (c) 2012 by Total Control Software +// Licence: wxWindows license +//-------------------------------------------------------------------------- + + +#include + +// TODO: Switch these APIs to use the new wxPyBuffer class + +void wxPyCopyBitmapFromBuffer(wxBitmap* bmp, + buffer data, Py_ssize_t DATASIZE, + wxBitmapBufferFormat format, int stride) +{ + int height = bmp->GetHeight(); + int width = bmp->GetWidth(); + + switch (format) { + // A simple sequence of RGB bytes + case wxBitmapBufferFormat_RGB: + { + if (DATASIZE < width * height * 3) { + wxPyErr_SetString(PyExc_ValueError, "Invalid data buffer size."); + return; + } + wxNativePixelData pixData(*bmp, wxPoint(0,0), wxSize(width, height)); + if (! pixData) { + wxPyErr_SetString(PyExc_RuntimeError, + "Failed to gain raw access to bitmap data."); + return; + } + + wxNativePixelData::Iterator p(pixData); + for (int y=0; y> 24) & 0xFF : 255; + pix.Red() = (value >> 16) & 0xFF; + pix.Green() = (value >> 8) & 0xFF; + pix.Blue() = (value >> 0) & 0xFF; + ++pix; + ++bufptr; + } + rowStart += stride; + } + break; + } + } +} + + +// Some helper macros used below to help declutter the code +#define MAKE_PIXDATA(type) \ + type pixData(*bmp, wxPoint(0,0), wxSize(width, height)); \ + if (! pixData) { \ + wxPyErr_SetString(PyExc_RuntimeError, "Failed to gain raw access to bitmap data."); \ + return; \ + } \ + type::Iterator p(pixData); \ + type::Iterator rowStart + +#define CHECK_BUFFERSIZE(size_needed) \ + if (DATASIZE < size_needed) { \ + wxPyErr_SetString(PyExc_ValueError, "Invalid data buffer size."); \ + return; \ + } + + +void wxPyCopyBitmapToBuffer(wxBitmap* bmp, + buffer data, Py_ssize_t DATASIZE, + wxBitmapBufferFormat format, int stride) +{ + int height = bmp->GetHeight(); + int width = bmp->GetWidth(); + int depth = bmp->GetDepth(); + + // images loaded from a file may not have set the depth, at least on Mac... + if (depth == -1) { + if (bmp->HasAlpha()) + depth = 32; + else + depth = 24; + } + + switch (format) { + // A simple sequence of RGB bytes + case wxBitmapBufferFormat_RGB: + { + CHECK_BUFFERSIZE(width * height * 3); + if (depth == 24) { + MAKE_PIXDATA(wxNativePixelData); + + for (int y=0; y