diff --git a/PaintPlugin/src/DkPaintPlugin.cpp b/PaintPlugin/src/DkPaintPlugin.cpp index 4a85d5c..a7ca787 100644 --- a/PaintPlugin/src/DkPaintPlugin.cpp +++ b/PaintPlugin/src/DkPaintPlugin.cpp @@ -67,6 +67,77 @@ bool DkPaintPlugin::hideHUD() const { * @param run ID * @param current image in the Nomacs viewport **/ +const int ArrowWidth = 10; +const int ArrowHeight = 18; +const int TextEnlarge = 15; +QPainterPath getArrowHead(QPainterPath line, const int thickness) { + QPointF p1 = line.pointAtPercent(0.0); + QPointF p2 = line.pointAtPercent(1.0); + QLineF base(p1, p2); + // Create the vector for the position of the base of the arrowhead + QLineF temp(QPoint(0,0), p2-p1); + int val = ArrowHeight + thickness*4; + if (base.length() < val) { + val = (base.length() + thickness*2); + } + temp.setLength(base.length() + thickness*2 - val); + // Move across the line up to the head + QPointF bottonTranslation(temp.p2()); + + // Rotate base of the arrowhead + base.setLength(ArrowWidth + thickness*2); + base.setAngle(base.angle() + 90); + // Move to the correct point + QPointF temp2 = p1 - base.p2(); + // Center it + QPointF centerTranslation((temp2.x()/2), (temp2.y()/2)); + + base.translate(bottonTranslation); + base.translate(centerTranslation); + + QPainterPath path; + path.moveTo(p2); + path.lineTo(base.p1()); + path.lineTo(base.p2()); + path.lineTo(p2); + path.closeSubpath(); + return path; +} + +// gets a shorter line to prevent overlap in the point of the arrow +QLineF getShorterLine(QPainterPath line, const int thickness) { + QPointF p1 = line.pointAtPercent(0.0); + QPointF p2 = line.pointAtPercent(1.0); + QLineF l(p1, p2); + int val = ArrowHeight + thickness*4; + if (l.length() < val) { + val = (l.length() + thickness*2); + } + l.setLength(l.length() + thickness*2 - val); + return l.toLine(); +} + +// blur selected rectangle region +void getBlur(QPainterPath rect, QPainter *painter, QPixmap &pixmap, int radius){ + auto pixelRatio = pixmap.devicePixelRatio(); + QRectF selection = rect.boundingRect(); + QRect selectionScaled = selection.toRect(); + + QGraphicsBlurEffect *blur = new QGraphicsBlurEffect; + blur->setBlurRadius(radius); + QGraphicsPixmapItem *item = new QGraphicsPixmapItem ( + pixmap.copy(selectionScaled)); + item->setGraphicsEffect(blur); + + QGraphicsScene scene; + scene.addItem(item); + + scene.render(painter, selection, QRectF()); + blur->setBlurRadius(radius+2); + scene.render(painter, selection, QRectF()); + scene.render(painter, selection, QRectF()); +} + QSharedPointer DkPaintPlugin::runPlugin(const QString &runID, QSharedPointer image) const { if (!image) @@ -180,11 +251,16 @@ void DkPaintViewPort::init() { connect(paintToolbar, SIGNAL(panSignal(bool)), this, SLOT(setPanning(bool)), Qt::UniqueConnection); connect(paintToolbar, SIGNAL(cancelSignal()), this, SLOT(discardChangesAndClose()), Qt::UniqueConnection); connect(paintToolbar, SIGNAL(undoSignal()), this, SLOT(undoLastPaint()), Qt::UniqueConnection); + connect(paintToolbar, SIGNAL(modeChangeSignal(int)), this, SLOT(setMode(int)), Qt::UniqueConnection); connect(paintToolbar, SIGNAL(applySignal()), this, SLOT(applyChangesAndClose()), Qt::UniqueConnection); + connect(paintToolbar, SIGNAL(textChangeSignal(const QString&)), this, SLOT(textChange(const QString&)), Qt::UniqueConnection); + connect(paintToolbar, SIGNAL(editFinishSignal()), this, SLOT(textEditFinsh()), Qt::UniqueConnection); + connect(this, SIGNAL(editShowSignal(bool)), paintToolbar, SLOT(showLineEdit(bool)), Qt::UniqueConnection); loadSettings(); paintToolbar->setPenColor(pen.color()); paintToolbar->setPenWidth(pen.width()); + textinputenable = false; } void DkPaintViewPort::undoLastPaint() { @@ -194,6 +270,7 @@ void DkPaintViewPort::undoLastPaint() { paths.pop_back(); pathsPen.pop_back(); + pathsMode.pop_back(); update(); } @@ -216,10 +293,25 @@ void DkPaintViewPort::mousePressEvent(QMouseEvent *event) { if(QRectF(QPointF(), viewport->getImage().size()).contains(mapToImage(event->pos()))) { isOutside = false; + + // roll back the empty painterpath generated by click mouse + if(!paths.empty()) + if(paths.last().isEmpty()) + undoLastPaint(); + + // create new painterpath paths.append(QPainterPath()); paths.last().moveTo(mapToImage(event->pos())); - paths.last().lineTo(mapToImage(event->pos())+QPointF(0.1,0)); + //paths.last().lineTo(mapToImage(event->pos())+QPointF(0.1,0)); + begin = mapToImage(event->pos()); pathsPen.append(pen); + pathsMode.append(selectedMode); + if(selectedMode == mode_text) + { + textinputenable = true; + // lineedit show only when in text mode and mouse click + emit editShowSignal(true); + } update(); } else @@ -257,10 +349,45 @@ void DkPaintViewPort::mouseMoveEvent(QMouseEvent *event) { paths.append(QPainterPath()); paths.last().moveTo(mapToImage(event->pos())); pathsPen.append(pen); + pathsMode.append(selectedMode); } else { QPointF point = mapToImage(event->pos()); - paths.last().lineTo(point); + switch (selectedMode) + { + case mode_pencil: + paths.last().lineTo(point); + break; + + case mode_line: + case mode_arrow: + //paths.last().clear(); + paths.last() = QPainterPath(); + paths.last().moveTo(begin); + paths.last().lineTo(point); + break; + + case mode_circle: + //paths.last().clear(); + paths.last() = QPainterPath(); + paths.last().addEllipse(QRectF(begin, point)); + break; + + case mode_square: + case mode_square_fill: + case mode_blur: + //paths.last().clear(); + paths.last() = QPainterPath(); + paths.last().addRect(QRectF(begin, point)); + break; + + case mode_text: + break; + + default: + paths.last().lineTo(point); + break; + } update(); } isOutside = false; @@ -294,7 +421,40 @@ void DkPaintViewPort::paintEvent(QPaintEvent *event) { for (int idx = 0; idx < paths.size(); idx++) { painter.setPen(pathsPen.at(idx)); - painter.drawPath(paths.at(idx)); + if(pathsMode.at(idx) == mode_arrow){ + painter.fillPath(getArrowHead(paths.at(idx), pathsPen.at(idx).width()), QBrush(pathsPen.at(idx).color())); + painter.drawLine(getShorterLine(paths.at(idx), pathsPen.at(idx).width())); + } + //else if(pathsMode.at(idx) == mode_square_fill || pathsMode.at(idx) == mode_text) + else if(pathsMode.at(idx) == mode_square_fill) + painter.fillPath(paths.at(idx), QBrush(pathsPen.at(idx).color())); + else if(pathsMode.at(idx) == mode_text){ + painter.fillPath(paths.at(idx), QBrush(pathsPen.at(idx).color())); + //painter.setPen(QPen(QBrush(QColor(0,0,0,180)),1,Qt::DashLine)); + //painter.setBrush(QBrush(QColor(255,255,255,120))); + //painter.drawRect(paths.at(idx).boundingRect()); + //painter.setPen(pathsPen.at(idx)); + QPointF p = paths.at(idx).boundingRect().bottomRight(); + if((idx == paths.size()-1) && (textinputenable)) + { + painter.setPen(QPen(QBrush(QColor(0,0,0,180)),pathsPen.at(idx).width(),Qt::DotLine)); + if(sbuffer.isEmpty()) + painter.drawLine(QLineF(begin, begin-QPoint(0, pathsPen.at(idx).width()*10))); + else + painter.drawLine(QLineF(p, p-QPoint(0, pathsPen.at(idx).width()*10))); + } + //painter.drawPoint(paths.at(idx).boundingRect().bottomRight()); + } + else if(pathsMode.at(idx) == mode_blur){ + if(parent()){ + nmc::DkBaseViewPort* viewport = dynamic_cast(parent()); + QImage img = viewport->getImage(); + QPixmap pixmap = QPixmap::fromImage(img).copy(); + getBlur(paths.at(idx), &painter, pixmap, pathsPen.at(idx).width()); + } + } + else + painter.drawPath(paths.at(idx)); } painter.end(); @@ -323,7 +483,18 @@ QImage DkPaintViewPort::getPaintedImage() { for (int idx = 0; idx < paths.size(); idx++) { painter.setPen(pathsPen.at(idx)); - painter.drawPath(paths.at(idx)); + if(pathsMode.at(idx) == mode_arrow){ + painter.fillPath(getArrowHead(paths.at(idx), pathsPen.at(idx).width()), QBrush(pathsPen.at(idx).color())); + painter.drawLine(getShorterLine(paths.at(idx), pathsPen.at(idx).width())); + } + else if(pathsMode.at(idx) == mode_square_fill || pathsMode.at(idx) == mode_text) + painter.fillPath(paths.at(idx), QBrush(pathsPen.at(idx).color())); + else if(pathsMode.at(idx) == mode_blur){ + QPixmap pixmap = QPixmap::fromImage(img).copy(); + getBlur(paths.at(idx), &painter, pixmap, pathsPen.at(idx).width()); + } + else + painter.drawPath(paths.at(idx)); } painter.end(); @@ -335,9 +506,38 @@ QImage DkPaintViewPort::getPaintedImage() { return QImage(); } +void DkPaintViewPort::setMode(int mode){ + selectedMode = mode; + setCursor(defaultCursor); + emit editShowSignal(false); + + this->repaint(); +} + +void DkPaintViewPort::textChange(const QString &text){ + QFont font; + font.setFamily(font.defaultFamily()); + font.setPixelSize(pen.width()*TextEnlarge); + if(textinputenable) + { + sbuffer = text; + paths.last() = QPainterPath(); + paths.last().addText(begin, font, text); + update(); + } +} + +void DkPaintViewPort::textEditFinsh(){ + if(sbuffer.isEmpty()) + undoLastPaint(); + textinputenable = false; + emit editShowSignal(false); +} + void DkPaintViewPort::clear() { paths.clear(); pathsPen.clear(); + pathsMode.clear(); } void DkPaintViewPort::setBrush(const QBrush& brush) { @@ -426,6 +626,15 @@ void DkPaintToolBar::createIcons() { icons[pan_icon] = nmc::DkImage::loadIcon(":/nomacs/img/pan.svg"); icons[pan_icon].addPixmap(nmc::DkImage::loadIcon(":/nomacs/img/pan-checked.svg"), QIcon::Normal, QIcon::On); icons[undo_icon] = nmc::DkImage::loadIcon(":/nomacs/img/rotate-cc.svg"); + + icons[pencil_icon] = nmc::DkImage::loadIcon(":/nomacsPluginPaint/img/pencil.svg"); + icons[line_icon] = nmc::DkImage::loadIcon(":/nomacsPluginPaint/img/line.svg"); + icons[arrow_icon] = nmc::DkImage::loadIcon(":/nomacsPluginPaint/img/arrow.svg"); + icons[circle_icon] = nmc::DkImage::loadIcon(":/nomacsPluginPaint/img/circle-outline.svg"); + icons[square_icon] = nmc::DkImage::loadIcon(":/nomacsPluginPaint/img/square-outline.svg"); + icons[square_fill_icon] = nmc::DkImage::loadIcon(":/nomacsPluginPaint/img/square.svg"); + icons[blur_icon] = nmc::DkImage::loadIcon(":/nomacsPluginPaint/img/blur.svg"); + icons[text_icon] = nmc::DkImage::loadIcon(":/nomacsPluginPaint/img/text.svg"); } void DkPaintToolBar::createLayout() { @@ -448,6 +657,51 @@ void DkPaintToolBar::createLayout() { panAction->setCheckable(true); panAction->setChecked(false); + // brush modes + pencilAction = new QAction(icons[pencil_icon], tr("Pencil"), this); + pencilAction->setObjectName("pencilAction"); + pencilAction->setCheckable(true); + pencilAction->setChecked(true); + + lineAction = new QAction(icons[line_icon], tr("Line"), this); + lineAction->setObjectName("lineAction"); + lineAction->setCheckable(true); + lineAction->setChecked(false); + + arrowAction = new QAction(icons[arrow_icon], tr("Arrow"), this); + arrowAction->setObjectName("arrowAction"); + arrowAction->setCheckable(true); + arrowAction->setChecked(false); + + circleAction = new QAction(icons[circle_icon], tr("Circle"), this); + circleAction->setObjectName("circleAction"); + circleAction->setCheckable(true); + circleAction->setChecked(false); + + squareAction = new QAction(icons[square_icon], tr("Square"), this); + squareAction->setObjectName("squareAction"); + squareAction->setCheckable(true); + squareAction->setChecked(false); + + squarefillAction = new QAction(icons[square_fill_icon], tr("Filled Square"), this); + squarefillAction->setObjectName("squarefillAction"); + squarefillAction->setCheckable(true); + squarefillAction->setChecked(false); + + blurAction = new QAction(icons[blur_icon], tr("Blur"), this); + blurAction->setObjectName("blurAction"); + blurAction->setCheckable(true); + blurAction->setChecked(false); + + textAction = new QAction(icons[text_icon], tr("Text"), this); + textAction->setObjectName("textAction"); + textAction->setCheckable(true); + textAction->setChecked(false); + + textInput = new QLineEdit(this); + textInput->setObjectName("textInput"); + textInput->setFixedWidth(100); + // pen color penCol = QColor(0,0,0); penColButton = new QPushButton(this); @@ -478,15 +732,51 @@ void DkPaintToolBar::createLayout() { alphaBox->setMinimum(0); alphaBox->setMaximum(100); + QActionGroup *modesGroup = new QActionGroup(this); + modesGroup->addAction(pencilAction); + modesGroup->addAction(lineAction); + modesGroup->addAction(arrowAction); + modesGroup->addAction(circleAction); + modesGroup->addAction(squareAction); + modesGroup->addAction(squarefillAction); + modesGroup->addAction(blurAction); + modesGroup->addAction(textAction); + + toolbarWidgetList = QMap(); + addAction(applyAction); addAction(cancelAction); addSeparator(); addAction(panAction); addAction(undoAction); addSeparator(); + addAction(pencilAction); + addAction(lineAction); + addAction(arrowAction); + addAction(circleAction); + addAction(squareAction); + addAction(squarefillAction); + addAction(blurAction); + addAction(textAction); + addSeparator(); addWidget(widthBox); addWidget(penColButton); addWidget(alphaBox); + addSeparator(); + //addWidget(textInput); + toolbarWidgetList.insert(textInput->objectName(), this->addWidget(textInput)); + + showLineEdit(false); +} + +void DkPaintToolBar::showLineEdit(bool show) { + if(show) + { + toolbarWidgetList.value(textInput->objectName())->setVisible(true); + textInput->setFocus(); + } + else + toolbarWidgetList.value(textInput->objectName())->setVisible(false); } void DkPaintToolBar::setVisible(bool visible) { @@ -535,11 +825,52 @@ void DkPaintToolBar::on_panAction_toggled(bool checked) { emit panSignal(checked); } +void DkPaintToolBar::on_pencilAction_toggled(bool checked){ + emit modeChangeSignal(mode_pencil); +} + +void DkPaintToolBar::on_lineAction_toggled(bool checked){ + emit modeChangeSignal(mode_line); +} + +void DkPaintToolBar::on_arrowAction_toggled(bool checked){ + emit modeChangeSignal(mode_arrow); +} + +void DkPaintToolBar::on_circleAction_toggled(bool checked){ + emit modeChangeSignal(mode_circle); +} + +void DkPaintToolBar::on_squareAction_toggled(bool checked){ + emit modeChangeSignal(mode_square); +} + +void DkPaintToolBar::on_squarefillAction_toggled(bool checked){ + emit modeChangeSignal(mode_square_fill); +} + +void DkPaintToolBar::on_blurAction_toggled(bool checked){ + emit modeChangeSignal(mode_blur); +} + +void DkPaintToolBar::on_textAction_toggled(bool checked){ + emit modeChangeSignal(mode_text); +} + void DkPaintToolBar::on_widthBox_valueChanged(int val) { emit widthSignal(val); } +void DkPaintToolBar::on_textInput_textChanged(const QString &text){ + emit textChangeSignal(text); +} + +void DkPaintToolBar::on_textInput_editingFinished(){ + emit editFinishSignal(); + textInput->clear(); +} + void DkPaintToolBar::on_alphaBox_valueChanged(int val) { penAlpha = val; diff --git a/PaintPlugin/src/DkPaintPlugin.h b/PaintPlugin/src/DkPaintPlugin.h index 34c7f6f..e8784fe 100644 --- a/PaintPlugin/src/DkPaintPlugin.h +++ b/PaintPlugin/src/DkPaintPlugin.h @@ -34,8 +34,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -55,6 +57,17 @@ namespace nmp { class DkPaintViewPort; class DkPaintToolBar; +enum { + mode_pencil = 0, + mode_line, + mode_arrow, + mode_circle, + mode_square, + mode_square_fill, + mode_blur, + mode_text, +}; + class DkPaintPlugin : public QObject, nmc::DkViewPortInterface { Q_OBJECT Q_INTERFACES(nmc::DkViewPortInterface) @@ -68,6 +81,10 @@ class DkPaintPlugin : public QObject, nmc::DkViewPortInterface { QImage image() const override; bool hideHUD() const override; + QPainterPath getArrowHead(QPainterPath line, const int thickness); + QLineF getShorterLine(QPainterPath line, const int thickness); + void getBlur(QPainterPath rect, QPainter *painter, QPixmap &pixmap, int radius); + QSharedPointer runPlugin(const QString &runID = QString(), QSharedPointer image = QSharedPointer()) const override; nmc::DkPluginViewPort* getViewPort() override; bool createViewPort(QWidget* parent) override; @@ -104,6 +121,14 @@ public slots: virtual void setVisible(bool visible); void undoLastPaint(); +signals: + void editShowSignal(bool show); + +protected slots: + void setMode(int mode); + void textChange(const QString &text); + void textEditFinsh(); + protected: void mouseMoveEvent(QMouseEvent *event); void mousePressEvent(QMouseEvent *event); @@ -116,6 +141,13 @@ public slots: QVector paths; QVector pathsPen; + QVector pathsMode; + QPointF begin; + QString sbuffer; + + int selectedMode; + bool textinputenable; + QPainterPath ArrowHead; bool cancelTriggered; bool isOutside; @@ -140,9 +172,19 @@ class DkPaintToolBar : public QToolBar { pan_icon, undo_icon, + pencil_icon, + line_icon, + arrow_icon, + circle_icon, + square_icon, + square_fill_icon, + blur_icon, + text_icon, + icons_end, }; + DkPaintToolBar(const QString & title, QWidget * parent = 0); virtual ~DkPaintToolBar(); @@ -154,10 +196,21 @@ public slots: void on_applyAction_triggered(); void on_cancelAction_triggered(); void on_panAction_toggled(bool checked); + void on_pencilAction_toggled(bool checked); + void on_lineAction_toggled(bool checked); + void on_arrowAction_toggled(bool checked); + void on_circleAction_toggled(bool checked); + void on_squareAction_toggled(bool checked); + void on_squarefillAction_toggled(bool checked); + void on_blurAction_toggled(bool checked); + void on_textAction_toggled(bool checked); void on_penColButton_clicked(); void on_widthBox_valueChanged(int val); void on_alphaBox_valueChanged(int val); + void on_textInput_textChanged(const QString &text); + void on_textInput_editingFinished(); void on_undoAction_triggered(); + void showLineEdit(bool show); virtual void setVisible(bool visible); signals: @@ -169,6 +222,9 @@ public slots: void shadingHint(bool invert); void panSignal(bool checked); void undoSignal(); + void modeChangeSignal(int mode); + void textChangeSignal(const QString &text); + void editFinishSignal(); protected: void createLayout(); @@ -180,9 +236,21 @@ public slots: QSpinBox* alphaBox; QColor penCol; int penAlpha; + QMap toolbarWidgetList; QAction* panAction; QAction* undoAction; + QAction* pencilAction; + QAction* lineAction; + QAction* arrowAction; + QAction* circleAction; + QAction* squareAction; + QAction* squarefillAction; + QAction* blurAction; + QAction* textAction; + + QLineEdit* textInput; + QVector icons; // needed for colorizing }; diff --git a/PaintPlugin/src/DkPaintPlugin.json b/PaintPlugin/src/DkPaintPlugin.json index 4409bca..e92d0d4 100644 --- a/PaintPlugin/src/DkPaintPlugin.json +++ b/PaintPlugin/src/DkPaintPlugin.json @@ -3,7 +3,7 @@ "AuthorName" : "Tim Jerman", "Company" : "", "DateCreated" : "2014-05-01", - "DateModified" : "2018-11-30", + "DateModified" : "2020-01-29", "Description" : "Paint on an image. The color, size and opacity of the brush can be changed.", "Tagline" : "Draw with adjustable brushes to an image.", "PluginId" : "ad970ef36cc24737afd2b53ad015ff0d", diff --git a/PaintPlugin/src/img/arrow.svg b/PaintPlugin/src/img/arrow.svg new file mode 100644 index 0000000..048a0d3 --- /dev/null +++ b/PaintPlugin/src/img/arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/PaintPlugin/src/img/blur.svg b/PaintPlugin/src/img/blur.svg new file mode 100644 index 0000000..3cb0b2d --- /dev/null +++ b/PaintPlugin/src/img/blur.svg @@ -0,0 +1,4 @@ + + + + diff --git a/PaintPlugin/src/img/circle-outline.svg b/PaintPlugin/src/img/circle-outline.svg new file mode 100644 index 0000000..01ff3ee --- /dev/null +++ b/PaintPlugin/src/img/circle-outline.svg @@ -0,0 +1,3 @@ + + + diff --git a/PaintPlugin/src/img/line.svg b/PaintPlugin/src/img/line.svg new file mode 100644 index 0000000..c65c1d5 --- /dev/null +++ b/PaintPlugin/src/img/line.svg @@ -0,0 +1,3 @@ + + + diff --git a/PaintPlugin/src/img/pencil.svg b/PaintPlugin/src/img/pencil.svg new file mode 100644 index 0000000..30320d2 --- /dev/null +++ b/PaintPlugin/src/img/pencil.svg @@ -0,0 +1,3 @@ + + + diff --git a/PaintPlugin/src/img/square-outline.svg b/PaintPlugin/src/img/square-outline.svg new file mode 100644 index 0000000..63fbf2e --- /dev/null +++ b/PaintPlugin/src/img/square-outline.svg @@ -0,0 +1,3 @@ + + + diff --git a/PaintPlugin/src/img/square.svg b/PaintPlugin/src/img/square.svg new file mode 100644 index 0000000..31b8298 --- /dev/null +++ b/PaintPlugin/src/img/square.svg @@ -0,0 +1,3 @@ + + + diff --git a/PaintPlugin/src/img/text.svg b/PaintPlugin/src/img/text.svg new file mode 100644 index 0000000..19024ee --- /dev/null +++ b/PaintPlugin/src/img/text.svg @@ -0,0 +1,4 @@ + + + + diff --git a/PaintPlugin/src/nomacsPlugin.qrc b/PaintPlugin/src/nomacsPlugin.qrc index ea610cd..c70d8e9 100644 --- a/PaintPlugin/src/nomacsPlugin.qrc +++ b/PaintPlugin/src/nomacsPlugin.qrc @@ -1,5 +1,13 @@ img/description.png + img/pencil.svg + img/line.svg + img/arrow.svg + img/circle-outline.svg + img/square-outline.svg + img/square.svg + img/blur.svg + img/text.svg