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