diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 78b56a8..4dd34eb 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -3,14 +3,14 @@ name: CMake Build on: push: paths-ignore: - - 'doc/**' + - 'docs/**' - '.clang-format' - '.gitignore' - 'LICENSE' - 'README*' pull_request: paths-ignore: - - 'doc/**' + - 'docs/**' - '.clang-format' - '.gitignore' - 'LICENSE' diff --git a/.github/workflows/qmake.yml b/.github/workflows/qmake.yml index e279306..fe4ff04 100644 --- a/.github/workflows/qmake.yml +++ b/.github/workflows/qmake.yml @@ -3,14 +3,14 @@ name: QMake Build on: push: paths-ignore: - - 'doc/**' + - 'docs/**' - '.clang-format' - '.gitignore' - 'LICENSE' - 'README*' pull_request: paths-ignore: - - 'doc/**' + - 'docs/**' - '.clang-format' - '.gitignore' - 'LICENSE' diff --git a/Qt-Graphics.pro b/Qt-Graphics.pro index 36fca8f..32a2853 100644 --- a/Qt-Graphics.pro +++ b/Qt-Graphics.pro @@ -6,7 +6,7 @@ SUBDIRS += \ examples DISTFILES += \ - doc/** \ + docs/** \ .clang* \ LICENSE \ README* diff --git a/README.md b/README.md index 1cfa44a..507e254 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ ## 看图界面
- +
## Opengl看图界面 @@ -45,7 +45,7 @@ ## 马赛克绘制界面(橡皮擦效果)
- +
## 圆角编辑窗口(也可编辑成圆形图标) @@ -53,15 +53,13 @@ 1. 一定要保存为PNG,不然圆角处会变成黑色;
- - - +
## 简单图形绘制界面
- +
## 电影字幕拼接界面 @@ -71,7 +69,7 @@ 3. 虽然第一眼看起来模糊,但是实际生成的时候都是重新载入左侧原图剪切生成,保存后可用其他图片查看工具验证,或者按照上一点(2)查看;
- +
## GIF录制(egif和gif-h库)和截图功能 @@ -80,5 +78,5 @@ 2. 截屏之后可以使用(4)绘制图形;
- +
diff --git a/doc/RoundEdit_1.png b/doc/RoundEdit_1.png deleted file mode 100644 index d490ca3..0000000 Binary files a/doc/RoundEdit_1.png and /dev/null differ diff --git a/doc/RoundEdit_2.png b/doc/RoundEdit_2.png deleted file mode 100644 index 00ed734..0000000 Binary files a/doc/RoundEdit_2.png and /dev/null differ diff --git a/doc/RoundEdit_3.png b/doc/RoundEdit_3.png deleted file mode 100644 index 280d95b..0000000 Binary files a/doc/RoundEdit_3.png and /dev/null differ diff --git a/doc/DrawScene.png b/docs/DrawScene.png similarity index 100% rename from doc/DrawScene.png rename to docs/DrawScene.png diff --git a/doc/FilmSubTiltleSplicing.png b/docs/FilmSubTiltleSplicing.png similarity index 100% rename from doc/FilmSubTiltleSplicing.png rename to docs/FilmSubTiltleSplicing.png diff --git a/doc/ImageView.png b/docs/ImageView.png similarity index 100% rename from doc/ImageView.png rename to docs/ImageView.png diff --git a/doc/MaskEdit.png b/docs/MaskEdit.png similarity index 100% rename from doc/MaskEdit.png rename to docs/MaskEdit.png diff --git a/doc/Record_Screenshot.gif b/docs/Record_Screenshot.gif similarity index 100% rename from doc/Record_Screenshot.gif rename to docs/Record_Screenshot.gif diff --git a/docs/RoundEdit.jpg b/docs/RoundEdit.jpg new file mode 100644 index 0000000..4e6dde1 Binary files /dev/null and b/docs/RoundEdit.jpg differ diff --git a/examples/graphics/drawwidget.cpp b/examples/graphics/drawwidget.cpp index 6b9f3a8..d34a9f5 100644 --- a/examples/graphics/drawwidget.cpp +++ b/examples/graphics/drawwidget.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -100,11 +101,12 @@ void DrawWidget::onAddShape(QListWidgetItem *item) switch (type) { case BasicGraphicsItem::LINE: shape = new GraphicsLineItem; break; case BasicGraphicsItem::RECT: shape = new GraphicsRectItem; break; + case BasicGraphicsItem::ROUNDEDRECT: shape = new GraphicsRoundedRectItem; break; + case BasicGraphicsItem::ROTATEDRECT: shape = new GraphicsRotatedRectItem; break; case BasicGraphicsItem::CIRCLE: shape = new GraphicsCircleItem; break; case BasicGraphicsItem::POLYGON: shape = new GraphicsPolygonItem; break; case BasicGraphicsItem::RING: shape = new GraphicsRingItem; break; case BasicGraphicsItem::ARC: shape = new GraphicsArcItem; break; - case BasicGraphicsItem::ROTATEDRECT: shape = new GraphicsRotatedRectItem; break; default: break; } if (shape) { diff --git a/examples/graphics/maskdialog.cpp b/examples/graphics/maskdialog.cpp index 7fbdb83..f18d12e 100644 --- a/examples/graphics/maskdialog.cpp +++ b/examples/graphics/maskdialog.cpp @@ -166,24 +166,24 @@ void MaskDialog::onSave() auto newFlatBlueButton(const QString &text, QWidget *parent) -> QPushButton * { - QPushButton *button = new QPushButton(text, parent); + auto *button = new QPushButton(text, parent); return button; } void MaskDialog::setupUI() { - QPushButton *button1 = newFlatBlueButton(tr("Mask"), this); - QPushButton *button2 = newFlatBlueButton(tr("Erase"), this); - QPushButton *button3 = new QPushButton(tr("Clear"), this); + auto *button1 = newFlatBlueButton(tr("Mask"), this); + auto *button2 = newFlatBlueButton(tr("Erase"), this); + auto *button3 = new QPushButton(tr("Clear"), this); d_ptr->buttonGroup->addButton(button1, 1); d_ptr->buttonGroup->addButton(button2, 2); d_ptr->buttonGroup->addButton(button3, 3); d_ptr->buttonGroup->button(1)->setChecked(true); - QPushButton *saveButton = newFlatBlueButton(tr("Save"), this); + auto *saveButton = newFlatBlueButton(tr("Save"), this); connect(saveButton, &QPushButton::clicked, this, &MaskDialog::onSave); - QHBoxLayout *editLayout = new QHBoxLayout; + auto *editLayout = new QHBoxLayout; editLayout->addStretch(); editLayout->addWidget(new QLabel(tr("Pen Size: "), this)); editLayout->addWidget(d_ptr->penSizeSpinBox); @@ -195,7 +195,7 @@ void MaskDialog::setupUI() editLayout->addWidget(saveButton); editLayout->addStretch(); - QVBoxLayout *layout = new QVBoxLayout(this); + auto *layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 10); layout->addWidget(d_ptr->imageView); layout->addLayout(editLayout); diff --git a/examples/graphics/rounddialog.cc b/examples/graphics/rounddialog.cc index 231781e..c2bf606 100644 --- a/examples/graphics/rounddialog.cc +++ b/examples/graphics/rounddialog.cc @@ -2,26 +2,41 @@ #include "stretchparamssettingdailog.hpp" #include -#include +#include #include #include using namespace Graphics; +auto radiusImage(const QPixmap &pixmap, int radius) -> QImage +{ + QImage image(pixmap.size(), QImage::Format_RGBA8888_Premultiplied); + image.fill(Qt::transparent); + QPainter painter(&image); + painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); + painter.setBrush(pixmap); + painter.setPen(Qt::transparent); + painter.drawRoundedRect(pixmap.rect(), radius, radius); + return image; +} + class RoundDialog::RoundDialogPrivate { public: - RoundDialogPrivate(QWidget *parent) - : q_ptr(parent) - , graphicsRectItemPtr(new GraphicsRectItem) + explicit RoundDialogPrivate(RoundDialog *q) + : q_ptr(q) + , roundedRectItemPtr(new GraphicsRoundedRectItem) { + roundedRectItemPtr->setShowBoundingRect(true); + imageView = new ImageView(q_ptr); - buttonGroup = new QButtonGroup(q_ptr); - buttonGroup->setExclusive(true); - cropWidget = new QWidget(q_ptr); - rectGroupBox = new QGroupBox(QObject::tr("Rect Info"), q_ptr); + previewLabel = new QLabel(q_ptr); + previewLabel->setMinimumHeight(200); + previewLabel->setAlignment(Qt::AlignCenter); + + rectGroupBox = new QGroupBox(QObject::tr("Rounded rect info"), q_ptr); topLeftXSpinBox = new QSpinBox(q_ptr); topLeftXSpinBox->setKeyboardTracking(false); topLeftYSpinBox = new QSpinBox(q_ptr); @@ -30,31 +45,83 @@ class RoundDialog::RoundDialogPrivate widthSpinBox->setKeyboardTracking(false); heightSpinBox = new QSpinBox(q_ptr); heightSpinBox->setKeyboardTracking(false); - radiusSpinBox = new QSpinBox(q_ptr); radiusSpinBox->setKeyboardTracking(false); } ~RoundDialogPrivate() {} - QWidget *q_ptr; + void setRoundedRect(const RoundedRect &roundedRect) + { + roundedRectItemPtr->setRoundedRect(roundedRect); + radiusSpinBox->setRange(0, qMin(roundedRect.rect.width(), roundedRect.rect.height()) / 2.0); + if (radiusSpinBox->value() > radiusSpinBox->maximum()) { + radiusSpinBox->setValue(radiusSpinBox->maximum()); + } + + QMetaObject::invokeMethod( + roundedRectItemPtr.data(), [=] { roundedRectItemPtr->update(); }, Qt::QueuedConnection); + updatePreview(); + } + + void updatePreview() + { + static QAtomicInt count = 0; + count.ref(); + auto currentCount = count.loadAcquire(); + + QThreadPool::globalInstance()->start([this, currentCount] { + auto roundedRect = roundedRectItemPtr->roundedRect(); + auto previewPixmap = pixmap.copy(roundedRect.rect.toRect()); + previewPixmap = QPixmap::fromImage(radiusImage(previewPixmap, roundedRect.xRadius)); + previewPixmap = previewPixmap.scaled(previewLabel->size(), + Qt::KeepAspectRatio, + Qt::SmoothTransformation); + if (currentCount == count.loadAcquire()) { + QMetaObject::invokeMethod( + q_ptr, [=] { previewLabel->setPixmap(previewPixmap); }, Qt::QueuedConnection); + } + }); + + QThreadPool::globalInstance()->start([this, currentCount] { + auto roundedRect = roundedRectItemPtr->roundedRect(); + QImage image(pixmap.size(), QImage::Format_RGBA8888_Premultiplied); + image.fill(Qt::transparent); + QPainterPath p1, p2; + p1.addRect(pixmap.rect()); + p2.addRoundedRect(roundedRect.rect, roundedRect.xRadius, roundedRect.yRadius); + p1 = p1.subtracted(p2); + { + QPainter painter(&image); + painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); + painter.setOpacity(0.6); + painter.fillPath(p1, QBrush(Qt::black)); + } + if (currentCount == count.loadAcquire()) { + QMetaObject::invokeMethod( + q_ptr, + [=] { imageView->pixmapItem()->setMaskImage(image); }, + Qt::QueuedConnection); + } + }); + } + + RoundDialog *q_ptr; ImageView *imageView; - QWidget *cropWidget; - QButtonGroup *buttonGroup; + QLabel *previewLabel; + QGroupBox *rectGroupBox; QSpinBox *topLeftXSpinBox; QSpinBox *topLeftYSpinBox; QSpinBox *widthSpinBox; QSpinBox *heightSpinBox; - QSpinBox *radiusSpinBox; QString name; - QScopedPointer graphicsRectItemPtr; + QScopedPointer roundedRectItemPtr; QPixmap pixmap; - QPixmap cutPixmap; }; RoundDialog::RoundDialog(QWidget *parent) @@ -84,53 +151,22 @@ void RoundDialog::setPixmap(const QPixmap &pixmap) d_ptr->widthSpinBox->setRange(0, pixmap.width()); d_ptr->heightSpinBox->setRange(0, pixmap.height()); d_ptr->imageView->setPixmap(pixmap); - d_ptr->graphicsRectItemPtr->setRect(pixmap.rect().adjusted(10, 10, -10, -10)); - if (!d_ptr->imageView->scene()->items().contains(d_ptr->graphicsRectItemPtr.data())) { - d_ptr->imageView->scene()->addItem(d_ptr->graphicsRectItemPtr.data()); - } - buildConnect2(); -} -void RoundDialog::onStartRound(bool checked) -{ - QPushButton *button = qobject_cast(sender()); - if (!button) { - return; + RoundedRect roundedRect; + roundedRect.rect = pixmap.rect().adjusted(10, 10, -10, -10); + roundedRect.xRadius = roundedRect.yRadius = d_ptr->radiusSpinBox->value(); + d_ptr->setRoundedRect(roundedRect); + if (!d_ptr->imageView->scene()->items().contains(d_ptr->roundedRectItemPtr.data())) { + d_ptr->imageView->scene()->addItem(d_ptr->roundedRectItemPtr.data()); } - if (checked) { - if (d_ptr->buttonGroup->button(1)->isChecked()) { - d_ptr->cutPixmap = d_ptr->pixmap; - } else { - d_ptr->cutPixmap = d_ptr->pixmap.copy(d_ptr->graphicsRectItemPtr->rect().toRect()); - } - d_ptr->imageView->setPixmap(d_ptr->cutPixmap); - d_ptr->graphicsRectItemPtr->setRect(d_ptr->cutPixmap.rect().adjusted(10, 10, -10, -10)); - d_ptr->cropWidget->hide(); - d_ptr->graphicsRectItemPtr->hide(); - button->setText(tr("Reset Crop")); - d_ptr->radiusSpinBox->setRange(0, - qMin(d_ptr->cutPixmap.size().width(), - d_ptr->cutPixmap.size().height()) - / 2.0); - return; - } - d_ptr->cropWidget->show(); - d_ptr->rectGroupBox->show(); - setPixmap(d_ptr->pixmap); - d_ptr->graphicsRectItemPtr->show(); - button->setText(tr("Start Round")); + buildConnect2(); } void RoundDialog::onSave() { - if (d_ptr->cropWidget->isVisible()) { - return; - } - - QPixmap pixmap(d_ptr->imageView->pixmap()); - if (pixmap.isNull()) { - return; - } + auto roundedRect = d_ptr->roundedRectItemPtr->roundedRect(); + auto pixmap = d_ptr->pixmap.copy(roundedRect.rect.toRect()); + pixmap = QPixmap::fromImage(radiusImage(pixmap, roundedRect.xRadius)); StretchParamsSettingDailog::StretchParams params{pixmap.size(), Qt::KeepAspectRatio}; StretchParamsSettingDailog dialog(this); @@ -161,29 +197,10 @@ void RoundDialog::onSave() qDebug() << pixmap.save(filename, "PNG", params.quality); } -void RoundDialog::onButtonClicked(int id) +void RoundDialog::onRoundedRectChanged(const Graphics::RoundedRect &roundedRect) { - GraphicsPixmapItem *pixmapItem = d_ptr->imageView->pixmapItem(); - if (!pixmapItem) { - return; - } - switch (id) { - case 1: - d_ptr->graphicsRectItemPtr->hide(); - d_ptr->rectGroupBox->hide(); - d_ptr->radiusSpinBox - ->setRange(0, qMin(d_ptr->widthSpinBox->value(), d_ptr->heightSpinBox->value()) / 2); - break; - case 2: - d_ptr->graphicsRectItemPtr->show(); - d_ptr->rectGroupBox->show(); - break; - default: break; - } -} + auto rectF = roundedRect.rect; -void RoundDialog::onRectChanged(const QRectF &rectF) -{ d_ptr->topLeftXSpinBox->blockSignals(true); d_ptr->topLeftYSpinBox->blockSignals(true); d_ptr->widthSpinBox->blockSignals(true); @@ -199,91 +216,76 @@ void RoundDialog::onRectChanged(const QRectF &rectF) d_ptr->widthSpinBox->setRange(0, d_ptr->pixmap.width() - rectF.x()); d_ptr->heightSpinBox->setRange(0, d_ptr->pixmap.height() - rectF.y()); + d_ptr->updatePreview(); } void RoundDialog::onTopLeftXChanged(int value) { - QRectF rectF = d_ptr->graphicsRectItemPtr->rect(); - QPointF point1 = rectF.topLeft(); + auto roundedRect = d_ptr->roundedRectItemPtr->roundedRect(); + QPointF point1 = roundedRect.rect.topLeft(); point1.setX(value); - d_ptr->graphicsRectItemPtr->setRect(QRectF(point1, rectF.bottomRight())); - d_ptr->graphicsRectItemPtr->update(); + roundedRect.rect = QRectF(point1, roundedRect.rect.bottomRight()); + d_ptr->setRoundedRect(roundedRect); } void RoundDialog::onTopLeftYChanged(int value) { - QRectF rectF = d_ptr->graphicsRectItemPtr->rect(); - QPointF point1 = rectF.topLeft(); + auto roundedRect = d_ptr->roundedRectItemPtr->roundedRect(); + QPointF point1 = roundedRect.rect.topLeft(); point1.setY(value); - d_ptr->graphicsRectItemPtr->setRect(QRectF(point1, rectF.bottomRight())); - d_ptr->graphicsRectItemPtr->update(); + roundedRect.rect = QRectF(point1, roundedRect.rect.bottomRight()); + d_ptr->setRoundedRect(roundedRect); } void RoundDialog::onWidthChanged(int value) { - QRectF rectF = d_ptr->graphicsRectItemPtr->rect(); - QPointF point1 = rectF.bottomRight(); - point1.setX(value + rectF.x()); - d_ptr->graphicsRectItemPtr->setRect(QRectF(rectF.topLeft(), point1)); - d_ptr->graphicsRectItemPtr->update(); + auto roundedRect = d_ptr->roundedRectItemPtr->roundedRect(); + QPointF point1 = roundedRect.rect.bottomRight(); + point1.setX(value + roundedRect.rect.x()); + roundedRect.rect = QRectF(roundedRect.rect.topLeft(), point1); + d_ptr->setRoundedRect(roundedRect); } void RoundDialog::onHeightChanged(int value) { - QRectF rectF = d_ptr->graphicsRectItemPtr->rect(); - QPointF point1 = rectF.bottomRight(); - point1.setY(value + rectF.y()); - d_ptr->graphicsRectItemPtr->setRect(QRectF(rectF.topLeft(), point1)); - d_ptr->graphicsRectItemPtr->update(); -} - -auto radiusImage(const QPixmap &pixmap, int radius) -> QImage -{ - QImage image(pixmap.size(), QImage::Format_ARGB32); - image.fill(Qt::transparent); - QPainter painter(&image); - painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); - painter.setBrush(pixmap); - painter.setPen(Qt::transparent); - painter.drawRoundedRect(pixmap.rect(), radius, radius); - return image; + auto roundedRect = d_ptr->roundedRectItemPtr->roundedRect(); + QPointF point1 = roundedRect.rect.bottomRight(); + point1.setY(value + roundedRect.rect.y()); + roundedRect.rect = QRectF(roundedRect.rect.topLeft(), point1); + d_ptr->setRoundedRect(roundedRect); } void RoundDialog::onRadiusChanged(int value) { - if (d_ptr->cropWidget->isVisible()) { - return; - } - - d_ptr->imageView->setPixmap(QPixmap::fromImage(radiusImage(d_ptr->cutPixmap, value))); + auto roundedRect = d_ptr->roundedRectItemPtr->roundedRect(); + roundedRect.xRadius = roundedRect.yRadius = value; + d_ptr->setRoundedRect(roundedRect); } void RoundDialog::setupUI() { - QSplitter *splitter = new QSplitter(Qt::Horizontal, this); + auto *splitter = new QSplitter(Qt::Horizontal, this); splitter->addWidget(d_ptr->imageView); splitter->addWidget(toolWidget()); splitter->setStretchFactor(0, 1); splitter->setStretchFactor(1, 1); splitter->setSizes({INT_MAX, 1}); - QVBoxLayout *layout = new QVBoxLayout(this); + auto *layout = new QVBoxLayout(this); layout->setContentsMargins(QMargins()); layout->addWidget(splitter); } void RoundDialog::buildConnect() { - connect(d_ptr->buttonGroup, &QButtonGroup::idClicked, this, &RoundDialog::onButtonClicked); - connect(d_ptr->graphicsRectItemPtr.data(), - &Graphics::GraphicsRectItem::rectChanged, + connect(d_ptr->roundedRectItemPtr.data(), + &Graphics::GraphicsRoundedRectItem::roundedRectChanged, this, - &RoundDialog::onRectChanged); + &RoundDialog::onRoundedRectChanged); connect(d_ptr->radiusSpinBox, QOverload::of(&QSpinBox::valueChanged), this, &RoundDialog::onRadiusChanged); - d_ptr->buttonGroup->button(1)->click(); } void RoundDialog::buildConnect2() @@ -312,39 +314,22 @@ void RoundDialog::buildConnect2() QWidget *RoundDialog::toolWidget() { - QRadioButton *radioButton1 = new QRadioButton(tr("Original Image"), this); - QRadioButton *radioButton2 = new QRadioButton(tr("Rect Crop"), this); - d_ptr->buttonGroup->addButton(radioButton1, 1); - d_ptr->buttonGroup->addButton(radioButton2, 2); - - QFormLayout *formLayout = new QFormLayout(d_ptr->rectGroupBox); + auto *formLayout = new QFormLayout(d_ptr->rectGroupBox); formLayout->addRow(tr("TopLeft X:"), d_ptr->topLeftXSpinBox); formLayout->addRow(tr("TopLeft Y:"), d_ptr->topLeftYSpinBox); formLayout->addRow(tr("Width:"), d_ptr->widthSpinBox); formLayout->addRow(tr("Height:"), d_ptr->heightSpinBox); + formLayout->addRow(tr("Radius:"), d_ptr->radiusSpinBox); - QVBoxLayout *cropLayout = new QVBoxLayout(d_ptr->cropWidget); - cropLayout->setContentsMargins(QMargins()); - cropLayout->addWidget(radioButton1); - cropLayout->addWidget(radioButton2); - cropLayout->addWidget(d_ptr->rectGroupBox); - - QPushButton *startRoundButton = new QPushButton(tr("Start Round"), this); - startRoundButton->setCheckable(true); - connect(startRoundButton, &QPushButton::clicked, this, &RoundDialog::onStartRound); - - QFormLayout *radiusLayout = new QFormLayout; - radiusLayout->addRow(tr("Radius:"), d_ptr->radiusSpinBox); - - QPushButton *saveButton = new QPushButton(tr("Save"), this); + auto *saveButton = new QPushButton(tr("Save"), this); connect(saveButton, &QPushButton::clicked, this, &RoundDialog::onSave); - QWidget *toolWidget = new QWidget(this); + auto *toolWidget = new QWidget(this); toolWidget->setMaximumWidth(250); - QVBoxLayout *toolLayout = new QVBoxLayout(toolWidget); - toolLayout->addWidget(d_ptr->cropWidget); - toolLayout->addWidget(startRoundButton); - toolLayout->addLayout(radiusLayout); + auto *toolLayout = new QVBoxLayout(toolWidget); + toolLayout->addWidget(new QLabel(tr("Preview:"), this)); + toolLayout->addWidget(d_ptr->previewLabel); + toolLayout->addWidget(d_ptr->rectGroupBox); toolLayout->addWidget(saveButton); toolLayout->addStretch(); return toolWidget; diff --git a/examples/graphics/rounddialog.hpp b/examples/graphics/rounddialog.hpp index 44f8bcd..21ff65e 100644 --- a/examples/graphics/rounddialog.hpp +++ b/examples/graphics/rounddialog.hpp @@ -3,6 +3,10 @@ #include +namespace Graphics { +struct RoundedRect; +} + class RoundDialog : public QDialog { Q_OBJECT @@ -15,10 +19,8 @@ class RoundDialog : public QDialog void setPixmap(const QPixmap &pixmap); private slots: - void onStartRound(bool checked); void onSave(); - void onButtonClicked(int id); - void onRectChanged(const QRectF &rectF); + void onRoundedRectChanged(const Graphics::RoundedRect &roundedRect); void onTopLeftXChanged(int value); void onTopLeftYChanged(int value); void onWidthChanged(int value); diff --git a/examples/graphics/subtitlsplicingwidget.cc b/examples/graphics/subtitlsplicingwidget.cc index 2009c1b..88f517d 100644 --- a/examples/graphics/subtitlsplicingwidget.cc +++ b/examples/graphics/subtitlsplicingwidget.cc @@ -27,7 +27,7 @@ class GenerateTask : public QRunnable auto clipImage = i.copy(info.imageRect); QImage tmpImage(image.width(), image.height() + clipImage.height(), - QImage::Format_ARGB32); + QImage::Format_RGBA8888_Premultiplied); QPainter painter(&tmpImage); painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); painter.drawImage(image.rect(), image); @@ -141,7 +141,7 @@ void SubtitlSplicingWidget::onGenerated() // auto clipImage = view->clipImage(); // QImage tmpImage(image.width(), // image.height() + clipImage.height(), - // QImage::Format_ARGB32); + // QImage::Format_RGBA8888_Premultiplied); // QPainter painter(&tmpImage); // painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); // painter.drawImage(image.rect(), image); diff --git a/src/graphics/CMakeLists.txt b/src/graphics/CMakeLists.txt index 491e290..fdf3df1 100644 --- a/src/graphics/CMakeLists.txt +++ b/src/graphics/CMakeLists.txt @@ -20,6 +20,8 @@ set(PROJECT_SOURCES graphicsringitem.h graphicsrotatedrectitem.cpp graphicsrotatedrectitem.h + graphicsroundedrectitem.cc + graphicsroundedrectitem.hpp graphicstextitem.cc graphicstextitem.hpp imageview.cpp diff --git a/src/graphics/basicgraphicsitem.cpp b/src/graphics/basicgraphicsitem.cpp index 0a941f6..a3b2350 100644 --- a/src/graphics/basicgraphicsitem.cpp +++ b/src/graphics/basicgraphicsitem.cpp @@ -14,17 +14,19 @@ namespace Graphics { class BasicGraphicsItem::BasicGraphicsItemPrivate { public: - explicit BasicGraphicsItemPrivate(QObject *parent) - : q_ptr(parent) + explicit BasicGraphicsItemPrivate(BasicGraphicsItem *q) + : q_ptr(q) {} - QObject *q_ptr; + BasicGraphicsItem *q_ptr; + QString name; BasicGraphicsItem::MouseRegion mouseRegin = BasicGraphicsItem::None; int hoveredDotIndex = -1; QPointF clickedPos; QPolygonF cache; double margin = 10; + bool showBoundingRect = false; }; BasicGraphicsItem::BasicGraphicsItem(QGraphicsItem *parent) @@ -84,6 +86,17 @@ void BasicGraphicsItem::setItemEditable(bool editable) setAcceptHoverEvents(editable); } +void BasicGraphicsItem::setShowBoundingRect(bool show) +{ + d_ptr->showBoundingRect = show; + QMetaObject::invokeMethod(this, [this] { update(); }, Qt::QueuedConnection); +} + +bool BasicGraphicsItem::showBoundingRect() const +{ + return d_ptr->showBoundingRect; +} + //QVariant BasicGraphicsItem::itemChange(QGraphicsItem::GraphicsItemChange change, // const QVariant &value) //{ @@ -187,6 +200,18 @@ void BasicGraphicsItem::drawAnchor(QPainter *painter) } } +void BasicGraphicsItem::drawBoundingRect(QPainter *painter) +{ + if (!d_ptr->showBoundingRect || !isValid()) { + return; + } + + QPen outline(Qt::black, 1, Qt::DashLine); + outline.setCosmetic(true); + painter->setPen(outline); + painter->drawRect(boundingRect()); +} + void BasicGraphicsItem::setMyCursor(const QPointF ¢er, const QPointF &pos) { double angle = QLineF(center, pos).angle(); diff --git a/src/graphics/basicgraphicsitem.h b/src/graphics/basicgraphicsitem.h index d0c37d9..9f78ec4 100644 --- a/src/graphics/basicgraphicsitem.h +++ b/src/graphics/basicgraphicsitem.h @@ -15,7 +15,16 @@ class GRAPHICS_EXPORT BasicGraphicsItem : public QObject, public QAbstractGraphi Q_OBJECT public: // Why does add UserType not draw - enum Shape { LINE = /*UserType +*/ 1, RECT, CIRCLE, POLYGON, RING, ARC, ROTATEDRECT }; + enum Shape { + LINE = /*UserType +*/ 1, + RECT, + ROUNDEDRECT, + ROTATEDRECT, + CIRCLE, + POLYGON, + RING, + ARC + }; Q_ENUM(Shape) enum MouseRegion { DotRegion, All, Edge, None }; @@ -35,6 +44,9 @@ class GRAPHICS_EXPORT BasicGraphicsItem : public QObject, public QAbstractGraphi void setItemEditable(bool editable); + void setShowBoundingRect(bool show); + bool showBoundingRect() const; + protected: //QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override; @@ -52,6 +64,7 @@ class GRAPHICS_EXPORT BasicGraphicsItem : public QObject, public QAbstractGraphi [[nodiscard]] auto hoveredDotIndex() const -> int; void drawAnchor(QPainter *painter); + void drawBoundingRect(QPainter *painter); void setMyCursor(const QPointF ¢er, const QPointF &pos); diff --git a/src/graphics/graphics.pro b/src/graphics/graphics.pro index 57f0c75..eb713b0 100644 --- a/src/graphics/graphics.pro +++ b/src/graphics/graphics.pro @@ -16,6 +16,7 @@ SOURCES += \ graphicsrectitem.cpp \ graphicsringitem.cpp \ graphicsrotatedrectitem.cpp \ + graphicsroundedrectitem.cc \ graphicstextitem.cc \ imageview.cpp @@ -31,6 +32,7 @@ HEADERS += \ graphicsrectitem.h \ graphicsringitem.h \ graphicsrotatedrectitem.h \ + graphicsroundedrectitem.hpp \ graphicstextitem.hpp \ imageview.h diff --git a/src/graphics/graphicsarcitem.cpp b/src/graphics/graphicsarcitem.cpp index 74a5185..319630d 100644 --- a/src/graphics/graphicsarcitem.cpp +++ b/src/graphics/graphicsarcitem.cpp @@ -241,7 +241,7 @@ auto GraphicsArcItem::isValid() const -> bool auto GraphicsArcItem::type() const -> int { - return ARC; + return Shape::ARC; } auto GraphicsArcItem::boundingRect() const -> QRectF @@ -356,7 +356,7 @@ void GraphicsArcItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) return; } BasicGraphicsItem::hoverMoveEvent(event); - if (mouseRegion() == DotRegion){ + if (mouseRegion() == DotRegion) { return; } setMouseRegion(BasicGraphicsItem::None); @@ -404,6 +404,7 @@ void GraphicsArcItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *o } if (option->state & QStyle::State_Selected) { drawAnchor(painter); + drawBoundingRect(painter); } } diff --git a/src/graphics/graphicscircleitem.cpp b/src/graphics/graphicscircleitem.cpp index 1482398..71f908b 100644 --- a/src/graphics/graphicscircleitem.cpp +++ b/src/graphics/graphicscircleitem.cpp @@ -196,6 +196,7 @@ void GraphicsCircleItem::paint(QPainter *painter, const QStyleOptionGraphicsItem } if ((option->state & QStyle::State_Selected)) { drawAnchor(painter); + drawBoundingRect(painter); } } diff --git a/src/graphics/graphicslineitem.cpp b/src/graphics/graphicslineitem.cpp index 8797192..fb6e164 100644 --- a/src/graphics/graphicslineitem.cpp +++ b/src/graphics/graphicslineitem.cpp @@ -123,6 +123,7 @@ void GraphicsLineItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * } if (option->state & QStyle::State_Selected) { drawAnchor(painter); + drawBoundingRect(painter); } } diff --git a/src/graphics/graphicspixmapitem.cpp b/src/graphics/graphicspixmapitem.cpp index 3880dc9..39ccbaa 100644 --- a/src/graphics/graphicspixmapitem.cpp +++ b/src/graphics/graphicspixmapitem.cpp @@ -46,8 +46,9 @@ GraphicsPixmapItem::~GraphicsPixmapItem() {} void GraphicsPixmapItem::setCustomPixmap(const QPixmap &pixmap) { setPixmap(pixmap); - if (pixmap.size() != d_ptr->mask.size()) + if (pixmap.size() != d_ptr->mask.size()) { clearMask(); + } setPenSize(qMin(pixmap.width(), pixmap.height()) / 20.0); } @@ -59,22 +60,7 @@ void GraphicsPixmapItem::setMaskImage(const QImage &mask) if (pixmap().size() != mask.size()) { return; } - // generate 4 channel color mask image - QImage temp = QImage(pixmap().size(), QImage::Format_ARGB32); - temp.fill(Qt::transparent); - QRgb dstRgb = d_ptr->color.rgb(); - int cols = temp.width(); - int rows = temp.height(); - for (int y = 0; y < rows; y++) { - QRgb *dst_row = (QRgb *) temp.scanLine(y); - const uchar *mask_row = mask.scanLine(y); - for (int x = 0; x < cols; x++) { - if (mask_row[x] != 0) { - dst_row[x] = dstRgb; - } - } - } - d_ptr->mask = temp; + d_ptr->mask = mask; update(); } @@ -140,7 +126,7 @@ auto GraphicsPixmapItem::maskColor2() -> QColor void GraphicsPixmapItem::clearMask() { if (!pixmap().isNull()) { - d_ptr->mask = QImage(pixmap().size(), QImage::Format_ARGB32); + d_ptr->mask = QImage(pixmap().size(), QImage::Format_RGBA8888_Premultiplied); d_ptr->mask.fill(Qt::transparent); } update(); diff --git a/src/graphics/graphicspolygonitem.cpp b/src/graphics/graphicspolygonitem.cpp index 1e03fc7..0697cd7 100644 --- a/src/graphics/graphicspolygonitem.cpp +++ b/src/graphics/graphicspolygonitem.cpp @@ -1,8 +1,8 @@ #include "graphicspolygonitem.h" #include "graphics.h" -#include #include +#include #include #include @@ -18,17 +18,17 @@ struct GraphicsPolygonItem::GraphicsPolygonItemPrivate GraphicsPolygonItem::GraphicsPolygonItem(QGraphicsItem *parent) : BasicGraphicsItem(parent) - , d_ptr(new GraphicsPolygonItemPrivate){} + , d_ptr(new GraphicsPolygonItemPrivate) +{} -GraphicsPolygonItem::GraphicsPolygonItem(const QPolygonF& polygon, - QGraphicsItem* parent) +GraphicsPolygonItem::GraphicsPolygonItem(const QPolygonF &polygon, QGraphicsItem *parent) : BasicGraphicsItem(parent) , d_ptr(new GraphicsPolygonItemPrivate) { setPolygon(polygon); } -GraphicsPolygonItem::~GraphicsPolygonItem(){} +GraphicsPolygonItem::~GraphicsPolygonItem() {} inline auto checkPolygon(const QPolygonF &ply, const double margin) -> bool { @@ -71,7 +71,7 @@ void GraphicsPolygonItem::mousePressEvent(QGraphicsSceneMouseEvent *event) return; } QPolygonF pts_tmp = cache(); - pts_tmp.append( event->pos()); + pts_tmp.append(event->pos()); pointsChanged(pts_tmp); } @@ -105,7 +105,7 @@ void GraphicsPolygonItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) void GraphicsPolygonItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) { QPolygonF pts_tmp = cache(); - if(pts_tmp.size() > 0){ + if (pts_tmp.size() > 0) { pts_tmp.append(event->scenePos()); showHoverPolygon(pts_tmp); } @@ -113,9 +113,7 @@ void GraphicsPolygonItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) BasicGraphicsItem::hoverMoveEvent(event); } -void GraphicsPolygonItem::paint(QPainter *painter, - const QStyleOptionGraphicsItem *option, - QWidget*) +void GraphicsPolygonItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) { painter->setRenderHint(QPainter::Antialiasing); double linew = 2 * pen().widthF() / painter->transform().m11(); @@ -129,6 +127,7 @@ void GraphicsPolygonItem::paint(QPainter *painter, } if (option->state & QStyle::State_Selected) { drawAnchor(painter); + drawBoundingRect(painter); } } @@ -159,4 +158,4 @@ void GraphicsPolygonItem::showHoverPolygon(const QPolygonF &ply) update(); } -} +} // namespace Graphics diff --git a/src/graphics/graphicsrectitem.cpp b/src/graphics/graphicsrectitem.cpp index 003236a..db3b322 100644 --- a/src/graphics/graphicsrectitem.cpp +++ b/src/graphics/graphicsrectitem.cpp @@ -1,222 +1,55 @@ #include "graphicsrectitem.h" -#include "graphics.h" - -#include -#include -#include -#include -#include namespace Graphics { -struct GraphicsRectItem::GraphicsRectItemPrivate -{ - QRectF rect; - QRectF tempRect; - bool linehovered = false; - QLineF hoveredLine; -}; - GraphicsRectItem::GraphicsRectItem(QGraphicsItem *parent) - : BasicGraphicsItem(parent) - , d_ptr(new GraphicsRectItemPrivate) -{} - -GraphicsRectItem::GraphicsRectItem(const QRectF &rectF, QGraphicsItem *parent) - : BasicGraphicsItem(parent) - , d_ptr(new GraphicsRectItemPrivate) + : GraphicsRoundedRectItem(parent) { - setRect(rectF); + m_roundedRect = m_tempRoundedRect = RoundedRect(QRectF()); + buildConnect(); } -GraphicsRectItem::~GraphicsRectItem() {} - -inline auto checkRect(const QRectF &rect, const double margin) -> bool +GraphicsRectItem::GraphicsRectItem(const QRectF &rect, QGraphicsItem *parent) + : GraphicsRoundedRectItem(parent) { - return rect.isValid() && rect.x() > 0 && rect.y() > 0 && rect.width() > margin - && rect.height() > margin; + buildConnect(); + setRect(rect); } +GraphicsRectItem::~GraphicsRectItem() {} + void GraphicsRectItem::setRect(const QRectF &rect) { - if (!checkRect(rect, margin())) { - return; - } - prepareGeometryChange(); - d_ptr->rect = rect; - QPolygonF cache; - cache << rect.topLeft() << rect.bottomRight(); - setCache(cache); - emit rectChanged(d_ptr->rect); + RoundedRect roundedRect(rect); + setRoundedRect(roundedRect); } -auto GraphicsRectItem::rect() const -> QRectF +QRectF GraphicsRectItem::rect() const { - return d_ptr->rect; + return roundedRect().rect; } -auto GraphicsRectItem::isValid() const -> bool +bool GraphicsRectItem::isValid() const { - return checkRect(d_ptr->rect, margin()); + return GraphicsRoundedRectItem::isValid(); } -auto GraphicsRectItem::type() const -> int +int GraphicsRectItem::type() const { return RECT; } -void GraphicsRectItem::mousePressEvent(QGraphicsSceneMouseEvent *event) -{ - if (event->button() != Qt::LeftButton) { - return BasicGraphicsItem::mousePressEvent(event); - } - setClickedPos(event->scenePos()); - if (isValid()) { - return; - } - QPointF point = event->pos(); - QPolygonF pts_tmp = cache(); - pts_tmp.append(point); - pointsChanged(pts_tmp); -} - -auto polygonFromRect(const QRectF &rect) -> QPolygonF -{ - QPolygonF ply; - ply.append(rect.topLeft()); - ply.append(rect.topRight()); - ply.append(rect.bottomRight()); - ply.append(rect.bottomLeft()); - return ply; -} - -void GraphicsRectItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) -{ - if ((event->buttons() & Qt::LeftButton) == 0 || !isValid()) { - return; - } - if (!isSelected()) { - setSelected(true); - } - QPointF point = event->scenePos(); - QPolygonF pts_tmp = cache(); - QPointF dp = point - clickedPos(); - setClickedPos(point); - - if (d_ptr->linehovered) { - QPointF p1 = d_ptr->hoveredLine.p1(); - QPointF p2 = d_ptr->hoveredLine.p2(); - - QPolygonF ply1 = polygonFromRect(rect()); - int index0 = ply1.indexOf(p1); - int index1 = ply1.indexOf(p2); - if (index0 < 0 || index1 < 0) { - return; - } - if (abs(p1.x() - p2.x()) < 0.0001) { //vertical line - p1 = p1 + QPointF(dp.x(), 0); - p2 = p2 + QPointF(dp.x(), 0); - } else { - p1 = p1 + QPointF(0, dp.y()); - p2 = p2 + QPointF(0, dp.y()); - } - - ply1.replace(index0, p1); - ply1.replace(index1, p2); - - d_ptr->hoveredLine = QLineF(p1, p2); - - pts_tmp.clear(); - pts_tmp.append(ply1.at(0)); - pts_tmp.append(ply1.at(2)); - } else { - switch (mouseRegion()) { - case DotRegion: { - int index = hoveredDotIndex(); - pts_tmp.replace(index, point); - } break; - case All: pts_tmp.translate(dp); break; - default: return; - } - } - pointsChanged(pts_tmp); -} - -void GraphicsRectItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) -{ - QPointF point = event->scenePos(); - QPolygonF pts_tmp = cache(); - if (pts_tmp.size() == 1) { - pts_tmp.append(point); - showHoverRect(pts_tmp); - } - if (!isValid()) { - return; - } - BasicGraphicsItem::hoverMoveEvent(event); - if (mouseRegion() == DotRegion) { - return; - } - QPolygonF ply = polygonFromRect(rect()); - for (int i = 0; i < ply.count(); ++i) { - QLineF pl(ply.at(i), ply.at((i + 1) % 4)); - QPolygonF tmp = Graphics::boundingFromLine(pl, margin() / 4); - if (tmp.containsPoint(point, Qt::OddEvenFill)) { - d_ptr->linehovered = true; - d_ptr->hoveredLine = pl; - setCursor(Graphics::curorFromAngle(pl.angle())); - return; - } - } - - d_ptr->linehovered = false; -} - -void GraphicsRectItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *) -{ - painter->setRenderHint(QPainter::Antialiasing); - double linew = 2 * pen().widthF() / painter->transform().m11(); - painter->setPen(QPen(LineColor, linew)); - setMargin(painter->transform().m11()); - - if (isValid()) { - painter->drawRect(d_ptr->rect); - } else { - painter->drawRect(d_ptr->tempRect); - } - if (option->state & QStyle::State_Selected) { - drawAnchor(painter); - } -} - -void GraphicsRectItem::pointsChanged(const QPolygonF &ply) +void GraphicsRectItem::onRoundedRectChanged(const RoundedRect &roundedRect) { - QRectF rect = scene()->sceneRect(); - if (!rect.contains(ply.last())) { - return; - } - switch (ply.size()) { - case 1: setCache(ply); break; - case 2: { - QRectF rect_(ply[0], ply[1]); - if (checkRect(rect_, margin())) { - setRect(rect_); - } else { - return; - } - } break; - default: return; - } - update(); + emit rectChanged(roundedRect.rect); } -void GraphicsRectItem::showHoverRect(const QPolygonF &ply) +void GraphicsRectItem::buildConnect() { - if (ply.size() != 2) { - return; - } - d_ptr->tempRect = QRectF(ply[0], ply[1]); - update(); + connect(this, + &GraphicsRectItem::roundedRectChanged, + this, + &GraphicsRectItem::onRoundedRectChanged); } } // namespace Graphics diff --git a/src/graphics/graphicsrectitem.h b/src/graphics/graphicsrectitem.h index 21a5aa0..8279338 100644 --- a/src/graphics/graphicsrectitem.h +++ b/src/graphics/graphicsrectitem.h @@ -1,19 +1,18 @@ -#ifndef GRAPHICSRECTITEM_H -#define GRAPHICSRECTITEM_H +#pragma once -#include "basicgraphicsitem.h" +#include "graphicsroundedrectitem.hpp" namespace Graphics { -class GRAPHICS_EXPORT GraphicsRectItem : public BasicGraphicsItem +class GRAPHICS_EXPORT GraphicsRectItem : public GraphicsRoundedRectItem { Q_OBJECT public: explicit GraphicsRectItem(QGraphicsItem *parent = nullptr); - explicit GraphicsRectItem(const QRectF &, QGraphicsItem *parent = nullptr); + explicit GraphicsRectItem(const QRectF &rect, QGraphicsItem *parent = nullptr); ~GraphicsRectItem() override; - void setRect(const QRectF &); + void setRect(const QRectF &rect); [[nodiscard]] auto rect() const -> QRectF; [[nodiscard]] auto isValid() const -> bool override; @@ -22,22 +21,11 @@ class GRAPHICS_EXPORT GraphicsRectItem : public BasicGraphicsItem signals: void rectChanged(const QRectF &rectF); -protected: - void mousePressEvent(QGraphicsSceneMouseEvent *event) override; - void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; - void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override; - void paint(QPainter *painter, - const QStyleOptionGraphicsItem *option, - QWidget *widget = nullptr) override; +private slots: + void onRoundedRectChanged(const RoundedRect &roundedRect); private: - void pointsChanged(const QPolygonF &ply); - void showHoverRect(const QPolygonF &ply); - - struct GraphicsRectItemPrivate; - QScopedPointer d_ptr; + void buildConnect(); }; } // namespace Graphics - -#endif // GRAPHICSRECTITEM_H diff --git a/src/graphics/graphicsringitem.cpp b/src/graphics/graphicsringitem.cpp index 746e67d..c6e1896 100644 --- a/src/graphics/graphicsringitem.cpp +++ b/src/graphics/graphicsringitem.cpp @@ -241,6 +241,7 @@ void GraphicsRingItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * if (option->state & QStyle::State_Selected) { drawAnchor(painter); + drawBoundingRect(painter); } } diff --git a/src/graphics/graphicsrotatedrectitem.cpp b/src/graphics/graphicsrotatedrectitem.cpp index 848f4c4..9ed7d76 100644 --- a/src/graphics/graphicsrotatedrectitem.cpp +++ b/src/graphics/graphicsrotatedrectitem.cpp @@ -233,6 +233,7 @@ void GraphicsRotatedRectItem::paint(QPainter *painter, if (!isValid() || (option->state & QStyle::State_Selected)) { drawAnchor(painter); + drawBoundingRect(painter); } } diff --git a/src/graphics/graphicsroundedrectitem.cc b/src/graphics/graphicsroundedrectitem.cc new file mode 100644 index 0000000..b27e98a --- /dev/null +++ b/src/graphics/graphicsroundedrectitem.cc @@ -0,0 +1,243 @@ +#include "graphicsroundedrectitem.hpp" +#include "graphics.h" + +#include +#include +#include +#include +#include + +namespace Graphics { + +inline auto checkRect(const QRectF &rect, const double margin) -> bool +{ + return rect.isValid() && rect.x() > 0 && rect.y() > 0 && rect.width() > margin + && rect.height() > margin; +} + +RoundedRect::RoundedRect(const QRectF &rect, qreal xRadius, qreal yRadius) +{ + this->rect = rect; + this->xRadius = xRadius; + this->yRadius = yRadius; +} + +bool RoundedRect::isValid() const +{ + return rect.isValid() && xRadius >= 0 && yRadius >= 0 && xRadius < rect.width() / 2 + && yRadius < rect.height() / 2; +} + +class GraphicsRoundedRectItem::GraphicsRoundedRectItemPrivate +{ +public: + explicit GraphicsRoundedRectItemPrivate(GraphicsRoundedRectItem *q) + : q_ptr(q) + { + qRegisterMetaType("Graphics::RoundedRect"); + } + + GraphicsRoundedRectItem *q_ptr = nullptr; + + bool linehovered = false; + QLineF hoveredLine; +}; + +GraphicsRoundedRectItem::GraphicsRoundedRectItem(QGraphicsItem *parent) + : BasicGraphicsItem(parent) + , d_ptr(new GraphicsRoundedRectItemPrivate(this)) +{} + +GraphicsRoundedRectItem::GraphicsRoundedRectItem(const RoundedRect &roundedRect, + QGraphicsItem *parent) + : BasicGraphicsItem(parent) + , d_ptr(new GraphicsRoundedRectItemPrivate(this)) +{ + setRoundedRect(roundedRect); +} + +GraphicsRoundedRectItem::~GraphicsRoundedRectItem() {} + +void GraphicsRoundedRectItem::setRoundedRect(const RoundedRect &roundedRect) +{ + if (!roundedRect.isValid() || !checkRect(roundedRect.rect, margin())) { + return; + } + prepareGeometryChange(); + m_roundedRect = roundedRect; + QPolygonF cache; + cache << roundedRect.rect.topLeft() << roundedRect.rect.bottomRight(); + setCache(cache); + emit roundedRectChanged(m_roundedRect); +} + +RoundedRect GraphicsRoundedRectItem::roundedRect() const +{ + return m_roundedRect; +} + +auto GraphicsRoundedRectItem::isValid() const -> bool +{ + return m_roundedRect.isValid() && checkRect(m_roundedRect.rect, margin()); +} + +auto GraphicsRoundedRectItem::type() const -> int +{ + return ROUNDEDRECT; +} + +void GraphicsRoundedRectItem::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + if (event->button() != Qt::LeftButton) { + return BasicGraphicsItem::mousePressEvent(event); + } + setClickedPos(event->scenePos()); + if (isValid()) { + return; + } + QPointF point = event->pos(); + QPolygonF pts_tmp = cache(); + pts_tmp.append(point); + pointsChanged(pts_tmp); +} + +auto polygonFromRect(const QRectF &rect) -> QPolygonF +{ + QPolygonF ply; + ply.append(rect.topLeft()); + ply.append(rect.topRight()); + ply.append(rect.bottomRight()); + ply.append(rect.bottomLeft()); + return ply; +} + +void GraphicsRoundedRectItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + if ((event->buttons() & Qt::LeftButton) == 0 || !isValid()) { + return; + } + if (!isSelected()) { + setSelected(true); + } + QPointF point = event->scenePos(); + QPolygonF pts_tmp = cache(); + QPointF dp = point - clickedPos(); + setClickedPos(point); + + if (d_ptr->linehovered) { + QPointF p1 = d_ptr->hoveredLine.p1(); + QPointF p2 = d_ptr->hoveredLine.p2(); + + QPolygonF ply1 = polygonFromRect(m_roundedRect.rect); + int index0 = ply1.indexOf(p1); + int index1 = ply1.indexOf(p2); + if (index0 < 0 || index1 < 0) { + return; + } + if (abs(p1.x() - p2.x()) < 0.0001) { //vertical line + p1 = p1 + QPointF(dp.x(), 0); + p2 = p2 + QPointF(dp.x(), 0); + } else { + p1 = p1 + QPointF(0, dp.y()); + p2 = p2 + QPointF(0, dp.y()); + } + + ply1.replace(index0, p1); + ply1.replace(index1, p2); + + d_ptr->hoveredLine = QLineF(p1, p2); + + pts_tmp.clear(); + pts_tmp.append(ply1.at(0)); + pts_tmp.append(ply1.at(2)); + } else { + switch (mouseRegion()) { + case DotRegion: { + int index = hoveredDotIndex(); + pts_tmp.replace(index, point); + } break; + case All: pts_tmp.translate(dp); break; + default: return; + } + } + pointsChanged(pts_tmp); +} + +void GraphicsRoundedRectItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) +{ + QPointF point = event->scenePos(); + QPolygonF pts_tmp = cache(); + if (pts_tmp.size() == 1) { + pts_tmp.append(point); + showHoverRect(pts_tmp); + } + if (!isValid()) { + return; + } + BasicGraphicsItem::hoverMoveEvent(event); + if (mouseRegion() == DotRegion) { + return; + } + QPolygonF ply = polygonFromRect(m_roundedRect.rect); + for (int i = 0; i < ply.count(); ++i) { + QLineF pl(ply.at(i), ply.at((i + 1) % 4)); + QPolygonF tmp = Graphics::boundingFromLine(pl, margin() / 4); + if (tmp.containsPoint(point, Qt::OddEvenFill)) { + d_ptr->linehovered = true; + d_ptr->hoveredLine = pl; + setCursor(Graphics::curorFromAngle(pl.angle())); + return; + } + } + + d_ptr->linehovered = false; +} + +void GraphicsRoundedRectItem::paint(QPainter *painter, + const QStyleOptionGraphicsItem *option, + QWidget *) +{ + painter->setRenderHint(QPainter::Antialiasing); + double linew = 2 * pen().widthF() / painter->transform().m11(); + painter->setPen(QPen(LineColor, linew)); + setMargin(painter->transform().m11()); + auto roundedRect = isValid() ? m_roundedRect : m_tempRoundedRect; + painter->drawRoundedRect(roundedRect.rect, roundedRect.xRadius, roundedRect.yRadius); + + if (option->state & QStyle::State_Selected) { + drawAnchor(painter); + drawBoundingRect(painter); + } +} + +void GraphicsRoundedRectItem::pointsChanged(const QPolygonF &ply) +{ + QRectF rect = scene()->sceneRect(); + if (!rect.contains(ply.last())) { + return; + } + switch (ply.size()) { + case 1: setCache(ply); break; + case 2: { + QRectF rect_(ply[0], ply[1]); + if (checkRect(rect_, margin())) { + setRoundedRect({rect_, m_roundedRect.xRadius, m_roundedRect.yRadius}); + } else { + return; + } + } break; + default: return; + } + update(); +} + +void GraphicsRoundedRectItem::showHoverRect(const QPolygonF &ply) +{ + if (ply.size() != 2) { + return; + } + m_tempRoundedRect.rect = QRectF(ply[0], ply[1]); + update(); +} + +} // namespace Graphics diff --git a/src/graphics/graphicsroundedrectitem.hpp b/src/graphics/graphicsroundedrectitem.hpp new file mode 100644 index 0000000..1961ac2 --- /dev/null +++ b/src/graphics/graphicsroundedrectitem.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include "basicgraphicsitem.h" + +namespace Graphics { + +struct GRAPHICS_EXPORT RoundedRect +{ + RoundedRect() = default; + RoundedRect(const QRectF &rect, qreal xRadius = 0, qreal yRadius = 0); + + bool isValid() const; + + QRectF rect; + qreal xRadius = 10; + qreal yRadius = 10; +}; + +class GRAPHICS_EXPORT GraphicsRoundedRectItem : public BasicGraphicsItem +{ + Q_OBJECT +public: + explicit GraphicsRoundedRectItem(QGraphicsItem *parent = nullptr); + explicit GraphicsRoundedRectItem(const RoundedRect &roundedRect, + QGraphicsItem *parent = nullptr); + virtual ~GraphicsRoundedRectItem() override; + + void setRoundedRect(const RoundedRect &roundedRect); + [[nodiscard]] auto roundedRect() const -> RoundedRect; + + [[nodiscard]] auto isValid() const -> bool override; + [[nodiscard]] auto type() const -> int override; + +signals: + void roundedRectChanged(const RoundedRect &roundedRect); + +protected: + void mousePressEvent(QGraphicsSceneMouseEvent *event) override; + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override; + void paint(QPainter *painter, + const QStyleOptionGraphicsItem *option, + QWidget *widget = nullptr) override; + + RoundedRect m_roundedRect; + RoundedRect m_tempRoundedRect; + +private: + void pointsChanged(const QPolygonF &ply); + void showHoverRect(const QPolygonF &ply); + + class GraphicsRoundedRectItemPrivate; + QScopedPointer d_ptr; +}; + +} // namespace Graphics