File indexing completed on 2024-04-28 04:32:21
0001 /* 0002 * SPDX-FileCopyrightText: 2008 Kare Sars <kare dot sars at iki dot fi> 0003 * SPDX-FileCopyrightText: 2014 Gregor Mitsch : port to KDE5 frameworks 0004 * 0005 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0006 */ 0007 0008 #include "ksaneviewer.h" 0009 0010 #include "selectionitem.h" 0011 #include "hiderectitem.h" 0012 0013 #include <QGraphicsPixmapItem> 0014 #include <QGraphicsScene> 0015 #include <QGraphicsRectItem> 0016 #include <QScrollBar> 0017 #include <QAction> 0018 #include <QList> 0019 #include <QVector> 0020 #include <QIcon> 0021 0022 #include <KLocalizedString> 0023 0024 #include <math.h> 0025 0026 namespace KSaneIface 0027 { 0028 0029 struct KSaneViewer::Private { 0030 QGraphicsScene *scene; 0031 SelectionItem *selection; 0032 QImage *img; 0033 0034 QList<SelectionItem *> selectionList; 0035 SelectionItem::Intersects change; 0036 0037 QPointF lastSPoint; 0038 int m_left_last_x; 0039 int m_left_last_y; 0040 0041 QAction *zoomInAction; 0042 QAction *zoomOutAction; 0043 QAction *zoomSelAction; 0044 QAction *zoom2FitAction; 0045 QAction *clrSelAction; 0046 0047 HideRectItem *hideLeft; 0048 HideRectItem *hideRight; 0049 HideRectItem *hideTop; 0050 HideRectItem *hideBottom; 0051 HideRectItem *hideArea; 0052 0053 bool multiSelectionEnabled = true; 0054 0055 int wheelDelta = 0; 0056 0057 int currentImageWidth; 0058 int currentImageHeight; 0059 0060 QGraphicsPolygonItem * border; 0061 }; 0062 0063 KSaneViewer::KSaneViewer(QImage *img, QWidget *parent) : QGraphicsView(parent), d(new Private) 0064 { 0065 d->img = img; 0066 0067 setMouseTracking(true); 0068 0069 // Init the scene 0070 d->scene = new QGraphicsScene(this); 0071 const auto dpr = img->devicePixelRatio(); 0072 0073 d->currentImageWidth = img->width(); 0074 d->currentImageHeight = img->height(); 0075 0076 d->scene->setSceneRect(0, 0, d->currentImageWidth / dpr, d->currentImageHeight / dpr); 0077 setScene(d->scene); 0078 0079 d->selection = new SelectionItem(QRectF()); 0080 d->selection->setZValue(10); 0081 d->selection->setSaved(false); 0082 d->selection->setMaxRight(d->currentImageWidth); 0083 d->selection->setMaxBottom(d->currentImageHeight); 0084 d->selection->setRect(d->scene->sceneRect()); 0085 d->selection->setVisible(false); 0086 0087 d->hideTop = new HideRectItem; 0088 d->hideBottom = new HideRectItem; 0089 d->hideRight = new HideRectItem; 0090 d->hideLeft = new HideRectItem; 0091 d->hideArea = new HideRectItem; 0092 d->hideArea->setOpacity(0.6); 0093 0094 d->scene->addItem(d->selection); 0095 d->scene->addItem(d->hideLeft); 0096 d->scene->addItem(d->hideRight); 0097 d->scene->addItem(d->hideTop); 0098 d->scene->addItem(d->hideBottom); 0099 d->scene->addItem(d->hideArea); 0100 0101 QPolygonF polygon(QRectF(QPointF(0,0),QSizeF(d->currentImageWidth, d->currentImageHeight))); 0102 QPen pen(Qt::gray, 0, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); 0103 d->border = d->scene->addPolygon(polygon, pen); 0104 0105 d->change = SelectionItem::None; 0106 d->selectionList.clear(); 0107 0108 // create context menu 0109 d->zoomInAction = new QAction(QIcon::fromTheme(QLatin1String("zoom-in")), i18n("Zoom In"), this); 0110 connect(d->zoomInAction, &QAction::triggered, this, &KSaneViewer::zoomIn); 0111 0112 d->zoomOutAction = new QAction(QIcon::fromTheme(QLatin1String("zoom-out")), i18n("Zoom Out"), this); 0113 connect(d->zoomOutAction, &QAction::triggered, this, &KSaneViewer::zoomOut); 0114 0115 d->zoomSelAction = new QAction(QIcon::fromTheme(QLatin1String("zoom-fit-best")), i18n("Zoom to Selection"), this); 0116 connect(d->zoomSelAction, &QAction::triggered, this, &KSaneViewer::zoomSel); 0117 0118 d->zoom2FitAction = new QAction(QIcon::fromTheme(QLatin1String("document-preview")), i18n("Zoom to Fit"), this); 0119 connect(d->zoom2FitAction, &QAction::triggered, this, &KSaneViewer::zoom2Fit); 0120 0121 d->clrSelAction = new QAction(QIcon::fromTheme(QLatin1String("edit-clear")), i18n("Clear Selections"), this); 0122 connect(d->clrSelAction, &QAction::triggered, this, &KSaneViewer::clearSelections); 0123 0124 addAction(d->zoomInAction); 0125 addAction(d->zoomOutAction); 0126 addAction(d->zoomSelAction); 0127 addAction(d->zoom2FitAction); 0128 addAction(d->clrSelAction); 0129 setContextMenuPolicy(Qt::ActionsContextMenu); 0130 0131 setFrameShape(QFrame::NoFrame); 0132 } 0133 0134 // ------------------------------------------------------------------------ 0135 void KSaneViewer::drawBackground(QPainter *painter, const QRectF &rect) 0136 { 0137 painter->fillRect(rect, QWidget::palette().color(QPalette::Window)); 0138 QRectF r = rect & sceneRect(); 0139 const qreal dpr = d->img->devicePixelRatio(); 0140 QRectF srcRect = QRectF(r.topLeft() * dpr, r.size() * dpr); 0141 painter->drawImage(r, *d->img, srcRect); 0142 d->border->setPolygon(QPolygonF(QRectF(QPointF(0,0),QSizeF(d->currentImageWidth, d->currentImageHeight)))); 0143 } 0144 0145 // ------------------------------------------------------------------------ 0146 KSaneViewer::~KSaneViewer() 0147 { 0148 // first remove any old saved selections 0149 clearSavedSelections(); 0150 0151 delete d; 0152 } 0153 0154 // ------------------------------------------------------------------------ 0155 int KSaneViewer::currentImageHeight() const 0156 { 0157 return d->currentImageHeight; 0158 } 0159 0160 // ------------------------------------------------------------------------ 0161 int KSaneViewer::currentImageWidth() const 0162 { 0163 return d->currentImageWidth; 0164 } 0165 0166 // ------------------------------------------------------------------------ 0167 void KSaneViewer::setQImage(QImage *img) 0168 { 0169 if (img == nullptr) { 0170 return; 0171 } 0172 0173 // remove selections 0174 clearSelections(); 0175 0176 // clear zoom 0177 resetTransform(); 0178 0179 const auto dpr = img->devicePixelRatio(); 0180 0181 d->currentImageWidth = img->width(); 0182 d->currentImageHeight = img->height(); 0183 0184 d->scene->setSceneRect(0, 0, d->currentImageWidth / dpr, d->currentImageHeight / dpr); 0185 d->selection->setMaxRight(d->currentImageWidth); 0186 d->selection->setMaxBottom(d->currentImageHeight); 0187 0188 d->selection->setDevicePixelRatio(dpr); 0189 d->hideTop->setDevicePixelRatio(dpr); 0190 d->hideBottom->setDevicePixelRatio(dpr); 0191 d->hideRight->setDevicePixelRatio(dpr); 0192 d->hideLeft->setDevicePixelRatio(dpr); 0193 d->hideArea->setDevicePixelRatio(dpr); 0194 0195 d->img = img; 0196 } 0197 0198 // ------------------------------------------------------------------------ 0199 void KSaneViewer::updateImage() 0200 { 0201 setCacheMode(QGraphicsView::CacheNone); 0202 repaint(); 0203 setCacheMode(QGraphicsView::CacheBackground); 0204 } 0205 0206 // ------------------------------------------------------------------------ 0207 void KSaneViewer::zoomIn() 0208 { 0209 scale(1.3, 1.3); 0210 d->selection->saveZoom(transform().m11()); 0211 for (int i = 0; i < d->selectionList.size(); ++i) { 0212 d->selectionList[i]->saveZoom(transform().m11()); 0213 } 0214 } 0215 0216 // ------------------------------------------------------------------------ 0217 void KSaneViewer::zoomOut() 0218 { 0219 scale(1.0 / 1.3, 1.0 / 1.3); 0220 d->selection->saveZoom(transform().m11()); 0221 for (int i = 0; i < d->selectionList.size(); ++i) { 0222 d->selectionList[i]->saveZoom(transform().m11()); 0223 } 0224 } 0225 0226 // ------------------------------------------------------------------------ 0227 void KSaneViewer::zoomSel() 0228 { 0229 if (d->selection->isVisible()) { 0230 fitInView(d->selection->boundingRect() , Qt::KeepAspectRatio); 0231 d->selection->saveZoom(transform().m11()); 0232 for (int i = 0; i < d->selectionList.size(); ++i) { 0233 d->selectionList[i]->saveZoom(transform().m11()); 0234 } 0235 } else { 0236 zoom2Fit(); 0237 } 0238 } 0239 0240 // ------------------------------------------------------------------------ 0241 void KSaneViewer::zoom2Fit() 0242 { 0243 fitInView(d->img->rect(), Qt::KeepAspectRatio); 0244 d->selection->saveZoom(transform().m11()); 0245 for (int i = 0; i < d->selectionList.size(); ++i) { 0246 d->selectionList[i]->saveZoom(transform().m11()); 0247 } 0248 } 0249 0250 // ------------------------------------------------------------------------ 0251 void KSaneViewer::setTLX(float ratio) 0252 { 0253 if (!d->selection->isVisible()) { 0254 return; // only correct the selection if it is visible 0255 } 0256 QRectF rect = d->selection->rect(); 0257 rect.setLeft(ratio * d->img->width()); 0258 d->selection->setRect(rect); 0259 updateSelVisibility(); 0260 } 0261 0262 // ------------------------------------------------------------------------ 0263 void KSaneViewer::setTLY(float ratio) 0264 { 0265 if (!d->selection->isVisible()) { 0266 return; // only correct the selection if it is visible 0267 } 0268 QRectF rect = d->selection->rect(); 0269 rect.setTop(ratio * d->img->height()); 0270 d->selection->setRect(rect); 0271 updateSelVisibility(); 0272 } 0273 0274 // ------------------------------------------------------------------------ 0275 void KSaneViewer::setBRX(float ratio) 0276 { 0277 if (!d->selection->isVisible()) { 0278 return; // only correct the selection if it is visible 0279 } 0280 QRectF rect = d->selection->rect(); 0281 rect.setRight(ratio * d->img->width()); 0282 d->selection->setRect(rect); 0283 updateSelVisibility(); 0284 } 0285 0286 // ------------------------------------------------------------------------ 0287 void KSaneViewer::setBRY(float ratio) 0288 { 0289 if (!d->selection->isVisible()) { 0290 return; // only correct the selection if it is visible 0291 } 0292 QRectF rect = d->selection->rect(); 0293 rect.setBottom(ratio * d->img->height()); 0294 d->selection->setRect(rect); 0295 updateSelVisibility(); 0296 } 0297 0298 // ------------------------------------------------------------------------ 0299 void KSaneViewer::setSelection(float tl_x, float tl_y, float br_x, float br_y) 0300 { 0301 QRectF rect; 0302 rect.setCoords(tl_x * d->img->width(), 0303 tl_y * d->img->height(), 0304 br_x * d->img->width(), 0305 br_y * d->img->height()); 0306 0307 d->selection->setRect(rect); 0308 updateSelVisibility(); 0309 } 0310 0311 // ------------------------------------------------------------------------ 0312 void KSaneViewer::setHighlightArea(float tl_x, float tl_y, float br_x, float br_y) 0313 { 0314 QRectF rect; 0315 0316 // Left reason for rect: setCoords(x1,y1,x2,y2) != setRect(x1,x2, width, height) 0317 rect.setCoords(0, 0, tl_x * d->img->width(), d->img->height()); 0318 d->hideLeft->setRect(rect); 0319 0320 // Right 0321 rect.setCoords(br_x * d->img->width(), 0322 0, 0323 d->img->width(), 0324 d->img->height()); 0325 d->hideRight->setRect(rect); 0326 0327 // Top 0328 rect.setCoords(tl_x * d->img->width(), 0329 0, 0330 br_x * d->img->width(), 0331 tl_y * d->img->height()); 0332 d->hideTop->setRect(rect); 0333 0334 // Bottom 0335 rect.setCoords(tl_x * d->img->width(), 0336 br_y * d->img->height(), 0337 br_x * d->img->width(), 0338 d->img->height()); 0339 d->hideBottom->setRect(rect); 0340 0341 // hide area 0342 rect.setCoords(tl_x * d->img->width(), tl_y * d->img->height(), 0343 br_x * d->img->width(), br_y * d->img->height()); 0344 0345 d->hideArea->setRect(rect); 0346 0347 d->hideLeft->show(); 0348 d->hideRight->show(); 0349 d->hideTop->show(); 0350 d->hideBottom->show(); 0351 // the hide area is hidden until setHighlightShown is called. 0352 d->hideArea->hide(); 0353 } 0354 0355 // ------------------------------------------------------------------------ 0356 void KSaneViewer::setHighlightShown(int percentage, QColor hideColor) 0357 { 0358 if (percentage >= 100) { 0359 d->hideArea->hide(); 0360 return; 0361 } 0362 0363 d->hideArea->setBrush(hideColor); 0364 0365 qreal diff = d->hideBottom->rect().top() - d->hideTop->rect().bottom(); 0366 diff -= (diff * percentage) / 100; 0367 0368 QRectF rect = d->hideArea->rect(); 0369 rect.setTop(d->hideBottom->rect().top() - diff); 0370 0371 d->hideArea->setRect(rect); 0372 0373 d->hideArea->show(); 0374 } 0375 0376 // ------------------------------------------------------------------------ 0377 void KSaneViewer::updateHighlight() 0378 { 0379 if (d->selection->isVisible()) { 0380 QRectF rect; 0381 // Left 0382 rect.setCoords(0, 0, d->selection->rect().left(), d->img->height()); 0383 d->hideLeft->setRect(rect); 0384 0385 // Right 0386 rect.setCoords(d->selection->rect().right(), 0387 0, 0388 d->img->width(), 0389 d->img->height()); 0390 d->hideRight->setRect(rect); 0391 0392 // Top 0393 rect.setCoords(d->selection->rect().left(), 0394 0, 0395 d->selection->rect().right(), 0396 d->selection->rect().top()); 0397 d->hideTop->setRect(rect); 0398 0399 // Bottom 0400 rect.setCoords(d->selection->rect().left(), 0401 d->selection->rect().bottom(), 0402 d->selection->rect().right(), 0403 d->img->height()); 0404 d->hideBottom->setRect(rect); 0405 0406 d->hideLeft->show(); 0407 d->hideRight->show(); 0408 d->hideTop->show(); 0409 d->hideBottom->show(); 0410 d->hideArea->hide(); 0411 } else { 0412 d->hideLeft->hide(); 0413 d->hideRight->hide(); 0414 d->hideTop->hide(); 0415 d->hideBottom->hide(); 0416 d->hideArea->hide(); 0417 } 0418 } 0419 0420 // ------------------------------------------------------------------------ 0421 void KSaneViewer::clearHighlight() 0422 { 0423 d->hideLeft->hide(); 0424 d->hideRight->hide(); 0425 d->hideTop->hide(); 0426 d->hideBottom->hide(); 0427 d->hideArea->hide(); 0428 } 0429 0430 // ------------------------------------------------------------------------ 0431 void KSaneViewer::updateSelVisibility() 0432 { 0433 if ((d->selection->rect().width() > 0.001) && 0434 (d->selection->rect().height() > 0.001) && 0435 ((d->img->width() - d->selection->rect().width() > 0.1) || 0436 (d->img->height() - d->selection->rect().height() > 0.1))) { 0437 d->selection->setVisible(true); 0438 } else { 0439 d->selection->setVisible(false); 0440 } 0441 updateHighlight(); 0442 } 0443 0444 // ---- Return the saved selection list size + 1 if the selection is visible - 0445 int KSaneViewer::selListSize() 0446 { 0447 if (d->selection->isVisible()) { 0448 return (d->selectionList.size() + 1); 0449 } else { 0450 return d->selectionList.size(); 0451 } 0452 } 0453 0454 // ---- First return the "saved" selection sthen the active selection ----------- 0455 bool KSaneViewer::selectionAt(int index, float &tl_x, float &tl_y, float &br_x, float &br_y) 0456 { 0457 if ((index < 0) || (index > d->selectionList.size())) { 0458 activeSelection(tl_x, tl_y, br_x, br_y); 0459 return false; 0460 } 0461 if (index == d->selectionList.size()) { 0462 return activeSelection(tl_x, tl_y, br_x, br_y); 0463 } 0464 0465 tl_x = d->selectionList[index]->rect().left() / d->img->width(); 0466 tl_y = d->selectionList[index]->rect().top() / d->img->height(); 0467 br_x = d->selectionList[index]->rect().right() / d->img->width(); 0468 br_y = d->selectionList[index]->rect().bottom() / d->img->height(); 0469 return true; 0470 } 0471 0472 // ------------------------------------------------------------------------ 0473 bool KSaneViewer::activeSelection(float &tl_x, float &tl_y, float &br_x, float &br_y) 0474 { 0475 if (!d->selection->isVisible()) { 0476 tl_x = 0.0; 0477 tl_y = 0.0; 0478 br_x = 1.0; 0479 br_y = 1.0; 0480 return true; 0481 } 0482 0483 tl_x = d->selection->rect().left() / d->img->width(); 0484 tl_y = d->selection->rect().top() / d->img->height(); 0485 br_x = d->selection->rect().right() / d->img->width(); 0486 br_y = d->selection->rect().bottom() / d->img->height(); 0487 0488 if ((tl_x == br_x) || (tl_y == br_y)) { 0489 tl_x = 0.0; 0490 tl_y = 0.0; 0491 br_x = 1.0; 0492 br_y = 1.0; 0493 return false; // just precaution 0494 } 0495 return true; 0496 } 0497 0498 // ------------------------------------------------------------------------ 0499 void KSaneViewer::clearActiveSelection() 0500 { 0501 d->selection->setRect(QRectF(0, 0, 0, 0)); 0502 d->selection->intersects(QPointF(100, 100)); // don't show the add sign 0503 d->selection->setVisible(false); 0504 } 0505 0506 // ------------------------------------------------------------------------ 0507 void KSaneViewer::clearSavedSelections() 0508 { 0509 // first remove any old saved selections 0510 SelectionItem *tmp; 0511 while (!d->selectionList.isEmpty()) { 0512 tmp = d->selectionList.takeFirst(); 0513 d->scene->removeItem(tmp); 0514 delete tmp; 0515 } 0516 } 0517 0518 // ------------------------------------------------------------------------ 0519 void KSaneViewer::clearSelections() 0520 { 0521 clearActiveSelection(); 0522 clearSavedSelections(); 0523 updateSelVisibility(); 0524 } 0525 0526 // ------------------------------------------------------------------------ 0527 void KSaneViewer::setMultiselectionEnabled(bool enabled) 0528 { 0529 d->multiSelectionEnabled = enabled; 0530 clearSelections(); 0531 d->selection->setAddButtonEnabled(enabled); 0532 } 0533 0534 0535 // ------------------------------------------------------------------------ 0536 void KSaneViewer::wheelEvent(QWheelEvent *e) 0537 { 0538 if (e->modifiers() == Qt::ControlModifier) { 0539 d->wheelDelta += e->angleDelta().y(); 0540 0541 while (d->wheelDelta >= QWheelEvent::DefaultDeltasPerStep) { 0542 zoomIn(); 0543 d->wheelDelta -= QWheelEvent::DefaultDeltasPerStep; 0544 } 0545 0546 while (d->wheelDelta <= -QWheelEvent::DefaultDeltasPerStep) { 0547 zoomOut(); 0548 d->wheelDelta += QWheelEvent::DefaultDeltasPerStep; 0549 } 0550 } else { 0551 QGraphicsView::wheelEvent(e); 0552 } 0553 } 0554 0555 // ------------------------------------------------------------------------ 0556 void KSaneViewer::mousePressEvent(QMouseEvent *e) 0557 { 0558 if (e->button() == Qt::LeftButton) { 0559 d->m_left_last_x = e->x(); 0560 d->m_left_last_y = e->y(); 0561 QPointF scenePoint = scenePos(e) * d->selection->devicePixelRatio(); 0562 d->lastSPoint = scenePoint; 0563 if (e->modifiers() != Qt::ControlModifier) { 0564 if (!d->selection->isVisible()) { 0565 d->selection->setVisible(true); 0566 d->selection->setRect(QRectF(scenePoint, QSizeF(0, 0))); 0567 d->selection->intersects(scenePoint); // just to disable add/remove 0568 d->change = SelectionItem::BottomRight; 0569 } else if (d->selection->intersects(scenePoint) == SelectionItem::None) { 0570 d->selection->setRect(QRectF(scenePoint, QSizeF(0, 0))); 0571 d->change = SelectionItem::BottomRight; 0572 } 0573 updateHighlight(); 0574 } 0575 } 0576 QGraphicsView::mousePressEvent(e); 0577 } 0578 0579 // ------------------------------------------------------------------------ 0580 void KSaneViewer::mouseReleaseEvent(QMouseEvent *e) 0581 { 0582 bool removed = false; 0583 if (e->button() == Qt::LeftButton) { 0584 if ((d->selection->rect().width() < 0.001) || 0585 (d->selection->rect().height() < 0.001)) { 0586 Q_EMIT newSelection(0.0, 0.0, 1.0, 1.0); 0587 clearActiveSelection(); 0588 } 0589 0590 QPointF scenePoint = scenePos(e) * d->selection->devicePixelRatio(); 0591 for (int i = 0; i < d->selectionList.size(); i++) { 0592 if (d->selectionList[i]->intersects(scenePoint) == SelectionItem::AddRemove) { 0593 d->scene->removeItem(d->selectionList[i]); 0594 SelectionItem *tmp = d->selectionList[i]; 0595 d->selectionList.removeAt(i); 0596 d->selection->setVisible(true); 0597 d->selection->setRect(tmp->rect()); 0598 d->selection->intersects(scenePoint); // just to enable add/remove 0599 delete tmp; 0600 removed = true; 0601 break; 0602 } 0603 } 0604 if (!removed && (d->selection->intersects(scenePoint) == SelectionItem::AddRemove)) { 0605 // add the current selection 0606 SelectionItem *tmp = new SelectionItem(d->selection->rect()); 0607 tmp->setDevicePixelRatio(d->img->devicePixelRatio()); 0608 d->selectionList.push_back(tmp); 0609 d->selectionList.back()->setSaved(true); 0610 d->selectionList.back()->saveZoom(transform().m11()); 0611 d->scene->addItem(d->selectionList.back()); 0612 d->selectionList.back()->setZValue(9); 0613 d->selectionList.back()->intersects(scenePoint); 0614 0615 // clear the old one 0616 Q_EMIT newSelection(0.0, 0.0, 1.0, 1.0); 0617 clearActiveSelection(); 0618 } 0619 } 0620 0621 if ((e->modifiers() != Qt::ControlModifier) && 0622 (d->selection->isVisible()) && 0623 (d->img->width() > 0.001) && 0624 (d->img->height() > 0.001)) { 0625 float tlx = d->selection->rect().left() / d->img->width(); 0626 float tly = d->selection->rect().top() / d->img->height(); 0627 float brx = d->selection->rect().right() / d->img->width(); 0628 float bry = d->selection->rect().bottom() / d->img->height(); 0629 0630 Q_EMIT newSelection(tlx, tly, brx, bry); 0631 } 0632 updateHighlight(); 0633 QGraphicsView::mouseReleaseEvent(e); 0634 } 0635 0636 // ------------------------------------------------------------------------ 0637 void KSaneViewer::mouseMoveEvent(QMouseEvent *e) 0638 { 0639 QPointF scenePoint = scenePos(e) * d->selection->devicePixelRatio(); 0640 0641 if (e->buttons()&Qt::LeftButton) { 0642 if (e->modifiers() == Qt::ControlModifier) { 0643 int dx = e->x() - d->m_left_last_x; 0644 int dy = e->y() - d->m_left_last_y; 0645 verticalScrollBar()->setValue(verticalScrollBar()->value() - dy); 0646 horizontalScrollBar()->setValue(horizontalScrollBar()->value() - dx); 0647 d->m_left_last_x = e->x(); 0648 d->m_left_last_y = e->y(); 0649 } else { 0650 ensureVisible(QRectF(scenePoint, QSizeF(0, 0)), 1, 1); 0651 QRectF rect = d->selection->rect(); 0652 switch (d->change) { 0653 case SelectionItem::None: 0654 // should not be here :) 0655 break; 0656 case SelectionItem::Top: 0657 if (scenePoint.y() < rect.bottom()) { 0658 rect.setTop(scenePoint.y()); 0659 } else { 0660 d->change = SelectionItem::Bottom; 0661 rect.setBottom(scenePoint.y()); 0662 } 0663 break; 0664 case SelectionItem::TopRight: 0665 if (scenePoint.x() > rect.left()) { 0666 rect.setRight(scenePoint.x()); 0667 } else { 0668 rect.setLeft(scenePoint.x()); 0669 d->change = SelectionItem::TopLeft; 0670 } 0671 if (scenePoint.y() < rect.bottom()) { 0672 rect.setTop(scenePoint.y()); 0673 } else { 0674 rect.setBottom(scenePoint.y()); 0675 d->change = SelectionItem::BottomLeft; 0676 } // FIXME arrow 0677 break; 0678 case SelectionItem::Right: 0679 if (scenePoint.x() > rect.left()) { 0680 rect.setRight(scenePoint.x()); 0681 } else { 0682 rect.setLeft(scenePoint.x()); 0683 d->change = SelectionItem::Left; 0684 } 0685 break; 0686 case SelectionItem::BottomRight: 0687 if (scenePoint.x() > rect.left()) { 0688 rect.setRight(scenePoint.x()); 0689 } else { 0690 rect.setLeft(scenePoint.x()); 0691 d->change = SelectionItem::BottomLeft; 0692 } 0693 if (scenePoint.y() > rect.top()) { 0694 rect.setBottom(scenePoint.y()); 0695 } else { 0696 rect.setTop(scenePoint.y()); 0697 d->change = SelectionItem::TopRight; 0698 } // FIXME arrow 0699 break; 0700 case SelectionItem::Bottom: 0701 if (scenePoint.y() > rect.top()) { 0702 rect.setBottom(scenePoint.y()); 0703 } else { 0704 d->change = SelectionItem::Top; 0705 rect.setTop(scenePoint.y()); 0706 } 0707 break; 0708 case SelectionItem::BottomLeft: 0709 if (scenePoint.x() < rect.right()) { 0710 rect.setLeft(scenePoint.x()); 0711 } else { 0712 rect.setRight(scenePoint.x()); 0713 d->change = SelectionItem::BottomRight; 0714 } 0715 if (scenePoint.y() > rect.top()) { 0716 rect.setBottom(scenePoint.y()); 0717 } else { 0718 rect.setTop(scenePoint.y()); 0719 d->change = SelectionItem::TopLeft; 0720 } // FIXME arrow 0721 break; 0722 case SelectionItem::Left: 0723 if (scenePoint.x() < rect.right()) { 0724 rect.setLeft(scenePoint.x()); 0725 } else { 0726 rect.setRight(scenePoint.x()); 0727 d->change = SelectionItem::Right; 0728 } 0729 break; 0730 case SelectionItem::TopLeft: 0731 if (scenePoint.x() < rect.right()) { 0732 rect.setLeft(scenePoint.x()); 0733 } else { 0734 rect.setRight(scenePoint.x()); 0735 d->change = SelectionItem::TopRight; 0736 } 0737 if (scenePoint.y() < rect.bottom()) { 0738 rect.setTop(scenePoint.y()); 0739 } else { 0740 rect.setBottom(scenePoint.y()); 0741 d->change = SelectionItem::BottomLeft; 0742 }// FIXME arrow 0743 break; 0744 case SelectionItem::Move: 0745 rect.translate(d->selection->fixTranslation(scenePoint - d->lastSPoint)); 0746 break; 0747 case SelectionItem::AddRemove: 0748 // do nothing 0749 break; 0750 } 0751 d->selection->setRect(rect); 0752 } 0753 } else if (d->selection->isVisible()) { 0754 d->change = d->selection->intersects(scenePoint); 0755 0756 switch (d->change) { 0757 case SelectionItem::None: 0758 viewport()->setCursor(Qt::CrossCursor); 0759 break; 0760 case SelectionItem::Top: 0761 viewport()->setCursor(Qt::SizeVerCursor); 0762 break; 0763 case SelectionItem::TopRight: 0764 viewport()->setCursor(Qt::SizeBDiagCursor); 0765 break; 0766 case SelectionItem::Right: 0767 viewport()->setCursor(Qt::SizeHorCursor); 0768 break; 0769 case SelectionItem::BottomRight: 0770 viewport()->setCursor(Qt::SizeFDiagCursor); 0771 break; 0772 case SelectionItem::Bottom: 0773 viewport()->setCursor(Qt::SizeVerCursor); 0774 break; 0775 case SelectionItem::BottomLeft: 0776 viewport()->setCursor(Qt::SizeBDiagCursor); 0777 break; 0778 case SelectionItem::Left: 0779 viewport()->setCursor(Qt::SizeHorCursor); 0780 break; 0781 case SelectionItem::TopLeft: 0782 viewport()->setCursor(Qt::SizeFDiagCursor); 0783 break; 0784 case SelectionItem::Move: 0785 viewport()->setCursor(Qt::SizeAllCursor); 0786 break; 0787 case SelectionItem::AddRemove: 0788 viewport()->setCursor(Qt::ArrowCursor); 0789 break; 0790 } 0791 } else { 0792 viewport()->setCursor(Qt::CrossCursor); 0793 } 0794 0795 // now check the selection list 0796 for (int i = 0; i < d->selectionList.size(); i++) { 0797 if (d->selectionList[i]->intersects(scenePoint) == SelectionItem::AddRemove) { 0798 viewport()->setCursor(Qt::ArrowCursor); 0799 } 0800 } 0801 0802 d->lastSPoint = scenePoint; 0803 updateHighlight(); 0804 QGraphicsView::mouseMoveEvent(e); 0805 } 0806 0807 // The change trigger before adding to the sum 0808 static const int DIFF_TRIGGER = 8; 0809 0810 // The selection start/stop level trigger 0811 static const int SUM_TRIGGER = 4; 0812 0813 // The selection start/stop level trigger for the floating average 0814 static const int AVERAGE_TRIGGER = 7; 0815 0816 // The selection start/stop margin 0817 static const int SEL_MARGIN = 3; 0818 0819 // Maximum number of allowed selections (this could be a settable variable) 0820 static const int MAX_NUM_SELECTIONS = 8; 0821 0822 // floating average 'div' must be one less than 'count' 0823 static const int AVERAGE_COUNT = 50; 0824 static const int AVERAGE_MULT = 49; 0825 0826 // Minimum selection area compared to the whole image 0827 static const float MIN_AREA_SIZE = 0.01; 0828 // ------------------------------------------------------------------------ 0829 void KSaneViewer::findSelections(float area) 0830 { 0831 // Reduce the size of the image to decrease noise and calculation time 0832 float multiplier = sqrt(area / (d->img->height() * d->img->width())); 0833 0834 int width = (int)(d->img->width() * multiplier); 0835 int height = (int)(d->img->height() * multiplier); 0836 0837 QImage img = d->img->scaled(width, height, Qt::KeepAspectRatio); 0838 height = img.height(); // the size was probably not exact 0839 width = img.width(); 0840 0841 QVector<qint64> colSums(width + SEL_MARGIN + 1); 0842 qint64 rowSum; 0843 colSums.fill(0); 0844 int pix; 0845 int diff; 0846 int hSelStart = -1; 0847 int hSelEnd = -1; 0848 int hSelMargin = 0; 0849 int wSelStart = -1; 0850 int wSelEnd = -1; 0851 int wSelMargin = 0; 0852 0853 for (int h = 1; h < height; h++) { 0854 rowSum = 0; 0855 if (h < height - 1) { 0856 // Special case for the left most pixel 0857 pix = qGray(img.pixel(0, h)); 0858 diff = qAbs(pix - qGray(img.pixel(1, h))); 0859 diff += qAbs(pix - qGray(img.pixel(0, h - 1))); 0860 diff += qAbs(pix - qGray(img.pixel(0, h + 1))); 0861 if (diff > DIFF_TRIGGER) { 0862 colSums[0] += diff; 0863 rowSum += diff; 0864 } 0865 0866 // Special case for the right most pixel 0867 pix = qGray(img.pixel(width - 1, h)); 0868 diff = qAbs(pix - qGray(img.pixel(width - 2, h))); 0869 diff += qAbs(pix - qGray(img.pixel(width - 1, h - 1))); 0870 diff += qAbs(pix - qGray(img.pixel(width - 1, h + 1))); 0871 if (diff > DIFF_TRIGGER) { 0872 colSums[width - 1] += diff; 0873 rowSum += diff; 0874 } 0875 0876 for (int w = 1; w < (width - 1); w++) { 0877 pix = qGray(img.pixel(w, h)); 0878 diff = 0; 0879 // how much does the pixel differ from the surrounding 0880 diff += qAbs(pix - qGray(img.pixel(w - 1, h))); 0881 diff += qAbs(pix - qGray(img.pixel(w + 1, h))); 0882 diff += qAbs(pix - qGray(img.pixel(w, h - 1))); 0883 diff += qAbs(pix - qGray(img.pixel(w, h + 1))); 0884 if (diff > DIFF_TRIGGER) { 0885 colSums[w] += diff; 0886 rowSum += diff; 0887 } 0888 } 0889 } 0890 0891 if ((rowSum / width) > SUM_TRIGGER) { 0892 if (hSelStart < 0) { 0893 if (hSelMargin < SEL_MARGIN) { 0894 hSelMargin++; 0895 } 0896 if (hSelMargin == SEL_MARGIN) { 0897 hSelStart = h - SEL_MARGIN + 1; 0898 } 0899 } 0900 } else { 0901 if (hSelStart >= 0) { 0902 if (hSelMargin > 0) { 0903 hSelMargin--; 0904 } 0905 } 0906 if ((hSelStart > -1) && ((hSelMargin == 0) || (h == height - 1))) { 0907 if (h == height - 1) { 0908 hSelEnd = h - hSelMargin; 0909 } else { 0910 hSelEnd = h - SEL_MARGIN; 0911 } 0912 // We have the end of the vertical selection 0913 // now figure out the horizontal part of the selection 0914 for (int w = 0; w <= width; w++) { // colSums[width] will be 0 0915 if ((colSums[w] / (h - hSelStart)) > SUM_TRIGGER) { 0916 if (wSelStart < 0) { 0917 if (wSelMargin < SEL_MARGIN) { 0918 wSelMargin++; 0919 } 0920 if (wSelMargin == SEL_MARGIN) { 0921 wSelStart = w - SEL_MARGIN + 1; 0922 } 0923 } 0924 } else { 0925 if (wSelStart >= 0) { 0926 if (wSelMargin > 0) { 0927 wSelMargin--; 0928 } 0929 } 0930 if ((wSelStart >= 0) && ((wSelMargin == 0) || (w == width))) { 0931 if (w == width) { 0932 wSelEnd = width; 0933 } else { 0934 wSelEnd = w - SEL_MARGIN + 1; 0935 } 0936 0937 // we have the end of a horizontal selection 0938 if ((wSelEnd - wSelStart) < width) { 0939 // skip selections that span the whole width 0940 // calculate the coordinates in the original size 0941 int x1 = wSelStart / multiplier; 0942 int y1 = hSelStart / multiplier; 0943 int x2 = wSelEnd / multiplier; 0944 int y2 = hSelEnd / multiplier; 0945 float selArea = (float)(wSelEnd - wSelStart) * (float)(hSelEnd - hSelStart); 0946 if (selArea > (area * MIN_AREA_SIZE)) { 0947 SelectionItem *tmp = new SelectionItem(QRect(QPoint(x1, y1), QPoint(x2, y2))); 0948 tmp->setDevicePixelRatio(d->img->devicePixelRatio()); 0949 d->selectionList.push_back(tmp); 0950 d->selectionList.back()->setSaved(true); 0951 d->selectionList.back()->saveZoom(transform().m11()); 0952 d->scene->addItem(d->selectionList.back()); 0953 d->selectionList.back()->setZValue(9); 0954 } 0955 } 0956 wSelStart = -1; 0957 wSelEnd = -1; 0958 (void)hSelEnd; // clang static analyzer report hSelEnd is never used. 0959 wSelMargin = 0; 0960 } 0961 } 0962 } 0963 hSelStart = -1; 0964 hSelEnd = -1; 0965 hSelMargin = 0; 0966 colSums.fill(0); 0967 } 0968 } 0969 } 0970 0971 if (d->selectionList.size() > MAX_NUM_SELECTIONS) { 0972 // smaller area or should we give up?? 0973 clearSavedSelections(); 0974 //findSelections(area/2); 0975 // instead of trying to find probably broken selections just give up 0976 // and do not force broken selections on the user. 0977 } else { 0978 // 1/multiplier is the error margin caused by the resolution reduction 0979 refineSelections(qRound(1 / multiplier)); 0980 // check that the selections are big enough 0981 float minArea = d->img->height() * d->img->width() * MIN_AREA_SIZE; 0982 0983 int i = 0; 0984 while (i < d->selectionList.size()) { 0985 if ((d->selectionList[i]->rect().width() * d->selectionList[i]->rect().height()) < minArea) { 0986 d->scene->removeItem(d->selectionList[i]); 0987 d->selectionList.removeAt(i); 0988 } else { 0989 i++; 0990 } 0991 } 0992 } 0993 } 0994 0995 QSize KSaneViewer::sizeHint() const 0996 { 0997 return QSize(250, 300); // a sensible size for a scan preview 0998 } 0999 1000 void KSaneViewer::refineSelections(int pixelMargin) 1001 { 1002 // The end result 1003 int hSelStart; 1004 int hSelEnd; 1005 int wSelStart; 1006 int wSelEnd; 1007 1008 for (int i = 0; i < d->selectionList.size(); i++) { 1009 QRectF selRect = d->selectionList.at(i)->rect(); 1010 1011 // original values 1012 hSelStart = (int)selRect.top(); 1013 hSelEnd = (int)selRect.bottom(); 1014 wSelStart = (int)selRect.left(); 1015 wSelEnd = (int)selRect.right(); 1016 1017 // Top 1018 // Too long iteration should not be a problem since the loop should be interrupted by the limit 1019 hSelStart = refineRow(hSelStart - pixelMargin, hSelEnd, wSelStart, wSelEnd); 1020 1021 // Bottom (from the bottom up wards) 1022 hSelEnd = refineRow(hSelEnd + pixelMargin, hSelStart, wSelStart, wSelEnd); 1023 1024 // Left 1025 wSelStart = refineColumn(wSelStart - pixelMargin, wSelEnd, hSelStart, hSelEnd); 1026 1027 // Right 1028 wSelEnd = refineColumn(wSelEnd + pixelMargin, wSelStart, hSelStart, hSelEnd); 1029 1030 // Now update the selection 1031 d->selectionList.at(i)->setRect(QRectF(QPointF(wSelStart, hSelStart), QPointF(wSelEnd, hSelEnd))); 1032 } 1033 } 1034 1035 int KSaneViewer::refineRow(int fromRow, int toRow, int colStart, int colEnd) 1036 { 1037 int pix; 1038 int diff; 1039 float rowTrigger; 1040 int row; 1041 int addSub = (fromRow < toRow) ? 1 : -1; 1042 1043 colStart -= 2; //add some margin 1044 colEnd += 2; //add some margin 1045 1046 if (colStart < 1) { 1047 colStart = 1; 1048 } 1049 if (colEnd >= d->img->width() - 1) { 1050 colEnd = d->img->width() - 2; 1051 } 1052 1053 if (fromRow < 1) { 1054 fromRow = 1; 1055 } 1056 if (fromRow >= d->img->height() - 1) { 1057 fromRow = d->img->height() - 2; 1058 } 1059 1060 if (toRow < 1) { 1061 toRow = 1; 1062 } 1063 if (toRow >= d->img->height() - 1) { 1064 toRow = d->img->height() - 2; 1065 } 1066 1067 row = fromRow; 1068 while (row != toRow) { 1069 rowTrigger = 0; 1070 for (int w = colStart; w < colEnd; w++) { 1071 diff = 0; 1072 pix = qGray(d->img->pixel(w, row)); 1073 // how much does the pixel differ from the surrounding 1074 diff += qAbs(pix - qGray(d->img->pixel(w - 1, row))); 1075 diff += qAbs(pix - qGray(d->img->pixel(w + 1, row))); 1076 diff += qAbs(pix - qGray(d->img->pixel(w, row - 1))); 1077 diff += qAbs(pix - qGray(d->img->pixel(w, row + 1))); 1078 if (diff <= DIFF_TRIGGER) { 1079 diff = 0; 1080 } 1081 1082 rowTrigger = ((rowTrigger * AVERAGE_MULT) + diff) / AVERAGE_COUNT; 1083 1084 if (rowTrigger > AVERAGE_TRIGGER) { 1085 break; 1086 } 1087 } 1088 1089 if (rowTrigger > AVERAGE_TRIGGER) { 1090 // row == 1 _probably_ means that the selection should start from 0 1091 // but that can not be detected if we start from 1 => include one extra column 1092 if (row == 1) { 1093 row = 0; 1094 } 1095 if (row == (d->img->width() - 2)) { 1096 row = d->img->width(); 1097 } 1098 return row; 1099 } 1100 row += addSub; 1101 } 1102 return row; 1103 } 1104 1105 int KSaneViewer::refineColumn(int fromCol, int toCol, int rowStart, int rowEnd) 1106 { 1107 int pix; 1108 int diff; 1109 float colTrigger; 1110 int col; 1111 int count; 1112 int addSub = (fromCol < toCol) ? 1 : -1; 1113 1114 rowStart -= 2; //add some margin 1115 rowEnd += 2; //add some margin 1116 1117 if (rowStart < 1) { 1118 rowStart = 1; 1119 } 1120 if (rowEnd >= d->img->height() - 1) { 1121 rowEnd = d->img->height() - 2; 1122 } 1123 1124 if (fromCol < 1) { 1125 fromCol = 1; 1126 } 1127 if (fromCol >= d->img->width() - 1) { 1128 fromCol = d->img->width() - 2; 1129 } 1130 1131 if (toCol < 1) { 1132 toCol = 1; 1133 } 1134 if (toCol >= d->img->width() - 1) { 1135 toCol = d->img->width() - 2; 1136 } 1137 1138 col = fromCol; 1139 while (col != toCol) { 1140 colTrigger = 0; 1141 count = 0; 1142 for (int row = rowStart; row < rowEnd; row++) { 1143 count++; 1144 diff = 0; 1145 pix = qGray(d->img->pixel(col, row)); 1146 // how much does the pixel differ from the surrounding 1147 diff += qAbs(pix - qGray(d->img->pixel(col - 1, row))); 1148 diff += qAbs(pix - qGray(d->img->pixel(col + 1, row))); 1149 diff += qAbs(pix - qGray(d->img->pixel(col, row - 1))); 1150 diff += qAbs(pix - qGray(d->img->pixel(col, row + 1))); 1151 if (diff <= DIFF_TRIGGER) { 1152 diff = 0; 1153 } 1154 1155 colTrigger = ((colTrigger * AVERAGE_MULT) + diff) / AVERAGE_COUNT; 1156 1157 if (colTrigger > AVERAGE_TRIGGER) { 1158 break; 1159 } 1160 } 1161 1162 if (colTrigger > AVERAGE_TRIGGER) { 1163 // col == 1 _probably_ means that the selection should start from 0 1164 // but that can not be detected if we start from 1 => include one extra column 1165 if (col == 1) { 1166 col = 0; 1167 } 1168 if (col == (d->img->width() - 2)) { 1169 col = d->img->width(); 1170 } 1171 return col; 1172 } 1173 col += addSub; 1174 } 1175 return col; 1176 } 1177 1178 QPointF KSaneViewer::scenePos(QMouseEvent *e) const 1179 { 1180 // QGraphicsView::mapToScene() maps only QPoints, but in highdpi mode we want 1181 // to deal with non-rounded coordinates, that's why QPainterPath wrapper is used. 1182 // QMouseEvent::localPos() currently returns a rounded QPointF 1183 // (https://codereview.qt-project.org/259785), so we have to extract a fractional 1184 // part from QMouseEvent::screenPos(). 1185 const QPointF screenPos = e->screenPos(); 1186 QPointF delta = screenPos - screenPos.toPoint(); 1187 return mapToScene(QPainterPath(e->pos() + delta)).currentPosition(); 1188 } 1189 1190 } // NameSpace KSaneIface 1191 1192 #include "moc_ksaneviewer.cpp"