1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the tools applications of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include "qpixeltool.h" |
30 | |
31 | #include <qapplication.h> |
32 | #include <qdesktopwidget.h> |
33 | #include <qdir.h> |
34 | #include <qapplication.h> |
35 | #include <qscreen.h> |
36 | #if QT_CONFIG(clipboard) |
37 | #include <qclipboard.h> |
38 | #endif |
39 | #include <qpainter.h> |
40 | #include <qevent.h> |
41 | #include <qfiledialog.h> |
42 | #include <qmessagebox.h> |
43 | #include <qsettings.h> |
44 | #include <qmenu.h> |
45 | #include <qactiongroup.h> |
46 | #include <qimagewriter.h> |
47 | #include <qscreen.h> |
48 | #include <qstandardpaths.h> |
49 | #include <qtextstream.h> |
50 | #include <qwindow.h> |
51 | #include <private/qhighdpiscaling_p.h> |
52 | |
53 | #include <qdebug.h> |
54 | |
55 | QT_BEGIN_NAMESPACE |
56 | |
57 | static QPoint initialPos(const QSettings &settings, const QSize &initialSize) |
58 | { |
59 | const QPoint defaultPos = QGuiApplication::primaryScreen()->availableGeometry().topLeft(); |
60 | const QPoint savedPos = |
61 | settings.value(key: QLatin1String("position" ), defaultValue: QVariant(defaultPos)).toPoint(); |
62 | auto savedScreen = QGuiApplication::screenAt(point: savedPos); |
63 | return savedScreen != nullptr |
64 | && savedScreen->availableGeometry().intersects(r: QRect(savedPos, initialSize)) |
65 | ? savedPos : defaultPos; |
66 | } |
67 | |
68 | QPixelTool::QPixelTool(QWidget *parent) |
69 | : QWidget(parent) |
70 | { |
71 | setWindowTitle(QCoreApplication::applicationName()); |
72 | QSettings settings(QLatin1String("QtProject" ), QLatin1String("QPixelTool" )); |
73 | m_autoUpdate = settings.value(key: QLatin1String("autoUpdate" ), defaultValue: 0).toBool(); |
74 | m_gridSize = settings.value(key: QLatin1String("gridSize" ), defaultValue: 1).toInt(); |
75 | m_gridActive = settings.value(key: QLatin1String("gridActive" ), defaultValue: 1).toInt(); |
76 | m_zoom = settings.value(key: QLatin1String("zoom" ), defaultValue: 4).toInt(); |
77 | m_initialSize = settings.value(key: QLatin1String("initialSize" ), defaultValue: QSize(250, 200)).toSize(); |
78 | m_lcdMode = settings.value(key: QLatin1String("lcdMode" ), defaultValue: 0).toInt(); |
79 | |
80 | move(initialPos(settings, initialSize: m_initialSize)); |
81 | |
82 | setMouseTracking(true); |
83 | setAttribute(Qt::WA_OpaquePaintEvent); |
84 | m_updateId = startTimer(interval: 30); |
85 | } |
86 | |
87 | QPixelTool::~QPixelTool() |
88 | { |
89 | QSettings settings(QLatin1String("QtProject" ), QLatin1String("QPixelTool" )); |
90 | settings.setValue(key: QLatin1String("autoUpdate" ), value: int(m_autoUpdate)); |
91 | settings.setValue(key: QLatin1String("gridSize" ), value: m_gridSize); |
92 | settings.setValue(key: QLatin1String("gridActive" ), value: m_gridActive); |
93 | settings.setValue(key: QLatin1String("zoom" ), value: m_zoom); |
94 | settings.setValue(key: QLatin1String("initialSize" ), value: size()); |
95 | settings.setValue(key: QLatin1String("position" ), value: pos()); |
96 | settings.setValue(key: QLatin1String("lcdMode" ), value: m_lcdMode); |
97 | } |
98 | |
99 | void QPixelTool::setPreviewImage(const QImage &image) |
100 | { |
101 | m_preview_mode = true; |
102 | m_preview_image = image; |
103 | m_freeze = true; |
104 | } |
105 | |
106 | void QPixelTool::timerEvent(QTimerEvent *event) |
107 | { |
108 | if (event->timerId() == m_updateId && !m_freeze) { |
109 | grabScreen(); |
110 | } else if (event->timerId() == m_displayZoomId) { |
111 | killTimer(id: m_displayZoomId); |
112 | setZoomVisible(false); |
113 | } else if (event->timerId() == m_displayGridSizeId) { |
114 | killTimer(id: m_displayGridSizeId); |
115 | m_displayGridSize = false; |
116 | } |
117 | } |
118 | |
119 | void render_string(QPainter *p, int w, int h, const QString &text, int flags) |
120 | { |
121 | p->setBrush(QColor(255, 255, 255, 191)); |
122 | p->setPen(Qt::black); |
123 | QRect bounds; |
124 | p->drawText(x: 0, y: 0, w, h, flags: Qt::TextDontPrint | flags, str: text, br: &bounds); |
125 | |
126 | if (bounds.x() == 0) bounds.adjust(dx1: 0, dy1: 0, dx2: 10, dy2: 0); |
127 | else bounds.adjust(dx1: -10, dy1: 0, dx2: 0, dy2: 0); |
128 | |
129 | if (bounds.y() == 0) bounds.adjust(dx1: 0, dy1: 0, dx2: 0, dy2: 10); |
130 | else bounds.adjust(dx1: 0, dy1: -10, dx2: 0, dy2: 0); |
131 | |
132 | p->drawRect(r: bounds); |
133 | p->drawText(r: bounds, flags, text); |
134 | } |
135 | |
136 | static QImage imageLCDFilter(const QImage &image, int lcdMode) |
137 | { |
138 | Q_ASSERT(lcdMode > 0 && lcdMode < 5); |
139 | const bool vertical = (lcdMode > 2); |
140 | QImage scaled(image.width() * (vertical ? 1 : 3), |
141 | image.height() * (vertical ? 3 : 1), |
142 | image.format()); |
143 | |
144 | const int w = image.width(); |
145 | const int h = image.height(); |
146 | if (!vertical) { |
147 | for (int y = 0; y < h; ++y) { |
148 | const QRgb *in = reinterpret_cast<const QRgb *>(image.scanLine(y)); |
149 | QRgb *out = reinterpret_cast<QRgb *>(scaled.scanLine(y)); |
150 | if (lcdMode == 1) { |
151 | for (int x = 0; x < w; ++x) { |
152 | *out++ = in[x] & 0xffff0000; |
153 | *out++ = in[x] & 0xff00ff00; |
154 | *out++ = in[x] & 0xff0000ff; |
155 | } |
156 | } else { |
157 | for (int x = 0; x < w; ++x) { |
158 | *out++ = in[x] & 0xff0000ff; |
159 | *out++ = in[x] & 0xff00ff00; |
160 | *out++ = in[x] & 0xffff0000; |
161 | } |
162 | } |
163 | } |
164 | } else { |
165 | for (int y = 0; y < h; ++y) { |
166 | const QRgb *in = reinterpret_cast<const QRgb *>(image.scanLine(y)); |
167 | QRgb *out1 = reinterpret_cast<QRgb *>(scaled.scanLine(y * 3 + 0)); |
168 | QRgb *out2 = reinterpret_cast<QRgb *>(scaled.scanLine(y * 3 + 1)); |
169 | QRgb *out3 = reinterpret_cast<QRgb *>(scaled.scanLine(y * 3 + 2)); |
170 | if (lcdMode == 2) { |
171 | for (int x = 0; x < w; ++x) { |
172 | out1[x] = in[x] & 0xffff0000; |
173 | out2[x] = in[x] & 0xff00ff00; |
174 | out3[x] = in[x] & 0xff0000ff; |
175 | } |
176 | } else { |
177 | for (int x = 0; x < w; ++x) { |
178 | out1[x] = in[x] & 0xff0000ff; |
179 | out2[x] = in[x] & 0xff00ff00; |
180 | out3[x] = in[x] & 0xffff0000; |
181 | } |
182 | } |
183 | } |
184 | } |
185 | return scaled; |
186 | } |
187 | |
188 | void QPixelTool::paintEvent(QPaintEvent *) |
189 | { |
190 | QPainter p(this); |
191 | |
192 | if (m_preview_mode) { |
193 | QPixmap pixmap(40, 40); |
194 | QPainter pt(&pixmap); |
195 | pt.fillRect(x: 0, y: 0, w: 20, h: 20, c: Qt::white); |
196 | pt.fillRect(x: 20, y: 20, w: 20, h: 20, c: Qt::white); |
197 | pt.fillRect(x: 20, y: 0, w: 20, h: 20, c: Qt::lightGray); |
198 | pt.fillRect(x: 0, y: 20, w: 20, h: 20, c: Qt::lightGray); |
199 | pt.end(); |
200 | p.fillRect(x: 0, y: 0, w: width(), h: height(), b: pixmap); |
201 | } |
202 | |
203 | int w = width(); |
204 | int h = height(); |
205 | |
206 | p.save(); |
207 | if (m_lcdMode == 0) { |
208 | p.scale(sx: m_zoom, sy: m_zoom); |
209 | p.drawPixmap(x: 0, y: 0, pm: m_buffer); |
210 | } else { |
211 | if (m_lcdMode <= 2) |
212 | p.scale(sx: m_zoom / 3.0, sy: m_zoom); |
213 | else |
214 | p.scale(sx: m_zoom, sy: m_zoom / 3.0); |
215 | p.drawImage(x: 0, y: 0, image: imageLCDFilter(image: m_buffer.toImage(), lcdMode: m_lcdMode)); |
216 | } |
217 | p.restore(); |
218 | |
219 | // Draw the grid on top. |
220 | if (m_gridActive) { |
221 | p.setPen(m_gridActive == 1 ? Qt::black : Qt::white); |
222 | int incr = m_gridSize * m_zoom; |
223 | if (m_lcdMode == 0 || m_lcdMode > 2) { |
224 | for (int x=0; x<w; x+=incr) |
225 | p.drawLine(x1: x, y1: 0, x2: x, y2: h); |
226 | } |
227 | if (m_lcdMode <= 2) { |
228 | for (int y=0; y<h; y+=incr) |
229 | p.drawLine(x1: 0, y1: y, x2: w, y2: y); |
230 | } |
231 | } |
232 | |
233 | QFont f(QLatin1String("courier" )); |
234 | f.setBold(true); |
235 | p.setFont(f); |
236 | |
237 | if (m_displayZoom) { |
238 | render_string(p: &p, w, h, |
239 | text: QLatin1String("Zoom: x" ) + QString::number(m_zoom), |
240 | flags: Qt::AlignTop | Qt::AlignRight); |
241 | } |
242 | |
243 | if (m_displayGridSize) { |
244 | render_string(p: &p, w, h, |
245 | text: QLatin1String("Grid size: " ) + QString::number(m_gridSize), |
246 | flags: Qt::AlignBottom | Qt::AlignLeft); |
247 | } |
248 | |
249 | if (m_freeze) { |
250 | QString str = QString::asprintf(format: "%8X (%3d,%3d,%3d,%3d)" , |
251 | m_currentColor, |
252 | (0xff000000 & m_currentColor) >> 24, |
253 | (0x00ff0000 & m_currentColor) >> 16, |
254 | (0x0000ff00 & m_currentColor) >> 8, |
255 | (0x000000ff & m_currentColor)); |
256 | render_string(p: &p, w, h, |
257 | text: str, |
258 | flags: Qt::AlignBottom | Qt::AlignRight); |
259 | } |
260 | |
261 | if (m_mouseDown && m_dragStart != m_dragCurrent) { |
262 | int x1 = (m_dragStart.x() / m_zoom) * m_zoom; |
263 | int y1 = (m_dragStart.y() / m_zoom) * m_zoom; |
264 | int x2 = (m_dragCurrent.x() / m_zoom) * m_zoom; |
265 | int y2 = (m_dragCurrent.y() / m_zoom) * m_zoom; |
266 | QRect r = QRect(x1, y1, x2 - x1, y2 - y1).normalized(); |
267 | p.setBrush(Qt::NoBrush); |
268 | p.setPen(QPen(Qt::red, 3, Qt::SolidLine)); |
269 | p.drawRect(r); |
270 | p.setPen(QPen(Qt::black, 1, Qt::SolidLine)); |
271 | p.drawRect(r); |
272 | |
273 | QString str = QString::asprintf(format: "Rect: x=%d, y=%d, w=%d, h=%d" , |
274 | r.x() / m_zoom, |
275 | r.y() / m_zoom, |
276 | r.width() / m_zoom, |
277 | r.height() / m_zoom); |
278 | render_string(p: &p, w, h, text: str, flags: Qt::AlignBottom | Qt::AlignLeft); |
279 | } |
280 | |
281 | |
282 | } |
283 | |
284 | void QPixelTool::keyPressEvent(QKeyEvent *e) |
285 | { |
286 | switch (e->key()) { |
287 | case Qt::Key_Space: |
288 | toggleFreeze(); |
289 | break; |
290 | case Qt::Key_Plus: |
291 | increaseZoom(); |
292 | break; |
293 | case Qt::Key_Minus: |
294 | decreaseZoom(); |
295 | break; |
296 | case Qt::Key_PageUp: |
297 | setGridSize(m_gridSize + 1); |
298 | break; |
299 | case Qt::Key_PageDown: |
300 | setGridSize(m_gridSize - 1); |
301 | break; |
302 | case Qt::Key_G: |
303 | toggleGrid(); |
304 | break; |
305 | case Qt::Key_A: |
306 | m_autoUpdate = !m_autoUpdate; |
307 | break; |
308 | #if QT_CONFIG(clipboard) |
309 | case Qt::Key_C: |
310 | if (e->modifiers().testFlag(flag: Qt::ControlModifier)) |
311 | copyToClipboard(); |
312 | else |
313 | copyColorToClipboard(); |
314 | break; |
315 | #endif // QT_CONFIG(clipboard) |
316 | case Qt::Key_S: |
317 | if (e->modifiers() & Qt::ControlModifier) { |
318 | releaseKeyboard(); |
319 | saveToFile(); |
320 | } |
321 | break; |
322 | case Qt::Key_Control: |
323 | grabKeyboard(); |
324 | break; |
325 | case Qt::Key_F1: |
326 | aboutPixelTool(); |
327 | break; |
328 | } |
329 | } |
330 | |
331 | void QPixelTool::keyReleaseEvent(QKeyEvent *e) |
332 | { |
333 | switch(e->key()) { |
334 | case Qt::Key_Control: |
335 | releaseKeyboard(); |
336 | break; |
337 | default: |
338 | break; |
339 | } |
340 | } |
341 | |
342 | void QPixelTool::resizeEvent(QResizeEvent *) |
343 | { |
344 | grabScreen(); |
345 | } |
346 | |
347 | void QPixelTool::mouseMoveEvent(QMouseEvent *e) |
348 | { |
349 | if (m_mouseDown) |
350 | m_dragCurrent = e->pos(); |
351 | |
352 | int x = e->x() / m_zoom; |
353 | int y = e->y() / m_zoom; |
354 | |
355 | QImage im = m_buffer.toImage().convertToFormat(f: QImage::Format_ARGB32); |
356 | if (x < im.width() && y < im.height() && x >= 0 && y >= 0) { |
357 | m_currentColor = im.pixel(x, y); |
358 | update(); |
359 | } |
360 | } |
361 | |
362 | void QPixelTool::mousePressEvent(QMouseEvent *e) |
363 | { |
364 | if (!m_freeze) |
365 | return; |
366 | m_mouseDown = true; |
367 | m_dragStart = e->pos(); |
368 | } |
369 | |
370 | void QPixelTool::mouseReleaseEvent(QMouseEvent *) |
371 | { |
372 | m_mouseDown = false; |
373 | } |
374 | |
375 | static QAction *(QMenu &, const QString &title, |
376 | bool value, const QKeySequence &key) |
377 | { |
378 | QAction *result = menu.addAction(text: title); |
379 | result->setCheckable(true); |
380 | result->setChecked(value); |
381 | result->setShortcut(key); |
382 | return result; |
383 | } |
384 | |
385 | static QAction *(QMenu &, const QString &title, |
386 | bool value, const QKeySequence &key, |
387 | QActionGroup *group) |
388 | { |
389 | QAction *result = addCheckableAction(menu, title, value, key); |
390 | result->setActionGroup(group); |
391 | return result; |
392 | } |
393 | |
394 | void QPixelTool::(QContextMenuEvent *e) |
395 | { |
396 | const bool tmpFreeze = m_freeze; |
397 | m_freeze = true; |
398 | |
399 | QMenu ; |
400 | menu.addAction(text: QLatin1String("Qt Pixel Zooming Tool" ))->setEnabled(false); |
401 | menu.addSeparator(); |
402 | |
403 | // Grid color options... |
404 | QActionGroup *gridGroup = new QActionGroup(&menu); |
405 | addCheckableAction(menu, title: QLatin1String("White grid" ), value: m_gridActive == 2, |
406 | key: Qt::Key_W, group: gridGroup); |
407 | QAction *blackGrid = addCheckableAction(menu, title: QLatin1String("Black grid" ), |
408 | value: m_gridActive == 1, key: Qt::Key_B, group: gridGroup); |
409 | QAction *noGrid = addCheckableAction(menu, title: QLatin1String("No grid" ), value: m_gridActive == 0, |
410 | key: Qt::Key_N, group: gridGroup); |
411 | menu.addSeparator(); |
412 | |
413 | // Grid size options |
414 | menu.addAction(text: QLatin1String("Increase grid size" ), |
415 | object: this, slot: &QPixelTool::increaseGridSize, shortcut: Qt::Key_PageUp); |
416 | menu.addAction(text: QLatin1String("Decrease grid size" ), |
417 | object: this, slot: &QPixelTool::decreaseGridSize, shortcut: Qt::Key_PageDown); |
418 | menu.addSeparator(); |
419 | |
420 | QActionGroup *lcdGroup = new QActionGroup(&menu); |
421 | addCheckableAction(menu, title: QLatin1String("No subpixels" ), value: m_lcdMode == 0, |
422 | key: QKeySequence(), group: lcdGroup); |
423 | QAction *rgbPixels = addCheckableAction(menu, title: QLatin1String("RGB subpixels" ), |
424 | value: m_lcdMode == 1, key: QKeySequence(), group: lcdGroup); |
425 | QAction *bgrPixels = addCheckableAction(menu, title: QLatin1String("BGR subpixels" ), |
426 | value: m_lcdMode == 2, key: QKeySequence(), group: lcdGroup); |
427 | QAction *vrgbPixels = addCheckableAction(menu, title: QLatin1String("VRGB subpixels" ), |
428 | value: m_lcdMode == 3, key: QKeySequence(), group: lcdGroup); |
429 | QAction *vbgrPixels = addCheckableAction(menu, title: QLatin1String("VBGR subpixels" ), |
430 | value: m_lcdMode == 4, key: QKeySequence(), group: lcdGroup); |
431 | menu.addSeparator(); |
432 | |
433 | // Zoom options |
434 | menu.addAction(text: QLatin1String("Zoom in" ), |
435 | object: this, slot: &QPixelTool::increaseZoom, shortcut: Qt::Key_Plus); |
436 | menu.addAction(text: QLatin1String("Zoom out" ), |
437 | object: this, slot: &QPixelTool::decreaseZoom, shortcut: Qt::Key_Minus); |
438 | menu.addSeparator(); |
439 | |
440 | // Freeze / Autoupdate |
441 | QAction *freeze = addCheckableAction(menu, title: QLatin1String("Frozen" ), |
442 | value: tmpFreeze, key: Qt::Key_Space); |
443 | QAction *autoUpdate = addCheckableAction(menu, title: QLatin1String("Continuous update" ), |
444 | value: m_autoUpdate, key: Qt::Key_A); |
445 | menu.addSeparator(); |
446 | |
447 | // Copy to clipboard / save |
448 | menu.addAction(text: QLatin1String("Save as image..." ), |
449 | object: this, slot: &QPixelTool::saveToFile, shortcut: QKeySequence::SaveAs); |
450 | #if QT_CONFIG(clipboard) |
451 | menu.addAction(text: QLatin1String("Copy to clipboard" ), |
452 | object: this, slot: &QPixelTool::copyToClipboard, shortcut: QKeySequence::Copy); |
453 | menu.addAction(text: QLatin1String("Copy color value to clipboard" ), |
454 | object: this, slot: &QPixelTool::copyColorToClipboard, shortcut: Qt::Key_C); |
455 | #endif // QT_CONFIG(clipboard) |
456 | |
457 | menu.addSeparator(); |
458 | menu.addAction(text: QLatin1String("About Qt" ), qApp, slot: &QApplication::aboutQt); |
459 | menu.addAction(text: QLatin1String("About Qt Pixeltool" ), object: this, slot: &QPixelTool::aboutPixelTool); |
460 | |
461 | menu.exec(pos: mapToGlobal(e->pos())); |
462 | |
463 | // Read out grid settings |
464 | if (noGrid->isChecked()) |
465 | m_gridActive = 0; |
466 | else if (blackGrid->isChecked()) |
467 | m_gridActive = 1; |
468 | else |
469 | m_gridActive = 2; |
470 | |
471 | // Read out lcd settings |
472 | if (rgbPixels->isChecked()) |
473 | m_lcdMode = 1; |
474 | else if (bgrPixels->isChecked()) |
475 | m_lcdMode = 2; |
476 | else if (vrgbPixels->isChecked()) |
477 | m_lcdMode = 3; |
478 | else if (vbgrPixels->isChecked()) |
479 | m_lcdMode = 4; |
480 | else |
481 | m_lcdMode = 0; |
482 | |
483 | m_autoUpdate = autoUpdate->isChecked(); |
484 | m_freeze = freeze->isChecked(); |
485 | |
486 | // LCD mode looks off unless zoom is dividable by 3 |
487 | if (m_lcdMode && m_zoom % 3) |
488 | setZoom(qMax(a: 3, b: (m_zoom + 1) / 3)); |
489 | } |
490 | |
491 | QSize QPixelTool::sizeHint() const |
492 | { |
493 | return m_initialSize; |
494 | } |
495 | |
496 | static inline QString pixelToolTitle(QPoint pos, const QColor ¤tColor) |
497 | { |
498 | if (QHighDpiScaling::isActive()) { |
499 | if (auto screen = QGuiApplication::screenAt(point: pos)) |
500 | pos = QHighDpi::toNativePixels(value: pos, context: screen); |
501 | } |
502 | return QCoreApplication::applicationName() + QLatin1String(" [" ) |
503 | + QString::number(pos.x()) |
504 | + QLatin1String(", " ) + QString::number(pos.y()) + QLatin1String("] " ) |
505 | + currentColor.name(); |
506 | } |
507 | |
508 | void QPixelTool::grabScreen() |
509 | { |
510 | if (m_preview_mode) { |
511 | int w = qMin(a: width() / m_zoom + 1, b: m_preview_image.width()); |
512 | int h = qMin(a: height() / m_zoom + 1, b: m_preview_image.height()); |
513 | m_buffer = QPixmap::fromImage(image: m_preview_image).copy(ax: 0, ay: 0, awidth: w, aheight: h); |
514 | update(); |
515 | return; |
516 | } |
517 | |
518 | QPoint mousePos = QCursor::pos(); |
519 | if (mousePos == m_lastMousePos && !m_autoUpdate) |
520 | return; |
521 | |
522 | if (m_lastMousePos != mousePos) |
523 | setWindowTitle(pixelToolTitle(pos: mousePos, currentColor: m_currentColor)); |
524 | |
525 | int w = int(width() / float(m_zoom)); |
526 | int h = int(height() / float(m_zoom)); |
527 | |
528 | if (width() % m_zoom > 0) |
529 | ++w; |
530 | if (height() % m_zoom > 0) |
531 | ++h; |
532 | |
533 | int x = mousePos.x() - w/2; |
534 | int y = mousePos.y() - h/2; |
535 | |
536 | const QBrush darkBrush = palette().color(cr: QPalette::Dark); |
537 | const QDesktopWidget *desktopWidget = QApplication::desktop(); |
538 | if (QScreen *screen = this->screen()) { |
539 | m_buffer = screen->grabWindow(window: desktopWidget->winId(), x, y, w, h); |
540 | } else { |
541 | m_buffer = QPixmap(w, h); |
542 | m_buffer.fill(fillColor: darkBrush.color()); |
543 | } |
544 | QRegion geom(x, y, w, h); |
545 | QRect screenRect; |
546 | const auto screens = QGuiApplication::screens(); |
547 | for (auto screen : screens) |
548 | screenRect |= screen->geometry(); |
549 | geom -= screenRect; |
550 | const auto rectsInRegion = geom.rectCount(); |
551 | if (rectsInRegion > 0) { |
552 | QPainter p(&m_buffer); |
553 | p.translate(dx: -x, dy: -y); |
554 | p.setPen(Qt::NoPen); |
555 | p.setBrush(darkBrush); |
556 | p.drawRects(rects: geom.begin(), rectCount: rectsInRegion); |
557 | } |
558 | |
559 | update(); |
560 | |
561 | m_currentColor = m_buffer.toImage().pixel(pt: m_buffer.rect().center()); |
562 | m_lastMousePos = mousePos; |
563 | } |
564 | |
565 | void QPixelTool::startZoomVisibleTimer() |
566 | { |
567 | if (m_displayZoomId > 0) { |
568 | killTimer(id: m_displayZoomId); |
569 | } |
570 | m_displayZoomId = startTimer(interval: 5000); |
571 | setZoomVisible(true); |
572 | } |
573 | |
574 | void QPixelTool::startGridSizeVisibleTimer() |
575 | { |
576 | if (m_gridActive) { |
577 | if (m_displayGridSizeId > 0) |
578 | killTimer(id: m_displayGridSizeId); |
579 | m_displayGridSizeId = startTimer(interval: 5000); |
580 | m_displayGridSize = true; |
581 | update(); |
582 | } |
583 | } |
584 | |
585 | void QPixelTool::setZoomVisible(bool visible) |
586 | { |
587 | m_displayZoom = visible; |
588 | update(); |
589 | } |
590 | |
591 | void QPixelTool::toggleFreeze() |
592 | { |
593 | m_freeze = !m_freeze; |
594 | if (!m_freeze) |
595 | m_dragStart = m_dragCurrent = QPoint(); |
596 | } |
597 | |
598 | void QPixelTool::increaseZoom() |
599 | { |
600 | if (!m_lcdMode) |
601 | setZoom(m_zoom + 1); |
602 | else |
603 | setZoom(m_zoom + 3); |
604 | } |
605 | |
606 | void QPixelTool::decreaseZoom() |
607 | { |
608 | if (!m_lcdMode) |
609 | setZoom(m_zoom - 1); |
610 | else |
611 | setZoom(m_zoom - 3); |
612 | } |
613 | |
614 | void QPixelTool::setZoom(int zoom) |
615 | { |
616 | if (zoom > 0) { |
617 | QPoint pos = m_lastMousePos; |
618 | m_lastMousePos = QPoint(); |
619 | m_zoom = zoom; |
620 | grabScreen(); |
621 | m_lastMousePos = pos; |
622 | m_dragStart = m_dragCurrent = QPoint(); |
623 | startZoomVisibleTimer(); |
624 | } |
625 | } |
626 | |
627 | void QPixelTool::toggleGrid() |
628 | { |
629 | if (++m_gridActive > 2) |
630 | m_gridActive = 0; |
631 | update(); |
632 | } |
633 | |
634 | void QPixelTool::setGridSize(int gridSize) |
635 | { |
636 | if (m_gridActive && gridSize > 0) { |
637 | m_gridSize = gridSize; |
638 | startGridSizeVisibleTimer(); |
639 | update(); |
640 | } |
641 | } |
642 | |
643 | #if QT_CONFIG(clipboard) |
644 | void QPixelTool::copyToClipboard() |
645 | { |
646 | QGuiApplication::clipboard()->setPixmap(m_buffer); |
647 | } |
648 | |
649 | void QPixelTool::copyColorToClipboard() |
650 | { |
651 | QGuiApplication::clipboard()->setText(QColor(m_currentColor).name()); |
652 | } |
653 | #endif // QT_CONFIG(clipboard) |
654 | |
655 | void QPixelTool::saveToFile() |
656 | { |
657 | bool oldFreeze = m_freeze; |
658 | m_freeze = true; |
659 | |
660 | QFileDialog fileDialog(this); |
661 | fileDialog.setWindowTitle(QLatin1String("Save as image" )); |
662 | fileDialog.setAcceptMode(QFileDialog::AcceptSave); |
663 | fileDialog.setDirectory(QStandardPaths::writableLocation(type: QStandardPaths::PicturesLocation)); |
664 | QStringList mimeTypes; |
665 | const QByteArrayList supportedMimeTypes = QImageWriter::supportedMimeTypes(); |
666 | for (const QByteArray &mimeTypeB : supportedMimeTypes) |
667 | mimeTypes.append(t: QString::fromLatin1(str: mimeTypeB)); |
668 | fileDialog.setMimeTypeFilters(mimeTypes); |
669 | const QString pngType = QLatin1String("image/png" ); |
670 | if (mimeTypes.contains(str: pngType)) { |
671 | fileDialog.selectMimeTypeFilter(filter: pngType); |
672 | fileDialog.setDefaultSuffix(QLatin1String("png" )); |
673 | } |
674 | |
675 | while (fileDialog.exec() == QDialog::Accepted |
676 | && !m_buffer.save(fileName: fileDialog.selectedFiles().constFirst())) { |
677 | QMessageBox::warning(parent: this, title: QLatin1String("Unable to write image" ), |
678 | text: QLatin1String("Unable to write " ) |
679 | + QDir::toNativeSeparators(pathName: fileDialog.selectedFiles().first())); |
680 | } |
681 | m_freeze = oldFreeze; |
682 | } |
683 | |
684 | QTextStream &operator<<(QTextStream &str, const QScreen *screen) |
685 | { |
686 | const QRect geometry = screen->geometry(); |
687 | str << '"' << screen->name() << "\" " << geometry.width() |
688 | << 'x' << geometry.height() << Qt::forcesign << geometry.x() << geometry.y() |
689 | << Qt::noforcesign << ", " << qRound(d: screen->logicalDotsPerInch()) << "DPI" |
690 | << ", Depth: " << screen->depth() << ", " << screen->refreshRate() << "Hz" ; |
691 | const qreal dpr = screen->devicePixelRatio(); |
692 | if (!qFuzzyCompare(p1: dpr, p2: qreal(1))) |
693 | str << ", DPR: " << dpr; |
694 | return str; |
695 | } |
696 | |
697 | QString QPixelTool::aboutText() const |
698 | { |
699 | const QList<QScreen *> screens = QGuiApplication::screens(); |
700 | const QScreen *windowScreen = windowHandle()->screen(); |
701 | |
702 | QString result; |
703 | QTextStream str(&result); |
704 | str << "<html></head><body><h2>Qt Pixeltool</h2><p>Qt " << QT_VERSION_STR |
705 | << "</p><p>Copyright (C) 2017 The Qt Company Ltd.</p><h3>Screens</h3><ul>" ; |
706 | for (const QScreen *screen : screens) |
707 | str << "<li>" << (screen == windowScreen ? "* " : " " ) << screen << "</li>" ; |
708 | str << "<ul></body></html>" ; |
709 | return result; |
710 | } |
711 | |
712 | void QPixelTool::aboutPixelTool() |
713 | { |
714 | QMessageBox aboutBox(QMessageBox::Information, tr(s: "About Qt Pixeltool" ), aboutText(), |
715 | QMessageBox::Close, this); |
716 | aboutBox.setWindowFlags(aboutBox.windowFlags() & ~Qt::WindowContextHelpButtonHint); |
717 | aboutBox.setTextInteractionFlags(Qt::TextBrowserInteraction); |
718 | aboutBox.exec(); |
719 | } |
720 | |
721 | QT_END_NAMESPACE |
722 | |