Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_tool_utils.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2009 Boudewijn Rempt <boud@valdyas.org>
3 * SPDX-FileCopyrightText: 2018 Emmet & Eoin O'Neill <emmetoneill.pdx@gmail.com>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#include <kis_tool_utils.h>
9
10#include <KoMixColorsOp.h>
11#include <kis_group_layer.h>
12#include <kis_transaction.h>
15#include <kconfiggroup.h>
16#include <ksharedconfig.h>
17#include "kis_layer_utils.h"
18#include "kis_command_utils.h"
20
21#include "kis_canvas2.h"
22#include <QPainterPath>
23#include <kis_shape_layer.h>
24#include <KoShapeManager.h>
25#include <KoShape.h>
26#include <KoPathShape.h>
27#include <KoSvgTextShape.h>
28#include <KoSvgTextProperties.h>
29#include <KisViewManager.h>
30#include <kis_node_manager.h>
31#include <KoSelection.h>
32
33
34#include "KisAnimAutoKey.h"
35
36#include <QApplication>
37
38namespace KisToolUtils {
39
40 bool sampleColor(KoColor &out_color, KisPaintDeviceSP dev, const QPoint &pos,
41 KoColor const *const blendColor, int radius, int blend, bool pure)
42 {
43 KIS_ASSERT(dev);
44
45 // Bugfix hack forcing pure on first sample to avoid wrong
46 // format blendColor on newly initialized Krita.
47 static bool firstTime = true;
48 if (firstTime == true) {
49 pure = true;
50 firstTime = false;
51 }
52
53 const KoColorSpace *cs = dev->colorSpace();
54 KoColor sampledColor = KoColor::createTransparent(cs);
55
56 // Wrap around color sampling is supported on any paint device
57 bool oldSupportsWraparound = dev->supportsWraproundMode();
59
60 // Sampling radius.
61 if (!pure && radius > 1) {
62 QScopedPointer<KoMixColorsOp::Mixer> mixer(cs->mixColorsOp()->createMixer());
63
65 const int effectiveRadius = radius - 1;
66
67 const QRect sampleRect(pos.x() - effectiveRadius, pos.y() - effectiveRadius,
68 2 * effectiveRadius + 1, 2 * effectiveRadius + 1);
69 KisSequentialConstIterator it(dev, sampleRect);
70
71 const int radiusSq = pow2(effectiveRadius);
72
73 int nConseqPixels = it.nConseqPixels();
74 while (it.nextPixels(nConseqPixels)) {
75 const QPoint realPos(it.x(), it.y());
76 if (kisSquareDistance(realPos, pos) < radiusSq) {
77 mixer->accumulateAverage(it.oldRawData(), nConseqPixels);
78 }
79 }
80
81 mixer->computeMixedColor(sampledColor.data());
82
83 } else {
84 dev->pixel(pos.x(), pos.y(), &sampledColor);
85 }
86
87 dev->setSupportsWraparoundMode(oldSupportsWraparound);
88
89 // Color blending.
90 if (!pure && blendColor && blend < 100) {
91 //Scale from 0..100% to 0..255 range for mixOp weights.
92 quint8 blendScaled = static_cast<quint8>(blend * 2.55f);
93
94 const quint8 *colors[2];
95 colors[0] = blendColor->data();
96 colors[1] = sampledColor.data();
97 qint16 weights[2];
98 weights[0] = 255 - blendScaled;
99 weights[1] = blendScaled;
100
101 const KoMixColorsOp *mixOp = dev->colorSpace()->mixColorsOp();
102 mixOp->mixColors(colors, weights, 2, sampledColor.data());
103 }
104
105 sampledColor.convertTo(dev->compositionSourceColorSpace());
106
107 bool validColorSampled = sampledColor.opacityU8() != OPACITY_TRANSPARENT_U8;
108
109 if (validColorSampled) {
110 out_color = sampledColor;
111 }
112
113 return validColorSampled;
114 }
115
116 KisNodeSP findNode(KisNodeSP node, const QPoint &point, bool wholeGroup, bool editableOnly)
117 {
118 KisNodeSP foundNode = 0;
119 while (node) {
120 KisLayerSP layer = qobject_cast<KisLayer*>(node.data());
121
122 if (!layer || !layer->isEditable()) {
123 node = node->prevSibling();
124 continue;
125 }
126
127 KoColor color(layer->projection()->colorSpace());
128 layer->projection()->pixel(point.x(), point.y(), &color);
129
130 KisGroupLayerSP group = dynamic_cast<KisGroupLayer*>(layer.data());
131
132 if ((group && group->passThroughMode()) || color.opacityU8() != OPACITY_TRANSPARENT_U8) {
133 if (layer->inherits("KisGroupLayer") && (!editableOnly || layer->isEditable())) {
134 // if this is a group and the pixel is transparent, don't even enter it
135 foundNode = findNode(node->lastChild(), point, wholeGroup, editableOnly);
136 }
137 else {
138 foundNode = !wholeGroup ? node : node->parent();
139 }
140
141 }
142
143 if (foundNode) break;
144
145 node = node->prevSibling();
146 }
147
148 return foundNode;
149 }
150
151 KisNodeList findNodes(KisNodeSP node, const QPoint &point, bool wholeGroup, bool includeGroups, bool editableOnly)
152 {
153 KisNodeList foundNodes;
154 while (node) {
155 KisLayerSP layer = qobject_cast<KisLayer*>(node.data());
156
157 if (!layer || !layer->isEditable()) {
158 node = node->nextSibling();
159 continue;
160 }
161
162 KoColor color(layer->projection()->colorSpace());
163 layer->projection()->pixel(point.x(), point.y(), &color);
164 const bool isTransparent = color.opacityU8() == OPACITY_TRANSPARENT_U8;
165
166 KisGroupLayerSP group = dynamic_cast<KisGroupLayer*>(layer.data());
167
168 if (group) {
169 if (!isTransparent || group->passThroughMode()) {
170 foundNodes << findNodes(node->firstChild(), point, wholeGroup, includeGroups, editableOnly);
171 if (includeGroups) {
172 foundNodes << node;
173 }
174 }
175 } else {
176 if (!isTransparent) {
177 if (wholeGroup) {
178 if (!foundNodes.contains(node->parent())) {
179 foundNodes << node->parent();
180 }
181 } else {
182 foundNodes << node;
183 }
184 }
185 }
186
187 node = node->nextSibling();
188 }
189
190 return foundNodes;
191 }
192
193 bool clearImage(KisImageSP image, KisNodeList nodes, KisSelectionSP selection)
194 {
195 KisNodeList masks;
196
197 Q_FOREACH (KisNodeSP node, nodes) {
198 if (node->inherits("KisMask")) {
199 masks.append(node);
200 }
201 }
202
203 // To prevent deleting same layer multiple times
205 nodes.append(masks);
206
207 if (nodes.isEmpty()) {
208 return false;
209 }
210
213
214 Q_FOREACH (KisNodeSP node, nodes) {
215 KisLayerUtils::recursiveApplyNodes(node, [&applicator, selection, masks] (KisNodeSP node) {
216
217 // applied on masks if selected explicitly
218 if (node->inherits("KisMask") && !masks.contains(node)) {
219 return;
220 }
221
222 if(node->hasEditablePaintDevice()) {
223 KUndo2Command *cmd =
224 new KisCommandUtils::LambdaCommand(kundo2_i18n("Clear"),
225 [node, selection] () {
226 KisPaintDeviceSP device = node->paintDevice();
227
228 QScopedPointer<KisCommandUtils::CompositeCommand> parentCommand(
229 new KisCommandUtils::CompositeCommand());
230
231 KUndo2Command *autoKeyframeCommand = KisAutoKey::tryAutoCreateDuplicatedFrame(device);
232 if (autoKeyframeCommand) {
233 parentCommand->addCommand(autoKeyframeCommand);
234 }
235
236 KisTransaction transaction(kundo2_noi18n("internal-clear-command"), device);
237
238 QRect dirtyRect;
239 if (selection) {
240 dirtyRect = selection->selectedRect();
241 device->clearSelection(selection);
242 } else {
243 dirtyRect = device->extent();
244 device->clear();
245 }
246
247 device->setDirty(dirtyRect);
248 parentCommand->addCommand(transaction.endAndTake());
249
250 return parentCommand.take();
251 });
253 }
254 });
255 }
256
257 applicator.end();
258
259 return true;
260 }
261
262 const QString ColorSamplerConfig::CONFIG_GROUP_NAME = "tool_color_sampler";
263
264 ColorSamplerConfig::ColorSamplerConfig()
265 : toForegroundColor(true)
266 , updateColor(true)
267 , addColorToCurrentPalette(false)
268 , normaliseValues(false)
269 , sampleMerged(true)
270 , radius(1)
271 , blend(100)
272 {
273 }
274
276 {
278 props.setProperty("toForegroundColor", toForegroundColor);
279 props.setProperty("updateColor", updateColor);
280 props.setProperty("addPalette", addColorToCurrentPalette);
281 props.setProperty("normaliseValues", normaliseValues);
282 props.setProperty("sampleMerged", sampleMerged);
283 props.setProperty("radius", radius);
284 props.setProperty("blend", blend);
285
286 KConfigGroup config = KSharedConfig::openConfig()->group(CONFIG_GROUP_NAME);
287
288 config.writeEntry("ColorSamplerDefaultActivation", props.toXML());
289 }
290
292 {
294
295 KConfigGroup config = KSharedConfig::openConfig()->group(CONFIG_GROUP_NAME);
296 props.fromXML(config.readEntry("ColorSamplerDefaultActivation"));
297
298 toForegroundColor = props.getBool("toForegroundColor", true);
299 updateColor = props.getBool("updateColor", true);
300 addColorToCurrentPalette = props.getBool("addPalette", false);
301 normaliseValues = props.getBool("normaliseValues", false);
302 sampleMerged = props.getBool("sampleMerged", true);
303 radius = props.getInt("radius", 1);
304 blend = props.getInt("blend", 100);
305 }
306
307 void KRITAUI_EXPORT setCursorPos(const QPoint &point)
308 {
309 // https://bugreports.qt.io/browse/QTBUG-99009
310 QScreen *screen = qApp->screenAt(point);
311 if (!screen) {
312 screen = qApp->primaryScreen();
313 }
314 QCursor::setPos(screen, point);
315 }
316
317 // get all shape layers with shapes at point. This is a bit coarser than 'FindNodes',
318 // note that point is in Document coordinates instead of image coordinates.
319 QList<KisShapeLayerSP> findShapeLayers(KisNodeSP root, const QPointF &point, bool editableOnly) {
320 QList<KisShapeLayerSP> foundNodes;
322 if ((node->isEditable(true) && editableOnly) || !editableOnly) {
323
324 KisShapeLayerSP shapeLayer = dynamic_cast<KisShapeLayer*>(node.data());
325 if (shapeLayer && shapeLayer->isEditable() && shapeLayer->shapeManager()->shapeAt(point)) {
326 foundNodes.append(shapeLayer);
327 }
328 }
329 });
330 return foundNodes;
331 }
332
333 QPainterPath shapeHoverInfoCrossLayer(KoCanvasBase *canvas, const QPointF &point, QString &shapeType, bool *isHorizontal, bool skipCurrentShapes)
334 {
335 QPainterPath p;
336 KisCanvas2 *canvas2 = dynamic_cast<KisCanvas2*>(canvas);
337 if (!canvas2) return p;
338
339 QList<KoShape*> currentShapes = canvas->shapeManager()->selection()->selectedShapes();
340 QList<KisShapeLayerSP> candidates = findShapeLayers(canvas2->image()->root(), point, true);
341 KisShapeLayerSP shapeLayer = candidates.isEmpty()? nullptr: candidates.last();
342
343 if (shapeLayer) {
344 KoShape *shape = shapeLayer->shapeManager()->shapeAt(point);
345 if (shape && !(currentShapes.contains(shape) && skipCurrentShapes)) {
346 shapeType = shape->shapeId();
347 KoSvgTextShape *t = dynamic_cast<KoSvgTextShape *>(shape);
348 if (t && isHorizontal) {
349 p.addRect(t->boundingRect());
350 *isHorizontal = t->writingMode() == KoSvgText::HorizontalTB;
351 if (!t->shapesInside().isEmpty()) {
352 QPainterPath paths;
353 Q_FOREACH(KoShape *s, t->shapesInside()) {
354 KoPathShape *path = dynamic_cast<KoPathShape *>(s);
355 if (path) {
356 paths.addPath(t->absoluteTransformation().map(path->absoluteTransformation().map(path->outline())));
357 }
358 }
359 if (!paths.isEmpty()) {
360 p = paths;
361 }
362 }
363 } else {
364 p = shape->absoluteTransformation().map(shape->outline());
365 }
366 }
367 }
368
369 return p;
370 }
371
372 bool selectShapeCrossLayer(KoCanvasBase *canvas, const QPointF &point, const QString &shapeType, bool skipCurrentShapes)
373 {
374 KisCanvas2 *canvas2 = dynamic_cast<KisCanvas2*>(canvas);
375 if (!canvas2) return false;
376
377 QList<KoShape*> currentShapes = canvas->shapeManager()->selection()->selectedShapes();
378 QList<KisShapeLayerSP> candidates = findShapeLayers(canvas2->image()->root(), point, true);
379 KisShapeLayerSP shapeLayer = candidates.isEmpty()? nullptr: candidates.last();
380
381 if (shapeLayer) {
382 KoShape *shape = shapeLayer->shapeManager()->shapeAt(point);
383 if (shape
384 && !(currentShapes.contains(shape) && skipCurrentShapes)
385 && (shapeType.isEmpty() || shapeType == shape->shapeId())) {
386 canvas2->viewManager()->nodeManager()->slotNonUiActivatedNode(shapeLayer);
387 canvas2->shapeManager()->selection()->deselectAll();
388 canvas2->shapeManager()->selection()->select(shape);
389 } else {
390 return false;
391 }
392 } else {
393 return false;
394 }
395
396 return true;
397 }
398
399}
const Params2D p
QVector< KisImageSignalType > KisImageSignalVector
const quint8 OPACITY_TRANSPARENT_U8
KisImageWSP image() const
KoShapeManager shapeManager
KisViewManager * viewManager() const
void slotNonUiActivatedNode(KisNodeSP node)
bool supportsWraproundMode() const
virtual const KoColorSpace * compositionSourceColorSpace() const
const KoColorSpace * colorSpace() const
void setSupportsWraparoundMode(bool value)
bool pixel(qint32 x, qint32 y, QColor *c) const
void applyCommand(KUndo2Command *command, KisStrokeJobData::Sequentiality sequentiality=KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity=KisStrokeJobData::NORMAL)
ALWAYS_INLINE int x() const
ALWAYS_INLINE const quint8 * oldRawData() const
ALWAYS_INLINE int y() const
KoShapeManager * shapeManager() const
KisNodeManager * nodeManager() const
The node manager handles everything about nodes.
virtual KoShapeManager * shapeManager() const =0
KoMixColorsOp * mixColorsOp
void convertTo(const KoColorSpace *cs, KoColorConversionTransformation::Intent renderingIntent, KoColorConversionTransformation::ConversionFlags conversionFlags)
Definition KoColor.cpp:136
static KoColor createTransparent(const KoColorSpace *cs)
Definition KoColor.cpp:681
quint8 * data()
Definition KoColor.h:144
quint8 opacityU8() const
Definition KoColor.cpp:341
virtual Mixer * createMixer() const =0
virtual void mixColors(const quint8 *const *colors, const qint16 *weights, int nColors, quint8 *dst, int weightSum=255) const =0
The position of a path point within a path shape.
Definition KoPathShape.h:63
void deselectAll()
clear the selections list
void select(KoShape *shape)
const QList< KoShape * > selectedShapes() const
KoShape * shapeAt(const QPointF &position, KoFlake::ShapeSelection selection=KoFlake::ShapeOnTop, bool omitHiddenShapes=true)
KoSelection * selection
QString shapeId() const
Definition KoShape.cpp:1057
virtual QPainterPath outline() const
Definition KoShape.cpp:630
QTransform absoluteTransformation() const
Definition KoShape.cpp:382
QRectF boundingRect() const override
Get the bounding box of the shape.
QList< KoShape * > shapesInside
KoSvgText::WritingMode writingMode() const
writingMode There's a number of places we need to check the writing mode to provide proper controls.
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
T pow2(const T &x)
Definition kis_global.h:166
qreal kisSquareDistance(const QPointF &pt1, const QPointF &pt2)
Definition kis_global.h:194
KUndo2MagicString kundo2_i18n(const char *text)
void recursiveApplyNodes(NodePointer node, Functor func)
void filterMergeableNodes(KisNodeList &nodes, bool allowMasks)
bool selectShapeCrossLayer(KoCanvasBase *canvas, const QPointF &point, const QString &shapeType, bool skipCurrentShapes)
selectShapeCrossLayer Tries to select a shape under the cursor regardless of which layer it is on,...
bool clearImage(KisImageSP image, KisNodeList nodes, KisSelectionSP selection)
void KRITAUI_EXPORT setCursorPos(const QPoint &point)
KisNodeList findNodes(KisNodeSP node, const QPoint &point, bool wholeGroup, bool includeGroups, bool editableOnly)
QPainterPath shapeHoverInfoCrossLayer(KoCanvasBase *canvas, const QPointF &point, QString &shapeType, bool *isHorizontal, bool skipCurrentShapes)
shapeHoverInfoCrossLayer get hover info of shapes on all layers.
KisNodeSP findNode(KisNodeSP node, const QPoint &point, bool wholeGroup, bool editableOnly)
bool sampleColor(KoColor &out_color, KisPaintDeviceSP dev, const QPoint &pos, KoColor const *const blendColor, int radius, int blend, bool pure)
QList< KisShapeLayerSP > findShapeLayers(KisNodeSP root, const QPointF &point, bool editableOnly)
@ HorizontalTB
Definition KoSvgText.h:38
bool isEditable(bool checkVisibility=true) const
bool hasEditablePaintDevice() const
KisPaintDeviceSP projection() const override
Definition kis_layer.cc:820
KisNodeSP prevSibling() const
Definition kis_node.cpp:402
KisNodeSP firstChild() const
Definition kis_node.cpp:361
KisNodeWSP parent
Definition kis_node.cpp:86
KisNodeSP lastChild() const
Definition kis_node.cpp:367
KisNodeSP nextSibling() const
Definition kis_node.cpp:408
void toXML(QDomDocument &, QDomElement &) const override
bool fromXML(const QString &xml, bool clear=true) override
virtual void setProperty(const QString &name, const QVariant &value)
bool getBool(const QString &name, bool def=false) const
int getInt(const QString &name, int def=0) const
static const QString CONFIG_GROUP_NAME