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

0001 /*
0002  *  SPDX-FileCopyrightText: 2009 Boudewijn Rempt <boud@valdyas.org>
0003  *  SPDX-FileCopyrightText: 2018 Emmet & Eoin O'Neill <emmetoneill.pdx@gmail.com>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include <kis_tool_utils.h>
0009 
0010 #include <KoMixColorsOp.h>
0011 #include <kis_group_layer.h>
0012 #include <kis_transaction.h>
0013 #include <kis_sequential_iterator.h>
0014 #include <kis_properties_configuration.h>
0015 #include <kconfiggroup.h>
0016 #include <ksharedconfig.h>
0017 #include "kis_layer_utils.h"
0018 #include "kis_command_utils.h"
0019 #include "kis_processing_applicator.h"
0020 
0021 #include "kis_canvas2.h"
0022 #include <QPainterPath>
0023 #include <kis_shape_layer.h>
0024 #include <KoShapeManager.h>
0025 #include <KoShape.h>
0026 #include <KoPathShape.h>
0027 #include <KoSvgTextShape.h>
0028 #include <KoSvgTextProperties.h>
0029 #include <KisViewManager.h>
0030 #include <kis_node_manager.h>
0031 #include <KoSelection.h>
0032 
0033 
0034 #include "KisAnimAutoKey.h"
0035 
0036 #include <QApplication>
0037 
0038 namespace KisToolUtils {
0039 
0040     bool sampleColor(KoColor &out_color, KisPaintDeviceSP dev, const QPoint &pos,
0041                    KoColor const *const blendColor, int radius, int blend, bool pure)
0042     {
0043         KIS_ASSERT(dev);
0044 
0045         // Bugfix hack forcing pure on first sample to avoid wrong
0046         // format blendColor on newly initialized Krita.
0047         static bool firstTime = true;
0048         if (firstTime == true) {
0049             pure = true;
0050             firstTime = false;
0051         }
0052 
0053         const KoColorSpace *cs = dev->colorSpace();
0054         KoColor sampledColor = KoColor::createTransparent(cs);
0055 
0056         // Sampling radius.
0057         if (!pure && radius > 1) {
0058             QScopedPointer<KoMixColorsOp::Mixer> mixer(cs->mixColorsOp()->createMixer());
0059 
0060             QVector<const quint8*> pixels;
0061             const int effectiveRadius = radius - 1;
0062 
0063             const QRect sampleRect(pos.x() - effectiveRadius, pos.y() - effectiveRadius,
0064                                  2 * effectiveRadius + 1, 2 * effectiveRadius + 1);
0065             KisSequentialConstIterator it(dev, sampleRect);
0066 
0067             const int radiusSq = pow2(effectiveRadius);
0068 
0069             int nConseqPixels = it.nConseqPixels();
0070             while (it.nextPixels(nConseqPixels)) {
0071                 const QPoint realPos(it.x(),  it.y());
0072                 if (kisSquareDistance(realPos, pos) < radiusSq) {
0073                     mixer->accumulateAverage(it.oldRawData(), nConseqPixels);
0074                 }
0075             }
0076 
0077             mixer->computeMixedColor(sampledColor.data());
0078 
0079         } else {
0080             dev->pixel(pos.x(), pos.y(), &sampledColor);
0081         }
0082         
0083         // Color blending.
0084         if (!pure && blendColor && blend < 100) {
0085             //Scale from 0..100% to 0..255 range for mixOp weights.
0086             quint8 blendScaled = static_cast<quint8>(blend * 2.55f);
0087 
0088             const quint8 *colors[2];
0089             colors[0] = blendColor->data();
0090             colors[1] = sampledColor.data();
0091             qint16 weights[2];
0092             weights[0] = 255 - blendScaled;
0093             weights[1] = blendScaled;
0094 
0095             const KoMixColorsOp *mixOp = dev->colorSpace()->mixColorsOp();
0096             mixOp->mixColors(colors, weights, 2, sampledColor.data());
0097         }
0098 
0099         sampledColor.convertTo(dev->compositionSourceColorSpace());
0100 
0101         bool validColorSampled = sampledColor.opacityU8() != OPACITY_TRANSPARENT_U8;
0102 
0103         if (validColorSampled) {
0104             out_color = sampledColor;
0105         }
0106 
0107         return validColorSampled;
0108     }
0109 
0110     KisNodeSP findNode(KisNodeSP node, const QPoint &point, bool wholeGroup, bool editableOnly)
0111     {
0112         KisNodeSP foundNode = 0;
0113         while (node) {
0114             KisLayerSP layer = qobject_cast<KisLayer*>(node.data());
0115 
0116             if (!layer || !layer->isEditable()) {
0117                 node = node->prevSibling();
0118                 continue;
0119             }
0120 
0121             KoColor color(layer->projection()->colorSpace());
0122             layer->projection()->pixel(point.x(), point.y(), &color);
0123 
0124             KisGroupLayerSP group = dynamic_cast<KisGroupLayer*>(layer.data());
0125 
0126             if ((group && group->passThroughMode()) ||  color.opacityU8() != OPACITY_TRANSPARENT_U8) {
0127                 if (layer->inherits("KisGroupLayer") && (!editableOnly || layer->isEditable())) {
0128                     // if this is a group and the pixel is transparent, don't even enter it
0129                     foundNode = findNode(node->lastChild(), point, wholeGroup, editableOnly);
0130                 }
0131                 else {
0132                     foundNode = !wholeGroup ? node : node->parent();
0133                 }
0134 
0135             }
0136 
0137             if (foundNode) break;
0138 
0139             node = node->prevSibling();
0140         }
0141 
0142         return foundNode;
0143     }
0144 
0145     KisNodeList findNodes(KisNodeSP node, const QPoint &point, bool wholeGroup, bool includeGroups, bool editableOnly)
0146     {
0147         KisNodeList foundNodes;
0148         while (node) {
0149             KisLayerSP layer = qobject_cast<KisLayer*>(node.data());
0150 
0151             if (!layer || !layer->isEditable()) {
0152                 node = node->nextSibling();
0153                 continue;
0154             }
0155 
0156             KoColor color(layer->projection()->colorSpace());
0157             layer->projection()->pixel(point.x(), point.y(), &color);
0158             const bool isTransparent = color.opacityU8() == OPACITY_TRANSPARENT_U8;
0159 
0160             KisGroupLayerSP group = dynamic_cast<KisGroupLayer*>(layer.data());
0161 
0162             if (group) {
0163                 if (!isTransparent || group->passThroughMode()) {
0164                     foundNodes << findNodes(node->firstChild(), point, wholeGroup, includeGroups, editableOnly);
0165                     if (includeGroups) {
0166                         foundNodes << node;
0167                     }
0168                 }
0169             } else {
0170                 if (!isTransparent) {
0171                     if (wholeGroup) {
0172                         if (!foundNodes.contains(node->parent())) {
0173                             foundNodes << node->parent();
0174                         }
0175                     } else {
0176                         foundNodes << node;
0177                     }
0178                 }
0179             }
0180 
0181             node = node->nextSibling();
0182         }
0183 
0184         return foundNodes;
0185     }
0186 
0187     bool clearImage(KisImageSP image, KisNodeList nodes, KisSelectionSP selection)
0188     {
0189         KisNodeList masks;
0190 
0191         Q_FOREACH (KisNodeSP node, nodes) {
0192             if (node->inherits("KisMask")) {
0193                 masks.append(node);
0194             }
0195         }
0196 
0197         // To prevent deleting same layer multiple times
0198         KisLayerUtils::filterMergeableNodes(nodes);
0199         nodes.append(masks);
0200 
0201         if (nodes.isEmpty()) {
0202             return false;
0203         }
0204 
0205         KisProcessingApplicator applicator(image, 0, KisProcessingApplicator::NONE,
0206                                            KisImageSignalVector(), kundo2_i18n("Clear"));
0207 
0208         Q_FOREACH (KisNodeSP node, nodes) {
0209             KisLayerUtils::recursiveApplyNodes(node, [&applicator, selection, masks] (KisNodeSP node) {
0210 
0211                 // applied on masks if selected explicitly
0212                 if (node->inherits("KisMask") && !masks.contains(node)) {
0213                     return;
0214                 }
0215 
0216                 if(node->hasEditablePaintDevice()) {
0217                     KUndo2Command *cmd =
0218                         new KisCommandUtils::LambdaCommand(kundo2_i18n("Clear"),
0219                             [node, selection] () {
0220                                 KisPaintDeviceSP device = node->paintDevice();
0221 
0222                                 QScopedPointer<KisCommandUtils::CompositeCommand> parentCommand(
0223                                     new KisCommandUtils::CompositeCommand());
0224 
0225                                 KUndo2Command *autoKeyframeCommand = KisAutoKey::tryAutoCreateDuplicatedFrame(device);
0226                                 if (autoKeyframeCommand) {
0227                                     parentCommand->addCommand(autoKeyframeCommand);
0228                                 }
0229 
0230                                 KisTransaction transaction(kundo2_noi18n("internal-clear-command"), device);
0231 
0232                                 QRect dirtyRect;
0233                                 if (selection) {
0234                                     dirtyRect = selection->selectedRect();
0235                                     device->clearSelection(selection);
0236                                 } else {
0237                                     dirtyRect = device->extent();
0238                                     device->clear();
0239                                 }
0240 
0241                                 device->setDirty(dirtyRect);
0242                                 parentCommand->addCommand(transaction.endAndTake());
0243 
0244                                 return parentCommand.take();
0245                             });
0246                     applicator.applyCommand(cmd, KisStrokeJobData::CONCURRENT);
0247                 }
0248             });
0249         }
0250 
0251         applicator.end();
0252 
0253         return true;
0254     }
0255 
0256     const QString ColorSamplerConfig::CONFIG_GROUP_NAME = "tool_color_sampler";
0257 
0258     ColorSamplerConfig::ColorSamplerConfig()
0259         : toForegroundColor(true)
0260         , updateColor(true)
0261         , addColorToCurrentPalette(false)
0262         , normaliseValues(false)
0263         , sampleMerged(true)
0264         , radius(1)
0265         , blend(100)
0266     {
0267     }
0268 
0269     void ColorSamplerConfig::save() const
0270     {
0271         KisPropertiesConfiguration props;
0272         props.setProperty("toForegroundColor", toForegroundColor);
0273         props.setProperty("updateColor", updateColor);
0274         props.setProperty("addPalette", addColorToCurrentPalette);
0275         props.setProperty("normaliseValues", normaliseValues);
0276         props.setProperty("sampleMerged", sampleMerged);
0277         props.setProperty("radius", radius);
0278         props.setProperty("blend", blend);
0279 
0280         KConfigGroup config =  KSharedConfig::openConfig()->group(CONFIG_GROUP_NAME);
0281 
0282         config.writeEntry("ColorSamplerDefaultActivation", props.toXML());
0283     }
0284 
0285     void ColorSamplerConfig::load()
0286     {
0287         KisPropertiesConfiguration props;
0288 
0289         KConfigGroup config =  KSharedConfig::openConfig()->group(CONFIG_GROUP_NAME);
0290         props.fromXML(config.readEntry("ColorSamplerDefaultActivation"));
0291 
0292         toForegroundColor = props.getBool("toForegroundColor", true);
0293         updateColor = props.getBool("updateColor", true);
0294         addColorToCurrentPalette = props.getBool("addPalette", false);
0295         normaliseValues = props.getBool("normaliseValues", false);
0296         sampleMerged = props.getBool("sampleMerged", true);
0297         radius = props.getInt("radius", 1);
0298         blend = props.getInt("blend", 100);
0299     }
0300 
0301     void KRITAUI_EXPORT setCursorPos(const QPoint &point)
0302     {
0303         // https://bugreports.qt.io/browse/QTBUG-99009
0304         QScreen *screen = qApp->screenAt(point);
0305         if (!screen) {
0306             screen = qApp->primaryScreen();
0307         }
0308         QCursor::setPos(screen, point);
0309     }
0310 
0311     // get all shape layers with shapes at point. This is a bit coarser than 'FindNodes',
0312     // note that point is in Document coordinates instead of image coordinates.
0313     QList<KisShapeLayerSP> findShapeLayers(KisNodeSP root, const QPointF &point, bool editableOnly) {
0314         QList<KisShapeLayerSP> foundNodes;
0315         KisLayerUtils::recursiveApplyNodes(root, [&] (KisNodeSP node) {
0316             if ((node->isEditable(true) && editableOnly) || !editableOnly) {
0317 
0318                 KisShapeLayerSP shapeLayer = dynamic_cast<KisShapeLayer*>(node.data());
0319                 if (shapeLayer && shapeLayer->isEditable() && shapeLayer->shapeManager()->shapeAt(point)) {
0320                     foundNodes.append(shapeLayer);
0321                 }
0322             }
0323         });
0324         return foundNodes;
0325     }
0326 
0327     QPainterPath shapeHoverInfoCrossLayer(KoCanvasBase *canvas, const QPointF &point, QString &shapeType, bool *isHorizontal, bool skipCurrentShapes)
0328     {
0329         QPainterPath p;
0330         KisCanvas2 *canvas2 = dynamic_cast<KisCanvas2*>(canvas);
0331         if (!canvas2) return p;
0332 
0333         QList<KoShape*> currentShapes = canvas->shapeManager()->selection()->selectedShapes();
0334         QList<KisShapeLayerSP> candidates = findShapeLayers(canvas2->image()->root(), point, true);
0335         KisShapeLayerSP shapeLayer = candidates.isEmpty()? nullptr: candidates.last();
0336 
0337         if (shapeLayer) {
0338             KoShape *shape = shapeLayer->shapeManager()->shapeAt(point);
0339             if (shape && !(currentShapes.contains(shape) && skipCurrentShapes)) {
0340                 shapeType = shape->shapeId();
0341                 KoSvgTextShape *t = dynamic_cast<KoSvgTextShape *>(shape);
0342                 if (t && isHorizontal) {
0343                     p.addRect(t->boundingRect());
0344                     *isHorizontal = t->writingMode() == KoSvgText::HorizontalTB;
0345                     if (!t->shapesInside().isEmpty()) {
0346                         QPainterPath paths;
0347                         Q_FOREACH(KoShape *s, t->shapesInside()) {
0348                             KoPathShape *path = dynamic_cast<KoPathShape *>(s);
0349                             if (path) {
0350                                 paths.addPath(t->absoluteTransformation().map(path->absoluteTransformation().map(path->outline())));
0351                             }
0352                         }
0353                         if (!paths.isEmpty()) {
0354                             p = paths;
0355                         }
0356                     }
0357                 } else {
0358                     p = shape->absoluteTransformation().map(shape->outline());
0359                 }
0360             }
0361         }
0362 
0363         return p;
0364     }
0365 
0366     bool selectShapeCrossLayer(KoCanvasBase *canvas, const QPointF &point, const QString &shapeType, bool skipCurrentShapes)
0367     {
0368         KisCanvas2 *canvas2 = dynamic_cast<KisCanvas2*>(canvas);
0369         if (!canvas2) return false;
0370 
0371         QList<KoShape*> currentShapes = canvas->shapeManager()->selection()->selectedShapes();
0372         QList<KisShapeLayerSP> candidates = findShapeLayers(canvas2->image()->root(), point, true);
0373         KisShapeLayerSP shapeLayer = candidates.isEmpty()? nullptr: candidates.last();
0374 
0375         if (shapeLayer) {
0376             KoShape *shape = shapeLayer->shapeManager()->shapeAt(point);
0377             if (shape
0378                     && !(currentShapes.contains(shape) && skipCurrentShapes)
0379                     && (shapeType.isEmpty() || shapeType == shape->shapeId())) {
0380                 canvas2->viewManager()->nodeManager()->slotNonUiActivatedNode(shapeLayer);
0381                 canvas2->shapeManager()->selection()->deselectAll();
0382                 canvas2->shapeManager()->selection()->select(shape);
0383             } else {
0384                 return false;
0385             }
0386         } else {
0387             return false;
0388         }
0389 
0390         return true;
0391     }
0392 
0393 }