File indexing completed on 2025-02-02 14:20:08
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"