mirror of
https://github.com/wxWidgets/Phoenix.git
synced 2026-01-06 03:50:06 +01:00
Merge pull request #2014 from arjones6/master
Add wxDC.DrawLinesFromBuffer to draw directly from numpy array memory using the Buffer protocol
This commit is contained in:
131
demo/DrawLinesFromBuffer.py
Normal file
131
demo/DrawLinesFromBuffer.py
Normal file
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import time
|
||||
|
||||
import wx
|
||||
|
||||
npts = 100000
|
||||
|
||||
def TestLinesFromBuffer(dc, log):
|
||||
global npts
|
||||
try:
|
||||
import numpy as np
|
||||
start = time.time()
|
||||
|
||||
w, h = dc.GetSize()
|
||||
|
||||
vs = np.linspace(0, 1, npts).reshape((npts, 1))
|
||||
|
||||
x1 = np.cos(vs * 12 * np.pi) * w/2 * vs + w/2
|
||||
x2 = np.cos(vs * 16 * np.pi) * w/2 * vs + w/2
|
||||
y1 = np.sin(vs * 12 * np.pi) * w/2 * vs + h/2
|
||||
y2 = np.sin(vs * 16 * np.pi) * w/2 * vs + h/2
|
||||
|
||||
|
||||
# Data has to be the same size as a C integer
|
||||
pts1 = np.append(x1, y1, 1).astype('intc')
|
||||
pts2 = np.append(x2, y2, 1).astype('intc')
|
||||
|
||||
dc.SetPen(wx.BLACK_PEN)
|
||||
t1 = time.time()
|
||||
dc.DrawLines(pts1)
|
||||
t2 = time.time()
|
||||
|
||||
dc.SetPen(wx.RED_PEN)
|
||||
t3 = time.time()
|
||||
dc.DrawLinesFromBuffer(pts2)
|
||||
t4 = time.time()
|
||||
|
||||
log.write("%s pts: %s seconds with DrawLines %s seconds with DrawLinesFromBuffer\n" % (npts, t2 - t1, t4 - t3))
|
||||
|
||||
except ImportError:
|
||||
log.write("Couldn't import numpy")
|
||||
pass
|
||||
|
||||
|
||||
|
||||
# Class used for all the various sample pages; the mechanics are the same
|
||||
# for each one with regards to the notebook. The only difference is
|
||||
# the function we use to draw on it.
|
||||
class DrawPanel(wx.Panel):
|
||||
def __init__(self, parent, drawFun, log):
|
||||
wx.Panel.__init__(self, parent, -1)
|
||||
self.SetBackgroundColour(wx.WHITE)
|
||||
|
||||
self.log = log
|
||||
self.drawFun = drawFun
|
||||
self.Bind(wx.EVT_PAINT, self.OnPaint)
|
||||
|
||||
def OnSize(self, evt):
|
||||
self.Refresh()
|
||||
|
||||
def OnPaint(self, evt):
|
||||
dc = wx.PaintDC(self)
|
||||
dc.Clear()
|
||||
self.drawFun(dc,self.log)
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
def runTest(frame, nb, log):
|
||||
global npts
|
||||
|
||||
panel = wx.Panel(nb, -1)
|
||||
vsizer = wx.BoxSizer(wx.VERTICAL)
|
||||
hsizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
hsizer.Add(wx.StaticText(panel, -1, "# of Points"), 0, wx.ALIGN_CENTER|wx.ALL, 5)
|
||||
npts_ctrl = wx.TextCtrl(panel, -1, str(npts))
|
||||
hsizer.Add(npts_ctrl, 0, wx.ALIGN_CENTER|wx.ALL, 5)
|
||||
|
||||
button = wx.Button(panel, -1, "Refresh")
|
||||
hsizer.Add(button, 0, wx.ALIGN_CENTER|wx.ALL, 5)
|
||||
vsizer.Add(hsizer, 0, wx.ALIGN_CENTER, 0)
|
||||
|
||||
win = DrawPanel(panel, TestLinesFromBuffer, log)
|
||||
vsizer.Add(win, 1, wx.GROW, 0)
|
||||
panel.SetSizer(vsizer)
|
||||
|
||||
def update_npts(evt):
|
||||
global npts
|
||||
|
||||
val = npts_ctrl.GetValue()
|
||||
try:
|
||||
npts = int(val)
|
||||
except ValueError:
|
||||
log.write("Error converting %s to an int" % (val))
|
||||
win.Refresh()
|
||||
button.Bind(wx.EVT_BUTTON, update_npts)
|
||||
|
||||
return panel
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
|
||||
overview = """\
|
||||
|
||||
The DrawLinesFromBuffer function has been added to wx.DC to provide
|
||||
a way to draw directly from a numpy array or other object that implements
|
||||
the python buffer protocol.
|
||||
|
||||
<pre>
|
||||
DrawLinesFromBuffer(pyBuff)
|
||||
</pre>
|
||||
|
||||
The buffer object needs to provide an array of C integers organized as
|
||||
x, y point pairs. The size of a C integer is platform dependent.
|
||||
With numpy, the intc data type will provide the appropriate element size.
|
||||
|
||||
If called with an object that doesn't support
|
||||
the python buffer protocol, or if the underlying element size does not
|
||||
match the size of a C integer, a TypeError exception is raised. If
|
||||
the buffer provided has float data with the same element size as a
|
||||
C integer, no error will be raised, but the lines will not be drawn
|
||||
in the appropriate places.
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys,os
|
||||
import run
|
||||
run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:])
|
||||
|
||||
@@ -50,6 +50,7 @@ _treeList = [
|
||||
'ActivityIndicator',
|
||||
'GenericCheckBox',
|
||||
'CheckListCtrl',
|
||||
'DrawLinesFromBuffer',
|
||||
]),
|
||||
|
||||
# managed windows == things with a (optional) caption you can close
|
||||
@@ -275,6 +276,7 @@ _treeList = [
|
||||
'Cairo_Snippets',
|
||||
'ColourDB',
|
||||
'DragScroller',
|
||||
'DrawLinesFromBuffer',
|
||||
'DrawXXXList',
|
||||
'FileHistory',
|
||||
'FontEnumerator',
|
||||
|
||||
25
etg/dc.py
25
etg/dc.py
@@ -310,6 +310,9 @@ def run():
|
||||
'(PyObject* textList, PyObject* pyPoints, PyObject* foregroundList, PyObject* backgroundList)',
|
||||
body="return wxPyDrawTextList(*self, textList, pyPoints, foregroundList, backgroundList);")
|
||||
|
||||
c.addCppMethod('PyObject*', '_DrawLinesFromBuffer',
|
||||
'(PyObject* pyBuff)',
|
||||
body="return wxPyDrawLinesFromBuffer(*self, pyBuff);")
|
||||
|
||||
c.addPyMethod('DrawPointList', '(self, points, pens=None)',
|
||||
doc="""\
|
||||
@@ -475,6 +478,28 @@ def run():
|
||||
""")
|
||||
|
||||
|
||||
c.addPyMethod('DrawLinesFromBuffer', '(self, pyBuff)',
|
||||
doc="""\
|
||||
Implementation of DrawLines that can use numpy arrays, or anything else that uses the
|
||||
python buffer protocol directly without any element conversion. This provides a
|
||||
significant performance increase over the standard DrawLines function.
|
||||
|
||||
The pyBuff argument needs to provide an array of C integers organized as
|
||||
x, y point pairs. The size of a C integer is platform dependent.
|
||||
With numpy, the intc data type will provide the appropriate element size.
|
||||
|
||||
If called with an object that doesn't support
|
||||
the python buffer protocol, or if the underlying element size does not
|
||||
match the size of a C integer, a TypeError exception is raised. If
|
||||
the buffer provided has float data with the same element size as a
|
||||
C integer, no error will be raised, but the lines will not be drawn
|
||||
in the appropriate places.
|
||||
|
||||
:param pyBuff: A python buffer containing integer pairs
|
||||
""",
|
||||
body="""\
|
||||
return self._DrawLinesFromBuffer(pyBuff)
|
||||
""")
|
||||
|
||||
|
||||
#-----------------------------------------------------------------
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
// Licence: wxWindows license
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
|
||||
typedef bool (*wxPyDrawListOp_t)(wxDC& dc, PyObject* coords);
|
||||
|
||||
PyObject* wxPyDrawXXXList(wxDC& dc, wxPyDrawListOp_t doDraw,
|
||||
@@ -466,3 +465,53 @@ error0:
|
||||
PyErr_SetString(PyExc_TypeError, "Expected a sequence of length-2 sequences or wx.Points.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
PyObject* wxPyDrawLinesFromBuffer(wxDC& dc, PyObject* pyBuff)
|
||||
{
|
||||
wxPyBlock_t blocked = wxPyBeginBlockThreads();
|
||||
Py_buffer view;
|
||||
PyObject* retval;
|
||||
|
||||
|
||||
if (!PyObject_CheckBuffer(pyBuff)) {
|
||||
goto err0;
|
||||
}
|
||||
|
||||
if (PyObject_GetBuffer(pyBuff, &view, PyBUF_CONTIG) < 0) {
|
||||
goto err1;
|
||||
}
|
||||
|
||||
if (view.itemsize * 2 != sizeof(wxPoint)) {
|
||||
goto err2;
|
||||
}
|
||||
|
||||
dc.DrawLines(view.len / view.itemsize / 2, (wxPoint *)view.buf);
|
||||
|
||||
PyBuffer_Release(&view);
|
||||
|
||||
Py_INCREF(Py_None);
|
||||
retval = Py_None;
|
||||
goto exit;
|
||||
|
||||
|
||||
err0:
|
||||
PyErr_SetString(PyExc_TypeError, "Expected a buffer object");
|
||||
retval = NULL;
|
||||
goto exit;
|
||||
|
||||
err1:
|
||||
// PyObject_GetBuffer raises exception already
|
||||
retval = NULL;
|
||||
goto exit;
|
||||
|
||||
err2:
|
||||
PyErr_SetString(PyExc_TypeError, "Item size does not match wxPoint size");
|
||||
retval = NULL;
|
||||
goto exit;
|
||||
|
||||
exit:
|
||||
wxPyEndBlockThreads(blocked);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
65
unittests/test_dcDrawLinesFromBuffer.py
Normal file
65
unittests/test_dcDrawLinesFromBuffer.py
Normal file
@@ -0,0 +1,65 @@
|
||||
import unittest
|
||||
from unittests import wtc
|
||||
import wx
|
||||
import random
|
||||
try:
|
||||
import numpy as np
|
||||
haveNumpy = True
|
||||
except ImportError:
|
||||
haveNumpy = False
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
w = 600
|
||||
h = 400
|
||||
|
||||
def makeRandomLines():
|
||||
lines = []
|
||||
|
||||
for i in range(num):
|
||||
x1 = random.randint(0, w)
|
||||
y1 = random.randint(0, h)
|
||||
x2 = random.randint(0, w)
|
||||
y2 = random.randint(0, h)
|
||||
lines.append( (x1,y1, x2,y2) )
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class dcDrawLists_Tests(wtc.WidgetTestCase):
|
||||
|
||||
@unittest.skipIf(not haveNumpy, "Numpy required for this test")
|
||||
def test_dcDrawLinesFromBuffer(self):
|
||||
pnl = wx.Panel(self.frame)
|
||||
self.frame.SetSize((w,h))
|
||||
dc = wx.ClientDC(pnl)
|
||||
dc.SetPen(wx.Pen("BLACK", 1))
|
||||
|
||||
xs = np.linspace(0, w, w + 1)
|
||||
ys = np.sin(xs / w * 2 * np.pi)
|
||||
|
||||
xs.shape = xs.size, 1
|
||||
ys.shape = ys.size, 1
|
||||
pts = np.append(xs, ys, 1)
|
||||
|
||||
dc.DrawLinesFromBuffer(pts.astype('intc'))
|
||||
self.assertRaises(TypeError, dc.DrawLinesFromBuffer,
|
||||
pts.astype('int64') if np.intc(1).nbytes != np.int64(1).nbytes else pts.astype('int32'))
|
||||
self.assertRaises(TypeError, dc.DrawLinesFromBuffer, pts.tolist())
|
||||
del dc
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#---------------------------------------------------------------------------
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user