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
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
59 : QWidget(parent)
60 , d(new KPixmapRegionSelectorWidgetPrivate(this))
61{
63
64 hboxLayout->addStretch();
66 hboxLayout->addItem(vboxLayout);
67
68 vboxLayout->addStretch();
69 d->m_label = new QLabel(this);
70 d->m_label->setAttribute(Qt::WA_NoSystemBackground, true); // setBackgroundMode( Qt::NoBackground );
71 d->m_label->installEventFilter(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
86
87QPixmap KPixmapRegionSelectorWidget::pixmap() const
88{
89 return d->m_unzoomedPixmap;
90}
91
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);
99}
100
102{
103 d->m_selectedRegion = d->m_originalPixmap.rect();
104 d->m_rubberBand->hide();
105 d->updatePixmap();
106}
107
109{
110 return d->m_selectedRegion;
111}
112
114{
115 if (!rect.isValid()) {
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(), QColor(0, 0, 0, 100));
143 }
144
145 QPixmap pixmap = m_linedPixmap;
146 painter.begin(&pixmap);
147 painter.drawPixmap(m_selectedRegion.topLeft(), m_originalPixmap, m_selectedRegion);
148
149 painter.end();
150
151 m_label->setPixmap(pixmap);
152
153 qApp->sendPostedEvents(nullptr, 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
170{
171 QMenu *popup = new QMenu(this);
172 popup->setObjectName(QStringLiteral("PixmapRegionSelectorPopup"));
173 popup->addSection(tr("Image Operations", "@title:menu"));
174
175 popup->addAction(QIcon::fromTheme(QStringLiteral("object-rotate-right")),
176 tr("&Rotate Clockwise", "@action:inmenu"),
177 this,
179 popup->addAction(QIcon::fromTheme(QStringLiteral("object-rotate-left")),
180 tr("Rotate &Counterclockwise", "@action:inmenu"),
181 this,
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
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(QTransform().rotate(90.0));
198 } else if (direction == Rotate180) {
199 img = img.transformed(QTransform().rotate(180.0));
200 } else {
201 img = img.transformed(QTransform().rotate(270.0));
202 }
203
204 d->m_unzoomedPixmap = QPixmap::fromImage(img);
205
206 img = d->m_originalPixmap.toImage();
207 if (direction == Rotate90) {
208 img = img.transformed(QTransform().rotate(90.0));
209 } else if (direction == Rotate180) {
210 img = img.transformed(QTransform().rotate(180.0));
211 } else {
212 img = img.transformed(QTransform().rotate(270.0));
213 }
214
215 d->m_originalPixmap = QPixmap::fromImage(img);
216
217 d->m_linedPixmap = QPixmap();
218
219 if (d->m_forcedAspectRatio > 0 && d->m_forcedAspectRatio != 1) {
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(x, y, d->m_selectedRegion.height(), 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(x, y, d->m_selectedRegion.height(), d->m_selectedRegion.width());
236 d->updatePixmap();
237 // qApp->sendPostedEvents(0,QEvent::LayoutRequest);
238 // updatePixmap();
239 } break;
240 default:
242 }
243 }
244
245 Q_EMIT pixmapRotated();
246}
247
252
257
258bool KPixmapRegionSelectorWidget::eventFilter(QObject *obj, QEvent *ev)
259{
260 if (ev->type() == QEvent::MouseButtonPress) {
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(mev->globalPosition().toPoint());
267 delete popup;
268 return true;
269 }
270
272
273 if (d->m_selectedRegion.contains(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 }
282
283 d->m_tempFirstClick = mev->pos();
284
285 return true;
286 }
287
288 if (ev->type() == QEvent::MouseMove) {
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(d->m_tempFirstClick, 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(-d->m_selectedRegion.x(), 0);
301 mouseOutside = true;
302 } else if (mevx > d->m_originalPixmap.width()) {
303 d->m_selectedRegion.translate(d->m_originalPixmap.width() - d->m_selectedRegion.width() - d->m_selectedRegion.x(), 0);
304 mouseOutside = true;
305 }
306 if (mevy < 0) {
307 d->m_selectedRegion.translate(0, -d->m_selectedRegion.y());
308 mouseOutside = true;
309 } else if (mevy > d->m_originalPixmap.height()) {
310 d->m_selectedRegion.translate(0, 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(mev->position().x() - d->m_tempFirstClick.x(), //
319 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(-d->m_selectedRegion.x(), 0);
324 } else if (d->m_selectedRegion.right() > d->m_originalPixmap.width()) {
325 d->m_selectedRegion.translate(-(d->m_selectedRegion.right() - d->m_originalPixmap.width()), 0);
326 }
327
328 if (d->m_selectedRegion.y() < 0) {
329 d->m_selectedRegion.translate(0, -d->m_selectedRegion.y());
330 } else if (d->m_selectedRegion.bottom() > d->m_originalPixmap.height()) {
331 d->m_selectedRegion.translate(0, -(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) {
342
343 if (d->m_state == KPixmapRegionSelectorWidgetPrivate::Resizing && mev->pos() == d->m_tempFirstClick) {
345 }
346
347 d->m_state = KPixmapRegionSelectorWidgetPrivate::None;
349 d->m_rubberBand->hide();
350 return true;
351 }
352
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(startPoint.x() - endPoint.x());
371 int h = abs(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
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
443{
444 QImage origImage = d->m_unzoomedPixmap.toImage();
445 return origImage.copy(unzoomedSelectedRegion());
446}
447
449{
450 d->m_forcedAspectRatio = width / double(height);
451}
452
454{
455 d->m_forcedAspectRatio = 0;
456}
457
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();
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(d->m_label->width(), d->m_label->height());
493}
494
495#include "moc_kpixmapregionselectorwidget.cpp"
KPixmapRegionSelectorWidget is a widget that shows a picture and provides the user with a friendly wa...
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.
void restoreOverrideCursor()
void setOverrideCursor(const QCursor &cursor)
QIcon fromTheme(const QString &name)
QImage scaled(const QSize &size, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformMode) 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()
Q_EMITQ_EMIT
virtual bool eventFilter(QObject *watched, QEvent *event)
T qobject_cast(QObject *object)
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)
int height() const const
bool isNull() const const
QRect rect() const const
int width() const const
void setX(int x)
void setY(int y)
int x() const const
int y() const const
int height() const const
void setHeight(int height)
void setWidth(int width)
QSize size() const const
QPoint topLeft() const const
int width() const const
void setGeometry(const QRect &rect)
KeepAspectRatio
SizeAllCursor
RightButton
SmoothTransformation
WA_NoSystemBackground
void hide()
void show()
void resize(const QSize &)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:43 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.