6#include <qapplication.h>
9#if QT_CONFIG(clipboard)
10#include <qclipboard.h>
14#include <qfiledialog.h>
15#include <qmessagebox.h>
18#include <qactiongroup.h>
19#include <qimagewriter.h>
20#include <qstandardpaths.h>
21#include <qtextstream.h>
23#include <qmetaobject.h>
24#include <private/qhighdpiscaling_p.h>
32using namespace Qt::StringLiterals;
46 const QPoint defaultPos = QGuiApplication::primaryScreen()->availableGeometry().topLeft();
47 const QPoint savedPos =
48 settings.value(positionKey, QVariant(defaultPos)).toPoint();
49 auto *savedScreen = QGuiApplication::screenAt(savedPos);
50 return savedScreen !=
nullptr
51 && savedScreen->availableGeometry().intersects(QRect(savedPos, initialSize))
52 ? savedPos : defaultPos;
58 setWindowTitle(QCoreApplication::applicationName());
59 QSettings settings(organization, settingsGroup);
60 m_autoUpdate = settings.value(autoUpdateKey, 0).toBool();
61 m_gridSize = settings.value(gridSizeKey, 1).toInt();
62 m_gridActive = settings.value(gridActiveKey, 1).toInt();
63 m_zoom = settings.value(zoomKey, 4).toInt();
64 m_initialSize = settings.value(initialSizeKey, QSize(250, 200)).toSize();
65 m_lcdMode = settings.value(lcdModeKey, 0).toInt();
67 move(initialPos(settings, m_initialSize));
69 setMouseTracking(
true);
70 setAttribute(Qt::WA_OpaquePaintEvent);
71 m_updateId = startTimer(30);
76 QSettings settings(organization, settingsGroup);
77 settings.setValue(autoUpdateKey,
int(m_autoUpdate));
78 settings.setValue(gridSizeKey, m_gridSize);
79 settings.setValue(gridActiveKey, m_gridActive);
80 settings.setValue(zoomKey, m_zoom);
81 settings.setValue(initialSizeKey, size());
82 settings.setValue(positionKey, pos());
83 settings.setValue(lcdModeKey, m_lcdMode);
88 m_preview_mode =
true;
89 m_preview_image = image;
95 if (event->timerId() == m_updateId && !m_freeze) {
97 }
else if (event->timerId() == m_displayZoomId) {
98 killTimer(m_displayZoomId);
101 }
else if (event->timerId() == m_displayGridSizeId) {
102 killTimer(m_displayGridSizeId);
103 m_displayGridSizeId = 0;
104 m_displayGridSize =
false;
108void render_string(QPainter *p,
int w,
int h,
const QString &text,
int flags)
110 p->setBrush(QColor(255, 255, 255, 191));
111 p->setPen(Qt::black);
113 p->drawText(0, 0, w, h, Qt::TextDontPrint | flags, text, &bounds);
115 if (bounds.x() == 0) bounds.adjust(0, 0, 10, 0);
116 else bounds.adjust(-10, 0, 0, 0);
118 if (bounds.y() == 0) bounds.adjust(0, 0, 0, 10);
119 else bounds.adjust(0, -10, 0, 0);
122 p->drawText(bounds, flags, text);
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),
133 const int w = image.width();
134 const int h = image.height();
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));
140 for (
int x = 0; x < w; ++x) {
141 *out++ = in[x] & 0xffff0000;
142 *out++ = in[x] & 0xff00ff00;
143 *out++ = in[x] & 0xff0000ff;
146 for (
int x = 0; x < w; ++x) {
147 *out++ = in[x] & 0xff0000ff;
148 *out++ = in[x] & 0xff00ff00;
149 *out++ = in[x] & 0xffff0000;
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));
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;
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;
181 if (m_preview_mode) {
182 QPixmap pixmap(40, 40);
184 pt.fillRect(0, 0, 20, 20, Qt::white);
185 pt.fillRect(20, 20, 20, 20, Qt::white);
186 pt.fillRect(20, 0, 20, 20, Qt::lightGray);
187 pt.fillRect(0, 20, 20, 20, Qt::lightGray);
189 p.fillRect(0, 0, width(), height(), pixmap);
196 if (m_lcdMode == 0) {
197 p.scale(m_zoom, m_zoom);
198 p.drawPixmap(0, 0, m_buffer);
201 p.scale(m_zoom / 3.0, m_zoom);
203 p.scale(m_zoom, m_zoom / 3.0);
204 p.drawImage(0, 0, imageLCDFilter(m_buffer.toImage(), m_lcdMode));
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(x, 0, x, h);
216 if (m_lcdMode <= 2) {
217 for (
int y=0; y<h; y+=incr)
218 p.drawLine(0, y, w, y);
222 QFont f(QStringList{u"courier"_s}, -1, QFont::Bold);
226 render_string(&p, w, h,
227 "Zoom: x"_L1 + QString::number(m_zoom),
228 Qt::AlignTop | Qt::AlignRight);
231 if (m_displayGridSize) {
232 render_string(&p, w, h,
233 "Grid size: "_L1 + QString::number(m_gridSize),
234 Qt::AlignBottom | Qt::AlignLeft);
238 QString str = QString::asprintf(
"%8X (%3d,%3d,%3d,%3d)",
240 (0xff000000 & m_currentColor) >> 24,
241 (0x00ff0000 & m_currentColor) >> 16,
242 (0x0000ff00 & m_currentColor) >> 8,
243 (0x000000ff & m_currentColor));
244 render_string(&p, w, h,
246 Qt::AlignBottom | Qt::AlignRight);
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));
258 p.setPen(QPen(Qt::black, 1, Qt::SolidLine));
261 QString str = QString::asprintf(
"Rect: x=%d, y=%d, w=%d, h=%d",
265 r.height() / m_zoom);
266 render_string(&p, w, h, str, Qt::AlignBottom | Qt::AlignLeft);
287 case Qt::Key_PageDown:
294 m_autoUpdate = !m_autoUpdate;
296#if QT_CONFIG(clipboard)
298 if (e->modifiers().testFlag(Qt::ControlModifier))
301 copyColorToClipboard();
305 if (e->modifiers().testFlag(Qt::ControlModifier)) {
310 case Qt::Key_Control:
322 case Qt::Key_Control:
338 m_dragCurrent = e->pos();
340 const auto pos = e->position().toPoint();
341 const int x = pos.x() / m_zoom;
342 const int y = pos.y() / m_zoom;
344 QImage im = m_buffer.toImage().convertToFormat(QImage::Format_ARGB32);
345 if (x < im.width() && y < im.height() && x >= 0 && y >= 0) {
346 m_currentColor = im.pixel(x, y);
356 m_dragStart = e->pos();
365 bool value,
const QKeySequence &key)
367 QAction *result = menu.addAction(title);
368 result->setCheckable(
true);
369 result->setChecked(value);
370 result->setShortcut(key);
375 bool value,
const QKeySequence &key,
378 QAction *result = addCheckableAction(menu, title, value, key);
379 result->setActionGroup(group);
385 const bool tmpFreeze = m_freeze;
389 menu.addAction(
"Qt Pixel Zooming Tool"_L1)->setEnabled(
false);
393 auto *gridGroup =
new QActionGroup(&menu);
394 addCheckableAction(menu,
"White grid"_L1, m_gridActive == 2,
395 Qt::Key_W, gridGroup);
396 QAction *blackGrid = addCheckableAction(menu,
"Black grid"_L1,
397 m_gridActive == 1, Qt::Key_B, gridGroup);
398 QAction *noGrid = addCheckableAction(menu,
"No grid"_L1, m_gridActive == 0,
399 Qt::Key_N, gridGroup);
403 menu.addAction(
"Increase grid size"_L1, Qt::Key_PageUp,
404 this, &QPixelTool::increaseGridSize);
405 menu.addAction(
"Decrease grid size"_L1, Qt::Key_PageDown,
406 this, &QPixelTool::decreaseGridSize);
409 auto *lcdGroup =
new QActionGroup(&menu);
410 addCheckableAction(menu,
"No subpixels"_L1, m_lcdMode == 0,
411 QKeySequence(), lcdGroup);
412 QAction *rgbPixels = addCheckableAction(menu,
"RGB subpixels"_L1,
413 m_lcdMode == 1, QKeySequence(), lcdGroup);
414 QAction *bgrPixels = addCheckableAction(menu,
"BGR subpixels"_L1,
415 m_lcdMode == 2, QKeySequence(), lcdGroup);
416 QAction *vrgbPixels = addCheckableAction(menu,
"VRGB subpixels"_L1,
417 m_lcdMode == 3, QKeySequence(), lcdGroup);
418 QAction *vbgrPixels = addCheckableAction(menu,
"VBGR subpixels"_L1,
419 m_lcdMode == 4, QKeySequence(), lcdGroup);
423 menu.addAction(
"Zoom in"_L1, Qt::Key_Plus,
424 this, &QPixelTool::increaseZoom);
425 menu.addAction(
"Zoom out"_L1, Qt::Key_Minus,
426 this, &QPixelTool::decreaseZoom);
430 QAction *freeze = addCheckableAction(menu,
"Frozen"_L1,
431 tmpFreeze, Qt::Key_Space);
432 QAction *autoUpdate = addCheckableAction(menu,
"Continuous update"_L1,
433 m_autoUpdate, Qt::Key_A);
437 menu.addAction(
"Save as image..."_L1, QKeySequence::SaveAs,
438 this, &QPixelTool::saveToFile);
439#if QT_CONFIG(clipboard)
440 menu.addAction(
"Copy to clipboard"_L1, QKeySequence::Copy,
441 this, &QPixelTool::copyToClipboard);
442 menu.addAction(
"Copy color value to clipboard"_L1, Qt::Key_C,
443 this, &QPixelTool::copyColorToClipboard);
446 menu.addAction(
"Copy screen shot to clipboard"_L1, QKeySequence(),
447 this, &QPixelTool::copyScreenShotToClipboard);
448 menu.addAction(
"Copy screen info to clipboard"_L1, QKeySequence(),
449 this, &QPixelTool::copyScreenInfoToClipboard);
453 menu.addAction(
"About Qt"_L1,
qApp, &QApplication::aboutQt);
454 menu.addAction(
"About Qt Pixeltool"_L1,
this, &QPixelTool::aboutPixelTool);
456 menu.exec(mapToGlobal(e->pos()));
459 if (noGrid->isChecked())
461 else if (blackGrid->isChecked())
467 if (rgbPixels->isChecked())
469 else if (bgrPixels->isChecked())
471 else if (vrgbPixels->isChecked())
473 else if (vbgrPixels->isChecked())
478 m_autoUpdate = autoUpdate->isChecked();
479 m_freeze = freeze->isChecked();
482 if (m_lcdMode && (m_zoom % 3) != 0)
483 setZoom(qMax(3, (m_zoom + 1) / 3));
488 return m_initialSize;
491static inline QString
pixelToolTitle(QPoint pos,
const QScreen *screen,
const QColor ¤tColor)
493 if (screen !=
nullptr)
494 pos = QHighDpi::toNativePixels(pos, screen);
495 return QCoreApplication::applicationName() +
" ["_L1
496 + QString::number(pos.x())
497 +
", "_L1 + QString::number(pos.y()) +
"] "_L1
498 + currentColor.name();
503 if (m_preview_mode) {
504 int w = qMin(width() / m_zoom + 1, m_preview_image.width());
505 int h = qMin(height() / m_zoom + 1, m_preview_image.height());
506 m_buffer = QPixmap::fromImage(m_preview_image).copy(0, 0, w, h);
511 QPoint mousePos = QCursor::pos();
512 if (mousePos == m_lastMousePos && !m_autoUpdate)
515 QScreen *screen = QGuiApplication::screenAt(mousePos);
517 if (m_lastMousePos != mousePos)
518 setWindowTitle(pixelToolTitle(mousePos, screen, m_currentColor));
520 const auto widgetDpr = devicePixelRatioF();
521 const auto screenDpr = screen !=
nullptr ? screen->devicePixelRatio() : widgetDpr;
523 const auto factor = widgetDpr / screenDpr / qreal(m_zoom);
524 const QSize size{
int(
std::ceil(width() * factor)),
int(
std::ceil(height() * factor))};
525 const QPoint pos = mousePos - QPoint{size.width(), size.height()} / 2;
527 const QBrush darkBrush = palette().color(QPalette::Dark);
528 if (screen !=
nullptr) {
529 const QPoint screenPos = pos - screen->geometry().topLeft();
530 m_buffer = screen->grabWindow(0, screenPos.x(), screenPos.y(), size.width(), size.height());
532 m_buffer = QPixmap(size);
533 m_buffer.fill(darkBrush.color());
535 m_buffer.setDevicePixelRatio(widgetDpr);
537 QRegion geom(QRect{pos, size});
539 const auto screens = QGuiApplication::screens();
540 for (
auto *screen : screens)
541 screenRect |= screen->geometry();
543 const auto rectsInRegion = geom.rectCount();
544 if (!geom.isEmpty()) {
548 p.setBrush(darkBrush);
549 p.drawRects(geom.begin(), rectsInRegion);
554 m_currentColor = m_buffer.toImage().pixel(m_buffer.rect().center());
555 m_lastMousePos = mousePos;
560 if (m_displayZoomId > 0)
561 killTimer(m_displayZoomId);
562 m_displayZoomId = startTimer(5000);
569 if (m_displayGridSizeId > 0)
570 killTimer(m_displayGridSizeId);
571 m_displayGridSizeId = startTimer(5000);
572 m_displayGridSize =
true;
579 m_displayZoom = visible;
585 m_freeze = !m_freeze;
587 m_dragStart = m_dragCurrent = QPoint();
609 QPoint pos = m_lastMousePos;
610 m_lastMousePos = QPoint();
613 m_lastMousePos = pos;
614 m_dragStart = m_dragCurrent = QPoint();
615 startZoomVisibleTimer();
621 if (++m_gridActive > 2)
628 if (m_gridActive && gridSize > 0) {
629 m_gridSize = gridSize;
630 startGridSizeVisibleTimer();
635#if QT_CONFIG(clipboard)
636void QPixelTool::copyToClipboard()
638 QGuiApplication::clipboard()->setPixmap(m_buffer);
641void QPixelTool::copyColorToClipboard()
643 QGuiApplication::clipboard()->setText(QColor(m_currentColor).name());
646void QPixelTool::copyScreenShotToClipboard()
648 QPixmap screenShot = screen()->grabWindow();
649 QGuiApplication::clipboard()->setImage(screenShot.toImage());
652void QPixelTool::copyScreenInfoToClipboard()
654 const auto *screen =
this->screen();
656 QTextStream str(&text);
657 const auto geom = screen->geometry();
658 const auto availGeom = screen->availableGeometry();
659 auto orientationMt = QMetaEnum::fromType<Qt::ScreenOrientation>();
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(screen->orientation()))
674 str <<
"\nOrientation: " << orientation;
675 str <<
"\nRefresh rate: " << screen->refreshRate() <<
"Hz";
676 QGuiApplication::clipboard()->setText(text);
683 bool oldFreeze = m_freeze;
687 fileDialog.setWindowTitle(
"Save as image"_L1);
688 fileDialog.setAcceptMode(QFileDialog::AcceptSave);
689 fileDialog.setDirectory(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
690 QStringList mimeTypes;
691 const QByteArrayList supportedMimeTypes = QImageWriter::supportedMimeTypes();
692 for (
const QByteArray &mimeTypeB : supportedMimeTypes)
693 mimeTypes.append(QString::fromLatin1(mimeTypeB));
694 fileDialog.setMimeTypeFilters(mimeTypes);
695 const QString pngType =
"image/png"_L1;
696 if (mimeTypes.contains(pngType)) {
697 fileDialog.selectMimeTypeFilter(pngType);
698 fileDialog.setDefaultSuffix(
"png"_L1);
701 while (fileDialog.exec() == QDialog::Accepted
702 && !m_buffer.save(fileDialog.selectedFiles().constFirst())) {
703 QMessageBox::warning(
this,
"Unable to write image"_L1,
704 "Unable to write "_L1
705 + QDir::toNativeSeparators(fileDialog.selectedFiles().constFirst()));
707 m_freeze = oldFreeze;
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(screen->logicalDotsPerInch()) <<
"DPI"
716 <<
", Depth: " << screen->depth() <<
", " << screen->refreshRate() <<
"Hz";
717 const qreal dpr = screen->devicePixelRatio();
718 if (!qFuzzyCompare(dpr, qreal(1)))
719 str <<
", DPR: " << dpr;
725 const QList<QScreen *> screens = QGuiApplication::screens();
726 const QScreen *windowScreen = windowHandle()->screen();
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>";
740 QMessageBox aboutBox(QMessageBox::Information, tr(
"About Qt Pixeltool"), aboutText(),
741 QMessageBox::Close,
this);
742 aboutBox.setWindowFlags(aboutBox.windowFlags() & ~Qt::WindowContextHelpButtonHint);
743 aboutBox.setTextInteractionFlags(Qt::TextBrowserInteraction);
The QFileDialog class provides a dialog that allows users to select files or directories.
The QMessageBox class provides a modal dialog for informing the user or for asking the user a questio...
QPainter(QPaintDevice *)
Constructs a painter that begins painting the paint device immediately.
QT_FORWARD_DECLARE_CLASS(QTextStream)