diff --git a/TODO.txt b/TODO.txt index f8557946..2f3a472f 100644 --- a/TODO.txt +++ b/TODO.txt @@ -112,9 +112,6 @@ other dev stuff created/used that should not be done before there is an application object. - * Should the functions created for things like addCppMethod be - marked static? How about using inline? - * Move PyEvent and PyCommandEvent classes to an etg script and build them using extractor objects so they will be seen by the document generator and others. @@ -130,7 +127,6 @@ other dev stuff * PseudoDC (not actually an ETG script, probably a .sip) * msgout * quantize - * rawbmp * richmsgdlg (requires wrappers for wxGenericMessageDialog) * valgen (No. It could probably be reimplemented in Python easier than wrapping) * valnum (No. Ditto) diff --git a/docs/classic_vs_phoenix.txt b/docs/classic_vs_phoenix.txt index 5f1cdc16..3b2dd949 100644 --- a/docs/classic_vs_phoenix.txt +++ b/docs/classic_vs_phoenix.txt @@ -25,8 +25,6 @@ Functions and Classes Modifications AboutBox :func:`adv.AboutBox ` AboutDialogInfo :class:`adv.AboutDialogInfo` AcceleratorEntry_Create ``MISSING`` -AlphaPixelData ``MISSING`` -AlphaPixelData_Accessor ``MISSING`` ANIHandler ``MISSING`` App_CleanUp ``MISSING`` ArtProvider_Delete :meth:`ArtProvider.Delete` @@ -307,8 +305,6 @@ MutexGuiLeave ``MISSING`` MutexGuiLocker ``MISSING`` NamedColour :class:`Colour` NativeEncodingInfo ``MISSING`` -NativePixelData ``MISSING`` -NativePixelData_Accessor ``MISSING`` NcPaintEvent ``MISSING`` Notebook_GetClassDefaultAttributes :meth:`Notebook.GetClassDefaultAttributes ` NotebookEvent :class:`BookCtrlEvent` @@ -318,7 +314,6 @@ NullFileTypeInfo ``MISSING`` NumberEntryDialog ``MISSING`` Panel_GetClassDefaultAttributes :meth:`Panel.GetClassDefaultAttributes ` PCXHandler ``MISSING`` -PixelDataBase ``MISSING`` PlatformInformation_GetOperatingSystemDirectory :meth:`PlatformInfo.GetOperatingSystemDirectory` PNGHandler ``MISSING`` PNMHandler ``MISSING`` diff --git a/etg/_core.py b/etg/_core.py index 92a2e670..e1b56dda 100644 --- a/etg/_core.py +++ b/etg/_core.py @@ -87,6 +87,7 @@ INCLUDES = [ # base and core stuff 'imaglist', 'overlay', 'renderer', + 'rawbmp', # more core 'accel', diff --git a/etg/rawbmp.py b/etg/rawbmp.py new file mode 100644 index 00000000..4556a8cd --- /dev/null +++ b/etg/rawbmp.py @@ -0,0 +1,292 @@ +#--------------------------------------------------------------------------- +# Name: etg/rawbmp.py +# Author: Robin Dunn +# +# Created: 14-Aug-2012 +# Copyright: (c) 2012 by Total Control Software +# License: wxWindows License +#--------------------------------------------------------------------------- + +import etgtools +import etgtools.tweaker_tools as tools +from etgtools import (ClassDef, MethodDef, ParamDef, TypedefDef, WigCode, + CppMethodDef, PyMethodDef) + +PACKAGE = "wx" +MODULE = "_core" +NAME = "rawbmp" # Base name of the file to generate to for this script +DOCSTRING = "" + +# The classes and/or the basename of the Doxygen XML files to be processed by +# this script. +ITEMS = [ ] + +# NOTE: It is intentional that there are no items in the ITEMS list. This is +# because we will not be loading any classes from the doxygen XML files here, +# but rather will be constructing the extractor objects here in this module +# instead. + + +#--------------------------------------------------------------------------- + +def run(): + # Parse the XML file(s) building a collection of Extractor objects + module = etgtools.ModuleDef(PACKAGE, MODULE, NAME, DOCSTRING) + etgtools.parseDoxyXML(module, ITEMS) + + #----------------------------------------------------------------- + # Tweak the parsed meta objects in the module object as needed for + # customizing the generated code and docstrings. + + module.addHeaderCode("#include ") + + addPixelDataBaseClass(module) + + addPixelDataClass(module, 'wxNativePixelData', 'wxBitmap', bpp=24, + doc="""\ + NativePixelData: A class providing direct access to a wx.Bitmap's + internal data without alpha channel (RGB). + """) + addPixelDataClass(module, 'wxAlphaPixelData', 'wxBitmap', bpp=32, + doc="""\ + AlphaPixelData: A class providing direct access to a wx.Bitmap's + internal data including the alpha channel (RGBA). + """) + #addPixelDataClass(module, 'wxImagePixelData', 'wxImage', bpp=32, + # doc="""\ + # ImagePixelData: A class providing direct access to a wx.Image's + # internal data usign the same api as the other PixelData classes. + # """) + + + #----------------------------------------------------------------- + tools.doCommonTweaks(module) + tools.runGenerators(module) + + + +#--------------------------------------------------------------------------- + +def addPixelDataBaseClass(module): + # wxPixelDataBase is the common base class of the other pixel data classes + cls = ClassDef(name='wxPixelDataBase', items=[ + MethodDef( + name='wxPixelDataBase', isCtor=True, protection='protected'), + MethodDef( + type='wxPoint', name='GetOrigin', isConst=True, + briefDoc="Return the origin of the area this pixel data represents."), + MethodDef( + type='int', name='GetWidth', isConst=True, + briefDoc="Return the width of the area this pixel data represents."), + MethodDef( + type='int', name='GetHeight', isConst=True, + briefDoc="Return the height of the area this pixel data represents."), + MethodDef( + type='wxSize', name='GetSize', isConst=True, + briefDoc="Return the size of the area this pixel data represents."), + MethodDef( + type='int', name='GetRowStride', isConst=True, + briefDoc="Returns the distance between the start of one row to the start of the next row."), + ]) + + # TODO: Try to remember why I chose to do it this way instead of direclty + # returning an instance of the Iterator and giving it the methods needed + # to be a Python iterator... + + # TODO: Determine how much of a performance difference not using the + # PixelFacade class would make. Not using the __iter__ makes about 0.02 + # seconds difference per 100x100 bmp in samples/rawbmp/rawbmp1.py... + + cls.addPyMethod('__iter__', '(self)', + doc="""\ + Create and return an iterator/generator object for traversing + this pixel data object. + """, + body="""\ + width = self.GetWidth() + height = self.GetHeight() + pixels = self.GetPixels() # this is the C++ iterator + + # This class is a facade over the pixels object (using the one + # in the enclosing scope) that only allows Get() and Set() to + # be called. + class PixelFacade(object): + def Get(self): + return pixels.Get() + def Set(self, *args, **kw): + return pixels.Set(*args, **kw) + def __str__(self): + return str(self.Get()) + def __repr__(self): + return 'pixel(%d,%d): %s' % (x,y,self.Get()) + X = property(lambda self: x) + Y = property(lambda self: y) + + pf = PixelFacade() + for y in xrange(height): + pixels.MoveTo(self, 0, y) + for x in xrange(width): + # We always generate the same pf instance, but it + # accesses the pixels object which we use to iterate + # over the pixel buffer. + yield pf + pixels.nextPixel() + """) + + module.addItem(cls) + + + +def addPixelDataClass(module, pd, img, bpp, doc=""): + # This function creates a ClassDef for a PixelData class defined in C++. + # The C++ versions are template instantiations, so this allows this + # create nearly identical classes and just substitute the image class + # name and the pixel data class name. + + #itrName = 'Iterator' + + itrName = pd + '_Accessor' + module.addHeaderCode('typedef %s::Iterator %s;' % (pd, itrName)) + + + # First generate the class and methods for the PixelData class + cls = ClassDef(name=pd, bases=['wxPixelDataBase'], briefDoc=doc, items=[ + MethodDef(name=pd, isCtor=True, items=[ + ParamDef(type=img+'&', name='bmp')], + overloads=[ + MethodDef(name=pd, isCtor=True, items=[ + ParamDef(type=img+'&', name='bmp'), + ParamDef(type='const wxRect&', name='rect')]), + + MethodDef(name=pd, isCtor=True, items=[ + ParamDef(type=img+'&', name='bmp'), + ParamDef(type='const wxPoint&', name='pt' ), + ParamDef(type='const wxSize&', name='sz' )]), + ]), + + MethodDef(name='~'+pd, isDtor=True), + + MethodDef(type=itrName, name='GetPixels', isConst=True), + + CppMethodDef('int', '__nonzero__', '()', + body="""\ + return (int)self->operator bool(); + """), + ]) + + # add this class to the module + module.addItem(cls) + + + # Now do the class and methods for its C++ Iterator class + icls = ClassDef(name=itrName, items=[ + # Constructors + MethodDef(name=itrName, isCtor=True, items=[ + ParamDef(name='data', type=pd+'&')], + overloads=[ + MethodDef(name=itrName, isCtor=True, items=[ + ParamDef(name='bmp', type=img+'&'), + ParamDef(name='data', type=pd+'&')]), + MethodDef(name=itrName, isCtor=True)]), + + MethodDef(name='~'+itrName, isDtor=True), + + # Methods + MethodDef(type='void', name='Reset', items=[ + ParamDef(type='const %s&' % pd, name='data')]), + + MethodDef(type='bool', name='IsOk', isConst=True), + + CppMethodDef('int', '__nonzero__', '()', + body="""\ + return (int)self->IsOk(); + """), + + MethodDef(type='void', name='Offset', items=[ + ParamDef(type='const %s&' % pd, name='data'), + ParamDef(type='int', name='x'), + ParamDef(type='int', name='y')]), + + MethodDef(type='void', name='OffsetX', items=[ + ParamDef(type='const %s&' % pd, name='data'), + ParamDef(type='int', name='x')]), + + MethodDef(type='void', name='OffsetY', items=[ + ParamDef(type='const %s&' % pd, name='data'), + ParamDef(type='int', name='y')]), + + MethodDef(type='void', name='MoveTo', items=[ + ParamDef(type='const %s&' % pd, name='data'), + ParamDef(type='int', name='x'), + ParamDef(type='int', name='y')]), + + # should this return the iterator? + CppMethodDef('void', 'nextPixel', '()', body="++(*self);"), + + # NOTE: For now I'm not wrapping the Red, Green, Blue and Alpha + # functions because I can't hide the premultiplying needed on wxMSW + # if only the individual components are wrapped, plus it would mean 3 + # or 4 trips per pixel from Python to C++ instead of just one. + # Instead I'll add the Set and Get functions below and put the + # premultiplying in there. + ]) + + assert bpp in [24, 32] + + if bpp == 24: + icls.addCppMethod('void', 'Set', '(byte red, byte green, byte blue)', + body="""\ + self->Red() = red; + self->Green() = green; + self->Blue() = blue; + """) + + icls.addCppMethod('PyObject*', 'Get', '()', + body="""\ + wxPyThreadBlocker blocker; + PyObject* rv = PyTuple_New(3); + PyTuple_SetItem(rv, 0, PyInt_FromLong(self->Red())); + PyTuple_SetItem(rv, 1, PyInt_FromLong(self->Green())); + PyTuple_SetItem(rv, 2, PyInt_FromLong(self->Blue())); + return rv; + """) + + elif bpp == 32: + icls.addCppMethod('void', 'Set', '(byte red, byte green, byte blue, byte alpha)', + body="""\ + self->Red() = wxPy_premultiply(red, alpha); + self->Green() = wxPy_premultiply(green, alpha); + self->Blue() = wxPy_premultiply(blue, alpha); + self->Alpha() = alpha; + """) + + icls.addCppMethod('PyObject*', 'Get', '()', + body="""\ + wxPyThreadBlocker blocker; + PyObject* rv = PyTuple_New(4); + int red = self->Red(); + int green = self->Green(); + int blue = self->Blue(); + int alpha = self->Alpha(); + + PyTuple_SetItem(rv, 0, PyInt_FromLong( wxPy_unpremultiply(red, alpha) )); + PyTuple_SetItem(rv, 1, PyInt_FromLong( wxPy_unpremultiply(green, alpha) )); + PyTuple_SetItem(rv, 2, PyInt_FromLong( wxPy_unpremultiply(blue, alpha) )); + PyTuple_SetItem(rv, 3, PyInt_FromLong( alpha )); + return rv; + """) + + + + # add it to the main pixel data class as a nested class + #cls.insertItem(0, icls) + + # It's really a nested class, but we're pretending that it isn't (see the + # typedef above) so add it at the module level instead. + module.addItem(icls) + + +#--------------------------------------------------------------------------- +if __name__ == '__main__': + run() + diff --git a/samples/rawbmp/rawbmp1.py b/samples/rawbmp/rawbmp1.py new file mode 100644 index 00000000..62d78f1d --- /dev/null +++ b/samples/rawbmp/rawbmp1.py @@ -0,0 +1,163 @@ + +import sys +import wx + +# use the numpy code instead of the raw access code for comparison +USE_NUMPY = False + +# time the execution of making a bitmap? +TIMEIT = False +PASSES = 100 + +# how big to make the bitmaps +DIM = 100 + +# should we use a wx.GraphicsContext for painting? +TEST_GC = False + +#---------------------------------------------------------------------- +# attempt to import a numeric module if requested to + +if USE_NUMPY: + try: + import numpy + def makeByteArray(shape): + return numpy.empty(shape, numpy.uint8) + numtype = 'numpy' + except ImportError: + USE_NUMPY = False + + +#---------------------------------------------------------------------- + +class TestPanel(wx.Panel): + def __init__(self, parent, log): + self.log = log + wx.Panel.__init__(self, parent, -1) + self.Bind(wx.EVT_PAINT, self.OnPaint) + + if TIMEIT: + import timeit + timeit.s = self # Put self in timeit's global namespace as + # 's' so it can be found in the code + # snippets being tested. + if not USE_NUMPY: + t = timeit.Timer("bmp = s.MakeBitmap(10, 20, 30)") + else: + t = timeit.Timer("bmp = s.MakeBitmap2(10, 20, 30)") + log.write("Timing...\n") + num = PASSES + tm = t.timeit(num) + log.write("%d passes creating %dx%d images in %f seconds\n" + "\t%f seconds per pass " % (num, DIM,DIM, tm, tm/num)) + + if not USE_NUMPY: + log.write("using raw access\n") + self.redBmp = self.MakeBitmap(178, 34, 34) + self.greenBmp = self.MakeBitmap( 35, 142, 35) + self.blueBmp = self.MakeBitmap( 0, 0, 139) + else: + log.write("using %s\n" % numtype) + self.redBmp = self.MakeBitmap2(178, 34, 34) + self.greenBmp = self.MakeBitmap2( 35, 142, 35) + self.blueBmp = self.MakeBitmap2( 0, 0, 139) + + + def OnPaint(self, evt): + dc = wx.PaintDC(self) + if not TEST_GC: + dc.DrawBitmap(self.redBmp, 50, 50, True) + dc.DrawBitmap(self.greenBmp, 110, 110, True) + dc.DrawBitmap(self.blueBmp, 170, 50, True) + self.log.write("using wx.DC\n") + else: + gc = wx.GraphicsContext.Create(dc) + gc.DrawBitmap(self.redBmp, 50, 50, DIM,DIM) + gc.DrawBitmap(self.greenBmp, 110, 110, DIM,DIM) + gc.DrawBitmap(self.blueBmp, 170, 50, DIM,DIM) + self.log.write("using wx.GraphicsContext\n") + + + def MakeBitmap(self, red, green, blue, alpha=128): + # Create the bitmap that we will stuff pixel values into using + # the raw bitmap access classes. + bmp = wx.Bitmap(DIM, DIM, 32) + + # Create an object that facilitates access to the bitmap's + # pixel buffer + pixelData = wx.AlphaPixelData(bmp) + if not pixelData: + raise RuntimeError("Failed to gain raw access to bitmap data.") + + # We have two ways to access each pixel, first we'll use an + # iterator to set every pixel to the colour and alpha values + # passed in. + for pixel in pixelData: + pixel.Set(red, green, blue, alpha) + + # This block of code is another way to do the same as above, + # but with the accessor interface instead of the Python + # iterator. It is a bit faster than the above because it + # avoids the iterator/generator magic, but it is not nearly as + # 'clean' looking ;-) + #pixels = pixelData.GetPixels() + #for y in xrange(DIM): + # pixels.MoveTo(pixelData, 0, y) + # for x in xrange(DIM): + # pixels.Set(red, green, blue, alpha) + # pixels.nextPixel() + + + + # Next we'll use the pixel accessor to set the border pixels + # to be fully opaque + pixels = pixelData.GetPixels() + for x in xrange(DIM): + pixels.MoveTo(pixelData, x, 0) + pixels.Set(red, green, blue, wx.ALPHA_OPAQUE) + pixels.MoveTo(pixelData, x, DIM-1) + pixels.Set(red, green, blue, wx.ALPHA_OPAQUE) + for y in xrange(DIM): + pixels.MoveTo(pixelData, 0, y) + pixels.Set(red, green, blue, wx.ALPHA_OPAQUE) + pixels.MoveTo(pixelData, DIM-1, y) + pixels.Set(red, green, blue, wx.ALPHA_OPAQUE) + + return bmp + + + def MakeBitmap2(self, red, green, blue, alpha=128): + # Make an array of bytes that is DIM*DIM in size, with enough + # slots for each pixel to have a RGB and A value + arr = makeByteArray( (DIM,DIM, 4) ) + + # just some indexes to keep track of which byte is which + R, G, B, A = range(4) + + # initialize all pixel values to the values passed in + arr[:,:,R] = red + arr[:,:,G] = green + arr[:,:,B] = blue + arr[:,:,A] = alpha + + # Set the alpha for the border pixels to be fully opaque + arr[0, 0:DIM, A] = wx.ALPHA_OPAQUE # first row + arr[DIM-1, 0:DIM, A] = wx.ALPHA_OPAQUE # last row + arr[0:DIM, 0, A] = wx.ALPHA_OPAQUE # first col + arr[0:DIM, DIM-1, A] = wx.ALPHA_OPAQUE # last col + + # finally, use the array to create a bitmap + bmp = wx.Bitmap.FromBufferRGBA(DIM, DIM, arr) + return bmp + + + +#---------------------------------------------------------------------- + +if __name__ == '__main__': + app = wx.App() + frm = wx.Frame(None, title="RawBitmap Test", size=(500,350)) + pnl = TestPanel(frm, sys.stdout) + frm.Show() + app.MainLoop() + diff --git a/unittests/test_rawbmp.py b/unittests/test_rawbmp.py new file mode 100644 index 00000000..2dfd6d04 --- /dev/null +++ b/unittests/test_rawbmp.py @@ -0,0 +1,36 @@ +import imp_unittest, unittest +import wtc +import wx + +#--------------------------------------------------------------------------- + +class rawbmp_Tests(wtc.WidgetTestCase): + + def test_rawbmp1(self): + DIM = 100 + red = 10 + green = 20 + blue = 30 + alpha = 128 + + bmp = wx.Bitmap(DIM, DIM, 32) + pixelData = wx.AlphaPixelData(bmp) + self.assertTrue(pixelData) + + # Test using the __iter__ generator + for pixel in pixelData: + pixel.Set(red, green, blue, alpha) + + # This block of code is another way to do the same as above + pixels = pixelData.GetPixels() + for y in xrange(DIM): + pixels.MoveTo(pixelData, 0, y) + for x in xrange(DIM): + pixels.Set(red, green, blue, alpha) + pixels.nextPixel() + + +#--------------------------------------------------------------------------- + +if __name__ == '__main__': + unittest.main()