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 }