Add the raw bitmap access classes and various bits of magic.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxPython/Phoenix/trunk@72356 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Robin Dunn
2012-08-16 00:46:13 +00:00
parent d06a313afc
commit 932d7a6166
6 changed files with 492 additions and 9 deletions

View File

@@ -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)

View File

@@ -25,8 +25,6 @@ Functions and Classes Modifications
AboutBox :func:`adv.AboutBox <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 <Window.GetClassDefaultAttributes>`
NotebookEvent :class:`BookCtrlEvent`
@@ -318,7 +314,6 @@ NullFileTypeInfo ``MISSING``
NumberEntryDialog ``MISSING``
Panel_GetClassDefaultAttributes :meth:`Panel.GetClassDefaultAttributes <Window.GetClassDefaultAttributes>`
PCXHandler ``MISSING``
PixelDataBase ``MISSING``
PlatformInformation_GetOperatingSystemDirectory :meth:`PlatformInfo.GetOperatingSystemDirectory`
PNGHandler ``MISSING``
PNMHandler ``MISSING``

View File

@@ -87,6 +87,7 @@ INCLUDES = [ # base and core stuff
'imaglist',
'overlay',
'renderer',
'rawbmp',
# more core
'accel',

292
etg/rawbmp.py Normal file
View File

@@ -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 <wx/rawbmp.h>")
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()

163
samples/rawbmp/rawbmp1.py Normal file
View File

@@ -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()

36
unittests/test_rawbmp.py Normal file
View File

@@ -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()