File indexing completed on 2024-04-28 15:32:11

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2004 Antonio Larrosa <larrosa@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "kpixmapregionselectorwidget.h"
0009 #include <QAction>
0010 #include <QApplication>
0011 #include <QColor>
0012 #include <QCursor>
0013 #include <QHBoxLayout>
0014 #include <QImage>
0015 #include <QLabel>
0016 #include <QMenu>
0017 #include <QMouseEvent>
0018 #include <QPainter>
0019 #include <QRubberBand>
0020 #include <QVBoxLayout>
0021 
0022 class KPixmapRegionSelectorWidgetPrivate
0023 {
0024 public:
0025     KPixmapRegionSelectorWidgetPrivate(KPixmapRegionSelectorWidget *qq)
0026         : q(qq)
0027     {
0028     }
0029 
0030     KPixmapRegionSelectorWidget *const q;
0031 
0032     /**
0033      * Recalculates the pixmap that is shown based on the current selected area,
0034      * the original image, etc.
0035      */
0036     void updatePixmap();
0037 
0038     QRect calcSelectionRectangle(const QPoint &startPoint, const QPoint &endPoint);
0039 
0040     enum CursorState { None = 0, Resizing, Moving };
0041     CursorState m_state;
0042 
0043     QPixmap m_unzoomedPixmap;
0044     QPixmap m_originalPixmap;
0045     QPixmap m_linedPixmap;
0046     QRect m_selectedRegion;
0047     QLabel *m_label;
0048 
0049     QPoint m_tempFirstClick;
0050     double m_forcedAspectRatio;
0051 
0052     int m_maxWidth, m_maxHeight;
0053     double m_zoomFactor;
0054 
0055     QRubberBand *m_rubberBand;
0056 };
0057 
0058 KPixmapRegionSelectorWidget::KPixmapRegionSelectorWidget(QWidget *parent)
0059     : QWidget(parent)
0060     , d(new KPixmapRegionSelectorWidgetPrivate(this))
0061 {
0062     QHBoxLayout *hboxLayout = new QHBoxLayout(this);
0063 
0064     hboxLayout->addStretch();
0065     QVBoxLayout *vboxLayout = new QVBoxLayout();
0066     hboxLayout->addItem(vboxLayout);
0067 
0068     vboxLayout->addStretch();
0069     d->m_label = new QLabel(this);
0070     d->m_label->setAttribute(Qt::WA_NoSystemBackground, true); // setBackgroundMode( Qt::NoBackground );
0071     d->m_label->installEventFilter(this);
0072 
0073     vboxLayout->addWidget(d->m_label);
0074     vboxLayout->addStretch();
0075 
0076     hboxLayout->addStretch();
0077 
0078     d->m_forcedAspectRatio = 0;
0079 
0080     d->m_zoomFactor = 1.0;
0081     d->m_rubberBand = new QRubberBand(QRubberBand::Rectangle, d->m_label);
0082     d->m_rubberBand->hide();
0083 }
0084 
0085 KPixmapRegionSelectorWidget::~KPixmapRegionSelectorWidget() = default;
0086 
0087 QPixmap KPixmapRegionSelectorWidget::pixmap() const
0088 {
0089     return d->m_unzoomedPixmap;
0090 }
0091 
0092 void KPixmapRegionSelectorWidget::setPixmap(const QPixmap &pixmap)
0093 {
0094     Q_ASSERT(!pixmap.isNull()); // This class isn't designed to deal with null pixmaps.
0095     d->m_originalPixmap = pixmap;
0096     d->m_unzoomedPixmap = pixmap;
0097     d->m_label->setPixmap(pixmap);
0098     resetSelection();
0099 }
0100 
0101 void KPixmapRegionSelectorWidget::resetSelection()
0102 {
0103     d->m_selectedRegion = d->m_originalPixmap.rect();
0104     d->m_rubberBand->hide();
0105     d->updatePixmap();
0106 }
0107 
0108 QRect KPixmapRegionSelectorWidget::selectedRegion() const
0109 {
0110     return d->m_selectedRegion;
0111 }
0112 
0113 void KPixmapRegionSelectorWidget::setSelectedRegion(const QRect &rect)
0114 {
0115     if (!rect.isValid()) {
0116         resetSelection();
0117     } else {
0118         d->m_selectedRegion = rect;
0119         d->updatePixmap();
0120     }
0121 }
0122 
0123 void KPixmapRegionSelectorWidgetPrivate::updatePixmap()
0124 {
0125     Q_ASSERT(!m_originalPixmap.isNull());
0126     if (m_originalPixmap.isNull()) {
0127         m_label->setPixmap(m_originalPixmap);
0128         return;
0129     }
0130     if (m_selectedRegion.width() > m_originalPixmap.width()) {
0131         m_selectedRegion.setWidth(m_originalPixmap.width());
0132     }
0133     if (m_selectedRegion.height() > m_originalPixmap.height()) {
0134         m_selectedRegion.setHeight(m_originalPixmap.height());
0135     }
0136 
0137     QPainter painter;
0138     if (m_linedPixmap.isNull()) {
0139         m_linedPixmap = m_originalPixmap;
0140         QPainter p(&m_linedPixmap);
0141         p.setCompositionMode(QPainter::CompositionMode_SourceAtop);
0142         p.fillRect(m_linedPixmap.rect(), QColor(0, 0, 0, 100));
0143     }
0144 
0145     QPixmap pixmap = m_linedPixmap;
0146     painter.begin(&pixmap);
0147     painter.drawPixmap(m_selectedRegion.topLeft(), m_originalPixmap, m_selectedRegion);
0148 
0149     painter.end();
0150 
0151     m_label->setPixmap(pixmap);
0152 
0153     qApp->sendPostedEvents(nullptr, QEvent::LayoutRequest);
0154 
0155     if (m_selectedRegion == m_originalPixmap.rect()) { // d->m_label->rect()) //### CHECK!
0156         m_rubberBand->hide();
0157     } else {
0158         m_rubberBand->setGeometry(QRect(m_selectedRegion.topLeft(), m_selectedRegion.size()));
0159 
0160         /*        m_rubberBand->setGeometry(QRect(m_label -> mapToGlobal(m_selectedRegion.topLeft()),
0161                                                 m_selectedRegion.size()));
0162         */
0163         if (m_state != None) {
0164             m_rubberBand->show();
0165         }
0166     }
0167 }
0168 
0169 QMenu *KPixmapRegionSelectorWidget::createPopupMenu()
0170 {
0171     QMenu *popup = new QMenu(this);
0172     popup->setObjectName(QStringLiteral("PixmapRegionSelectorPopup"));
0173     popup->addSection(tr("Image Operations", "@title:menu"));
0174 
0175     popup->addAction(QIcon::fromTheme(QStringLiteral("object-rotate-right")),
0176                      tr("&Rotate Clockwise", "@action:inmenu"),
0177                      this,
0178                      &KPixmapRegionSelectorWidget::rotateClockwise);
0179     popup->addAction(QIcon::fromTheme(QStringLiteral("object-rotate-left")),
0180                      tr("Rotate &Counterclockwise", "@action:inmenu"),
0181                      this,
0182                      &KPixmapRegionSelectorWidget::rotateCounterclockwise);
0183 
0184     /*
0185        I wonder if it would be appropriate to have here an "Open with..." option to
0186        edit the image (antlarr)
0187     */
0188     return popup;
0189 }
0190 
0191 void KPixmapRegionSelectorWidget::rotate(RotateDirection direction)
0192 {
0193     int w = d->m_originalPixmap.width();
0194     int h = d->m_originalPixmap.height();
0195     QImage img = d->m_unzoomedPixmap.toImage();
0196     if (direction == Rotate90) {
0197         img = img.transformed(QTransform().rotate(90.0));
0198     } else if (direction == Rotate180) {
0199         img = img.transformed(QTransform().rotate(180.0));
0200     } else {
0201         img = img.transformed(QTransform().rotate(270.0));
0202     }
0203 
0204     d->m_unzoomedPixmap = QPixmap::fromImage(img);
0205 
0206     img = d->m_originalPixmap.toImage();
0207     if (direction == Rotate90) {
0208         img = img.transformed(QTransform().rotate(90.0));
0209     } else if (direction == Rotate180) {
0210         img = img.transformed(QTransform().rotate(180.0));
0211     } else {
0212         img = img.transformed(QTransform().rotate(270.0));
0213     }
0214 
0215     d->m_originalPixmap = QPixmap::fromImage(img);
0216 
0217     d->m_linedPixmap = QPixmap();
0218 
0219     if (d->m_forcedAspectRatio > 0 && d->m_forcedAspectRatio != 1) {
0220         resetSelection();
0221     } else {
0222         switch (direction) {
0223         case (Rotate90): {
0224             int x = h - d->m_selectedRegion.y() - d->m_selectedRegion.height();
0225             int y = d->m_selectedRegion.x();
0226             d->m_selectedRegion.setRect(x, y, d->m_selectedRegion.height(), d->m_selectedRegion.width());
0227             d->updatePixmap();
0228             //              qApp->sendPostedEvents(0,QEvent::LayoutRequest);
0229             //              updatePixmap();
0230 
0231         } break;
0232         case (Rotate270): {
0233             int x = d->m_selectedRegion.y();
0234             int y = w - d->m_selectedRegion.x() - d->m_selectedRegion.width();
0235             d->m_selectedRegion.setRect(x, y, d->m_selectedRegion.height(), d->m_selectedRegion.width());
0236             d->updatePixmap();
0237             //              qApp->sendPostedEvents(0,QEvent::LayoutRequest);
0238             //              updatePixmap();
0239         } break;
0240         default:
0241             resetSelection();
0242         }
0243     }
0244 
0245     Q_EMIT pixmapRotated();
0246 }
0247 
0248 void KPixmapRegionSelectorWidget::rotateClockwise()
0249 {
0250     rotate(Rotate90);
0251 }
0252 
0253 void KPixmapRegionSelectorWidget::rotateCounterclockwise()
0254 {
0255     rotate(Rotate270);
0256 }
0257 
0258 bool KPixmapRegionSelectorWidget::eventFilter(QObject *obj, QEvent *ev)
0259 {
0260     if (ev->type() == QEvent::MouseButtonPress) {
0261         QMouseEvent *mev = (QMouseEvent *)(ev);
0262         // qCDebug(KWidgetsAddonsLog) << QString("click at  %1,%2").arg( mev->x() ).arg( mev->y() );
0263 
0264         if (mev->button() == Qt::RightButton) {
0265             QMenu *popup = createPopupMenu();
0266             popup->exec(mev->globalPos());
0267             delete popup;
0268             return true;
0269         }
0270 
0271         QCursor cursor;
0272 
0273         if (d->m_selectedRegion.contains(mev->pos()) && d->m_selectedRegion != d->m_originalPixmap.rect()) {
0274             d->m_state = KPixmapRegionSelectorWidgetPrivate::Moving;
0275             cursor.setShape(Qt::SizeAllCursor);
0276             d->m_rubberBand->show();
0277         } else {
0278             d->m_state = KPixmapRegionSelectorWidgetPrivate::Resizing;
0279             cursor.setShape(Qt::CrossCursor);
0280         }
0281         QApplication::setOverrideCursor(cursor);
0282 
0283         d->m_tempFirstClick = mev->pos();
0284 
0285         return true;
0286     }
0287 
0288     if (ev->type() == QEvent::MouseMove) {
0289         QMouseEvent *mev = (QMouseEvent *)(ev);
0290 
0291         // qCDebug(KWidgetsAddonsLog) << QString("move to  %1,%2").arg( mev->x() ).arg( mev->y() );
0292 
0293         if (d->m_state == KPixmapRegionSelectorWidgetPrivate::Resizing) {
0294             setSelectedRegion(d->calcSelectionRectangle(d->m_tempFirstClick, mev->pos()));
0295         } else if (d->m_state == KPixmapRegionSelectorWidgetPrivate::Moving) {
0296             int mevx = mev->x();
0297             int mevy = mev->y();
0298             bool mouseOutside = false;
0299             if (mevx < 0) {
0300                 d->m_selectedRegion.translate(-d->m_selectedRegion.x(), 0);
0301                 mouseOutside = true;
0302             } else if (mevx > d->m_originalPixmap.width()) {
0303                 d->m_selectedRegion.translate(d->m_originalPixmap.width() - d->m_selectedRegion.width() - d->m_selectedRegion.x(), 0);
0304                 mouseOutside = true;
0305             }
0306             if (mevy < 0) {
0307                 d->m_selectedRegion.translate(0, -d->m_selectedRegion.y());
0308                 mouseOutside = true;
0309             } else if (mevy > d->m_originalPixmap.height()) {
0310                 d->m_selectedRegion.translate(0, d->m_originalPixmap.height() - d->m_selectedRegion.height() - d->m_selectedRegion.y());
0311                 mouseOutside = true;
0312             }
0313             if (mouseOutside) {
0314                 d->updatePixmap();
0315                 return true;
0316             };
0317 
0318             d->m_selectedRegion.translate(mev->x() - d->m_tempFirstClick.x(), //
0319                                           mev->y() - d->m_tempFirstClick.y());
0320 
0321             // Check that the region has not fallen outside the image
0322             if (d->m_selectedRegion.x() < 0) {
0323                 d->m_selectedRegion.translate(-d->m_selectedRegion.x(), 0);
0324             } else if (d->m_selectedRegion.right() > d->m_originalPixmap.width()) {
0325                 d->m_selectedRegion.translate(-(d->m_selectedRegion.right() - d->m_originalPixmap.width()), 0);
0326             }
0327 
0328             if (d->m_selectedRegion.y() < 0) {
0329                 d->m_selectedRegion.translate(0, -d->m_selectedRegion.y());
0330             } else if (d->m_selectedRegion.bottom() > d->m_originalPixmap.height()) {
0331                 d->m_selectedRegion.translate(0, -(d->m_selectedRegion.bottom() - d->m_originalPixmap.height()));
0332             }
0333 
0334             d->m_tempFirstClick = mev->pos();
0335             d->updatePixmap();
0336         }
0337         return true;
0338     }
0339 
0340     if (ev->type() == QEvent::MouseButtonRelease) {
0341         QMouseEvent *mev = (QMouseEvent *)(ev);
0342 
0343         if (d->m_state == KPixmapRegionSelectorWidgetPrivate::Resizing && mev->pos() == d->m_tempFirstClick) {
0344             resetSelection();
0345         }
0346 
0347         d->m_state = KPixmapRegionSelectorWidgetPrivate::None;
0348         QApplication::restoreOverrideCursor();
0349         d->m_rubberBand->hide();
0350         return true;
0351     }
0352 
0353     QWidget::eventFilter(obj, ev);
0354     return false;
0355 }
0356 
0357 QRect KPixmapRegionSelectorWidgetPrivate::calcSelectionRectangle(const QPoint &startPoint, const QPoint &_endPoint)
0358 {
0359     QPoint endPoint = _endPoint;
0360     if (endPoint.x() < 0) {
0361         endPoint.setX(0);
0362     } else if (endPoint.x() > m_originalPixmap.width()) {
0363         endPoint.setX(m_originalPixmap.width());
0364     }
0365     if (endPoint.y() < 0) {
0366         endPoint.setY(0);
0367     } else if (endPoint.y() > m_originalPixmap.height()) {
0368         endPoint.setY(m_originalPixmap.height());
0369     }
0370     int w = abs(startPoint.x() - endPoint.x());
0371     int h = abs(startPoint.y() - endPoint.y());
0372 
0373     if (m_forcedAspectRatio > 0) {
0374         double aspectRatio = w / double(h);
0375 
0376         if (aspectRatio > m_forcedAspectRatio) {
0377             h = (int)(w / m_forcedAspectRatio);
0378         } else {
0379             w = (int)(h * m_forcedAspectRatio);
0380         }
0381     }
0382 
0383     int x;
0384     int y;
0385     if (startPoint.x() < endPoint.x()) {
0386         x = startPoint.x();
0387     } else {
0388         x = startPoint.x() - w;
0389     }
0390 
0391     if (startPoint.y() < endPoint.y()) {
0392         y = startPoint.y();
0393     } else {
0394         y = startPoint.y() - h;
0395     }
0396 
0397     if (x < 0) {
0398         w += x;
0399         x = 0;
0400         h = (int)(w / m_forcedAspectRatio);
0401 
0402         if (startPoint.y() > endPoint.y()) {
0403             y = startPoint.y() - h;
0404         }
0405     } else if (x + w > m_originalPixmap.width()) {
0406         w = m_originalPixmap.width() - x;
0407         h = (int)(w / m_forcedAspectRatio);
0408 
0409         if (startPoint.y() > endPoint.y()) {
0410             y = startPoint.y() - h;
0411         }
0412     }
0413 
0414     if (y < 0) {
0415         h += y;
0416         y = 0;
0417         w = (int)(h * m_forcedAspectRatio);
0418 
0419         if (startPoint.x() > endPoint.x()) {
0420             x = startPoint.x() - w;
0421         }
0422     } else if (y + h > m_originalPixmap.height()) {
0423         h = m_originalPixmap.height() - y;
0424         w = (int)(h * m_forcedAspectRatio);
0425 
0426         if (startPoint.x() > endPoint.x()) {
0427             x = startPoint.x() - w;
0428         }
0429     }
0430 
0431     return QRect(x, y, w, h);
0432 }
0433 
0434 QRect KPixmapRegionSelectorWidget::unzoomedSelectedRegion() const
0435 {
0436     return QRect((int)(d->m_selectedRegion.x() / d->m_zoomFactor),
0437                  (int)(d->m_selectedRegion.y() / d->m_zoomFactor),
0438                  (int)(d->m_selectedRegion.width() / d->m_zoomFactor),
0439                  (int)(d->m_selectedRegion.height() / d->m_zoomFactor));
0440 }
0441 
0442 QImage KPixmapRegionSelectorWidget::selectedImage() const
0443 {
0444     QImage origImage = d->m_unzoomedPixmap.toImage();
0445     return origImage.copy(unzoomedSelectedRegion());
0446 }
0447 
0448 void KPixmapRegionSelectorWidget::setSelectionAspectRatio(int width, int height)
0449 {
0450     d->m_forcedAspectRatio = width / double(height);
0451 }
0452 
0453 void KPixmapRegionSelectorWidget::setFreeSelectionAspectRatio()
0454 {
0455     d->m_forcedAspectRatio = 0;
0456 }
0457 
0458 void KPixmapRegionSelectorWidget::setMaximumWidgetSize(int width, int height)
0459 {
0460     d->m_maxWidth = width;
0461     d->m_maxHeight = height;
0462 
0463     if (d->m_selectedRegion == d->m_originalPixmap.rect()) {
0464         d->m_selectedRegion = QRect();
0465     }
0466     d->m_originalPixmap = d->m_unzoomedPixmap;
0467 
0468     //   qCDebug(KWidgetsAddonsLog) << QString(" original Pixmap :") << d->m_originalPixmap.rect();
0469     //   qCDebug(KWidgetsAddonsLog) << QString(" unzoomed Pixmap : %1 x %2 ").arg(d->m_unzoomedPixmap.width()).arg(d->m_unzoomedPixmap.height());
0470 
0471     if (!d->m_originalPixmap.isNull() && (d->m_originalPixmap.width() > d->m_maxWidth || d->m_originalPixmap.height() > d->m_maxHeight)) {
0472         /* We have to resize the pixmap to get it complete on the screen */
0473         QImage image = d->m_originalPixmap.toImage();
0474         d->m_originalPixmap = QPixmap::fromImage(image.scaled(width, height, Qt::KeepAspectRatio, Qt::SmoothTransformation));
0475         double oldZoomFactor = d->m_zoomFactor;
0476         d->m_zoomFactor = d->m_originalPixmap.width() / (double)d->m_unzoomedPixmap.width();
0477 
0478         if (d->m_selectedRegion.isValid()) {
0479             d->m_selectedRegion = QRect((int)(d->m_selectedRegion.x() * d->m_zoomFactor / oldZoomFactor),
0480                                         (int)(d->m_selectedRegion.y() * d->m_zoomFactor / oldZoomFactor),
0481                                         (int)(d->m_selectedRegion.width() * d->m_zoomFactor / oldZoomFactor),
0482                                         (int)(d->m_selectedRegion.height() * d->m_zoomFactor / oldZoomFactor));
0483         }
0484     }
0485 
0486     if (!d->m_selectedRegion.isValid()) {
0487         d->m_selectedRegion = d->m_originalPixmap.rect();
0488     }
0489 
0490     d->m_linedPixmap = QPixmap();
0491     d->updatePixmap();
0492     resize(d->m_label->width(), d->m_label->height());
0493 }
0494 
0495 #include "moc_kpixmapregionselectorwidget.cpp"