Feature Request: Expose QPainter methods that accept pointers

Ognyan Moore ognyan.moore at gmail.com
Sat May 7 21:57:07 BST 2022


Hi Phil,

You are correct, PyQt bindings do provide us with the functionality we
need; we're hoping to have the other signatures enabled (assuming it's not
a high effort task).

Majority of our data is already in continuous numpy arrays, so we have a
strong interest in being able to pass those arrays to the draw methods in a
more direct fashion.  We are hoping to be able to do call the QPainter draw
methods by putting the array in the correct shape and with the correct data
type, without having to convert the it to a list, and without having
instantiate or cast each element to QLineF (or QPointF,
QPainter.PixmapFragment, QPolygonF objects).

With our QLineF instance, we hope to be able to pass the pointer of a
n_lines x 4 numpy array (of type double/float64) and. be able to call the
drawLines method in something like the following fashion.

import numpy as np
from PyQt6 import QtCore, QtGui, QtWidgets, sip
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 = sip.wrapinstance(lines, lines.ctypes.data, QtCore.QLineF)
# painter.drawLines(ptr, lines.shape[0])

# current implementation
https://doc-snapshots.qt.io/qt6-dev/qpainter.html#drawLines-5
ptr = list(map(sip.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')

For QPainter.drawPixmapFragments (the pyside equivalent of this works
actually, not sure if that was intentional on their part or not)

import numpy as np
from PyQt6 import QtCore, QtGui, QtWidgets, sip
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]


qimg = QtGui.QImage(200, 200, QtGui.QImage.Format.Format_RGB32)
qimg.fill(0)
painter = QtGui.QPainter(qimg)

# desired implementation
# frag_ptr = sip.wrapinstance(frags_array.ctypes.data,
QtGui.QPainter.PixmapFragment)
# painter.drawPixmapFragments(frag_ptr, frags_array.size, pix)

# current implementation
frag_ptr = list(map(sip.wrapinstance,
itertools.count(frags_array.ctypes.data, frags_array.strides[0]),
itertools.repeat(QtGui.QPainter.PixmapFragment, frags_array.shape[0])))
painter.drawPixmapFragments(frag_ptr[:frags_array.shape[0]], pix)
painter.end()
qimg.save('drawPixmapFragments.png')


Hopefully that clears things up some.  Thanks!
Ogi

On Sat, May 7, 2022 at 1:48 AM Phil Thompson <phil at riverbankcomputing.com>
wrote:

> On 07/05/2022 03:56, Ognyan Moore wrote:
> > Hi Phil,
> >
> > I am one of the maintainers of pyqtgraph, I'd like to request that you
> > enable some of the function signatures for QPainter methods that accept
> > pointers.  The following two would be more beneficial for us:
> >
> > QPainter::drawLines(const QLineF *lines, int lineCount)
> > QPainter::drawPixmapFragments(const QPainter::PixmapFragment
> > *fragments,
> > int fragmentCount, const QPixmap &pixmap, QPainter::PixmapFragmentHints
> > hints = PixmapFragmentHints())
> >
> > If it would not be too much trouble, enabling some of the other
> > QPainter
> > methods that enabled referencing pointers could also be beneficial, but
> > these won't have the same impact as the ones above
> >
> > 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)
> >
> > To demonstrate our usage, I'll highlight what we do with the PySide
> > bindings.  For the QPainter::drawPixmapFragments we're able to do
> > something
> > resembling the following
> >
> > import numpy as np
> > size = 1_000
> > arr = np.empty((size, 10), dtype=np.float64)
> > ptrs = shiboken6.wrapInstance(arr.ctypes.data,
> > QtGui.QPainter.PixmapFragment)
> > ...
> > QPainter.drawPixmapFragments(ptrs, size, pixmap)
> >
> > For the equivalent functionality with PyQt bindings
> >
> > ptrs = list(map(sip.wrapInstance,
> >    itertools.count(arr.ctypes.data, arr.strides[0]),
> >    itertools.repeat(QtGui.QPainter.PixmapFragment, arr.shape[0])))
> > QPainter.drawPixmapFragments(ptrs[:size], pixmap)
> >
> >
> > We do this right now with QImage, and in a round-about way with
> > QPolygonF
> > construction.
>
> All of those methods are supported, but maybe not in the way that you
> want.
>
> drawPixmapFragments() takes a list of PixmapFragment. The others takes a
> variable number of arguments of the appropriate type, so if you had a
> list of QLineF objects (called lines) you would call drawLines(*lines).
>
> Can you clarify?
>
> Phil
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://www.riverbankcomputing.com/pipermail/pyqt/attachments/20220507/7ade8a24/attachment-0001.htm>


More information about the PyQt mailing list