diff --git a/PdfForQtLib/sources/pdfpainter.cpp b/PdfForQtLib/sources/pdfpainter.cpp index 53f3324..3db4700 100644 --- a/PdfForQtLib/sources/pdfpainter.cpp +++ b/PdfForQtLib/sources/pdfpainter.cpp @@ -438,7 +438,7 @@ void PDFPrecompiledPageGenerator::performImagePainting(const QImage& image) matrix.map(QPointF(1.0, 1.0)), matrix.map(QPointF(0.0, 1.0)), matrix.map(QPointF(0.5, 0.5)), - }); + }, image); if (isTransparencyGroupActive()) { diff --git a/PdfForQtLib/sources/pdfsnapper.cpp b/PdfForQtLib/sources/pdfsnapper.cpp index 0859b98..164cd51 100644 --- a/PdfForQtLib/sources/pdfsnapper.cpp +++ b/PdfForQtLib/sources/pdfsnapper.cpp @@ -47,7 +47,7 @@ void PDFSnapInfo::addPageMediaBox(const QRectF& mediaBox) addLine(tl, bl); } -void PDFSnapInfo::addImage(const std::array& points) +void PDFSnapInfo::addImage(const std::array& points, const QImage& image) { m_snapPoints.insert(m_snapPoints.cend(), { SnapPoint(SnapType::ImageCorner, points[0]), @@ -61,6 +61,15 @@ void PDFSnapInfo::addImage(const std::array& points) { addLine(points[i], points[(i + 1) % 4]); } + + SnapImage snapImage; + snapImage.imagePath.moveTo(points[0]); + snapImage.imagePath.lineTo(points[1]); + snapImage.imagePath.lineTo(points[2]); + snapImage.imagePath.lineTo(points[3]); + snapImage.imagePath.lineTo(points[0]); + snapImage.image = image; + m_snapImages.emplace_back(qMove(snapImage)); } void PDFSnapInfo::addLine(const QPointF& start, const QPointF& end) @@ -140,6 +149,7 @@ bool PDFSnapper::isSnappingAllowed(PDFInteger pageIndex) const void PDFSnapper::updateSnappedPoint(const QPointF& mousePoint) { m_snappedPoint = std::nullopt; + m_snappedImage = std::nullopt; m_mousePoint = mousePoint; // Iterate trough all points, check, if some satisfies condition @@ -151,7 +161,17 @@ void PDFSnapper::updateSnappedPoint(const QPointF& mousePoint) if (distanceSquared < toleranceSquared && isSnappingAllowed(snapPoint.pageIndex)) { m_snappedPoint = snapPoint; - return; + break; + } + } + + // Iterate trough all images, check, if some is under mouse cursor + for (const ViewportSnapImage& snapImage : m_snapImages) + { + if (snapImage.viewportPath.contains(mousePoint)) + { + m_snappedImage = snapImage; + break; } } } @@ -220,6 +240,32 @@ void PDFSnapper::buildSnapPoints(const PDFWidgetSnapshot& snapshot) updateSnappedPoint(m_mousePoint); } +void PDFSnapper::buildSnapImages(const PDFWidgetSnapshot& snapshot) +{ + // First, clear all snap images + m_snapImages.clear(); + + // Second, create snapping points from snapshot + for (const PDFWidgetSnapshot::SnapshotItem& item : snapshot.items) + { + if (!item.compiledPage) + { + continue; + } + + const PDFSnapInfo* info = item.compiledPage->getSnapInfo(); + for (const PDFSnapInfo::SnapImage& snapImage : info->getSnapImages()) + { + ViewportSnapImage viewportSnapImage; + viewportSnapImage.image = snapImage.image; + viewportSnapImage.imagePath = snapImage.imagePath; + viewportSnapImage.pageIndex = item.pageIndex; + viewportSnapImage.viewportPath = item.pageToDeviceMatrix.map(snapImage.imagePath); + m_snapImages.emplace_back(qMove(viewportSnapImage)); + } + } +} + int PDFSnapper::getSnapPointTolerance() const { return m_snapPointTolerance; @@ -240,6 +286,16 @@ QPointF PDFSnapper::getSnappedPoint() const return m_mousePoint; } +const PDFSnapper::ViewportSnapImage* PDFSnapper::getSnappedImage() const +{ + if (m_snappedImage.has_value()) + { + return &*m_snappedImage; + } + + return nullptr; +} + void PDFSnapper::setReferencePoint(PDFInteger pageIndex, QPointF pagePoint) { m_currentPage = pageIndex; @@ -252,4 +308,15 @@ void PDFSnapper::clearReferencePoint() m_referencePoint = std::nullopt; } +void PDFSnapper::clear() +{ + clearReferencePoint(); + + m_snapPoints.clear(); + m_snapImages.clear(); + m_snappedPoint = std::nullopt; + m_snappedImage = std::nullopt; + m_mousePoint = QPointF(); +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfsnapper.h b/PdfForQtLib/sources/pdfsnapper.h index 91ba6eb..0782c3c 100644 --- a/PdfForQtLib/sources/pdfsnapper.h +++ b/PdfForQtLib/sources/pdfsnapper.h @@ -20,6 +20,9 @@ #include "pdfglobal.h" +#include +#include + #include #include @@ -63,6 +66,12 @@ public: QPointF point; }; + struct SnapImage + { + QPainterPath imagePath; + QImage image; + }; + /// Adds page media box. Media box must be in page coordinates. /// \param mediaBox Media box void addPageMediaBox(const QRectF& mediaBox); @@ -71,7 +80,8 @@ public: /// points are defined - four corners and center. /// \param points Four corner points in clockwise order, fifth point is center, /// all in page coordinates. - void addImage(const std::array& points); + /// \param image Image + void addImage(const std::array& points, const QImage& image); /// Adds line and line center points /// \param start Start point of line, in page coordinates @@ -84,9 +94,14 @@ public: /// Returns lines const std::vector& getLines() const { return m_snapLines; } + /// Returns snap images (together with painter path in page coordinates, + /// in which image is painted). + const std::vector& getSnapImages() const { return m_snapImages; } + private: std::vector m_snapPoints; std::vector m_snapLines; + std::vector m_snapImages; }; /// Snap engine, which handles snapping of points on the page. @@ -95,6 +110,18 @@ class PDFSnapper public: PDFSnapper(); + struct ViewportSnapPoint : public PDFSnapInfo::SnapPoint + { + QPointF viewportPoint; + PDFInteger pageIndex; + }; + + struct ViewportSnapImage : public PDFSnapInfo::SnapImage + { + PDFInteger pageIndex; + QPainterPath viewportPath; + }; + /// Sets snap point pixel size /// \param snapPointPixelSize Snap point diameter in pixels void setSnapPointPixelSize(int snapPointPixelSize) { m_snapPointPixelSize = snapPointPixelSize; } @@ -111,7 +138,7 @@ public: /// \param pageIndex Page index to be tested for allowing snapping bool isSnappingAllowed(PDFInteger pageIndex) const; - /// Updates snapped point using given information. If current page is set, it means, we are + /// Updates snapped point/image using given information. If current page is set, it means, we are /// using snapping info from current page, and if we are hovering at different page, /// then nothing happens. Otherwise, other page snap info is used to update snapped point. /// If mouse point distance from some snap point is lesser than tolerance, then new snap is set. @@ -126,6 +153,10 @@ public: /// \param snapshot Widget snapshot void buildSnapPoints(const PDFWidgetSnapshot& snapshot); + /// Builds snap images from the widget snapshot. + /// \param snapshot Widget snapshot + void buildSnapImages(const PDFWidgetSnapshot& snapshot); + /// Returns current snap point tolerance (while aiming with the mouse cursor, /// when mouse cursor is at most tolerance distance from some snap point, /// it is snapped. @@ -138,6 +169,10 @@ public: /// mouse cursor position is returned. QPointF getSnappedPoint() const; + /// Returns snapped image. If no image is present at mouse cursor, then + /// nullptr is returned. + const ViewportSnapImage* getSnappedImage() const; + /// Sets current page index and reference point /// \param pageIndex Page index /// \param pagePoint Page point @@ -146,14 +181,10 @@ public: /// Resets reference point (and current page) void clearReferencePoint(); + /// Clears all snapped data, including snap points, images and referenced data. + void clear(); + private: - - struct ViewportSnapPoint : public PDFSnapInfo::SnapPoint - { - QPointF viewportPoint; - PDFInteger pageIndex; - }; - struct SnappedPoint { PDFInteger pageIndex = -1; @@ -161,7 +192,9 @@ private: }; std::vector m_snapPoints; + std::vector m_snapImages; std::optional m_snappedPoint; + std::optional m_snappedImage; QPointF m_mousePoint; std::optional m_referencePoint; PDFInteger m_currentPage = -1; diff --git a/PdfForQtLib/sources/pdfwidgettool.cpp b/PdfForQtLib/sources/pdfwidgettool.cpp index 89b4196..57cfe6c 100644 --- a/PdfForQtLib/sources/pdfwidgettool.cpp +++ b/PdfForQtLib/sources/pdfwidgettool.cpp @@ -709,6 +709,7 @@ PDFToolManager::PDFToolManager(PDFDrawWidgetProxy* proxy, Actions actions, QObje m_predefinedTools[SelectTextTool] = new PDFSelectTextTool(proxy, actions.selectTextToolAction, actions.copyTextAction, actions.selectAllAction, actions.deselectAction, this); m_predefinedTools[MagnifierTool] = new PDFMagnifierTool(proxy, actions.magnifierAction, this); m_predefinedTools[ScreenshotTool] = new PDFScreenshotTool(proxy, actions.screenshotToolAction, this); + m_predefinedTools[ExtractImageTool] = new PDFExtractImageTool(proxy, actions.extractImageAction, this); for (PDFWidgetTool* tool : m_predefinedTools) { @@ -952,12 +953,12 @@ PDFPickTool::PDFPickTool(PDFDrawWidgetProxy* proxy, PDFPickTool::Mode mode, QObj m_mode(mode), m_pageIndex(-1) { - setCursor(Qt::BlankCursor); + setCursor((m_mode == Mode::Images) ? Qt::CrossCursor : Qt::BlankCursor); m_snapper.setSnapPointPixelSize(PDFWidgetUtils::scaleDPI_x(proxy->getWidget(), 10)); m_snapper.setSnapPointTolerance(m_snapper.getSnapPointPixelSize()); - connect(proxy, &PDFDrawWidgetProxy::drawSpaceChanged, this, &PDFPickTool::buildSnapPoints); - connect(proxy, &PDFDrawWidgetProxy::pageImageChanged, this, &PDFPickTool::buildSnapPoints); + connect(proxy, &PDFDrawWidgetProxy::drawSpaceChanged, this, &PDFPickTool::buildSnapData); + connect(proxy, &PDFDrawWidgetProxy::pageImageChanged, this, &PDFPickTool::buildSnapData); } void PDFPickTool::drawPage(QPainter* painter, @@ -988,26 +989,37 @@ void PDFPickTool::drawPage(QPainter* painter, painter->fillRect(selectionRectangle, selectionColor); } } + + if (m_mode == Mode::Images && m_snapper.getSnappedImage()) + { + const PDFSnapper::ViewportSnapImage* snappedImage = m_snapper.getSnappedImage(); + QColor selectionColor(Qt::blue); + selectionColor.setAlphaF(0.25); + painter->fillPath(snappedImage->viewportPath, selectionColor); + } } void PDFPickTool::drawPostRendering(QPainter* painter, QRect rect) const { - m_snapper.drawSnapPoints(painter); + if (m_mode != Mode::Images) + { + m_snapper.drawSnapPoints(painter); - QPoint snappedPoint = m_snapper.getSnappedPoint().toPoint(); - QPoint hleft = snappedPoint; - QPoint hright = snappedPoint; - QPoint vtop = snappedPoint; - QPoint vbottom = snappedPoint; + QPoint snappedPoint = m_snapper.getSnappedPoint().toPoint(); + QPoint hleft = snappedPoint; + QPoint hright = snappedPoint; + QPoint vtop = snappedPoint; + QPoint vbottom = snappedPoint; - hleft.setX(0); - hright.setX(rect.width()); - vtop.setY(0); - vbottom.setY(rect.height()); + hleft.setX(0); + hright.setX(rect.width()); + vtop.setY(0); + vbottom.setY(rect.height()); - painter->setPen(Qt::black); - painter->drawLine(hleft, hright); - painter->drawLine(vtop, vbottom); + painter->setPen(Qt::black); + painter->drawLine(hleft, hright); + painter->drawLine(vtop, vbottom); + } } void PDFPickTool::mousePressEvent(QWidget* widget, QMouseEvent* event) @@ -1017,41 +1029,52 @@ void PDFPickTool::mousePressEvent(QWidget* widget, QMouseEvent* event) if (event->button() == Qt::LeftButton) { - // Try to perform pick - QPointF pagePoint; - PDFInteger pageIndex = getProxy()->getPageUnderPoint(m_snapper.getSnappedPoint().toPoint(), &pagePoint); - if (pageIndex != -1 && // We have picked some point on page - (m_pageIndex == -1 || m_pageIndex == pageIndex)) // We are under current page + if (m_mode != Mode::Images) { - m_pageIndex = pageIndex; - m_pickedPoints.push_back(pagePoint); - m_snapper.setReferencePoint(pageIndex, pagePoint); - - // Emit signal about picked point - emit pointPicked(pageIndex, pagePoint); - - if (m_mode == Mode::Rectangles && m_pickedPoints.size() == 2) + // Try to perform pick point + QPointF pagePoint; + PDFInteger pageIndex = getProxy()->getPageUnderPoint(m_snapper.getSnappedPoint().toPoint(), &pagePoint); + if (pageIndex != -1 && // We have picked some point on page + (m_pageIndex == -1 || m_pageIndex == pageIndex)) // We are under current page { - QPointF first = m_pickedPoints.front(); - QPointF second = m_pickedPoints.back(); + m_pageIndex = pageIndex; + m_pickedPoints.push_back(pagePoint); + m_snapper.setReferencePoint(pageIndex, pagePoint); - const qreal xMin = qMin(first.x(), second.x()); - const qreal xMax = qMax(first.x(), second.x()); - const qreal yMin = qMin(first.y(), second.y()); - const qreal yMax = qMax(first.y(), second.y()); + // Emit signal about picked point + emit pointPicked(pageIndex, pagePoint); - QRectF pageRectangle(xMin, yMin, xMax - xMin, yMax - yMin); - emit rectanglePicked(pageIndex, pageRectangle); + if (m_mode == Mode::Rectangles && m_pickedPoints.size() == 2) + { + QPointF first = m_pickedPoints.front(); + QPointF second = m_pickedPoints.back(); - // We must reset tool, to pick next rectangle - resetTool(); + const qreal xMin = qMin(first.x(), second.x()); + const qreal xMax = qMax(first.x(), second.x()); + const qreal yMin = qMin(first.y(), second.y()); + const qreal yMax = qMax(first.y(), second.y()); + + QRectF pageRectangle(xMin, yMin, xMax - xMin, yMax - yMin); + emit rectanglePicked(pageIndex, pageRectangle); + + // We must reset tool, to pick next rectangle + resetTool(); + } + + buildSnapData(); + getProxy()->repaintNeeded(); + } + } + else + { + // Try to perform pick image + if (const PDFSnapper::ViewportSnapImage* snappedImage = m_snapper.getSnappedImage()) + { + emit imagePicked(snappedImage->image); } - - buildSnapPoints(); - getProxy()->repaintNeeded(); } } - else if (event->button() == Qt::RightButton) + else if (event->button() == Qt::RightButton && m_mode != Mode::Images) { // Reset tool to enable new picking (right button means reset the tool) resetTool(); @@ -1083,13 +1106,14 @@ void PDFPickTool::setActiveImpl(bool active) if (active) { - buildSnapPoints(); + buildSnapData(); } else { // Reset tool to reinitialize it for future use. If tool // is activated, then it should be in initial state. resetTool(); + m_snapper.clear(); } } @@ -1099,18 +1123,27 @@ void PDFPickTool::resetTool() m_pageIndex = -1; m_snapper.clearReferencePoint(); - buildSnapPoints(); + buildSnapData(); getProxy()->repaintNeeded(); } -void PDFPickTool::buildSnapPoints() +void PDFPickTool::buildSnapData() { if (!isActive()) { return; } - m_snapper.buildSnapPoints(getProxy()->getSnapshot()); + if (m_mode == Mode::Images) + { + // Snap images + m_snapper.buildSnapImages(getProxy()->getSnapshot()); + } + else + { + // Snap points + m_snapper.buildSnapPoints(getProxy()->getSnapshot()); + } } PDFScreenshotTool::PDFScreenshotTool(PDFDrawWidgetProxy* proxy, QAction* action, QObject* parent) : @@ -1143,4 +1176,33 @@ void PDFScreenshotTool::onRectanglePicked(PDFInteger pageIndex, QRectF pageRecta } } +PDFExtractImageTool::PDFExtractImageTool(PDFDrawWidgetProxy* proxy, QAction* action, QObject* parent) : + BaseClass(proxy, action, parent), + m_pickTool(nullptr) +{ + m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Images, this); + addTool(m_pickTool); + connect(m_pickTool, &PDFPickTool::imagePicked, this, &PDFExtractImageTool::onImagePicked); +} + +void PDFExtractImageTool::updateActions() +{ + // Jakub Melka: We do not call base class implementation here, because + // we must verify we have right to extract content (this tool extracts content) + + if (QAction* action = getAction()) + { + action->setChecked(isActive()); + action->setEnabled(getDocument() && getDocument()->getStorage().getSecurityHandler()->isAllowed(PDFSecurityHandler::Permission::CopyContent)); + } +} + +void PDFExtractImageTool::onImagePicked(const QImage& image) +{ + if (!image.isNull()) + { + QApplication::clipboard()->setImage(image, QClipboard::Clipboard); + } +} + } // namespace pdf diff --git a/PdfForQtLib/sources/pdfwidgettool.h b/PdfForQtLib/sources/pdfwidgettool.h index 52cbd2a..59a7dc5 100644 --- a/PdfForQtLib/sources/pdfwidgettool.h +++ b/PdfForQtLib/sources/pdfwidgettool.h @@ -308,13 +308,14 @@ public: signals: void pointPicked(PDFInteger pageIndex, QPointF pagePoint); void rectanglePicked(PDFInteger pageIndex, QRectF pageRectangle); + void imagePicked(const QImage& image); protected: virtual void setActiveImpl(bool active) override; private: void resetTool(); - void buildSnapPoints(); + void buildSnapData(); Mode m_mode; PDFSnapper m_snapper; @@ -345,6 +346,31 @@ private: PDFPickTool* m_pickTool; }; +/// Tool that extracts image from page and copies it to the clipboard, +/// using image original size (not zoomed size from widget area) +class PDFFORQTLIBSHARED_EXPORT PDFExtractImageTool : public PDFWidgetTool +{ + Q_OBJECT + +private: + using BaseClass = PDFWidgetTool; + +public: + /// Constructs new extract image tool + /// \param proxy Draw widget proxy + /// \param action Tool activation action + /// \param parent Parent object + explicit PDFExtractImageTool(PDFDrawWidgetProxy* proxy, QAction* action, QObject* parent); + +protected: + virtual void updateActions() override; + +private: + void onImagePicked(const QImage& image); + + PDFPickTool* m_pickTool; +}; + /// Manager used for managing tools, their activity, availability /// and other settings. It also defines a predefined set of tools, /// available for various purposes (text searching, magnifier tool etc.) @@ -366,6 +392,7 @@ public: QAction* copyTextAction = nullptr; QAction* magnifierAction = nullptr; QAction* screenshotToolAction = nullptr; + QAction* extractImageAction = nullptr; }; /// Construct new text search tool @@ -385,6 +412,7 @@ public: SelectTextTool, MagnifierTool, ScreenshotTool, + ExtractImageTool, ToolEnd }; diff --git a/PdfForQtViewer/pdfforqtviewer.qrc b/PdfForQtViewer/pdfforqtviewer.qrc index 809024c..9fd3a6f 100644 --- a/PdfForQtViewer/pdfforqtviewer.qrc +++ b/PdfForQtViewer/pdfforqtviewer.qrc @@ -42,5 +42,6 @@ resources/stop.svg resources/magnifier.svg resources/screenshot-tool.svg + resources/extract-image.svg diff --git a/PdfForQtViewer/pdfviewermainwindow.cpp b/PdfForQtViewer/pdfviewermainwindow.cpp index 0eb0cd6..ab4363e 100644 --- a/PdfForQtViewer/pdfviewermainwindow.cpp +++ b/PdfForQtViewer/pdfviewermainwindow.cpp @@ -191,6 +191,7 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) : ui->mainToolBar->addAction(ui->actionSelectText); ui->mainToolBar->addAction(ui->actionMagnifier); ui->mainToolBar->addAction(ui->actionScreenshot); + ui->mainToolBar->addAction(ui->actionExtractImage); connect(ui->actionZoom_In, &QAction::triggered, this, [this] { m_pdfWidget->getDrawWidgetProxy()->performOperation(pdf::PDFDrawWidgetProxy::ZoomIn); }); connect(ui->actionZoom_Out, &QAction::triggered, this, [this] { m_pdfWidget->getDrawWidgetProxy()->performOperation(pdf::PDFDrawWidgetProxy::ZoomOut); }); @@ -255,6 +256,7 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) : actions.copyTextAction = ui->actionCopyText; actions.magnifierAction = ui->actionMagnifier; actions.screenshotToolAction = ui->actionScreenshot; + actions.extractImageAction = ui->actionExtractImage; m_toolManager = new pdf::PDFToolManager(m_pdfWidget->getDrawWidgetProxy(), actions, this, this); m_pdfWidget->setToolManager(m_toolManager); updateMagnifierToolSettings(); diff --git a/PdfForQtViewer/pdfviewermainwindow.ui b/PdfForQtViewer/pdfviewermainwindow.ui index 93fb3b2..991c2e8 100644 --- a/PdfForQtViewer/pdfviewermainwindow.ui +++ b/PdfForQtViewer/pdfviewermainwindow.ui @@ -88,6 +88,7 @@ + @@ -486,6 +487,18 @@ Screenshot + + + true + + + + :/resources/extract-image.svg:/resources/extract-image.svg + + + Extract Image + + diff --git a/PdfForQtViewer/resources/extract-image.svg b/PdfForQtViewer/resources/extract-image.svg new file mode 100644 index 0000000..ed0f9cd --- /dev/null +++ b/PdfForQtViewer/resources/extract-image.svg @@ -0,0 +1,321 @@ + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Jakub Melka + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +