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

0001 /*
0002  *  SPDX-FileCopyrightText: 2022 Dmitry Kazakov <dimula73@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "KisAsyncColorSamplerHelper.h"
0008 
0009 #include <QPainter>
0010 
0011 #include "KoCanvasResourcesIds.h"
0012 #include "KoCanvasResourceProvider.h"
0013 #include "KoViewConverter.h"
0014 #include "KoIcon.h"
0015 #include "kis_cursor.h"
0016 #include "kis_signal_compressor_with_param.h"
0017 #include "kis_image_interfaces.h"
0018 #include "kis_config.h"
0019 #include "kis_canvas2.h"
0020 #include "KisViewManager.h"
0021 #include "KisDocument.h"
0022 #include "KisReferenceImagesLayer.h"
0023 #include "KisReferenceImagesDecoration.h"
0024 #include "kis_display_color_converter.h"
0025 #include "strokes/kis_color_sampler_stroke_strategy.h"
0026 
0027 
0028 namespace {
0029 std::pair<QRectF,QRectF> colorPreviewDocRectImpl(const QPointF &outlineDocPoint, bool colorPreviewShowComparePlate, const KoViewConverter *converter)
0030 {
0031     KisConfig cfg(true);
0032     const QRectF colorPreviewViewRect = cfg.colorPreviewRect();
0033 
0034     const QRectF colorPreviewBaseColorViewRect =
0035         colorPreviewShowComparePlate ?
0036             colorPreviewViewRect.translated(colorPreviewViewRect.width(), 0) :
0037             QRectF();
0038 
0039     const QRectF colorPreviewDocumentRect = converter->viewToDocument(colorPreviewViewRect);
0040     const QRectF colorPreviewBaseColorDocumentRect =
0041         converter->viewToDocument(colorPreviewBaseColorViewRect);
0042 
0043     return std::make_pair(colorPreviewDocumentRect.translated(outlineDocPoint),
0044                           colorPreviewBaseColorDocumentRect.translated(outlineDocPoint));
0045 }
0046 }
0047 
0048 struct KisAsyncColorSamplerHelper::Private
0049 {
0050     Private(KisCanvas2 *_canvas)
0051         : canvas(_canvas)
0052     {}
0053 
0054     KisCanvas2 *canvas;
0055 
0056     int sampleResourceId {0};
0057     bool sampleCurrentLayer {true};
0058     bool updateGlobalColor {true};
0059 
0060     bool isActive {false};
0061     bool showPreview {false};
0062     bool showComparePlate {false};
0063 
0064     KisStrokeId strokeId;
0065     typedef KisSignalCompressorWithParam<QPointF> SamplingCompressor;
0066     QScopedPointer<SamplingCompressor> samplingCompressor;
0067 
0068     QTimer activationDelayTimer;
0069 
0070     QRectF currentColorDocRect;
0071     QRectF baseColorDocRect;
0072 
0073     QColor currentColor;
0074     QColor baseColor;
0075 
0076     KisStrokesFacade *strokesFacade() const {
0077         return canvas->image().data();
0078     }
0079 
0080     const KoViewConverter &converter() const {
0081         return *canvas->imageView()->viewConverter();
0082     }
0083 
0084 };
0085 
0086 KisAsyncColorSamplerHelper::KisAsyncColorSamplerHelper(KisCanvas2 *canvas)
0087     : m_d(new Private(canvas))
0088 {
0089     using namespace std::placeholders; // For _1 placeholder
0090     std::function<void(QPointF)> callback =
0091         std::bind(&KisAsyncColorSamplerHelper::slotAddSamplingJob, this, _1);
0092     m_d->samplingCompressor.reset(
0093         new Private::SamplingCompressor(100, callback, KisSignalCompressor::FIRST_ACTIVE));
0094 
0095     m_d->activationDelayTimer.setInterval(100);
0096     m_d->activationDelayTimer.setSingleShot(true);
0097     connect(&m_d->activationDelayTimer, SIGNAL(timeout()), this, SLOT(activateDelayedPreview()));
0098 }
0099 
0100 KisAsyncColorSamplerHelper::~KisAsyncColorSamplerHelper()
0101 {
0102     KIS_SAFE_ASSERT_RECOVER_NOOP(!m_d->strokeId);
0103 }
0104 
0105 bool KisAsyncColorSamplerHelper::isActive() const
0106 {
0107     return m_d->isActive;
0108 }
0109 
0110 void KisAsyncColorSamplerHelper::activate(bool sampleCurrentLayer, bool pickFgColor)
0111 {
0112     KIS_SAFE_ASSERT_RECOVER_RETURN(!m_d->isActive);
0113     m_d->isActive = true;
0114 
0115     m_d->sampleResourceId =
0116         pickFgColor ?
0117             KoCanvasResource::ForegroundColor :
0118             KoCanvasResource::BackgroundColor;
0119 
0120     m_d->sampleCurrentLayer = sampleCurrentLayer;
0121     m_d->showComparePlate = false;
0122 
0123     m_d->activationDelayTimer.start();
0124 }
0125 
0126 void KisAsyncColorSamplerHelper::activateDelayedPreview()
0127 {
0128     // the event may come after we have finished color
0129     // picking if the user is quick
0130     if (!m_d->isActive) return;
0131 
0132     m_d->showPreview = true;
0133 
0134     const KoColor currentColor =
0135         m_d->canvas->resourceManager()->koColorResource(m_d->sampleResourceId);
0136     const QColor previewColor = m_d->canvas->displayColorConverter()->toQColor(currentColor);
0137 
0138     m_d->currentColor = previewColor;
0139     m_d->baseColor = previewColor;
0140 
0141     updateCursor(m_d->sampleCurrentLayer, m_d->sampleResourceId == KoCanvasResource::ForegroundColor);
0142 
0143     Q_EMIT sigRequestUpdateOutline();
0144 }
0145 
0146 void KisAsyncColorSamplerHelper::updateCursor(bool sampleCurrentLayer, bool pickFgColor)
0147 {
0148     const int sampleResourceId =
0149             pickFgColor ?
0150                 KoCanvasResource::ForegroundColor :
0151                 KoCanvasResource::BackgroundColor;
0152 
0153     QCursor cursor;
0154 
0155     if (sampleCurrentLayer) {
0156         if (sampleResourceId == KoCanvasResource::ForegroundColor) {
0157             cursor = KisCursor::samplerLayerForegroundCursor();
0158         } else {
0159             cursor = KisCursor::samplerLayerBackgroundCursor();
0160         }
0161     } else {
0162         if (sampleResourceId == KoCanvasResource::ForegroundColor) {
0163             cursor = KisCursor::samplerImageForegroundCursor();
0164         } else {
0165             cursor = KisCursor::samplerImageBackgroundCursor();
0166         }
0167     }
0168 
0169     Q_EMIT sigRequestCursor(cursor);
0170 }
0171 
0172 void KisAsyncColorSamplerHelper::setUpdateGlobalColor(bool value)
0173 {
0174     m_d->updateGlobalColor = value;
0175 }
0176 
0177 bool KisAsyncColorSamplerHelper::updateGlobalColor() const
0178 {
0179     return m_d->updateGlobalColor;
0180 }
0181 
0182 void KisAsyncColorSamplerHelper::deactivate()
0183 {
0184     KIS_SAFE_ASSERT_RECOVER(!m_d->strokeId) {
0185         endAction();
0186     }
0187 
0188     m_d->activationDelayTimer.stop();
0189 
0190     m_d->showPreview = false;
0191     m_d->showComparePlate = false;
0192 
0193     m_d->currentColorDocRect = QRectF();
0194     m_d->currentColor = QColor();
0195     m_d->baseColor = QColor();
0196     m_d->baseColorDocRect = QRectF();
0197 
0198     m_d->isActive = false;
0199 
0200     Q_EMIT sigRequestCursorReset();
0201     Q_EMIT sigRequestUpdateOutline();
0202 }
0203 
0204 void KisAsyncColorSamplerHelper::startAction(const QPointF &docPoint, int radius, int blend)
0205 {
0206     KisColorSamplerStrokeStrategy *strategy = new KisColorSamplerStrokeStrategy(radius, blend);
0207     connect(strategy, &KisColorSamplerStrokeStrategy::sigColorUpdated,
0208             this, &KisAsyncColorSamplerHelper::slotColorSamplingFinished);
0209     connect(strategy, &KisColorSamplerStrokeStrategy::sigFinalColorSelected,
0210             this, &KisAsyncColorSamplerHelper::sigFinalColorSelected);
0211 
0212     m_d->strokeId = m_d->strokesFacade()->startStroke(strategy);
0213     m_d->samplingCompressor->start(docPoint);
0214 }
0215 
0216 void KisAsyncColorSamplerHelper::continueAction(const QPointF &docPoint)
0217 {
0218     KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->strokeId);
0219     m_d->samplingCompressor->start(docPoint);
0220 }
0221 
0222 void KisAsyncColorSamplerHelper::endAction()
0223 {
0224     KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->strokeId);
0225 
0226     m_d->strokesFacade()->addJob(m_d->strokeId,
0227         new KisColorSamplerStrokeStrategy::FinalizeData());
0228 
0229     m_d->strokesFacade()->endStroke(m_d->strokeId);
0230     m_d->strokeId.clear();
0231 }
0232 
0233 QRectF KisAsyncColorSamplerHelper::colorPreviewDocRect(const QPointF &docPoint)
0234 {
0235     if (!m_d->showPreview) return QRectF();
0236 
0237     std::tie(m_d->currentColorDocRect, m_d->baseColorDocRect) =
0238             colorPreviewDocRectImpl(docPoint, m_d->showComparePlate, &m_d->converter());
0239 
0240     return m_d->currentColorDocRect | m_d->baseColorDocRect;
0241 }
0242 
0243 void KisAsyncColorSamplerHelper::paint(QPainter &gc, const KoViewConverter &converter)
0244 {
0245     if (!m_d->showPreview) return;
0246 
0247     const QRectF viewRect = converter.documentToView(m_d->currentColorDocRect);
0248     gc.fillRect(viewRect, m_d->currentColor);
0249 
0250     if (m_d->showComparePlate) {
0251         const QRectF baseColorRect = converter.documentToView(m_d->baseColorDocRect);
0252         gc.fillRect(baseColorRect, m_d->baseColor);
0253     }
0254 }
0255 
0256 void KisAsyncColorSamplerHelper::slotAddSamplingJob(const QPointF &docPoint)
0257 {
0258     /**
0259      * The actual sampling is delayed by a compressor, so we can get this
0260      * event when the stroke is already closed
0261      */
0262     if (!m_d->strokeId) return;
0263 
0264     KisImageSP image = m_d->canvas->image();
0265 
0266     const QPoint imagePoint = image->documentToImagePixelFloored(docPoint);
0267 
0268     if (!m_d->sampleCurrentLayer) {
0269         KisSharedPtr<KisReferenceImagesLayer> referencesLayer = m_d->canvas->imageView()->document()->referenceImagesLayer();
0270         if (referencesLayer && m_d->canvas->referenceImagesDecoration()->visible()) {
0271             QColor color = referencesLayer->getPixel(imagePoint);
0272             if (color.isValid() && color.alpha() != 0) {
0273                 slotColorSamplingFinished(KoColor(color, image->colorSpace()));
0274                 return;
0275             }
0276         }
0277     }
0278 
0279     KisPaintDeviceSP device = m_d->sampleCurrentLayer ?
0280         m_d->canvas->imageView()->currentNode()->colorSampleSourceDevice() :
0281         image->projection();
0282 
0283     if (device) {
0284         // Used for color sampler blending.
0285         const KoColor currentColor =
0286             m_d->canvas->resourceManager()->koColorResource(m_d->sampleResourceId);
0287 
0288         m_d->strokesFacade()->addJob(m_d->strokeId,
0289             new KisColorSamplerStrokeStrategy::Data(device, imagePoint, currentColor));
0290     } else {
0291         QString message = i18n("Color sampler does not work on this layer.");
0292         m_d->canvas->viewManager()->showFloatingMessage(message, koIcon("object-locked"));
0293     }
0294 }
0295 
0296 void KisAsyncColorSamplerHelper::slotColorSamplingFinished(const KoColor &rawColor)
0297 {
0298     KoColor color(rawColor);
0299 
0300     color.setOpacity(OPACITY_OPAQUE_U8);
0301 
0302     if (m_d->updateGlobalColor) {
0303         m_d->canvas->resourceManager()->setResource(m_d->sampleResourceId, color);
0304     }
0305 
0306     Q_EMIT sigRawColorSelected(rawColor);
0307     Q_EMIT sigColorSelected(color);
0308 
0309     if (!m_d->showPreview) return;
0310 
0311     const QColor previewColor = m_d->canvas->displayColorConverter()->toQColor(color);
0312 
0313     m_d->showComparePlate = true;
0314     m_d->currentColor = previewColor;
0315 
0316     Q_EMIT sigRequestUpdateOutline();
0317 }