/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the tools applications of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qpixeltool.h" #include #include #include #include #include #if QT_CONFIG(clipboard) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include QT_BEGIN_NAMESPACE static QPoint initialPos(const QSettings &settings, const QSize &initialSize) { const QPoint defaultPos = QGuiApplication::primaryScreen()->availableGeometry().topLeft(); const QPoint savedPos = settings.value(QLatin1String("position"), QVariant(defaultPos)).toPoint(); auto savedScreen = QGuiApplication::screenAt(savedPos); return savedScreen != nullptr && savedScreen->availableGeometry().intersects(QRect(savedPos, initialSize)) ? savedPos : defaultPos; } QPixelTool::QPixelTool(QWidget *parent) : QWidget(parent) { setWindowTitle(QCoreApplication::applicationName()); QSettings settings(QLatin1String("QtProject"), QLatin1String("QPixelTool")); m_autoUpdate = settings.value(QLatin1String("autoUpdate"), 0).toBool(); m_gridSize = settings.value(QLatin1String("gridSize"), 1).toInt(); m_gridActive = settings.value(QLatin1String("gridActive"), 1).toInt(); m_zoom = settings.value(QLatin1String("zoom"), 4).toInt(); m_initialSize = settings.value(QLatin1String("initialSize"), QSize(250, 200)).toSize(); m_lcdMode = settings.value(QLatin1String("lcdMode"), 0).toInt(); move(initialPos(settings, m_initialSize)); setMouseTracking(true); setAttribute(Qt::WA_OpaquePaintEvent); m_updateId = startTimer(30); } QPixelTool::~QPixelTool() { QSettings settings(QLatin1String("QtProject"), QLatin1String("QPixelTool")); settings.setValue(QLatin1String("autoUpdate"), int(m_autoUpdate)); settings.setValue(QLatin1String("gridSize"), m_gridSize); settings.setValue(QLatin1String("gridActive"), m_gridActive); settings.setValue(QLatin1String("zoom"), m_zoom); settings.setValue(QLatin1String("initialSize"), size()); settings.setValue(QLatin1String("position"), pos()); settings.setValue(QLatin1String("lcdMode"), m_lcdMode); } void QPixelTool::setPreviewImage(const QImage &image) { m_preview_mode = true; m_preview_image = image; m_freeze = true; } void QPixelTool::timerEvent(QTimerEvent *event) { if (event->timerId() == m_updateId && !m_freeze) { grabScreen(); } else if (event->timerId() == m_displayZoomId) { killTimer(m_displayZoomId); setZoomVisible(false); } else if (event->timerId() == m_displayGridSizeId) { killTimer(m_displayGridSizeId); m_displayGridSize = false; } } void render_string(QPainter *p, int w, int h, const QString &text, int flags) { p->setBrush(QColor(255, 255, 255, 191)); p->setPen(Qt::black); QRect bounds; p->drawText(0, 0, w, h, Qt::TextDontPrint | flags, text, &bounds); if (bounds.x() == 0) bounds.adjust(0, 0, 10, 0); else bounds.adjust(-10, 0, 0, 0); if (bounds.y() == 0) bounds.adjust(0, 0, 0, 10); else bounds.adjust(0, -10, 0, 0); p->drawRect(bounds); p->drawText(bounds, flags, text); } static QImage imageLCDFilter(const QImage &image, int lcdMode) { Q_ASSERT(lcdMode > 0 && lcdMode < 5); const bool vertical = (lcdMode > 2); QImage scaled(image.width() * (vertical ? 1 : 3), image.height() * (vertical ? 3 : 1), image.format()); const int w = image.width(); const int h = image.height(); if (!vertical) { for (int y = 0; y < h; ++y) { const QRgb *in = reinterpret_cast(image.scanLine(y)); QRgb *out = reinterpret_cast(scaled.scanLine(y)); if (lcdMode == 1) { for (int x = 0; x < w; ++x) { *out++ = in[x] & 0xffff0000; *out++ = in[x] & 0xff00ff00; *out++ = in[x] & 0xff0000ff; } } else { for (int x = 0; x < w; ++x) { *out++ = in[x] & 0xff0000ff; *out++ = in[x] & 0xff00ff00; *out++ = in[x] & 0xffff0000; } } } } else { for (int y = 0; y < h; ++y) { const QRgb *in = reinterpret_cast(image.scanLine(y)); QRgb *out1 = reinterpret_cast(scaled.scanLine(y * 3 + 0)); QRgb *out2 = reinterpret_cast(scaled.scanLine(y * 3 + 1)); QRgb *out3 = reinterpret_cast(scaled.scanLine(y * 3 + 2)); if (lcdMode == 2) { for (int x = 0; x < w; ++x) { out1[x] = in[x] & 0xffff0000; out2[x] = in[x] & 0xff00ff00; out3[x] = in[x] & 0xff0000ff; } } else { for (int x = 0; x < w; ++x) { out1[x] = in[x] & 0xff0000ff; out2[x] = in[x] & 0xff00ff00; out3[x] = in[x] & 0xffff0000; } } } } return scaled; } void QPixelTool::paintEvent(QPaintEvent *) { QPainter p(this); if (m_preview_mode) { QPixmap pixmap(40, 40); QPainter pt(&pixmap); pt.fillRect(0, 0, 20, 20, Qt::white); pt.fillRect(20, 20, 20, 20, Qt::white); pt.fillRect(20, 0, 20, 20, Qt::lightGray); pt.fillRect(0, 20, 20, 20, Qt::lightGray); pt.end(); p.fillRect(0, 0, width(), height(), pixmap); } int w = width(); int h = height(); p.save(); if (m_lcdMode == 0) { p.scale(m_zoom, m_zoom); p.drawPixmap(0, 0, m_buffer); } else { if (m_lcdMode <= 2) p.scale(m_zoom / 3.0, m_zoom); else p.scale(m_zoom, m_zoom / 3.0); p.drawImage(0, 0, imageLCDFilter(m_buffer.toImage(), m_lcdMode)); } p.restore(); // Draw the grid on top. if (m_gridActive) { p.setPen(m_gridActive == 1 ? Qt::black : Qt::white); int incr = m_gridSize * m_zoom; if (m_lcdMode == 0 || m_lcdMode > 2) { for (int x=0; x> 24, (0x00ff0000 & m_currentColor) >> 16, (0x0000ff00 & m_currentColor) >> 8, (0x000000ff & m_currentColor)); render_string(&p, w, h, str, Qt::AlignBottom | Qt::AlignRight); } if (m_mouseDown && m_dragStart != m_dragCurrent) { int x1 = (m_dragStart.x() / m_zoom) * m_zoom; int y1 = (m_dragStart.y() / m_zoom) * m_zoom; int x2 = (m_dragCurrent.x() / m_zoom) * m_zoom; int y2 = (m_dragCurrent.y() / m_zoom) * m_zoom; QRect r = QRect(x1, y1, x2 - x1, y2 - y1).normalized(); p.setBrush(Qt::NoBrush); p.setPen(QPen(Qt::red, 3, Qt::SolidLine)); p.drawRect(r); p.setPen(QPen(Qt::black, 1, Qt::SolidLine)); p.drawRect(r); QString str = QString::asprintf("Rect: x=%d, y=%d, w=%d, h=%d", r.x() / m_zoom, r.y() / m_zoom, r.width() / m_zoom, r.height() / m_zoom); render_string(&p, w, h, str, Qt::AlignBottom | Qt::AlignLeft); } } void QPixelTool::keyPressEvent(QKeyEvent *e) { switch (e->key()) { case Qt::Key_Space: toggleFreeze(); break; case Qt::Key_Plus: increaseZoom(); break; case Qt::Key_Minus: decreaseZoom(); break; case Qt::Key_PageUp: setGridSize(m_gridSize + 1); break; case Qt::Key_PageDown: setGridSize(m_gridSize - 1); break; case Qt::Key_G: toggleGrid(); break; case Qt::Key_A: m_autoUpdate = !m_autoUpdate; break; #if QT_CONFIG(clipboard) case Qt::Key_C: if (e->modifiers().testFlag(Qt::ControlModifier)) copyToClipboard(); else copyColorToClipboard(); break; #endif // QT_CONFIG(clipboard) case Qt::Key_S: if (e->modifiers() & Qt::ControlModifier) { releaseKeyboard(); saveToFile(); } break; case Qt::Key_Control: grabKeyboard(); break; case Qt::Key_F1: aboutPixelTool(); break; } } void QPixelTool::keyReleaseEvent(QKeyEvent *e) { switch(e->key()) { case Qt::Key_Control: releaseKeyboard(); break; default: break; } } void QPixelTool::resizeEvent(QResizeEvent *) { grabScreen(); } void QPixelTool::mouseMoveEvent(QMouseEvent *e) { if (m_mouseDown) m_dragCurrent = e->pos(); int x = e->x() / m_zoom; int y = e->y() / m_zoom; QImage im = m_buffer.toImage().convertToFormat(QImage::Format_ARGB32); if (x < im.width() && y < im.height() && x >= 0 && y >= 0) { m_currentColor = im.pixel(x, y); update(); } } void QPixelTool::mousePressEvent(QMouseEvent *e) { if (!m_freeze) return; m_mouseDown = true; m_dragStart = e->pos(); } void QPixelTool::mouseReleaseEvent(QMouseEvent *) { m_mouseDown = false; } static QAction *addCheckableAction(QMenu &menu, const QString &title, bool value, const QKeySequence &key) { QAction *result = menu.addAction(title); result->setCheckable(true); result->setChecked(value); result->setShortcut(key); return result; } static QAction *addCheckableAction(QMenu &menu, const QString &title, bool value, const QKeySequence &key, QActionGroup *group) { QAction *result = addCheckableAction(menu, title, value, key); result->setActionGroup(group); return result; } void QPixelTool::contextMenuEvent(QContextMenuEvent *e) { const bool tmpFreeze = m_freeze; m_freeze = true; QMenu menu; menu.addAction(QLatin1String("Qt Pixel Zooming Tool"))->setEnabled(false); menu.addSeparator(); // Grid color options... QActionGroup *gridGroup = new QActionGroup(&menu); addCheckableAction(menu, QLatin1String("White grid"), m_gridActive == 2, Qt::Key_W, gridGroup); QAction *blackGrid = addCheckableAction(menu, QLatin1String("Black grid"), m_gridActive == 1, Qt::Key_B, gridGroup); QAction *noGrid = addCheckableAction(menu, QLatin1String("No grid"), m_gridActive == 0, Qt::Key_N, gridGroup); menu.addSeparator(); // Grid size options menu.addAction(QLatin1String("Increase grid size"), this, &QPixelTool::increaseGridSize, Qt::Key_PageUp); menu.addAction(QLatin1String("Decrease grid size"), this, &QPixelTool::decreaseGridSize, Qt::Key_PageDown); menu.addSeparator(); QActionGroup *lcdGroup = new QActionGroup(&menu); addCheckableAction(menu, QLatin1String("No subpixels"), m_lcdMode == 0, QKeySequence(), lcdGroup); QAction *rgbPixels = addCheckableAction(menu, QLatin1String("RGB subpixels"), m_lcdMode == 1, QKeySequence(), lcdGroup); QAction *bgrPixels = addCheckableAction(menu, QLatin1String("BGR subpixels"), m_lcdMode == 2, QKeySequence(), lcdGroup); QAction *vrgbPixels = addCheckableAction(menu, QLatin1String("VRGB subpixels"), m_lcdMode == 3, QKeySequence(), lcdGroup); QAction *vbgrPixels = addCheckableAction(menu, QLatin1String("VBGR subpixels"), m_lcdMode == 4, QKeySequence(), lcdGroup); menu.addSeparator(); // Zoom options menu.addAction(QLatin1String("Zoom in"), this, &QPixelTool::increaseZoom, Qt::Key_Plus); menu.addAction(QLatin1String("Zoom out"), this, &QPixelTool::decreaseZoom, Qt::Key_Minus); menu.addSeparator(); // Freeze / Autoupdate QAction *freeze = addCheckableAction(menu, QLatin1String("Frozen"), tmpFreeze, Qt::Key_Space); QAction *autoUpdate = addCheckableAction(menu, QLatin1String("Continuous update"), m_autoUpdate, Qt::Key_A); menu.addSeparator(); // Copy to clipboard / save menu.addAction(QLatin1String("Save as image..."), this, &QPixelTool::saveToFile, QKeySequence::SaveAs); #if QT_CONFIG(clipboard) menu.addAction(QLatin1String("Copy to clipboard"), this, &QPixelTool::copyToClipboard, QKeySequence::Copy); menu.addAction(QLatin1String("Copy color value to clipboard"), this, &QPixelTool::copyColorToClipboard, Qt::Key_C); #endif // QT_CONFIG(clipboard) menu.addSeparator(); menu.addAction(QLatin1String("About Qt"), qApp, &QApplication::aboutQt); menu.addAction(QLatin1String("About Qt Pixeltool"), this, &QPixelTool::aboutPixelTool); menu.exec(mapToGlobal(e->pos())); // Read out grid settings if (noGrid->isChecked()) m_gridActive = 0; else if (blackGrid->isChecked()) m_gridActive = 1; else m_gridActive = 2; // Read out lcd settings if (rgbPixels->isChecked()) m_lcdMode = 1; else if (bgrPixels->isChecked()) m_lcdMode = 2; else if (vrgbPixels->isChecked()) m_lcdMode = 3; else if (vbgrPixels->isChecked()) m_lcdMode = 4; else m_lcdMode = 0; m_autoUpdate = autoUpdate->isChecked(); m_freeze = freeze->isChecked(); // LCD mode looks off unless zoom is dividable by 3 if (m_lcdMode && m_zoom % 3) setZoom(qMax(3, (m_zoom + 1) / 3)); } QSize QPixelTool::sizeHint() const { return m_initialSize; } static inline QString pixelToolTitle(QPoint pos, const QColor ¤tColor) { if (QHighDpiScaling::isActive()) { if (auto screen = QGuiApplication::screenAt(pos)) pos = QHighDpi::toNativePixels(pos, screen); } return QCoreApplication::applicationName() + QLatin1String(" [") + QString::number(pos.x()) + QLatin1String(", ") + QString::number(pos.y()) + QLatin1String("] ") + currentColor.name(); } void QPixelTool::grabScreen() { if (m_preview_mode) { int w = qMin(width() / m_zoom + 1, m_preview_image.width()); int h = qMin(height() / m_zoom + 1, m_preview_image.height()); m_buffer = QPixmap::fromImage(m_preview_image).copy(0, 0, w, h); update(); return; } QPoint mousePos = QCursor::pos(); if (mousePos == m_lastMousePos && !m_autoUpdate) return; if (m_lastMousePos != mousePos) setWindowTitle(pixelToolTitle(mousePos, m_currentColor)); int w = int(width() / float(m_zoom)); int h = int(height() / float(m_zoom)); if (width() % m_zoom > 0) ++w; if (height() % m_zoom > 0) ++h; int x = mousePos.x() - w/2; int y = mousePos.y() - h/2; const QBrush darkBrush = palette().color(QPalette::Dark); const QDesktopWidget *desktopWidget = QApplication::desktop(); if (QScreen *screen = QGuiApplication::screens().value(desktopWidget->screenNumber(this), nullptr)) { m_buffer = screen->grabWindow(desktopWidget->winId(), x, y, w, h); } else { m_buffer = QPixmap(w, h); m_buffer.fill(darkBrush.color()); } QRegion geom(x, y, w, h); QRect screenRect; const auto screens = QGuiApplication::screens(); for (auto screen : screens) screenRect |= screen->geometry(); geom -= screenRect; const auto rectsInRegion = geom.rectCount(); if (rectsInRegion > 0) { QPainter p(&m_buffer); p.translate(-x, -y); p.setPen(Qt::NoPen); p.setBrush(darkBrush); p.drawRects(geom.begin(), rectsInRegion); } update(); m_currentColor = m_buffer.toImage().pixel(m_buffer.rect().center()); m_lastMousePos = mousePos; } void QPixelTool::startZoomVisibleTimer() { if (m_displayZoomId > 0) { killTimer(m_displayZoomId); } m_displayZoomId = startTimer(5000); setZoomVisible(true); } void QPixelTool::startGridSizeVisibleTimer() { if (m_gridActive) { if (m_displayGridSizeId > 0) killTimer(m_displayGridSizeId); m_displayGridSizeId = startTimer(5000); m_displayGridSize = true; update(); } } void QPixelTool::setZoomVisible(bool visible) { m_displayZoom = visible; update(); } void QPixelTool::toggleFreeze() { m_freeze = !m_freeze; if (!m_freeze) m_dragStart = m_dragCurrent = QPoint(); } void QPixelTool::increaseZoom() { if (!m_lcdMode) setZoom(m_zoom + 1); else setZoom(m_zoom + 3); } void QPixelTool::decreaseZoom() { if (!m_lcdMode) setZoom(m_zoom - 1); else setZoom(m_zoom - 3); } void QPixelTool::setZoom(int zoom) { if (zoom > 0) { QPoint pos = m_lastMousePos; m_lastMousePos = QPoint(); m_zoom = zoom; grabScreen(); m_lastMousePos = pos; m_dragStart = m_dragCurrent = QPoint(); startZoomVisibleTimer(); } } void QPixelTool::toggleGrid() { if (++m_gridActive > 2) m_gridActive = 0; update(); } void QPixelTool::setGridSize(int gridSize) { if (m_gridActive && gridSize > 0) { m_gridSize = gridSize; startGridSizeVisibleTimer(); update(); } } #if QT_CONFIG(clipboard) void QPixelTool::copyToClipboard() { QGuiApplication::clipboard()->setPixmap(m_buffer); } void QPixelTool::copyColorToClipboard() { QGuiApplication::clipboard()->setText(QColor(m_currentColor).name()); } #endif // QT_CONFIG(clipboard) void QPixelTool::saveToFile() { bool oldFreeze = m_freeze; m_freeze = true; QFileDialog fileDialog(this); fileDialog.setWindowTitle(QLatin1String("Save as image")); fileDialog.setAcceptMode(QFileDialog::AcceptSave); fileDialog.setDirectory(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); QStringList mimeTypes; const QByteArrayList supportedMimeTypes = QImageWriter::supportedMimeTypes(); for (const QByteArray &mimeTypeB : supportedMimeTypes) mimeTypes.append(QString::fromLatin1(mimeTypeB)); fileDialog.setMimeTypeFilters(mimeTypes); const QString pngType = QLatin1String("image/png"); if (mimeTypes.contains(pngType)) { fileDialog.selectMimeTypeFilter(pngType); fileDialog.setDefaultSuffix(QLatin1String("png")); } while (fileDialog.exec() == QDialog::Accepted && !m_buffer.save(fileDialog.selectedFiles().constFirst())) { QMessageBox::warning(this, QLatin1String("Unable to write image"), QLatin1String("Unable to write ") + QDir::toNativeSeparators(fileDialog.selectedFiles().first())); } m_freeze = oldFreeze; } QTextStream &operator<<(QTextStream &str, const QScreen *screen) { const QRect geometry = screen->geometry(); str << '"' << screen->name() << "\" " << geometry.width() << 'x' << geometry.height() << forcesign << geometry.x() << geometry.y() << noforcesign << ", " << qRound(screen->logicalDotsPerInch()) << "DPI" << ", Depth: " << screen->depth() << ", " << screen->refreshRate() << "Hz"; const qreal dpr = screen->devicePixelRatio(); if (!qFuzzyCompare(dpr, qreal(1))) str << ", DPR: " << dpr; return str; } QString QPixelTool::aboutText() const { const QList screens = QGuiApplication::screens(); const QScreen *windowScreen = windowHandle()->screen(); QString result; QTextStream str(&result); str << "

Qt Pixeltool

Qt " << QT_VERSION_STR << "

Copyright (C) 2017 The Qt Company Ltd.

Screens

    "; for (const QScreen *screen : screens) str << "
  • " << (screen == windowScreen ? "* " : " ") << screen << "
  • "; str << "
      "; return result; } void QPixelTool::aboutPixelTool() { QMessageBox aboutBox(QMessageBox::Information, tr("About Qt Pixeltool"), aboutText(), QMessageBox::Close, this); aboutBox.setWindowFlags(aboutBox.windowFlags() & ~Qt::WindowContextHelpButtonHint); aboutBox.setTextInteractionFlags(Qt::TextBrowserInteraction); aboutBox.exec(); } QT_END_NAMESPACE