File indexing completed on 2024-05-12 16:01:41

0001 /*
0002  * SPDX-FileCopyrightText: 2008 Sven Langkamp <sven.langkamp@gmail.com>
0003  *
0004  *  SPDX-License-Identifier: LGPL-2.0-or-later
0005  */
0006 
0007 #include "kis_selection_decoration.h"
0008 
0009 #include <QPainter>
0010 #include <QVarLengthArray>
0011 #include <QApplication>
0012 #include <QMainWindow>
0013 #include <QWindow>
0014 #include <QScreen>
0015 
0016 #include <kis_debug.h>
0017 #include <klocalizedstring.h>
0018 
0019 #include <KisMainWindow.h>
0020 #include "kis_types.h"
0021 #include "KisViewManager.h"
0022 #include "kis_selection.h"
0023 #include "kis_image.h"
0024 #include "flake/kis_shape_selection.h"
0025 #include "kis_pixel_selection.h"
0026 #include "kis_update_outline_job.h"
0027 #include "kis_selection_manager.h"
0028 #include "canvas/kis_canvas2.h"
0029 #include "kis_canvas_resource_provider.h"
0030 #include "kis_coordinates_converter.h"
0031 #include "kis_config.h"
0032 #include "kis_config_notifier.h"
0033 #include "kis_image_config.h"
0034 #include "KisImageConfigNotifier.h"
0035 #include "kis_painting_tweaks.h"
0036 #include "KisView.h"
0037 #include "kis_selection_mask.h"
0038 #include <KisPart.h>
0039 
0040 static const unsigned int ANT_LENGTH = 4;
0041 static const unsigned int ANT_SPACE = 4;
0042 static const unsigned int ANT_ADVANCE_WIDTH = ANT_LENGTH + ANT_SPACE;
0043 
0044 KisSelectionDecoration::KisSelectionDecoration(QPointer<KisView>_view)
0045     : KisCanvasDecoration("selection", _view),
0046       m_signalCompressor(50 /*ms*/, KisSignalCompressor::FIRST_ACTIVE),
0047       m_offset(0),
0048       m_mode(Ants)
0049 {
0050     Q_ASSERT(view());
0051     Q_ASSERT(view()->mainWindow());
0052     Q_ASSERT(view()->mainWindow()->windowHandle());
0053 
0054     m_window = view()->mainWindow()->windowHandle();
0055     connect(m_window, SIGNAL(screenChanged(QScreen*)), this, SLOT(screenChanged(QScreen*)));
0056 
0057     m_screen = m_window->screen();
0058     connect(m_screen, SIGNAL(physicalDotsPerInchChanged(qreal)), this, SLOT(initializePens()));
0059 
0060     initializePens();
0061 
0062     connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
0063     connect(KisImageConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged()));
0064     slotConfigChanged();
0065 
0066     m_antsTimer = new QTimer(this);
0067     m_antsTimer->setInterval(150);
0068     m_antsTimer->setSingleShot(false);
0069     connect(m_antsTimer, SIGNAL(timeout()), SLOT(antsAttackEvent()));
0070 
0071     connect(&m_signalCompressor, SIGNAL(timeout()), SLOT(slotStartUpdateSelection()));
0072 
0073     // selections should be at the top of the stack
0074     setPriority(100);
0075 }
0076 
0077 KisSelectionDecoration::~KisSelectionDecoration()
0078 {
0079 }
0080 
0081 KisSelectionDecoration::Mode KisSelectionDecoration::mode() const
0082 {
0083     return m_mode;
0084 }
0085 
0086 void KisSelectionDecoration::setMode(Mode mode)
0087 {
0088     m_mode = mode;
0089     selectionChanged();
0090 }
0091 
0092 bool KisSelectionDecoration::selectionIsActive()
0093 {
0094     KisSelectionSP selection = view()->selection();
0095     return visible() && selection &&
0096         (selection->hasNonEmptyPixelSelection() || selection->hasNonEmptyShapeSelection()) &&
0097             selection->isVisible();
0098 }
0099 
0100 void KisSelectionDecoration::initializePens()
0101 {
0102     /**
0103      * On MacOS in HiDPI mode devicePixelRatio is always fixed to 2.0,
0104      * so we should use physical DPI for better guess of the screen
0105      * detail level (which is divided by the pixel ratio in Qt).
0106      */
0107     const qreal dotsPerInch = m_screen->physicalDotsPerInch() * m_screen->devicePixelRatio();
0108     int screenScale = 1;
0109 
0110     if (dotsPerInch < 220) {
0111         screenScale = 1;
0112     }
0113     else if (dotsPerInch < 300) {
0114         screenScale = 2;
0115     }
0116     else if (dotsPerInch < 500) {
0117         screenScale = 3;
0118     }
0119     else {
0120         screenScale = 4;
0121     }
0122 
0123     KisPaintingTweaks::initAntsPen(&m_antsPen, &m_outlinePen,
0124                                    ANT_LENGTH, ANT_SPACE);
0125 
0126     if (screenScale > 1) {
0127         m_antsPen.setWidth(screenScale);
0128         m_outlinePen.setWidth(screenScale);
0129     }
0130     else {
0131         m_antsPen.setCosmetic(true);
0132         m_outlinePen.setCosmetic(true);
0133     }
0134 }
0135 
0136 void KisSelectionDecoration::selectionChanged()
0137 {
0138     KisSelectionMaskSP mask = qobject_cast<KisSelectionMask*>(view()->currentNode().data());
0139     if (!mask || !mask->active() || !mask->visible(true)) {
0140         mask = 0;
0141     }
0142 
0143     if (!view()->isCurrent() ||
0144         view()->viewManager()->mainWindow() == KisPart::instance()->currentMainwindow()) {
0145 
0146         view()->image()->setOverlaySelectionMask(mask);
0147     }
0148 
0149     KisSelectionSP selection = view()->selection();
0150 
0151     if (!mask && selection && selectionIsActive()) {
0152         if ((m_mode == Ants && selection->outlineCacheValid()) ||
0153             (m_mode == Mask && selection->thumbnailImageValid())) {
0154 
0155             m_signalCompressor.stop();
0156 
0157             if (m_mode == Ants) {
0158                 m_outlinePath = selection->outlineCache();
0159                 m_antsTimer->start();
0160             } else {
0161                 m_thumbnailImage = selection->thumbnailImage();
0162                 m_thumbnailImageTransform = selection->thumbnailImageTransform();
0163             }
0164             if (view() && view()->canvasBase()) {
0165                 view()->canvasBase()->updateCanvas();
0166             }
0167 
0168         } else {
0169             m_signalCompressor.start();
0170         }
0171     } else {
0172         m_signalCompressor.stop();
0173         m_outlinePath = QPainterPath();
0174         m_thumbnailImage = QImage();
0175         m_thumbnailImageTransform = QTransform();
0176         view()->canvasBase()->updateCanvas();
0177         m_antsTimer->stop();
0178     }
0179 }
0180 
0181 void KisSelectionDecoration::slotStartUpdateSelection()
0182 {
0183     KisSelectionSP selection = view()->selection();
0184     if (!selection) return;
0185 
0186     view()->image()->addSpontaneousJob(new KisUpdateOutlineJob(selection, m_mode == Mask, m_maskColor));
0187 }
0188 
0189 void KisSelectionDecoration::slotConfigChanged()
0190 {
0191     KisImageConfig imageConfig(true);
0192     KisConfig cfg(true);
0193 
0194     m_maskColor = imageConfig.selectionOverlayMaskColor();
0195     m_antialiasSelectionOutline = cfg.antialiasSelectionOutline();
0196 }
0197 
0198 void KisSelectionDecoration::screenChanged(QScreen *screen)
0199 {
0200     m_screen = screen;
0201     initializePens();
0202 }
0203 
0204 void KisSelectionDecoration::antsAttackEvent()
0205 {
0206     KisSelectionSP selection = view()->selection();
0207     if (!selection) return;
0208 
0209     if (selectionIsActive()) {
0210         m_offset = (m_offset + 1) % (ANT_ADVANCE_WIDTH);
0211         m_antsPen.setDashOffset(m_offset);
0212         view()->canvasBase()->updateCanvas();
0213     }
0214 }
0215 
0216 void KisSelectionDecoration::drawDecoration(QPainter& gc, const QRectF& updateRect, const KisCoordinatesConverter *converter, KisCanvas2 *canvas)
0217 {
0218     Q_UNUSED(updateRect);
0219     Q_UNUSED(canvas);
0220 
0221     if (!selectionIsActive()) return;
0222     if ((m_mode == Ants && m_outlinePath.isEmpty()) ||
0223         (m_mode == Mask && m_thumbnailImage.isNull())) return;
0224 
0225     QTransform transform = converter->imageToWidgetTransform();
0226 
0227     gc.save();
0228     gc.setTransform(transform, false);
0229 
0230     if (m_mode == Mask) {
0231         gc.setRenderHints(QPainter::SmoothPixmapTransform |
0232                           QPainter::HighQualityAntialiasing, false);
0233 
0234         gc.setTransform(m_thumbnailImageTransform, true);
0235         gc.drawImage(QPoint(), m_thumbnailImage);
0236 
0237         QRect r1 = m_thumbnailImageTransform.inverted().mapRect(view()->image()->bounds());
0238         QRect r2 = m_thumbnailImage.rect();
0239 
0240         QPainterPath p1;
0241         p1.addRect(r1);
0242 
0243         QPainterPath p2;
0244         p2.addRect(r2);
0245 
0246         gc.setBrush(m_maskColor);
0247         gc.setPen(Qt::NoPen);
0248         gc.drawPath(p1 - p2);
0249 
0250     } else /* if (m_mode == Ants) */ {
0251         gc.setRenderHints(QPainter::Antialiasing | QPainter::HighQualityAntialiasing, m_antialiasSelectionOutline);
0252 
0253         // render selection outline in white
0254         gc.setPen(m_outlinePen);
0255         gc.drawPath(m_outlinePath);
0256 
0257         // render marching ants in black (above the white outline)
0258         gc.setPen(m_antsPen);
0259         gc.drawPath(m_outlinePath);
0260     }
0261     gc.restore();
0262 }
0263 
0264 void KisSelectionDecoration::setVisible(bool v)
0265 {
0266     KisCanvasDecoration::setVisible(v);
0267     selectionChanged();
0268 }
0269 
0270 void KisSelectionDecoration::notifyWindowMinimized(bool minimized)
0271 {
0272     if(minimized) {
0273         m_antsTimer->stop();
0274     } else {
0275         m_antsTimer->start();
0276     }
0277 }
0278