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

source code of kwidgetsaddons/src/kpixmapregionselectorwidget.cpp