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"