File indexing completed on 2024-12-22 04:13:17

0001 /*
0002  * SPDX-FileCopyrightText: 2016 Wolthera van Hovell tot Westerflier <griffinvalley@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include <QDesktopWidget>
0008 #include <QGuiApplication>
0009 #include <QApplication>
0010 #include <QScreen>
0011 #include <QColor>
0012 #include <QVBoxLayout>
0013 #include <QLabel>
0014 #include <QPushButton>
0015 #include <QWindow>
0016 #include <QTimer>
0017 
0018 #include <kis_canvas2.h>
0019 
0020 #include "kis_shared_ptr.h"
0021 #include "kis_icon.h"
0022 #include "kis_image.h"
0023 #include "kis_wrapped_rect.h"
0024 #include "KisDocument.h"
0025 #include "KisPart.h"
0026 #include "KisReferenceImagesLayer.h"
0027 #include "KisScreenColorSampler.h"
0028 #include "KisDlgInternalColorSelector.h"
0029 #include <KisStaticInitializer.h>
0030 
0031 struct KisScreenColorSampler::Private
0032 {
0033     QPushButton *screenColorSamplerButton = 0;
0034     QLabel *lblScreenColorInfo = 0;
0035 
0036     KoColor currentColor = KoColor();
0037     KoColor beforeScreenColorSampling = KoColor();
0038 
0039     bool performRealColorSamplingOfCanvas {true};
0040 
0041     KisScreenColorSamplingEventFilter *colorSamplingEventFilter = 0;
0042 
0043     QWidget *inputGrabberWidget {nullptr};
0044 
0045 #ifdef Q_OS_WIN32
0046     QTimer *updateTimer = 0;
0047     QWindow dummyTransparentWindow;
0048 #endif
0049 
0050     void updateInputGrabberWidget()
0051     {
0052         inputGrabberWidget = qApp->activeWindow();
0053     }
0054 };
0055 
0056 KisScreenColorSampler::KisScreenColorSampler(bool showInfoLabel, QWidget *parent) : KisScreenColorSamplerBase(parent), m_d(new Private)
0057 {
0058     QVBoxLayout *layout = new QVBoxLayout(this);
0059     m_d->screenColorSamplerButton = new QPushButton();
0060 
0061     m_d->screenColorSamplerButton->setMinimumHeight(25);
0062     layout->addWidget(m_d->screenColorSamplerButton);
0063 
0064     if (showInfoLabel) {
0065         m_d->lblScreenColorInfo = new QLabel(QLatin1String("\n"));
0066         layout->addWidget(m_d->lblScreenColorInfo);
0067     }
0068 
0069     layout->setContentsMargins(0, 0, 0, 0);
0070 
0071     connect(m_d->screenColorSamplerButton, SIGNAL(clicked()), SLOT(sampleScreenColor()));
0072 
0073     updateIcons();
0074 
0075 #ifdef Q_OS_WIN32
0076     m_d->updateTimer = new QTimer(this);
0077     m_d->dummyTransparentWindow.resize(1, 1);
0078     m_d->dummyTransparentWindow.setFlags(Qt::Tool | Qt::FramelessWindowHint);
0079     connect(m_d->updateTimer, SIGNAL(timeout()), SLOT(updateColorSampling()));
0080 #endif
0081 }
0082 
0083 KisScreenColorSampler::~KisScreenColorSampler()
0084 {
0085 }
0086 
0087 void KisScreenColorSampler::updateIcons()
0088 {
0089     m_d->screenColorSamplerButton->setIcon(kisIcon("krita_tool_color_sampler"));
0090 }
0091 
0092 KoColor KisScreenColorSampler::currentColor()
0093 {
0094     return m_d->currentColor;
0095 }
0096 
0097 bool KisScreenColorSampler::performRealColorSamplingOfCanvas() const
0098 {
0099     return m_d->performRealColorSamplingOfCanvas;
0100 }
0101 
0102 void KisScreenColorSampler::setPerformRealColorSamplingOfCanvas(bool enable)
0103 {
0104     m_d->performRealColorSamplingOfCanvas = enable;
0105 }
0106 
0107 void KisScreenColorSampler::sampleScreenColor()
0108 {
0109     m_d->updateInputGrabberWidget();
0110     if (m_d->inputGrabberWidget == nullptr) {
0111         Q_EMIT sigNewColorSampled(currentColor());
0112         return;
0113     }
0114 
0115     if (!m_d->colorSamplingEventFilter) {
0116         m_d->colorSamplingEventFilter = new KisScreenColorSamplingEventFilter(this, this);
0117     }
0118     m_d->inputGrabberWidget->installEventFilter(m_d->colorSamplingEventFilter);
0119      // If user pushes Escape, the last color before sampling will be restored.
0120     m_d->beforeScreenColorSampling = currentColor();
0121     m_d->inputGrabberWidget->grabMouse(Qt::CrossCursor);
0122 
0123 #ifdef Q_OS_WIN32 // excludes WinCE and WinRT
0124     // On Windows mouse tracking doesn't work over other processes's windows
0125     m_d->updateTimer->start(30);
0126 
0127     // HACK: Because mouse grabbing doesn't work across processes, we have to have a dummy,
0128     // invisible window to catch the mouse click, otherwise we will click whatever we clicked
0129     // and loose focus.
0130     m_d->dummyTransparentWindow.show();
0131 #endif
0132     m_d->inputGrabberWidget->grabKeyboard();
0133      /* With setMouseTracking(true) the desired color can be more precisely sampled,
0134       * and continuously pushing the mouse button is not necessary.
0135       */
0136     m_d->inputGrabberWidget->setMouseTracking(true);
0137 
0138     m_d->screenColorSamplerButton->setDisabled(true);
0139 
0140     const QPoint globalPos = QCursor::pos();
0141     setCurrentColor(grabScreenColor(globalPos));
0142     updateColorLabelText(globalPos);
0143 }
0144 
0145 void KisScreenColorSampler::setCurrentColor(KoColor c)
0146 {
0147     m_d->currentColor = c;
0148 }
0149 
0150 KoColor KisScreenColorSampler::grabScreenColor(const QPoint &p)
0151 {
0152      // First check whether we're clicking on a Krita window for some real color sampling
0153     if (m_d->performRealColorSamplingOfCanvas) {
0154         Q_FOREACH(KisView *view, KisPart::instance()->views()) {
0155             const KisCanvas2 *canvas = view->canvasBase();
0156             const QWidget *canvasWidget = canvas->canvasWidget();
0157             QPoint widgetPoint = canvasWidget->mapFromGlobal(p);
0158 
0159             if (canvasWidget->visibleRegion().contains(widgetPoint)) {
0160                 KisImageWSP image = view->image();
0161 
0162                 if (image) {
0163                     QPointF imagePoint = canvas->coordinatesConverter()->widgetToImage(widgetPoint);
0164                     // sample from reference images first
0165                     KisSharedPtr<KisReferenceImagesLayer> referenceImageLayer = view->document()->referenceImagesLayer();
0166 
0167                     if (referenceImageLayer && canvas->referenceImagesDecoration()->visible()) {
0168                         QColor color = referenceImageLayer->getPixel(imagePoint);
0169                         if (color.isValid()) {
0170                             return KoColor(color, image->colorSpace());
0171                         }
0172                      }
0173                     if (image->wrapAroundModePermitted()) {
0174                         imagePoint = KisWrappedRect::ptToWrappedPt(imagePoint.toPoint(), image->bounds(), image->wrapAroundModeAxis());
0175                     }
0176                     KoColor sampledColor = KoColor();
0177                     image->projection()->pixel(imagePoint.x(), imagePoint.y(), &sampledColor);
0178                     return sampledColor;
0179                  }
0180              }
0181          }
0182      }
0183 
0184     const QDesktopWidget *desktop = QApplication::desktop();
0185     const QPixmap pixmap = QGuiApplication::screens().at(desktop->screenNumber(QApplication::activeWindow()))->grabWindow(desktop->winId(),
0186                                                                                               p.x(), p.y(), 1, 1);
0187     QImage i = pixmap.toImage();
0188     KoColor col = KoColor();
0189     col.fromQColor(QColor::fromRgb(i.pixel(0, 0)));
0190     return col;
0191 }
0192 
0193 void KisScreenColorSampler::updateColorLabelText(const QPoint &globalPos)
0194 {
0195     if (m_d->lblScreenColorInfo) {
0196         KoColor col = grabScreenColor(globalPos);
0197         QString colname = KoColor::toQString(col);
0198         QString location = QString::number(globalPos.x())+QString(", ")+QString::number(globalPos.y());
0199         m_d->lblScreenColorInfo->setWordWrap(true);
0200         m_d->lblScreenColorInfo->setText(location+QString(": ")+colname);
0201     }
0202 }
0203 
0204 bool KisScreenColorSampler::handleColorSamplingMouseMove(QMouseEvent *e)
0205 {
0206     // If the cross is visible the grabbed color will be black most of the times
0207     //cp->setCrossVisible(!cp->geometry().contains(e->pos()));
0208 
0209     continueUpdateColorSampling(e->globalPos());
0210     return true;
0211 }
0212 
0213 bool KisScreenColorSampler::handleColorSamplingMouseButtonRelease(QMouseEvent *e)
0214 {
0215     setCurrentColor(grabScreenColor(e->globalPos()));
0216     Q_EMIT sigNewColorSampled(currentColor());
0217     releaseColorSampling();
0218     return true;
0219 }
0220 
0221 bool KisScreenColorSampler::handleColorSamplingKeyPress(QKeyEvent *e)
0222 {
0223     if (e->matches(QKeySequence::Cancel)) {
0224         releaseColorSampling();
0225         setCurrentColor(m_d->beforeScreenColorSampling);
0226         Q_EMIT sigNewColorSampled(currentColor());
0227     } else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) {
0228         setCurrentColor(grabScreenColor(QCursor::pos()));
0229         Q_EMIT sigNewColorSampled(currentColor());
0230         releaseColorSampling();
0231     }
0232     e->accept();
0233     return true;
0234 }
0235 
0236 void KisScreenColorSampler::releaseColorSampling()
0237 {
0238     m_d->inputGrabberWidget->removeEventFilter(m_d->colorSamplingEventFilter);
0239     m_d->inputGrabberWidget->releaseMouse();
0240 #ifdef Q_OS_WIN32
0241     m_d->updateTimer->stop();
0242     m_d->dummyTransparentWindow.setVisible(false);
0243 #endif
0244     m_d->inputGrabberWidget->releaseKeyboard();
0245     m_d->inputGrabberWidget->setMouseTracking(false);
0246 
0247     if (m_d->lblScreenColorInfo) {
0248         m_d->lblScreenColorInfo->setText(QLatin1String("\n"));
0249     }
0250 
0251     m_d->screenColorSamplerButton->setDisabled(false);
0252 }
0253 
0254 void KisScreenColorSampler::changeEvent(QEvent *e)
0255 {
0256     QWidget::changeEvent(e);
0257 }
0258 
0259 void KisScreenColorSampler::updateColorSampling()
0260 {
0261     static QPoint lastGlobalPos;
0262     QPoint newGlobalPos = QCursor::pos();
0263     if (lastGlobalPos == newGlobalPos)
0264         return;
0265     lastGlobalPos = newGlobalPos;
0266 
0267     if (!rect().contains(mapFromGlobal(newGlobalPos))) { // Inside the dialog mouse tracking works, handleColorSamplingMouseMove will be called
0268         continueUpdateColorSampling(newGlobalPos);
0269 #ifdef Q_OS_WIN32
0270         m_d->dummyTransparentWindow.setPosition(newGlobalPos);
0271 #endif
0272     }
0273 }
0274 
0275 void KisScreenColorSampler::continueUpdateColorSampling(const QPoint &globalPos)
0276 {
0277     const KoColor color = grabScreenColor(globalPos);
0278     // QTBUG-39792, do not change standard, custom color selectors while moving as
0279     // otherwise it is not possible to pre-select a custom cell for assignment.
0280     setCurrentColor(color);
0281     Q_EMIT sigNewColorHovered(currentColor());
0282     updateColorLabelText(globalPos);
0283 }
0284 
0285 // Event filter to be installed on the dialog while in color-sampling mode.
0286 KisScreenColorSamplingEventFilter::KisScreenColorSamplingEventFilter(KisScreenColorSampler *w, QObject *parent)
0287     : QObject(parent)
0288     , m_w(w)
0289 {}
0290 
0291 bool KisScreenColorSamplingEventFilter::eventFilter(QObject *, QEvent *event)
0292 {
0293     switch (event->type()) {
0294     case QEvent::MouseMove:
0295         return m_w->handleColorSamplingMouseMove(static_cast<QMouseEvent *>(event));
0296     case QEvent::MouseButtonRelease:
0297         return m_w->handleColorSamplingMouseButtonRelease(static_cast<QMouseEvent *>(event));
0298     case QEvent::KeyPress:
0299         return m_w->handleColorSamplingKeyPress(static_cast<QKeyEvent *>(event));
0300     default:
0301         break;
0302     }
0303     return false;
0304 }
0305 
0306 // Register the color sampler factory with the internal color selector
0307 KIS_DECLARE_STATIC_INITIALIZER {
0308     KisDlgInternalColorSelector::setScreenColorSamplerFactory(KisScreenColorSampler::createScreenColorSampler);
0309 }
0310