Details
Description
Greeting PySide developers,
I am opening this issue as a feature request on behalf of pyqtgraph to expose more of the QPainter draw methods that accept pointers, so we can pass NumPy arrays to them in a more direct fashion. The overloaded method signature we are most interested in is QPainter::drawLines(const QLineF *lines int lineCount)
https://doc-snapshots.qt.io/qt6-dev/qpainter.html#drawLines
QPainter::draw methods are highly performance-sensitive operations in pyqtgraph. Given that we are working from structured NumPy arrays the overwhelming majority of the time, being able to leverage that to minimize iteration or casting would be something that we would find highly desirable.
Hopefully, the following example will highlight our intent.
import numpy as np from PySide6 import QtCore, QtGui, QtWidgets, shiboken6 import itertools import sys app = QtWidgets.QApplication([]) # array makeup [[x1, y1, x2, y2]] lines = np.array([ [0, 0, 0, 10], [0, 10, 10, 0], [10, 0, 20, 10]], dtype=np.float64 ) qimg = QtGui.QImage(20, 20, QtGui.QImage.Format.Format_RGB32) qimg.fill(0) painter = QtGui.QPainter(qimg) painter.setPen(QtCore.Qt.GlobalColor.cyan) # # desired implementation [https://doc-snapshots.qt.io/qt6-dev/qpainter.html#drawLines-4] # ptr = shiboken6.wrapInstance(lines, lines.ctypes.data, QtCore.QLineF) # painter.drawLines(ptr, lines.shape[0]) # alternative desired implementation # ptr = shiboken6.VoidPtr(lines) # painter.drawLines(ptr, lines.shape[0]) # current implementation [https://doc-snapshots.qt.io/qt6-dev/qpainter.html#drawLines-5] ptr = list(map(shiboken6.wrapinstance, itertools.count(lines.ctypes.data, lines.strides[0]), itertools.repeat(QtCore.QLineF, lines.shape[0]))) painter.drawLines(ptr) painter.end() qimg.save('drawLines.png')
Oddly enough, PySide bindings do offer this functionality with QPainter::drawPixmapFragments, this was discovered somewhat by accident. I am unsure if this was intentional or not, but we had some significant performance improvements by being able to pass NumPy arrays more directly by knowing the layout is of the PixmapFragment object, see this example
import numpy as np from PySide6 import QtCore, QtGui, QtWidgets, shiboken6 import itertools app = QtWidgets.QApplication([]) # make the pixmap pix = QtGui.QPixmap(51, 51) pix.fill(0) painter = QtGui.QPainter(pix) painter.setPen(QtCore.Qt.GlobalColor.cyan) painter.drawEllipse(0, 0, 50, 50) painter.end() # create numpy array representing fragments fieldnames = ['x', 'y', 'sourceLeft', 'sourceTop', 'width', 'height', 'scaleX', 'scaleY', 'rotation', 'opacity'] frags_array = np.zeros(3, dtype=[(name, 'f8') for name in fieldnames]) frags_array['sourceLeft'] = 0 frags_array['sourceTop'] = 0 frags_array['width'] = 51 frags_array['height'] = 51 frags_array['scaleX'] = 1.0 frags_array['scaleY'] = 1.0 frags_array['rotation'] = 0.0 frags_array['opacity'] = 1.0 frags_array['x'] = [50, 100, 150] frags_array['y'] = [50, 100, 150] # current implementation frag_ptr = shiboken6.wrapInstance(frags_array.ctypes.data, QtGui.QPainter.PixmapFragment) painter.drawPixmapFragments(frag_ptr, frags_array.size, pix) painter.end()
If this is something PySide developers would be interested in supporting, there are other draw methods that we would benefit from having this kind of ability to pass pointers to structured NumPy arrays.
QPainter::drawConvexPolygon(const QPointF *points, int pointCount) QPainter::drawPoints(const QPointF *points, int pointCount) QPainter::drawPolygon(const QPointF *points, int pointCount, Qt::FillRule fillRule = Qt::OddEvenFill) QPainter::drawRects(const QRectF *rectangles, int rectCount) QPainter::drawPolyline(const QPointF *points, int pointCount)
We do these kinds of operations in our construction of QPolygonF objects from NumPy arrays, which we found to be a much faster method of constructing QPainterPath than trying to go via the QDataStream >> QPainterPath method.
x = np.arange(100) y = np.ones(100) size = x.size polyline = QtGui.QPolygonF() polyline.resize(size) nbytes = 2 * len(polyline) * 8 ptr = polyline.data() buffer = shiboken6.VoidPtr(ptr, nbytes, True) memory = np.frombuffer(buffer, np.double).reshape((-1, 2)) memory[:, 0] = x memory[:, 1] = y
Please let me know if I can provide any other information of relevance and thanks for your work maintaining these bindings!
Attachments
Issue Links
- relates to
-
PYSIDE-1540 QPainter.drawPoints performance issue
- Closed