1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2004 Antonio Larrosa <larrosa@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "kpixmapregionselectorwidget.h"
9#include <QAction>
10#include <QApplication>
11#include <QColor>
12#include <QCursor>
13#include <QHBoxLayout>
14#include <QImage>
15#include <QLabel>
16#include <QMenu>
17#include <QMouseEvent>
18#include <QPainter>
19#include <QRubberBand>
20#include <QVBoxLayout>
21
22class KPixmapRegionSelectorWidgetPrivate
23{
24public:
25 KPixmapRegionSelectorWidgetPrivate(KPixmapRegionSelectorWidget *qq)
26 : q(qq)
27 {
28 }
29
30 KPixmapRegionSelectorWidget *const q;
31
32 /*!
33 * Recalculates the pixmap that is shown based on the current selected area,
34 * the original image, etc.
35 */
36 void updatePixmap();
37
38 QRect calcSelectionRectangle(const QPoint &startPoint, const QPoint &endPoint);
39
40 enum CursorState {
41 None = 0,
42 Resizing,
43 Moving
44 };
45 CursorState m_state;
46
47 QPixmap m_unzoomedPixmap;
48 QPixmap m_originalPixmap;
49 QPixmap m_linedPixmap;
50 QRect m_selectedRegion;
51 QLabel *m_label;
52
53 QPoint m_tempFirstClick;
54 double m_forcedAspectRatio;
55
56 int m_maxWidth, m_maxHeight;
57 double m_zoomFactor;
58
59 QRubberBand *m_rubberBand;
60};
61
62KPixmapRegionSelectorWidget::KPixmapRegionSelectorWidget(QWidget *parent)
63 : QWidget(parent)
64 , d(new KPixmapRegionSelectorWidgetPrivate(this))
65{
66 QHBoxLayout *hboxLayout = new QHBoxLayout(this);
67
68 hboxLayout->addStretch();
69 QVBoxLayout *vboxLayout = new QVBoxLayout();
70 hboxLayout->addItem(vboxLayout);
71
72 vboxLayout->addStretch();
73 d->m_label = new QLabel(this);
74 d->m_label->setAttribute(Qt::WA_NoSystemBackground, on: true); // setBackgroundMode( Qt::NoBackground );
75 d->m_label->installEventFilter(filterObj: this);
76
77 vboxLayout->addWidget(d->m_label);
78 vboxLayout->addStretch();
79
80 hboxLayout->addStretch();
81
82 d->m_forcedAspectRatio = 0;
83
84 d->m_zoomFactor = 1.0;
85 d->m_rubberBand = new QRubberBand(QRubberBand::Rectangle, d->m_label);
86 d->m_rubberBand->hide();
87}
88
89KPixmapRegionSelectorWidget::~KPixmapRegionSelectorWidget() = default;
90
91QPixmap KPixmapRegionSelectorWidget::pixmap() const
92{
93 return d->m_unzoomedPixmap;
94}
95
96void KPixmapRegionSelectorWidget::setPixmap(const QPixmap &pixmap)
97{
98 Q_ASSERT(!pixmap.isNull()); // This class isn't designed to deal with null pixmaps.
99 d->m_originalPixmap = pixmap;
100 d->m_unzoomedPixmap = pixmap;
101 d->m_label->setPixmap(pixmap);
102 resetSelection();
103}
104
105void KPixmapRegionSelectorWidget::resetSelection()
106{
107 d->m_selectedRegion = d->m_originalPixmap.rect();
108 d->m_rubberBand->hide();
109 d->updatePixmap();
110}
111
112QRect KPixmapRegionSelectorWidget::selectedRegion() const
113{
114 return d->m_selectedRegion;
115}
116
117void KPixmapRegionSelectorWidget::setSelectedRegion(const QRect &rect)
118{
119 if (!rect.isValid()) {
120 resetSelection();
121 } else {
122 d->m_selectedRegion = rect;
123 d->updatePixmap();
124 }
125}
126
127void KPixmapRegionSelectorWidgetPrivate::updatePixmap()
128{
129 Q_ASSERT(!m_originalPixmap.isNull());
130 if (m_originalPixmap.isNull()) {
131 m_label->setPixmap(m_originalPixmap);
132 return;
133 }
134 if (m_selectedRegion.width() > m_originalPixmap.width()) {
135 m_selectedRegion.setWidth(m_originalPixmap.width());
136 }
137 if (m_selectedRegion.height() > m_originalPixmap.height()) {
138 m_selectedRegion.setHeight(m_originalPixmap.height());
139 }
140
141 QPainter painter;
142 if (m_linedPixmap.isNull()) {
143 m_linedPixmap = m_originalPixmap;
144 QPainter p(&m_linedPixmap);
145 p.setCompositionMode(QPainter::CompositionMode_SourceAtop);
146 p.fillRect(m_linedPixmap.rect(), color: QColor(0, 0, 0, 100));
147 }
148
149 QPixmap pixmap = m_linedPixmap;
150 painter.begin(&pixmap);
151 painter.drawPixmap(p: m_selectedRegion.topLeft(), pm: m_originalPixmap, sr: m_selectedRegion);
152
153 painter.end();
154
155 m_label->setPixmap(pixmap);
156
157 qApp->sendPostedEvents(receiver: nullptr, event_type: QEvent::LayoutRequest);
158
159 if (m_selectedRegion == m_originalPixmap.rect()) { // d->m_label->rect()) //### CHECK!
160 m_rubberBand->hide();
161 } else {
162 m_rubberBand->setGeometry(QRect(m_selectedRegion.topLeft(), m_selectedRegion.size()));
163
164 /* m_rubberBand->setGeometry(QRect(m_label -> mapToGlobal(m_selectedRegion.topLeft()),
165 m_selectedRegion.size()));
166 */
167 if (m_state != None) {
168 m_rubberBand->show();
169 }
170 }
171}
172
173QMenu *KPixmapRegionSelectorWidget::createPopupMenu()
174{
175 QMenu *popup = new QMenu(this);
176 popup->setObjectName(QStringLiteral("PixmapRegionSelectorPopup"));
177 popup->addSection(text: tr(s: "Image Operations", c: "@title:menu"));
178
179 popup->addAction(icon: QIcon::fromTheme(QStringLiteral("object-rotate-right")),
180 text: tr(s: "&Rotate Clockwise", c: "@action:inmenu"),
181 args: this,
182 args: &KPixmapRegionSelectorWidget::rotateClockwise);
183 popup->addAction(icon: QIcon::fromTheme(QStringLiteral("object-rotate-left")),
184 text: tr(s: "Rotate &Counterclockwise", c: "@action:inmenu"),
185 args: this,
186 args: &KPixmapRegionSelectorWidget::rotateCounterclockwise);
187
188 /*
189 I wonder if it would be appropriate to have here an "Open with..." option to
190 edit the image (antlarr)
191 */
192 return popup;
193}
194
195void KPixmapRegionSelectorWidget::rotate(RotateDirection direction)
196{
197 int w = d->m_originalPixmap.width();
198 int h = d->m_originalPixmap.height();
199 QImage img = d->m_unzoomedPixmap.toImage();
200 if (direction == Rotate90) {
201 img = img.transformed(matrix: QTransform().rotate(a: 90.0));
202 } else if (direction == Rotate180) {
203 img = img.transformed(matrix: QTransform().rotate(a: 180.0));
204 } else {
205 img = img.transformed(matrix: QTransform().rotate(a: 270.0));
206 }
207
208 d->m_unzoomedPixmap = QPixmap::fromImage(image: img);
209
210 img = d->m_originalPixmap.toImage();
211 if (direction == Rotate90) {
212 img = img.transformed(matrix: QTransform().rotate(a: 90.0));
213 } else if (direction == Rotate180) {
214 img = img.transformed(matrix: QTransform().rotate(a: 180.0));
215 } else {
216 img = img.transformed(matrix: QTransform().rotate(a: 270.0));
217 }
218
219 d->m_originalPixmap = QPixmap::fromImage(image: img);
220
221 d->m_linedPixmap = QPixmap();
222
223 if (d->m_forcedAspectRatio > 0 && d->m_forcedAspectRatio != 1) {
224 resetSelection();
225 } else {
226 switch (direction) {
227 case (Rotate90): {
228 int x = h - d->m_selectedRegion.y() - d->m_selectedRegion.height();
229 int y = d->m_selectedRegion.x();
230 d->m_selectedRegion.setRect(ax: x, ay: y, aw: d->m_selectedRegion.height(), ah: d->m_selectedRegion.width());
231 d->updatePixmap();
232 // qApp->sendPostedEvents(0,QEvent::LayoutRequest);
233 // updatePixmap();
234
235 } break;
236 case (Rotate270): {
237 int x = d->m_selectedRegion.y();
238 int y = w - d->m_selectedRegion.x() - d->m_selectedRegion.width();
239 d->m_selectedRegion.setRect(ax: x, ay: y, aw: d->m_selectedRegion.height(), ah: d->m_selectedRegion.width());
240 d->updatePixmap();
241 // qApp->sendPostedEvents(0,QEvent::LayoutRequest);
242 // updatePixmap();
243 } break;
244 default:
245 resetSelection();
246 }
247 }
248
249 Q_EMIT pixmapRotated();
250}
251
252void KPixmapRegionSelectorWidget::rotateClockwise()
253{
254 rotate(direction: Rotate90);
255}
256
257void KPixmapRegionSelectorWidget::rotateCounterclockwise()
258{
259 rotate(direction: Rotate270);
260}
261
262bool KPixmapRegionSelectorWidget::eventFilter(QObject *obj, QEvent *ev)
263{
264 if (ev->type() == QEvent::MouseButtonPress) {
265 QMouseEvent *mev = (QMouseEvent *)(ev);
266 // qCDebug(KWidgetsAddonsLog) << QString("click at %1,%2").arg( mev->x() ).arg( mev->y() );
267
268 if (mev->button() == Qt::RightButton) {
269 QMenu *popup = createPopupMenu();
270 popup->exec(pos: mev->globalPosition().toPoint());
271 delete popup;
272 return true;
273 }
274
275 QCursor cursor;
276
277 if (d->m_selectedRegion.contains(p: mev->pos()) && d->m_selectedRegion != d->m_originalPixmap.rect()) {
278 d->m_state = KPixmapRegionSelectorWidgetPrivate::Moving;
279 cursor.setShape(Qt::SizeAllCursor);
280 d->m_rubberBand->show();
281 } else {
282 d->m_state = KPixmapRegionSelectorWidgetPrivate::Resizing;
283 cursor.setShape(Qt::CrossCursor);
284 }
285 QApplication::setOverrideCursor(cursor);
286
287 d->m_tempFirstClick = mev->pos();
288
289 return true;
290 }
291
292 if (ev->type() == QEvent::MouseMove) {
293 QMouseEvent *mev = (QMouseEvent *)(ev);
294
295 // qCDebug(KWidgetsAddonsLog) << QString("move to %1,%2").arg( mev->x() ).arg( mev->y() );
296
297 if (d->m_state == KPixmapRegionSelectorWidgetPrivate::Resizing) {
298 setSelectedRegion(d->calcSelectionRectangle(startPoint: d->m_tempFirstClick, endPoint: mev->pos()));
299 } else if (d->m_state == KPixmapRegionSelectorWidgetPrivate::Moving) {
300 int mevx = mev->position().x();
301 int mevy = mev->position().y();
302 bool mouseOutside = false;
303 if (mevx < 0) {
304 d->m_selectedRegion.translate(dx: -d->m_selectedRegion.x(), dy: 0);
305 mouseOutside = true;
306 } else if (mevx > d->m_originalPixmap.width()) {
307 d->m_selectedRegion.translate(dx: d->m_originalPixmap.width() - d->m_selectedRegion.width() - d->m_selectedRegion.x(), dy: 0);
308 mouseOutside = true;
309 }
310 if (mevy < 0) {
311 d->m_selectedRegion.translate(dx: 0, dy: -d->m_selectedRegion.y());
312 mouseOutside = true;
313 } else if (mevy > d->m_originalPixmap.height()) {
314 d->m_selectedRegion.translate(dx: 0, dy: d->m_originalPixmap.height() - d->m_selectedRegion.height() - d->m_selectedRegion.y());
315 mouseOutside = true;
316 }
317 if (mouseOutside) {
318 d->updatePixmap();
319 return true;
320 };
321
322 d->m_selectedRegion.translate(dx: mev->position().x() - d->m_tempFirstClick.x(), //
323 dy: mev->position().y() - d->m_tempFirstClick.y());
324
325 // Check that the region has not fallen outside the image
326 if (d->m_selectedRegion.x() < 0) {
327 d->m_selectedRegion.translate(dx: -d->m_selectedRegion.x(), dy: 0);
328 } else if (d->m_selectedRegion.right() > d->m_originalPixmap.width()) {
329 d->m_selectedRegion.translate(dx: -(d->m_selectedRegion.right() - d->m_originalPixmap.width()), dy: 0);
330 }
331
332 if (d->m_selectedRegion.y() < 0) {
333 d->m_selectedRegion.translate(dx: 0, dy: -d->m_selectedRegion.y());
334 } else if (d->m_selectedRegion.bottom() > d->m_originalPixmap.height()) {
335 d->m_selectedRegion.translate(dx: 0, dy: -(d->m_selectedRegion.bottom() - d->m_originalPixmap.height()));
336 }
337
338 d->m_tempFirstClick = mev->pos();
339 d->updatePixmap();
340 }
341 return true;
342 }
343
344 if (ev->type() == QEvent::MouseButtonRelease) {
345 QMouseEvent *mev = (QMouseEvent *)(ev);
346
347 if (d->m_state == KPixmapRegionSelectorWidgetPrivate::Resizing && mev->pos() == d->m_tempFirstClick) {
348 resetSelection();
349 }
350
351 d->m_state = KPixmapRegionSelectorWidgetPrivate::None;
352 QApplication::restoreOverrideCursor();
353 d->m_rubberBand->hide();
354 return true;
355 }
356
357 QWidget::eventFilter(watched: obj, event: ev);
358 return false;
359}
360
361QRect KPixmapRegionSelectorWidgetPrivate::calcSelectionRectangle(const QPoint &startPoint, const QPoint &_endPoint)
362{
363 QPoint endPoint = _endPoint;
364 if (endPoint.x() < 0) {
365 endPoint.setX(0);
366 } else if (endPoint.x() > m_originalPixmap.width()) {
367 endPoint.setX(m_originalPixmap.width());
368 }
369 if (endPoint.y() < 0) {
370 endPoint.setY(0);
371 } else if (endPoint.y() > m_originalPixmap.height()) {
372 endPoint.setY(m_originalPixmap.height());
373 }
374 int w = abs(x: startPoint.x() - endPoint.x());
375 int h = abs(x: startPoint.y() - endPoint.y());
376
377 if (m_forcedAspectRatio > 0) {
378 double aspectRatio = w / double(h);
379
380 if (aspectRatio > m_forcedAspectRatio) {
381 h = (int)(w / m_forcedAspectRatio);
382 } else {
383 w = (int)(h * m_forcedAspectRatio);
384 }
385 }
386
387 int x;
388 int y;
389 if (startPoint.x() < endPoint.x()) {
390 x = startPoint.x();
391 } else {
392 x = startPoint.x() - w;
393 }
394
395 if (startPoint.y() < endPoint.y()) {
396 y = startPoint.y();
397 } else {
398 y = startPoint.y() - h;
399 }
400
401 if (x < 0) {
402 w += x;
403 x = 0;
404 h = (int)(w / m_forcedAspectRatio);
405
406 if (startPoint.y() > endPoint.y()) {
407 y = startPoint.y() - h;
408 }
409 } else if (x + w > m_originalPixmap.width()) {
410 w = m_originalPixmap.width() - x;
411 h = (int)(w / m_forcedAspectRatio);
412
413 if (startPoint.y() > endPoint.y()) {
414 y = startPoint.y() - h;
415 }
416 }
417
418 if (y < 0) {
419 h += y;
420 y = 0;
421 w = (int)(h * m_forcedAspectRatio);
422
423 if (startPoint.x() > endPoint.x()) {
424 x = startPoint.x() - w;
425 }
426 } else if (y + h > m_originalPixmap.height()) {
427 h = m_originalPixmap.height() - y;
428 w = (int)(h * m_forcedAspectRatio);
429
430 if (startPoint.x() > endPoint.x()) {
431 x = startPoint.x() - w;
432 }
433 }
434
435 return QRect(x, y, w, h);
436}
437
438QRect KPixmapRegionSelectorWidget::unzoomedSelectedRegion() const
439{
440 return QRect((int)(d->m_selectedRegion.x() / d->m_zoomFactor),
441 (int)(d->m_selectedRegion.y() / d->m_zoomFactor),
442 (int)(d->m_selectedRegion.width() / d->m_zoomFactor),
443 (int)(d->m_selectedRegion.height() / d->m_zoomFactor));
444}
445
446QImage KPixmapRegionSelectorWidget::selectedImage() const
447{
448 QImage origImage = d->m_unzoomedPixmap.toImage();
449 return origImage.copy(rect: unzoomedSelectedRegion());
450}
451
452void KPixmapRegionSelectorWidget::setSelectionAspectRatio(int width, int height)
453{
454 d->m_forcedAspectRatio = width / double(height);
455}
456
457void KPixmapRegionSelectorWidget::setFreeSelectionAspectRatio()
458{
459 d->m_forcedAspectRatio = 0;
460}
461
462void KPixmapRegionSelectorWidget::setMaximumWidgetSize(int width, int height)
463{
464 d->m_maxWidth = width;
465 d->m_maxHeight = height;
466
467 if (d->m_selectedRegion == d->m_originalPixmap.rect()) {
468 d->m_selectedRegion = QRect();
469 }
470 d->m_originalPixmap = d->m_unzoomedPixmap;
471
472 // qCDebug(KWidgetsAddonsLog) << QString(" original Pixmap :") << d->m_originalPixmap.rect();
473 // qCDebug(KWidgetsAddonsLog) << QString(" unzoomed Pixmap : %1 x %2 ").arg(d->m_unzoomedPixmap.width()).arg(d->m_unzoomedPixmap.height());
474
475 if (!d->m_originalPixmap.isNull() && (d->m_originalPixmap.width() > d->m_maxWidth || d->m_originalPixmap.height() > d->m_maxHeight)) {
476 /* We have to resize the pixmap to get it complete on the screen */
477 QImage image = d->m_originalPixmap.toImage();
478 d->m_originalPixmap = QPixmap::fromImage(image: image.scaled(w: width, h: height, aspectMode: Qt::KeepAspectRatio, mode: Qt::SmoothTransformation));
479 double oldZoomFactor = d->m_zoomFactor;
480 d->m_zoomFactor = d->m_originalPixmap.width() / (double)d->m_unzoomedPixmap.width();
481
482 if (d->m_selectedRegion.isValid()) {
483 d->m_selectedRegion = QRect((int)(d->m_selectedRegion.x() * d->m_zoomFactor / oldZoomFactor),
484 (int)(d->m_selectedRegion.y() * d->m_zoomFactor / oldZoomFactor),
485 (int)(d->m_selectedRegion.width() * d->m_zoomFactor / oldZoomFactor),
486 (int)(d->m_selectedRegion.height() * d->m_zoomFactor / oldZoomFactor));
487 }
488 }
489
490 if (!d->m_selectedRegion.isValid()) {
491 d->m_selectedRegion = d->m_originalPixmap.rect();
492 }
493
494 d->m_linedPixmap = QPixmap();
495 d->updatePixmap();
496 resize(w: d->m_label->width(), h: d->m_label->height());
497}
498
499#include "moc_kpixmapregionselectorwidget.cpp"
500

source code of kwidgetsaddons/src/kpixmapregionselectorwidget.cpp