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#include <kis_action_registry.h>
33#include <KoToolBase.h>
34
35
36#include "KisAnimAutoKey.h"
37
38#include <QApplication>
39
40namespace KisToolUtils {
41
42 bool sampleColor(KoColor &out_color, KisPaintDeviceSP dev, const QPoint &pos,
43 KoColor const *const blendColor, int radius, int blend, bool pure)
44 {
45 KIS_ASSERT(dev);
46
47 // Bugfix hack forcing pure on first sample to avoid wrong
48 // format blendColor on newly initialized Krita.
49 static bool firstTime = true;
50 if (firstTime == true) {
51 pure = true;
52 firstTime = false;
53 }
54
55 const KoColorSpace *cs = dev->colorSpace();
56 KoColor sampledColor = KoColor::createTransparent(cs);
57
58 // Wrap around color sampling is supported on any paint device
59 bool oldSupportsWraparound = dev->supportsWraproundMode();
61
62 // Sampling radius.
63 if (!pure && radius > 1) {
64 QScopedPointer<KoMixColorsOp::Mixer> mixer(cs->mixColorsOp()->createMixer());
65
67 const int effectiveRadius = radius - 1;
68
69 const QRect sampleRect(pos.x() - effectiveRadius, pos.y() - effectiveRadius,
70 2 * effectiveRadius + 1, 2 * effectiveRadius + 1);
71 KisSequentialConstIterator it(dev, sampleRect);
72
73 const int radiusSq = pow2(effectiveRadius);
74
75 int nConseqPixels = it.nConseqPixels();
76 while (it.nextPixels(nConseqPixels)) {
77 const QPoint realPos(it.x(), it.y());
78 if (kisSquareDistance(realPos, pos) < radiusSq) {
79 mixer->accumulateAverage(it.oldRawData(), nConseqPixels);
80 }
81 }
82
83 mixer->computeMixedColor(sampledColor.data());
84
85 } else {
86 dev->pixel(pos.x(), pos.y(), &sampledColor);
87 }
88
89 dev->setSupportsWraparoundMode(oldSupportsWraparound);
90
91 // Color blending.
92 if (!pure && blendColor && blend < 100) {
93 //Scale from 0..100% to 0..255 range for mixOp weights.
94 quint8 blendScaled = static_cast<quint8>(blend * 2.55f);
95
96 const quint8 *colors[2];
97 colors[0] = blendColor->data();
98 colors[1] = sampledColor.data();
99 qint16 weights[2];
100 weights[0] = 255 - blendScaled;
101 weights[1] = blendScaled;
102
103 const KoMixColorsOp *mixOp = dev->colorSpace()->mixColorsOp();
104 mixOp->mixColors(colors, weights, 2, sampledColor.data());
105 }
106
107 sampledColor.convertTo(dev->compositionSourceColorSpace());
108
109 bool validColorSampled = sampledColor.opacityU8() != OPACITY_TRANSPARENT_U8;
110
111 if (validColorSampled) {
112 out_color = sampledColor;
113 }
114
115 return validColorSampled;
116 }
117
118 KisNodeSP findNode(KisNodeSP node, const QPoint &point, bool wholeGroup, bool editableOnly)
119 {
120 KisNodeSP foundNode = 0;
121 while (node) {
122 KisLayerSP layer = qobject_cast<KisLayer*>(node.data());
123
124 if (!layer || !layer->isEditable()) {
125 node = node->prevSibling();
126 continue;
127 }
128
129 KoColor color(layer->projection()->colorSpace());
130 layer->projection()->pixel(point.x(), point.y(), &color);
131
132 KisGroupLayerSP group = dynamic_cast<KisGroupLayer*>(layer.data());
133
134 if ((group && group->passThroughMode()) || color.opacityU8() != OPACITY_TRANSPARENT_U8) {
135 if (layer->inherits("KisGroupLayer") && (!editableOnly || layer->isEditable())) {
136 // if this is a group and the pixel is transparent, don't even enter it
137 foundNode = findNode(node->lastChild(), point, wholeGroup, editableOnly);
138 }
139 else {
140 foundNode = !wholeGroup ? node : node->parent();
141 }
142
143 }
144
145 if (foundNode) break;
146
147 node = node->prevSibling();
148 }
149
150 return foundNode;
151 }
152
153 KisNodeList findNodes(KisNodeSP node, const QPoint &point, bool wholeGroup, bool includeGroups, bool editableOnly)
154 {
155 KisNodeList foundNodes;
156 while (node) {
157 KisLayerSP layer = qobject_cast<KisLayer*>(node.data());
158
159 if (!layer || !layer->isEditable()) {
160 node = node->nextSibling();
161 continue;
162 }
163
164 KoColor color(layer->projection()->colorSpace());
165 layer->projection()->pixel(point.x(), point.y(), &color);
166 const bool isTransparent = color.opacityU8() == OPACITY_TRANSPARENT_U8;
167
168 KisGroupLayerSP group = dynamic_cast<KisGroupLayer*>(layer.data());
169
170 if (group) {
171 if (!isTransparent || group->passThroughMode()) {
172 foundNodes << findNodes(node->firstChild(), point, wholeGroup, includeGroups, editableOnly);
173 if (includeGroups) {
174 foundNodes << node;
175 }
176 }
177 } else {
178 if (!isTransparent) {
179 if (wholeGroup) {
180 if (!foundNodes.contains(node->parent())) {
181 foundNodes << node->parent();
182 }
183 } else {
184 foundNodes << node;
185 }
186 }
187 }
188
189 node = node->nextSibling();
190 }
191
192 return foundNodes;
193 }
194
195 bool clearImage(KisImageSP image, KisNodeList nodes, KisSelectionSP selection)
196 {
197 KisNodeList masks;
198
199 Q_FOREACH (KisNodeSP node, nodes) {
200 if (node->inherits("KisMask")) {
201 masks.append(node);
202 }
203 }
204
205 // To prevent deleting same layer multiple times
207 nodes.append(masks);
208
209 if (nodes.isEmpty()) {
210 return false;
211 }
212
215
216 Q_FOREACH (KisNodeSP node, nodes) {
217 KisLayerUtils::recursiveApplyNodes(node, [&applicator, selection, masks] (KisNodeSP node) {
218
219 // applied on masks if selected explicitly
220 if (node->inherits("KisMask") && !masks.contains(node)) {
221 return;
222 }
223
224 if(node->hasEditablePaintDevice()) {
225 KUndo2Command *cmd =
226 new KisCommandUtils::LambdaCommand(kundo2_i18n("Clear"),
227 [node, selection] () {
228 KisPaintDeviceSP device = node->paintDevice();
229
230 QScopedPointer<KisCommandUtils::CompositeCommand> parentCommand(
231 new KisCommandUtils::CompositeCommand());
232
233 KUndo2Command *autoKeyframeCommand = KisAutoKey::tryAutoCreateDuplicatedFrame(device);
234 if (autoKeyframeCommand) {
235 parentCommand->addCommand(autoKeyframeCommand);
236 }
237
238 KisTransaction transaction(kundo2_noi18n("internal-clear-command"), device);
239
240 QRect dirtyRect;
241 if (selection) {
242 dirtyRect = selection->selectedRect();
243 device->clearSelection(selection);
244 } else {
245 dirtyRect = device->extent();
246 device->clear();
247 }
248
249 device->setDirty(dirtyRect);
250 parentCommand->addCommand(transaction.endAndTake());
251
252 return parentCommand.take();
253 });
255 }
256 });
257 }
258
259 applicator.end();
260
261 return true;
262 }
263
264 const QString ColorSamplerConfig::CONFIG_GROUP_NAME = "tool_color_sampler";
265
266 ColorSamplerConfig::ColorSamplerConfig()
267 : toForegroundColor(true)
268 , updateColor(true)
269 , addColorToCurrentPalette(false)
270 , normaliseValues(false)
271 , sampleMerged(true)
272 , radius(1)
273 , blend(100)
274 {
275 }
276
278 {
280 props.setProperty("toForegroundColor", toForegroundColor);
281 props.setProperty("updateColor", updateColor);
282 props.setProperty("addPalette", addColorToCurrentPalette);
283 props.setProperty("normaliseValues", normaliseValues);
284 props.setProperty("sampleMerged", sampleMerged);
285 props.setProperty("radius", radius);
286 props.setProperty("blend", blend);
287
288 KConfigGroup config = KSharedConfig::openConfig()->group(CONFIG_GROUP_NAME);
289
290 config.writeEntry("ColorSamplerDefaultActivation", props.toXML());
291 }
292
294 {
296
297 KConfigGroup config = KSharedConfig::openConfig()->group(CONFIG_GROUP_NAME);
298 props.fromXML(config.readEntry("ColorSamplerDefaultActivation"));
299
300 toForegroundColor = props.getBool("toForegroundColor", true);
301 updateColor = props.getBool("updateColor", true);
302 addColorToCurrentPalette = props.getBool("addPalette", false);
303 normaliseValues = props.getBool("normaliseValues", false);
304 sampleMerged = props.getBool("sampleMerged", true);
305 radius = props.getInt("radius", 1);
306 blend = props.getInt("blend", 100);
307 }
308
309 void KRITAUI_EXPORT setCursorPos(const QPoint &point)
310 {
311 // https://bugreports.qt.io/browse/QTBUG-99009
312 QScreen *screen = qApp->screenAt(point);
313 if (!screen) {
314 screen = qApp->primaryScreen();
315 }
316 QCursor::setPos(screen, point);
317 }
318
319 // get all shape layers with shapes at point. This is a bit coarser than 'FindNodes',
320 // note that point is in Document coordinates instead of image coordinates.
321 QList<KisShapeLayerSP> findShapeLayers(KisNodeSP root, const QPointF &point, bool editableOnly) {
322 QList<KisShapeLayerSP> foundNodes;
324 if ((node->isEditable(true) && editableOnly) || !editableOnly) {
325
326 KisShapeLayerSP shapeLayer = dynamic_cast<KisShapeLayer*>(node.data());
327 if (shapeLayer && shapeLayer->isEditable() && shapeLayer->shapeManager()->shapeAt(point)) {
328 foundNodes.append(shapeLayer);
329 }
330 }
331 });
332 return foundNodes;
333 }
334
335 QPainterPath shapeHoverInfoCrossLayer(KoCanvasBase *canvas, const QPointF &point, QString &shapeType, bool *isHorizontal, bool skipCurrentShapes)
336 {
337 QPainterPath p;
338 KisCanvas2 *canvas2 = dynamic_cast<KisCanvas2*>(canvas);
339 if (!canvas2) return p;
340
341 QList<KoShape*> currentShapes = canvas->shapeManager()->selection()->selectedShapes();
342 QList<KisShapeLayerSP> candidates = findShapeLayers(canvas2->image()->root(), point, true);
343 KisShapeLayerSP shapeLayer = candidates.isEmpty()? nullptr: candidates.last();
344
345 if (shapeLayer) {
346 KoShape *shape = shapeLayer->shapeManager()->shapeAt(point);
347 if (shape && !(currentShapes.contains(shape) && skipCurrentShapes)) {
348 shapeType = shape->shapeId();
349 KoSvgTextShape *t = dynamic_cast<KoSvgTextShape *>(shape);
350 if (t) {
351 p.addRect(t->boundingRect());
352 if (isHorizontal) {
353 *isHorizontal = t->writingMode() == KoSvgText::HorizontalTB;
354 }
355 if (!t->shapesInside().isEmpty()) {
356 QPainterPath paths;
357 Q_FOREACH(KoShape *s, t->shapesInside()) {
358 KoPathShape *path = dynamic_cast<KoPathShape *>(s);
359 if (path) {
360 paths.addPath(path->absoluteTransformation().map(path->outline()));
361 }
362 }
363 if (!paths.isEmpty()) {
364 p = paths;
365 }
366 }
367 } else {
368 p = shape->absoluteTransformation().map(shape->outline());
369 }
370 }
371 }
372
373 return p;
374 }
375
376 bool selectShapeCrossLayer(KoCanvasBase *canvas, const QPointF &point, const QString &shapeType, bool skipCurrentShapes)
377 {
378 KisCanvas2 *canvas2 = dynamic_cast<KisCanvas2*>(canvas);
379 if (!canvas2) return false;
380
381 QList<KoShape*> currentShapes = canvas->shapeManager()->selection()->selectedShapes();
382 QList<KisShapeLayerSP> candidates = findShapeLayers(canvas2->image()->root(), point, true);
383 KisShapeLayerSP shapeLayer = candidates.isEmpty()? nullptr: candidates.last();
384
385 if (shapeLayer) {
386 KoShape *shape = shapeLayer->shapeManager()->shapeAt(point);
387 if (shape
388 && !(currentShapes.contains(shape) && skipCurrentShapes)
389 && (shapeType.isEmpty() || shapeType == shape->shapeId())) {
390 canvas2->viewManager()->nodeManager()->slotNonUiActivatedNode(shapeLayer);
391 canvas2->shapeManager()->selection()->deselectAll();
392 canvas2->shapeManager()->selection()->select(shape);
393 } else {
394 return false;
395 }
396 } else {
397 return false;
398 }
399
400 return true;
401 }
402
403
405 : m_tool(tool)
406 {
407 }
408
410 {
412 QList<QAction *> actions;
413
414 actions << actionRegistry->makeQAction("movetool-move-up", actionRegistry);
415 actions << actionRegistry->makeQAction("movetool-move-down", actionRegistry);
416 actions << actionRegistry->makeQAction("movetool-move-left", actionRegistry);
417 actions << actionRegistry->makeQAction("movetool-move-right", actionRegistry);
418
419 actions << actionRegistry->makeQAction("movetool-move-up-more", actionRegistry);
420 actions << actionRegistry->makeQAction("movetool-move-down-more", actionRegistry);
421 actions << actionRegistry->makeQAction("movetool-move-left-more", actionRegistry);
422 actions << actionRegistry->makeQAction("movetool-move-right-more", actionRegistry);
423
424 return actions;
425 }
426
428 {
429 m_tool->action("movetool-move-up")->setEnabled(value);
430 m_tool->action("movetool-move-down")->setEnabled(value);
431 m_tool->action("movetool-move-left")->setEnabled(value);
432 m_tool->action("movetool-move-right")->setEnabled(value);
433 m_tool->action("movetool-move-up-more")->setEnabled(value);
434 m_tool->action("movetool-move-down-more")->setEnabled(value);
435 m_tool->action("movetool-move-left-more")->setEnabled(value);
436 m_tool->action("movetool-move-right-more")->setEnabled(value);
437 }
438}
float value(const T *src, size_t ch)
const Params2D p
QVector< KisImageSignalType > KisImageSignalVector
const quint8 OPACITY_TRANSPARENT_U8
QAction * makeQAction(const QString &name, QObject *parent=0)
static KisActionRegistry * instance()
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
static QList< QAction * > createActions()
void setInternalMoveShortcutsEnabled(bool value)
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:880
virtual QPainterPath outline() const
Definition KoShape.cpp:559
QTransform absoluteTransformation() const
Definition KoShape.cpp:335
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.
QAction * action(const QString &name) const
#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:826
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