Using QT, how would I go about taking user-supplied input (text) and drawing the font in such a way that it "follows" a circular path?
I really know nothing at all about QT but if I understood your question right, I found the solution with a simple google search. Code is below and here is the source link:
http://developer.qt.nokia.com/faq/answer/how_do_i_make_text_follow_the_line_curve_and_angle_of_the_qpainterpath
#include <QtGui>
#include <cmath>
class Widget : public QWidget
{
public:
Widget ()
: QWidget() { }
private:
void paintEvent ( QPaintEvent *)
{
QString hw("hello world");
int drawWidth = width() / 100;
QPainter painter(this);
QPen pen = painter.pen();
pen.setWidth(drawWidth);
pen.setColor(Qt::darkGreen);
painter.setPen(pen);
QPainterPath path(QPointF(0.0, 0.0));
QPointF c1(width()*0.2,height()*0.8);
QPointF c2(width()*0.8,height()*0.2);
path.cubicTo(c1,c2,QPointF(width(),height()));
//draw the bezier curve
painter.drawPath(path);
//Make the painter ready to draw chars
QFont font = painter.font();
font.setPixelSize(drawWidth*2);
painter.setFont(font);
pen.setColor(Qt::red);
painter.setPen(pen);
qreal percentIncrease = (qreal) 1/(hw.size()+1);
qreal percent = 0;
for ( int i = 0; i < hw.size(); i++ ) {
percent += percentIncrease;
QPointF point = path.pointAtPercent(percent);
qreal angle = path.angleAtPercent(percent);
qreal rad =qreal(0.017453292519943295769)*angle; // PI/180
// From the documentation:
/**
QTransform transforms a point in the plane to another point using the following formulas:
x' = m11*x + m21*y + dx
y' = m22*y + m12*x + dy
**/
// So the idea is to find the "new position of the character
// After we apply the world rotation.
// Then translate the painter back to the original position.
qreal sina = std::sin(rad);
qreal cosa = std::cos(rad);
// Finding the delta for the penwidth
// Don't divide by 2 because some space would be nice
qreal deltaPenX = cosa * pen.width();
qreal deltaPenY = sina * pen.width();
// Finding new posision after rotation
qreal newX = (cosa * point.x()) - (sina * point.y());
qreal newY = (cosa * point.y()) + (sina * point.x());
// Getting the delta distance
qreal deltaX = newX - point.x();
qreal deltaY = newY - point.y();
// Applying the rotation with the translation.
QTransform tran(cosa,sina,-sina,cosa,-deltaX + deltaPenX,-deltaY - deltaPenY);
painter.setWorldTransform(tran);
painter.drawText(point,QString(hw[i]));
}
}
};
int main(int argc, char **argv)
{
QApplication app(argc, argv);
Widget widget;
widget.show();
return app.exec();
}
Related
I have a (so far) fairly simple QWidget (size of say 300 * 300) that I am using to display a large QPixmap (e.g 5184 * 3456). The key parts of the code read as follows:
void DSSImageWidget::resizeEvent(QResizeEvent* e)
{
QSize sz = e->size();
qreal hScale = (qreal)sz.width() / (m_pixmap.width() + 4);
qreal vScale = (qreal)sz.height() / (m_pixmap.height() + 4);
m_scale = std::min(hScale, vScale);
update();
Inherited::resizeEvent(e);
}
void DSSImageWidget::paintEvent(QPaintEvent* event)
{
QPainter painter;
painter.begin(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
//QPointF whereScaled = m_where / (m_zoom * m_scale);
//qDebug() << "m_where:" << m_where.x() << m_where.y();
//qDebug() << whereScaled.x() << " " << whereScaled.y();
//painter.translate(-m_where);
painter.scale(m_zoom*m_scale, m_zoom*m_scale);
//painter.translate(m_where);
painter.drawPixmap(QPointF(0.0, 0.0), m_pixmap);
painter.setPen(QPen(QColor(255, 0, 0, alpha), 0.25, Qt::SolidLine, Qt::FlatCap, Qt::BevelJoin));
painter.setBrush(Qt::NoBrush);
painter.drawRect(QRectF(0, 0, m_pixmap.width(), m_pixmap.height()).adjusted(-2, -2, 2, 2));
painter.end();
}
void DSSImageWidget::wheelEvent(QWheelEvent* e)
{
qreal degrees = -e->angleDelta().y() / 8.0;
//
// If zooming in and zoom factor is currently 1.0
// then remember mouse location
//
if ((degrees > 0) && (m_zoom == 1.0))
{
m_where = e->position();
}
qreal steps = degrees / 60.0;
qreal factor = m_zoom * std::pow(1.125, steps);
m_zoom = std::clamp(factor, 1.0, 5.0);
update();
Inherited::wheelEvent(e);
}
This scales the pixmap about the window origin which is a good start, but it's not what I want. I want it to scale the pixmap so that the part of the image under the mouse pointer remains where it is and the image expands around that point.
I've played all sorts of tunes with the painter.translate() calls and the location I use to actually draw the pixmap, but I've so far failed with flying colours :).
Please could someone who knows how this stuff actually works put me out of my misery and tell me how I achieve my objective here?
Thank you
After quite some grappling with the code I've finally solved this. Given that it wasn't dead simple, I'm posting the relevant portions of the code here in the hope it will assist someone else.
From the header:
typedef QWidget
Inherited;
private:
bool initialised;
qreal m_scale, m_zoom;
QPointF m_origin;
QPixmap & m_pixmap;
QPointF m_pointInPixmap;
inline bool mouseOverImage(QPointF loc)
{
qreal x = loc.x(), y = loc.y(), ox = m_origin.x(), oy = m_origin.y();
return (
(x >= ox) &&
(x <= ox + (m_pixmap.width() * m_scale)) &&
(y >= oy) &&
(y <= oy + (m_pixmap.height() * m_scale)));
};
And the source code:
DSSImageWidget::DSSImageWidget(QPixmap& p, QWidget* parent)
: QWidget(parent),
initialised(false),
m_scale(1.0),
m_zoom(1.0),
m_origin(0.0, 0.0),
m_pixmap(p),
m_pointInPixmap((m_pixmap.width() / 2), (m_pixmap.height() / 2))
{
setAttribute(Qt::WA_MouseTracking);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
}
void DSSImageWidget::resizeEvent(QResizeEvent* e)
{
QSize sz = e->size();
qreal pixWidth = m_pixmap.width();
qreal pixHeight = m_pixmap.height();
qreal hScale = (qreal)sz.width() / pixWidth;
qreal vScale = (qreal)sz.height() / pixHeight;
m_scale = std::min(hScale, vScale);
qreal xoffset = 0.0, yoffset = 0.0;
if ((pixWidth * m_scale) < sz.width())
{
xoffset = (sz.width() - (pixWidth * m_scale)) / 2.0;
}
if ((pixHeight * m_scale) < sz.height())
{
yoffset = (sz.height() - (pixHeight * m_scale)) / 2.0;
}
m_origin = QPointF(xoffset, yoffset);
update();
Inherited::resizeEvent(e);
}
void DSSImageWidget::paintEvent(QPaintEvent* event)
{
QPainter painter;
qDebug() << "pointInPixmap: " << m_pointInPixmap.x() << m_pointInPixmap.y();
//
// Now calcualate the rectangle we're interested in
//
qreal width = m_pixmap.width();
qreal height = m_pixmap.height();
qreal x = m_pointInPixmap.x();
qreal y = m_pointInPixmap.y();
QRectF sourceRect(
x - (x / m_zoom),
y - (y / m_zoom),
width / m_zoom,
height / m_zoom
);
qDebug() << "sourceRect: " << sourceRect.x() << sourceRect.y() << sourceRect.width() << sourceRect.height();
//
// Now calculate the rectangle that is the intersection of this rectangle and the pixmap's rectangle.
//
sourceRect &= m_pixmap.rect();
painter.begin(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
painter.translate(m_origin);
painter.scale(m_zoom*m_scale, m_zoom*m_scale);
painter.translate(-m_origin);
//painter.drawPixmap(QPointF(0.0, 0.0), m_pixmap, sourceRect);
painter.drawPixmap(m_origin, m_pixmap, sourceRect);
painter.end();
}
#if QT_CONFIG(wheelevent)
void DSSImageWidget::wheelEvent(QWheelEvent* e)
{
qreal degrees = -e->angleDelta().y() / 8.0;
//
// If zooming in and zoom factor is currently 1.0
// then remember mouse location
//
if ((degrees > 0) && (m_zoom == 1.0))
{
QPointF mouseLocation = e->position();
if (mouseOverImage(mouseLocation))
{
m_pointInPixmap = QPointF((mouseLocation-m_origin) / m_scale);
}
else
{
m_pointInPixmap = QPointF((m_pixmap.width() / 2), (m_pixmap.height() / 2));
}
}
qreal steps = degrees / 60.0;
qreal factor = m_zoom * std::pow(1.125, steps);
m_zoom = std::clamp(factor, 1.0, 5.0);
if (degrees < 0 && m_zoom == 1.0)
{
m_pointInPixmap = QPointF((m_pixmap.width() / 2), (m_pixmap.height() / 2));
}
update();
Inherited::wheelEvent(e);
}
#endif
All the best, David
I am trying to extract collision coordinate between two rectangles moving inside a QGraphicsScene and send these coordinates to a QLineEdit. Basically the position of the rectangle when they collide.
I was reading this source and this source.
They advice to use QGraphicsItem::collidesWithPath but no success.
This is the snipped of code that does the collision with my own implementation:
MyItem::MyItem()
{
angle = (qrand() % 360);
setRotation(angle);
speed = 5;
int startX = 0;
int startY = 0;
if((qrand() % 1)){
startX = (qrand() % 200);
startY = (qrand() % 200);
}else{
startX = (qrand() % -100);
startY = (qrand() % -100);
}
setPos(mapToParent(startX, startY));
}
void MyItem::paint(QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
QRectF rec = boundingRect();
QBrush brush(Qt::gray);
if(scene()->collidingItems(this).isEmpty()){
brush.setColor(Qt::green); // no collision
}else{
brush.setColor(Qt::red); // yes collision
doCollision();
}
painter->fillRect(rec, brush);
painter->drawRect(rec);
}
void MyItem::advance(int phase)
{
if(!phase) return;
QPointF location = this->pos();
setPos(mapToParent(0, -(speed)));
}
void MyItem::doCollision()
{
if((qrand() %1)) {
setRotation(rotation() + (180+(qrand() % 10)));
}else{
setRotation(rotation() + (180+(qrand() % -10)));
}
// see if the new position is in bounds
QPointF newPoint = mapToParent(-(boundingRect().width()), -(boundingRect().width() + 2));
if(!scene()->sceneRect().contains((newPoint))){
newPoint = mapToParent(0, 0); // move it back in bounds
}else{
setPos(newPoint); // set new position
}
}
How to extract collision coordinates (x,y) between objects in QGraphicsScene and send the coordinates (x,y) to QLineEdit?
Thanks for shedding light on this issue.
I need to crop a QImage displayed on a QLabel. I want to do it with mouseevents like dragging a rectangle on the image and then on the releaseevent the image should crop to the size of the rectangle. I have implemented this code:
void surf_detection::mousePressEvent(QMouseEvent *ev)
{
if(ui->label_2->underMouse()){
cout <<"Entered Press"<<endl;
origin = ev->pos();
//if (!rubberBand)
rubberBand = new QRubberBand(QRubberBand::Rectangle, this);
rubberBand->show();
}
}
void surf_detection::mouseMoveEvent(QMouseEvent *ev)
{
rubberBand->setGeometry(QRect(origin, ev->pos()).normalized());
}
void surf_detection::mouseReleaseEvent(QMouseEvent *ev)
{
QPoint a = mapToGlobal(origin);
QPoint b = ev->globalPos();
a = ui->label_2->mapFromGlobal(a);
b = ui->label_2->mapFromGlobal(b);
rubberBand->hide();
QPixmap OriginalPix(*ui->label_2->pixmap());
double sx = ui->label_2->rect().width();
double sy = ui->label_2->rect().height();
sx = OriginalPix.width() / sx;
sy = OriginalPix.height() / sy;
a.x = int(a.x * sx);
b.x = int(b.x * sx);
a.y = int(a.y * sy);
b.y = int(b.y * sy);
QRect myRect(a,b);
QImage newImage;
newImage = OriginalPix.toImage();
QImage copyImage;
copyImage = copyImage.copy(myRect);
ui->label_2->setPixmap(QPixmap::fromImage(copyImage));
ui->label_2->repaint();
}
But this code gives me error at
a.x = int(a.x * sx);
b.x = int(b.x * sx);
a.y = int(a.y * sy);
b.y = int(b.y * sy);
error: invalid use of member function (did you forget the '()' ?)
a.x = int(a.x * sx);
^
How do i resolve this?
By listening to your compiler :
a.setX(int(a.x() * sx));
In my game, I'd like to fire rockets from a rocket launcher. The player holds a rocket launcher as a child item. The rockets must be parentless. I'm trying to position the rocket so that its back lines up with the back of the rocket launcher (the player is facing north in the screenshots) and centered horizontally within it:
Instead, what I'm getting is:
The rotation is also incorrect (run the example and move the mouse cursor around to see what I mean). Where am I going wrong in my code?
#include <QtWidgets>
QPointF moveBy(const QPointF &pos, qreal rotation, float distance)
{
return pos - QTransform().rotate(rotation).map(QPointF(0, distance));
}
float directionTo(const QPointF &source, const QPointF &target) {
QPointF toTarget(target.x() - source.x(), target.y() - source.y());
float facingTarget = qRadiansToDegrees(atan2(toTarget.y(), toTarget.x())) + 90.0f;
facingTarget = fmod(facingTarget, 360.0f);
if(facingTarget < 0)
facingTarget += 360.0f;
return facingTarget;
}
class Controller : public QObject
{
public:
Controller(QGraphicsScene *scene) :
mScene(scene)
{
mPlayer = scene->addRect(0, 0, 25, 25, QPen(Qt::blue));
mPlayer->setTransformOriginPoint(mPlayer->boundingRect().width() / 2, mPlayer->boundingRect().height() / 2);
mRocketLauncher = scene->addRect(0, 0, 16, 40, QPen(Qt::green));
mRocketLauncher->setParentItem(mPlayer);
mRocketLauncher->setPos(mPlayer->boundingRect().width() * 0.9 - mRocketLauncher->boundingRect().width() / 2,
-mRocketLauncher->boundingRect().height() * 0.3);
mRocket = scene->addRect(0, 0, 16, 20, QPen(Qt::red));
scene->installEventFilter(this);
QGraphicsTextItem *playerText = scene->addText("Player");
playerText->setPos(0, 100);
playerText->setDefaultTextColor(Qt::blue);
QGraphicsTextItem *rocketLauncherText = scene->addText("Rocket launcher");
rocketLauncherText->setPos(0, 120);
rocketLauncherText->setDefaultTextColor(Qt::green);
QGraphicsTextItem *rocketText = scene->addText("Rocket");
rocketText->setPos(0, 140);
rocketText->setDefaultTextColor(Qt::red);
}
bool eventFilter(QObject *, QEvent *event) {
if (event->type() == QEvent::GraphicsSceneMouseMove) {
const QGraphicsSceneMouseEvent *mouseEvent = static_cast<QGraphicsSceneMouseEvent*>(event);
mPlayer->setRotation(directionTo(mPlayer->sceneBoundingRect().center(), mouseEvent->scenePos()));
qreal rocketX = mRocketLauncher->sceneBoundingRect().center().x() - mRocket->boundingRect().width() / 2;
QPointF rocketPos(rocketX, 0);
rocketPos = moveBy(rocketPos, mPlayer->rotation(), mRocketLauncher->boundingRect().height() - mRocket->boundingRect().height());
mRocket->setPos(rocketPos);
mRocket->setRotation(mPlayer->rotation());
return true;
}
return false;
}
private:
QGraphicsScene *mScene;
QGraphicsRectItem *mPlayer;
QGraphicsRectItem *mRocketLauncher;
QGraphicsRectItem *mRocket;
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QGraphicsView view;
view.setMouseTracking(true);
QGraphicsScene *scene = new QGraphicsScene;
view.setScene(scene);
Controller controller(scene);
view.resize(300, 300);
view.show();
return app.exec();
}
The idea is to:
set rotation of both items;
get positions of bottom left corner of launcher and rocket in global (scene) coordinates;
shift rocket to make positinos equal.
Code:
mPlayer->setRotation(directionTo(mPlayer->sceneBoundingRect().center(),
mouseEvent->scenePos()));
mRocket->setRotation(mPlayer->rotation());
QPointF launcherPos = mRocketLauncher->mapToScene(
mRocketLauncher->boundingRect().bottomLeft());
QPointF currentRocketPos = mRocket->mapToScene(
mRocket->boundingRect().bottomLeft());
mRocket->setPos(mRocket->pos() - currentRocketPos + launcherPos);
I have my own derived class of type QGraphicsLineItem where I override paint() in order to render it as an arrow.
My test line is 160, 130, 260, 230
And my paint() implementation:
void MyQGraphicsLineItem::paint( QPainter* aPainter, const QStyleOptionGraphicsItem* aOption, QWidget* aWidget /*= nullptr*/ )
{
Q_UNUSED( aWidget );
aPainter->setClipRect( aOption->exposedRect );
// Get the line and its angle
QLineF cLine = line();
const qreal cLineAngle = cLine.angle();
// Create two copies of the line
QLineF head1 = cLine;
QLineF head2 = cLine;
// Shorten each line and set its angle relative to the main lines angle
// this gives up the "arrow head" lines
head1.setLength( 12 );
head1.setAngle( cLineAngle+-32 );
head2.setLength( 12 );
head2.setAngle( cLineAngle+32 );
// Draw shaft
aPainter->setPen( QPen( Qt::black, 1, Qt::SolidLine ) );
aPainter->drawLine( cLine );
// Draw arrow head
aPainter->setPen( QPen( Qt::red, 1, Qt::SolidLine ) );
aPainter->drawLine( head1 );
aPainter->setPen( QPen( Qt::magenta, 1, Qt::SolidLine ) );
aPainter->drawLine( head2 );
}
This draws an arrow which looks like this:
What I would like to do is be able to calculate the "outline" of this item, such that I can draw a filled QPolygon from the data.
I can't use any shortcuts such as drawing two lines with different pen widths because I want the outline to be an animated "dashed" line (aka marching ants).
I'm sure this is simple to calculate but my maths skills are very bad - I attempt to create a parallel line by doing the following:
Store the line angle.
Set the angle to 0.
Copy the line.
Use QLineF::translate() on the copy.
Set both lines angles back to the value you stored in 1 - this then causes the start and end pos of each line to be misaligned.
Hopefully someone can put me on the right track to creating a thick QPolygonF (or anything else if it makes sense) from this line which can then have an outline and fill set for painting.
Also I plan to have 1000's of these in my scene so ideally I'd also want a solution which won't take too much execution time or has a simple way of being optimized.
This image here is what I'm trying to achieve - imagine the red line is a qt dashed line rather than my very bad mspaint attempt at drawing it!
This solution works even if the arrow is moved and rotated in the scene later on:
arrow.h
#ifndef ARROW_H
#define ARROW_H
#include <QGraphicsLineItem>
#include <QObject>
#include <QtCore/qmath.h>
class Arrow : public QGraphicsLineItem, public QObject
{
public:
Arrow(qreal x1, qreal y1, qreal x2, qreal y2, QGraphicsItem* parent = 0);
virtual ~Arrow();
QPointF objectEndPoint1();
QPointF objectEndPoint2();
void setObjectEndPoint1(qreal x1, qreal y1);
void setObjectEndPoint2(qreal x2, qreal y2);
protected:
void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*);
void timerEvent(QTimerEvent* event);
private:
inline qreal pi() { return (qAtan(1.0)*4.0); }
inline qreal radians(qreal degrees) { return (degrees*pi()/180.0); }
void createArrow(qreal penWidth);
QPainterPath arrowPath;
QPainterPath strokePath;
QPainterPath fillPath;
int timerID_Anim;
int animFrame;
qreal animLength;
QVector<qreal> dashPattern;
};
#endif
arrow.cpp
#include "arrow.h"
#include <QPen>
#include <QPainter>
#include <QTimerEvent>
Arrow::Arrow(qreal x1, qreal y1, qreal x2, qreal y2, QGraphicsItem* parent) : QGraphicsLineItem(0, 0, x2, y2, parent)
{
setFlag(QGraphicsItem::ItemIsSelectable, true);
setObjectEndPoint1(x1, y1);
setObjectEndPoint2(x2, y2);
qreal dashLength = 3;
qreal dashSpace = 3;
animLength = dashLength + dashSpace;
dashPattern << dashLength << dashSpace;
createArrow(1.0);
animFrame = 0;
timerID_Anim = startTimer(100);
}
Arrow::~Arrow()
{
}
void Arrow::timerEvent(QTimerEvent* event)
{
if(event->timerId() == timerID_Anim)
{
animFrame++;
if(animFrame >= animLength) animFrame = 0;
}
update(); //This forces a repaint, even if the mouse isn't moving
}
void Arrow::createArrow(qreal penWidth)
{
QPen arrowPen = pen();
arrowPen.setWidthF(penWidth);
arrowPen.setDashPattern(dashPattern);
setPen(arrowPen);
QPointF p1 = line().p1();
QPointF p2 = line().p2();
qreal angle = line().angle();
qreal arrowHeadAngle = 32.0;
qreal length = line().length();
qreal arrowHeadLength = length/10.0;
QLineF arrowLine1(p1, p2);
QLineF arrowLine2(p1, p2);
arrowLine1.setAngle(angle + arrowHeadAngle);
arrowLine2.setAngle(angle - arrowHeadAngle);
arrowLine1.setLength(arrowHeadLength);
arrowLine2.setLength(arrowHeadLength);
QPainterPath linePath;
linePath.moveTo(p1);
linePath.lineTo(p2);
QPainterPath arrowheadPath;
arrowheadPath.moveTo(arrowLine1.p2());
arrowheadPath.lineTo(p1);
arrowheadPath.lineTo(arrowLine2.p2());
arrowheadPath.lineTo(p1);
arrowheadPath.lineTo(arrowLine1.p2());
arrowPath = QPainterPath();
arrowPath.addPath(linePath);
arrowPath.addPath(arrowheadPath);
}
void Arrow::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/)
{
QPen paintPen = pen();
QPainterPathStroker stroker;
stroker.setWidth(paintPen.widthF());
stroker.setCapStyle(Qt::FlatCap);
stroker.setJoinStyle(Qt::MiterJoin);
strokePath = stroker.createStroke(arrowPath);
strokePath = strokePath.simplified();
stroker.setDashOffset(animFrame);
stroker.setDashPattern(dashPattern);
fillPath = stroker.createStroke(strokePath);
paintPen.setDashOffset(animFrame);
painter->fillPath(fillPath, QBrush(QColor(255,0,0)));
painter->fillPath(strokePath, QBrush(QColor(0,255,0)));
}
QPointF Arrow::objectEndPoint1()
{
return scenePos();
}
QPointF Arrow::objectEndPoint2()
{
QLineF lyne = line();
qreal rot = radians(rotation());
qreal cosRot = qCos(rot);
qreal sinRot = qSin(rot);
qreal x2 = lyne.x2();
qreal y2 = lyne.y2();
qreal rotEnd2X = x2*cosRot - y2*sinRot;
qreal rotEnd2Y = x2*sinRot + y2*cosRot;
return (scenePos() + QPointF(rotEnd2X, rotEnd2Y));
}
void Arrow::setObjectEndPoint1(qreal x1, qreal y1)
{
QPointF endPt2 = objectEndPoint2();
qreal x2 = endPt2.x();
qreal y2 = endPt2.y();
qreal dx = x2 - x1;
qreal dy = y2 - y1;
setRotation(0);
setLine(0, 0, dx, dy);
setPos(x1, y1);
}
void Arrow::setObjectEndPoint2(qreal x2, qreal y2)
{
QPointF endPt1 = scenePos();
qreal x1 = endPt1.x();
qreal y1 = endPt1.y();
qreal dx = x2 - x1;
qreal dy = y2 - y1;
setRotation(0);
setLine(0, 0, dx, dy);
setPos(x1, y1);
}
I almost forgot about this question, here was my PyQt solution, I'm not sure if there is any way its performance can be improved.
class ArrowItem(QGraphicsLineItem):
def __init__(self, x, y , w, h, parent = None):
super(ArrowItem, self).__init__( x, y, w, h, parent)
self.init()
def paint(self, painter, option, widget):
painter.setClipRect( option.exposedRect )
painter.setBrush( Qt.yellow )
if self.isSelected():
p = QPen( Qt.red, 2, Qt.DashLine )
painter.setPen( p )
else:
p = QPen( Qt.black, 2, Qt.SolidLine )
p.setJoinStyle( Qt.RoundJoin )
painter.setPen( p )
painter.drawPath( self.shape() )
def shape(self):
# Calc arrow head lines based on the angle of the current line
cLine = self.line()
kArrowHeadLength = 13
kArrowHeadAngle = 32
cLineAngle = cLine.angle()
head1 = QLineF(cLine)
head2 = QLineF(cLine)
head1.setLength( kArrowHeadLength )
head1.setAngle( cLineAngle+-kArrowHeadAngle )
head2.setLength( kArrowHeadLength )
head2.setAngle( cLineAngle+kArrowHeadAngle )
# Create paths for each section of the arrow
mainLine = QPainterPath()
mainLine.moveTo( cLine.p2() )
mainLine.lineTo( cLine.p1() )
headLine1 = QPainterPath()
headLine1.moveTo( cLine.p1() )
headLine1.lineTo( head1.p2() )
headLine2 = QPainterPath()
headLine2.moveTo( cLine.p1() )
headLine2.lineTo( head2.p2() )
stroker = QPainterPathStroker()
stroker.setWidth( 4 )
# Join them together
stroke = stroker.createStroke( mainLine )
stroke.addPath( stroker.createStroke( headLine1 ) )
stroke.addPath( stroker.createStroke( headLine2 ) )
return stroke.simplified()
def boundingRect(self):
pPath = self.shape()
bRect = pPath.controlPointRect()
adjusted = QRectF( bRect.x()-1, bRect.y()-1, bRect.width()+2, bRect.height()+2 )
return adjusted
.. and of course set the item to be movable/selectable.
And so you can see the required class to get the "outlines" is QPainterPathStroker.
http://doc.qt.io/qt-5/qpainterpathstroker.html#details