KWidgetsAddons

kpixmapregionselectorwidget.cpp
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
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, true); // setBackgroundMode( Qt::NoBackground );
75 d->m_label->installEventFilter(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
90
91QPixmap KPixmapRegionSelectorWidget::pixmap() const
92{
93 return d->m_unzoomedPixmap;
94}
95
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);
103}
104
106{
107 d->m_selectedRegion = d->m_originalPixmap.rect();
108 d->m_rubberBand->hide();
109 d->updatePixmap();
110}
111
113{
114 return d->m_selectedRegion;
115}
116
118{
119 if (!rect.isValid()) {
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(), QColor(0, 0, 0, 100));
147 }
148
149 QPixmap pixmap = m_linedPixmap;
150 painter.begin(&pixmap);
151 painter.drawPixmap(m_selectedRegion.topLeft(), m_originalPixmap, m_selectedRegion);
152
153 painter.end();
154
155 m_label->setPixmap(pixmap);
156
157 qApp->sendPostedEvents(nullptr, 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
174{
175 QMenu *popup = new QMenu(this);
176 popup->setObjectName(QStringLiteral("PixmapRegionSelectorPopup"));
177 popup->addSection(tr("Image Operations", "@title:menu"));
178
179 popup->addAction(QIcon::fromTheme(QStringLiteral("object-rotate-right")),
180 tr("&Rotate Clockwise", "@action:inmenu"),
181 this,
183 popup->addAction(QIcon::fromTheme(QStringLiteral("object-rotate-left")),
184 tr("Rotate &Counterclockwise", "@action:inmenu"),
185 this,
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
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(QTransform().rotate(90.0));
202 } else if (direction == Rotate180) {
203 img = img.transformed(QTransform().rotate(180.0));
204 } else {
205 img = img.transformed(QTransform().rotate(270.0));
206 }
207
208 d->m_unzoomedPixmap = QPixmap::fromImage(img);
209
210 img = d->m_originalPixmap.toImage();
211 if (direction == Rotate90) {
212 img = img.transformed(QTransform().rotate(90.0));
213 } else if (direction == Rotate180) {
214 img = img.transformed(QTransform().rotate(180.0));
215 } else {
216 img = img.transformed(QTransform().rotate(270.0));
217 }
218
219 d->m_originalPixmap = QPixmap::fromImage(img);
220
221 d->m_linedPixmap = QPixmap();
222
223 if (d->m_forcedAspectRatio > 0 && d->m_forcedAspectRatio != 1) {
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(x, y, d->m_selectedRegion.height(), 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(x, y, d->m_selectedRegion.height(), d->m_selectedRegion.width());
240 d->updatePixmap();
241 // qApp->sendPostedEvents(0,QEvent::LayoutRequest);
242 // updatePixmap();
243 } break;
244 default:
246 }
247 }
248
249 Q_EMIT pixmapRotated();
250}
251
256
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(mev->globalPosition().toPoint());
271 delete popup;
272 return true;
273 }
274
275 QCursor cursor;
276
277 if (d->m_selectedRegion.contains(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 }
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(d->m_tempFirstClick, 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(-d->m_selectedRegion.x(), 0);
305 mouseOutside = true;
306 } else if (mevx > d->m_originalPixmap.width()) {
307 d->m_selectedRegion.translate(d->m_originalPixmap.width() - d->m_selectedRegion.width() - d->m_selectedRegion.x(), 0);
308 mouseOutside = true;
309 }
310 if (mevy < 0) {
311 d->m_selectedRegion.translate(0, -d->m_selectedRegion.y());
312 mouseOutside = true;
313 } else if (mevy > d->m_originalPixmap.height()) {
314 d->m_selectedRegion.translate(0, 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(mev->position().x() - d->m_tempFirstClick.x(), //
323 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(-d->m_selectedRegion.x(), 0);
328 } else if (d->m_selectedRegion.right() > d->m_originalPixmap.width()) {
329 d->m_selectedRegion.translate(-(d->m_selectedRegion.right() - d->m_originalPixmap.width()), 0);
330 }
331
332 if (d->m_selectedRegion.y() < 0) {
333 d->m_selectedRegion.translate(0, -d->m_selectedRegion.y());
334 } else if (d->m_selectedRegion.bottom() > d->m_originalPixmap.height()) {
335 d->m_selectedRegion.translate(0, -(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) {
349 }
350
351 d->m_state = KPixmapRegionSelectorWidgetPrivate::None;
353 d->m_rubberBand->hide();
354 return true;
355 }
356
357 QWidget::eventFilter(obj, 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(startPoint.x() - endPoint.x());
375 int h = abs(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
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
447{
448 QImage origImage = d->m_unzoomedPixmap.toImage();
449 return origImage.copy(unzoomedSelectedRegion());
450}
451
453{
454 d->m_forcedAspectRatio = width / double(height);
455}
456
458{
459 d->m_forcedAspectRatio = 0;
460}
461
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();
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(d->m_label->width(), d->m_label->height());
497}
498
499#include "moc_kpixmapregionselectorwidget.cpp"
void setSelectedRegion(const QRect &rect)
Sets the selected region to be rect (in zoomed pixmap coordinates)
void resetSelection()
Resets the selection to use the whole image.
virtual QMenu * createPopupMenu()
Creates a QMenu with the menu that appears when clicking with the right button on the label.
void rotateClockwise()
Rotates the current image 90º clockwise.
QRect unzoomedSelectedRegion() const
Returns the selected region ( in unzoomed, original pixmap coordinates )
KPixmapRegionSelectorWidget(QWidget *parent=nullptr)
Constructor for a KPixmapRegionSelectorWidget.
void setFreeSelectionAspectRatio()
Allows the user to do a selection which has any aspect ratio.
void setPixmap(const QPixmap &pixmap)
Sets the pixmap which will be shown for the user to select a region from.
void setSelectionAspectRatio(int width, int height)
Sets the aspect ration that the selected subimage should have.
~KPixmapRegionSelectorWidget() override
Destructor for a KPixmapRegionSelectorWidget.
void rotateCounterclockwise()
Rotates the current image 90º counterclockwise.
void rotate(RotateDirection direction)
Rotates the image as specified by the direction parameter, also tries to rotate the selected region s...
QRect selectedRegion() const
Returns the selected region ( in zoomed pixmap coordinates )
void setMaximumWidgetSize(int width, int height)
Sets the maximum size for the widget.
RotateDirection
This enum provides a rotation direction.
@ Rotate90
Rotate 90 degrees to the right.
@ Rotate270
Rotate 90 degrees to the left.
virtual void addItem(QLayoutItem *item) override
void addStretch(int stretch)
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
Type type() const const
void restoreOverrideCursor()
void setOverrideCursor(const QCursor &cursor)
QIcon fromTheme(const QString &name)
QImage copy(const QRect &rectangle) const const
QImage scaled(const QSize &size, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformMode) const const
QImage transformed(const QTransform &matrix, Qt::TransformationMode mode) const const
void setPixmap(const QPixmap &)
QAction * addAction(const QIcon &icon, const QString &text, Functor functor, const QKeySequence &shortcut)
QAction * addSection(const QIcon &icon, const QString &text)
QAction * exec()
QPoint pos() const const
Q_EMITQ_EMIT
virtual bool eventFilter(QObject *watched, QEvent *event)
QObject * parent() const const
void setObjectName(QAnyStringView name)
QString tr(const char *sourceText, const char *disambiguation, int n)
CompositionMode_SourceAtop
bool begin(QPaintDevice *device)
void drawPixmap(const QPoint &point, const QPixmap &pixmap)
bool end()
QPixmap fromImage(QImage &&image, Qt::ImageConversionFlags flags)
bool isNull() const const
void setX(int x)
void setY(int y)
int x() const const
int y() const const
QPoint toPoint() const const
qreal x() const const
qreal y() const const
Qt::MouseButton button() const const
QPointF globalPosition() const const
QPointF position() const const
KeepAspectRatio
SizeAllCursor
RightButton
SmoothTransformation
WA_NoSystemBackground
QWidget(QWidget *parent, Qt::WindowFlags f)
void resize(const QSize &)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Feb 28 2025 12:02:04 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.