Нахождение контура QGraphicsItem

У меня есть свой собственный производный класс типа QGraphicsLineItem, где я переопределяю paint (), чтобы отобразить его в виде стрелки.

Моя тестовая линия 160, 130, 260, 230

И моя реализация paint ():

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 );
}

Это рисует стрелку, которая выглядит следующим образом:

введите описание изображения здесь

Что я хотел бы сделать, так это уметь вычислять «контур» этого элемента так, чтобы я мог нарисовать заполненный QPolygon из данных.

Я не могу использовать какие-либо ярлыки, такие как рисование двух линий с разной шириной пера, потому что я хочу, чтобы контур был анимированной «пунктирной» линией (то есть походными муравьями).

Я уверен, что это просто рассчитать, но мои математические навыки очень плохи — я пытаюсь создать параллельную линию, выполнив следующие действия:

  1. Сохраните угол наклона линии.
  2. Установите угол на 0.
  3. Скопируйте строку.
  4. Используйте QLineF :: translate () для копии.
  5. Установите оба угла линий обратно к значению, которое вы сохранили в 1 — это приведет к смещению начального и конечного положений каждой линии.

Надеюсь, кто-то может поставить меня на правильный путь создания толстой QPolygonF (или чего-либо еще, если это имеет смысл) из этой линии, которая затем может иметь контур и набор заливки для рисования.

Кроме того, я планирую иметь тысячи таких в моей сцене, поэтому в идеале я бы хотел найти решение, которое не будет занимать слишком много времени выполнения или имеет простой способ оптимизации.

введите описание изображения здесь

Это изображение — вот то, чего я пытаюсь достичь — представьте, что красная линия — это пунктирная линия qt, а не моя очень плохая попытка нарисовать ее!

0

Решение

Это решение работает, даже если стрелка перемещается и поворачивается в сцене позже:

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);
}
1

Другие решения

Я почти забыл об этом вопросе, вот мое решение PyQt, я не уверен, есть ли способ улучшить его производительность.

Класс 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

.. и, конечно, установить элемент для перемещения / выбора.

И поэтому вы можете увидеть, что для получения «контуров» необходим класс QPainterPathStroker.

http://doc.qt.io/qt-5/qpainterpathstroker.html#details

0

По вопросам рекламы [email protected]