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