| 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 | |