Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_tool_gradient.cc
Go to the documentation of this file.
1/*
2 * kis_tool_gradient.cc - part of Krita
3 *
4 * SPDX-FileCopyrightText: 2002 Patrick Julien <freak@codepimps.org>
5 * SPDX-FileCopyrightText: 2003 Boudewijn Rempt <boud@valdyas.org>
6 * SPDX-FileCopyrightText: 2004-2007 Adrian Page <adrian@pagenet.plus.com>
7 * SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
8 *
9 * SPDX-License-Identifier: GPL-2.0-or-later
10 */
11
12#include "kis_tool_gradient.h"
13
14#include <cfloat>
15
16#include <QApplication>
17#include <QPainter>
18#include <QLabel>
19#include <QLayout>
20#include <QCheckBox>
21
22#include <kis_transaction.h>
23#include <kis_debug.h>
24#include <klocalizedstring.h>
25#include <kcombobox.h>
26
27
28#include <KoPointerEvent.h>
29#include <KoCanvasBase.h>
30#include <KoViewConverter.h>
31#include <KoUpdater.h>
32#include <KoProgressUpdater.h>
33
35#include <kis_painter.h>
37#include <kis_layer.h>
38#include <kis_selection.h>
39#include <kis_paint_layer.h>
40
41#include <canvas/kis_canvas2.h>
42#include <KisViewManager.h>
44#include <kis_slider_spin_box.h>
45#include <kis_cursor.h>
46#include <kis_config.h>
48#include "kis_command_utils.h"
51
52
54 : KisToolPaint(canvas, KisCursor::loadWithSize("tool_gradient_cursor.svg", 32, 32, 6, 6))
55{
56 setObjectName("tool_gradient");
57
58 m_startPos = QPointF(0, 0);
59 m_endPos = QPointF(0, 0);
60
63 m_reverse = false;
67
68 KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas);
69
70 connect(kritaCanvas->viewManager()->canvasResourceProvider(), SIGNAL(sigEffectiveCompositeOpChanged()), SLOT(resetCursorStyle()));
71}
72
76
78{
79 if (isEraser()) {
80 useCursor(KisCursor::loadWithSize("tool_gradient_eraser_cursor.svg", 32, 32, 6, 6));
81 } else {
83 }
84
86}
87
88void KisToolGradient::activate(const QSet<KoShape*> &shapes)
89{
91 m_configGroup = KSharedConfig::openConfig()->group(toolId());
92}
93
94void KisToolGradient::paint(QPainter &painter, const KoViewConverter &converter)
95{
97 paintLine(painter);
98 }
99 KisToolPaint::paint(painter, converter);
100}
101
103{
104 if (!nodeEditable()) {
105 event->ignore();
106 return;
107 }
108
110
111 m_startPos = convertToPixelCoordAndSnap(event, QPointF(), false);
113}
114
116{
123 //CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
124
125 // First ensure the old guideline is deleted
127
128 QPointF pos = convertToPixelCoordAndSnap(event, QPointF(), false);
129
130 if (event->modifiers() == Qt::ShiftModifier) {
131 m_endPos = straightLine(pos);
132 } else {
133 m_endPos = pos;
134 }
135
137}
138
140{
141 Q_UNUSED(event);
144
145 if (!currentNode())
146 return;
147
148 if (m_startPos == m_endPos) {
149 return;
150 }
151
152 KisImageSP image = this->image();
153
154 KisResourcesSnapshotSP resources =
155 new KisResourcesSnapshot(image, currentNode(), this->canvas()->resourceManager());
156
157 if (image && resources->currentNode()->paintDevice()) {
158 KUndo2MagicString actionName = kundo2_i18n("Gradient");
159 KisProcessingApplicator applicator(image, resources->currentNode(),
162 actionName);
163
164 applicator.applyCommand(
166 [resources, startPos = m_startPos, endPos = m_endPos,
167 shape = m_shape, repeat = m_repeat, reverse = m_reverse,
168 antiAliasThreshold = m_antiAliasThreshold, dither = m_dither,
169 ditherSteps = m_ditherSteps] () mutable {
170
171 KisNodeSP node = resources->currentNode();
172 KisPaintDeviceSP device = node->paintDevice();
174 const QRect bounds = device->defaultBounds()->bounds();
175
176 KisGradientPainter painter(device, resources->activeSelection());
177 resources->setupPainter(&painter);
178 painter.setProgress(helper.updater());
179
180 painter.beginTransaction();
181
182 painter.setGradientShape(shape);
183 painter.paintGradient(startPos, endPos,
184 repeat, antiAliasThreshold,
185 reverse, 0, 0,
186 bounds.width(), bounds.height(),
187 dither, ditherSteps);
188
189 return painter.endAndTakeTransaction();
190 }));
191 applicator.end();
192 }
193
195}
196
197QPointF KisToolGradient::straightLine(QPointF point)
198{
199 QPointF comparison = point - m_startPos;
200 QPointF result;
201
202 if (fabs(comparison.x()) > fabs(comparison.y())) {
203 result.setX(point.x());
204 result.setY(m_startPos.y());
205 } else {
206 result.setX(m_startPos.x());
207 result.setY(point.y());
208 }
209
210 return result;
211}
212
214{
215 QPointF viewStartPos = pixelToView(m_startPos);
216 QPointF viewStartEnd = pixelToView(m_endPos);
217
218 if (canvas()) {
219 QPainterPath path;
220 path.moveTo(viewStartPos);
221 path.lineTo(viewStartEnd);
222 paintToolOutline(&gc, path);
223 }
224}
225
227{
228 if (canvas()) {
229 QRectF bound(m_startPos, m_endPos);
230 canvas()->updateCanvas(convertToPt(bound.normalized().adjusted(-3, -3, 3, 3)));
231 }
232}
233
235{
236 QWidget *widget = KisToolPaint::createOptionWidget();
237 Q_CHECK_PTR(widget);
238 widget->setObjectName(toolId() + " option widget");
239
240
241 // Make sure to create the connections last after everything is set up. The initialized values
242 // won't be loaded from the configuration file if you add the widget before the connection
243 m_lbShape = new QLabel(i18n("Shape:"), widget);
244 m_cmbShape = new KComboBox(widget);
245 m_cmbShape->setObjectName("shape_combo");
246 m_cmbShape->addItem(i18nc("the gradient will be drawn linearly", "Linear"));
247 m_cmbShape->addItem(i18nc("the gradient will be drawn bilinearly", "Bi-Linear"));
248 m_cmbShape->addItem(i18nc("the gradient will be drawn radially", "Radial"));
249 m_cmbShape->addItem(i18nc("the gradient will be drawn in a square around a centre", "Square"));
250 m_cmbShape->addItem(i18nc("the gradient will be drawn as an asymmetric cone", "Conical"));
251 m_cmbShape->addItem(i18nc("the gradient will be drawn as a symmetric cone", "Conical Symmetric"));
252 m_cmbShape->addItem(i18nc("the gradient will be drawn as a spiral", "Spiral"));
253 m_cmbShape->addItem(i18nc("the gradient will be drawn as a reverse spiral", "Reverse Spiral"));
254 m_cmbShape->addItem(i18nc("the gradient will be drawn in a selection outline", "Shaped"));
256 connect(m_cmbShape, SIGNAL(currentIndexChanged(int)), this, SLOT(slotSetShape(int)));
257
258 m_lbRepeat = new QLabel(i18n("Repeat:"), widget);
259 m_cmbRepeat = new KComboBox(widget);
260 m_cmbRepeat->setObjectName("repeat_combo");
261 m_cmbRepeat->addItem(i18nc("The gradient will not repeat", "None"));
262 m_cmbRepeat->addItem(i18nc("The gradient will repeat forwards", "Forwards"));
263 m_cmbRepeat->addItem(i18nc("The gradient will repeat alternatingly", "Alternating"));
265 connect(m_cmbRepeat, SIGNAL(currentIndexChanged(int)), this, SLOT(slotSetRepeat(int)));
266
267 m_lbDither = new QLabel(i18n("Dither:"), widget);
268 m_cmbDither = new KComboBox(widget);
269 m_cmbDither->setObjectName("dither");
270 m_cmbDither->addItem(i18nc("The gradient will not be dithered", "None"));
271 m_cmbDither->addItem(i18nc("The gradient will be dithered with Blue Noise", "Blue Noise"));
272 m_cmbDither->addItem(i18nc("The gradient will be dithered with Bayer Matrix 2x2", "Bayer Matrix 2x2"));
273 m_cmbDither->addItem(i18nc("The gradient will be dithered with Bayer Matrix 4x4", "Bayer Matrix 4x4"));
274 m_cmbDither->addItem(i18nc("The gradient will be dithered with Bayer Matrix 8x8", "Bayer Matrix 8x8"));
276 connect(m_cmbDither, SIGNAL(currentIndexChanged(int)), this, SLOT(slotSetDither(int)));
277
278 m_lbDitherSteps = new QLabel(i18n("Dither steps:"), widget);
280 m_sbDitherSteps->setObjectName("dither_steps");
281 m_sbDitherSteps->setRange(2, 10000);
283 connect(m_sbDitherSteps, SIGNAL(valueChanged(int)), this, SLOT(slotSetDitherSteps(int)));
284
285 m_lbAntiAliasThreshold = new QLabel(i18n("Anti-alias threshold:"), widget);
287 m_slAntiAliasThreshold->setObjectName("threshold_slider");
289 m_slAntiAliasThreshold->setSingleStep(0.001);
291 connect(m_slAntiAliasThreshold, SIGNAL(valueChanged(qreal)), this, SLOT(slotSetAntiAliasThreshold(qreal)));
292
293 m_ckReverse = new QCheckBox(i18nc("the gradient will be drawn with the color order reversed", "Reverse"), widget);
294 m_ckReverse->setObjectName("reverse_check");
295 connect(m_ckReverse, SIGNAL(toggled(bool)), this, SLOT(slotSetReverse(bool)));
297
298
299 // load configuration settings into widget (updating UI will update internal variables from signals/slots)
300 slotSetDither((int)m_configGroup.readEntry("dither", 0));
302 m_ckReverse->setChecked((bool)m_configGroup.readEntry("reverse", false));
303 m_cmbShape->setCurrentIndex((int)m_configGroup.readEntry("shape", 0));
304 m_cmbRepeat->setCurrentIndex((int)m_configGroup.readEntry("repeat", 0));
305 m_slAntiAliasThreshold->setValue((qreal)m_configGroup.readEntry("antialiasThreshold", 0.0));
306
307 return widget;
308}
309
311{
313 m_configGroup.writeEntry("shape", shape);
314}
315
317{
319 m_configGroup.writeEntry("repeat", repeat);
320}
321
323{
324 m_reverse = state;
325 m_configGroup.writeEntry("reverse", state);
326}
327
329{
330 if (m_cmbDither->currentIndex() != dither) {
331 m_cmbDither->setCurrentIndex(dither);
332 }
333
334 m_dither = static_cast<KisGradientPainter::enumDither>(dither);
335 m_configGroup.writeEntry("dither", dither);
336
338}
339
341{
343 m_configGroup.writeEntry("ditherSteps", value);
344}
345
347{
349 m_configGroup.writeEntry("antialiasThreshold", value);
350}
351
352
float value(const T *src, size_t ch)
void updateCanvas(const QRectF &rc) override
KisViewManager * viewManager() const
static QCursor loadWithSize(const QString &cursorName, int width, int height, int hotspotX=-1, int hotspotY=-1)
virtual QRect bounds() const =0
This class is a spinbox in which you can click and drag to set the value. A slider like bar is displa...
void setValue(qreal newValue)
void setRange(qreal newMinimum, qreal newMaximum, int newNumberOfDecimals=0, bool computeNewFastSliderStep=true)
Set the minimum and the maximum values of the range.
The KisIntParseSpinBox class is a cleverer SpinBox, able to parse arithmetic expressions.
void setValue(int value, bool overwriteExpression=false)
Set the value of the spinbox.
KisDefaultBoundsBaseSP defaultBounds() const
KUndo2Command * endAndTakeTransaction()
void beginTransaction(const KUndo2MagicString &transactionName=KUndo2MagicString(), int timedID=-1)
Begin an undoable paint operation.
void setProgress(KoUpdater *progressUpdater)
void applyCommand(KUndo2Command *command, KisStrokeJobData::Sequentiality sequentiality=KisStrokeJobData::SEQUENTIAL, KisStrokeJobData::Exclusivity exclusivity=KisStrokeJobData::NORMAL)
The KisResourcesSnapshot class takes a snapshot of the various resources like colors and settings use...
void setupPainter(KisPainter *painter)
KisSelectionSP activeSelection() const
void endPrimaryAction(KoPointerEvent *event) override
void resetCursorStyle() override
KisToolGradient(KoCanvasBase *canvas)
QLabel * m_lbAntiAliasThreshold
void continuePrimaryAction(KoPointerEvent *event) override
QWidget * createOptionWidget() override
KComboBox * m_cmbRepeat
KComboBox * m_cmbDither
void activate(const QSet< KoShape * > &shapes) override
KisGradientPainter::enumDither m_dither
KConfigGroup m_configGroup
KisGradientPainter::enumGradientShape m_shape
void beginPrimaryAction(KoPointerEvent *event) override
void paintLine(QPainter &gc)
KisDoubleSliderSpinBox * m_slAntiAliasThreshold
~KisToolGradient() override
QCheckBox * m_ckReverse
void slotSetAntiAliasThreshold(qreal)
KComboBox * m_cmbShape
KisIntParseSpinBox * m_sbDitherSteps
QPointF straightLine(QPointF point)
void paint(QPainter &painter, const KoViewConverter &converter) override
KisGradientPainter::enumGradientRepeat m_repeat
QWidget * createOptionWidget() override
void activate(const QSet< KoShape * > &shapes) override
void paint(QPainter &gc, const KoViewConverter &converter) override
void setMode(ToolMode mode) override
virtual void addOptionWidgetOption(QWidget *control, QWidget *label=nullptr)
Add a widget and a label to the current option widget layout.
bool isEraser() const
void showControl(QWidget *control, bool value)
KisCanvasResourceProvider * canvasResourceProvider()
Qt::KeyboardModifiers modifiers() const
Q_INVOKABLE QString toolId() const
void useCursor(const QCursor &cursor)
#define bounds(x, a, b)
#define CHECK_MODE_SANITY_OR_RETURN(_mode)
Definition kis_tool.h:27
KUndo2MagicString kundo2_i18n(const char *text)
virtual KisPaintDeviceSP paintDevice() const =0
The LambdaCommand struct is a shorthand for creation of AggregateCommand commands using C++ lambda fe...
void setGradientShape(enumGradientShape shape)
static bool isBayerDither(enumDither dither)
static constexpr int defaultDitherSteps
bool paintGradient(const QPointF &gradientVectorStart, const QPointF &gradientVectorEnd, enumGradientRepeat repeat, double antiAliasThreshold, bool reverseGradient, qint32 startx, qint32 starty, qint32 width, qint32 height, enumDither dither=DitherNone, int ditherSteps=defaultDitherSteps)
virtual ToolMode mode() const
Definition kis_tool.cc:407
virtual void resetCursorStyle()
Definition kis_tool.cc:613
bool nodeEditable()
Checks checks if the current node is editable.
Definition kis_tool.cc:651
QPointF pixelToView(const QPoint &pixelCoord) const
Definition kis_tool.cc:269
KisNodeSP currentNode() const
Definition kis_tool.cc:370
bool overrideCursorIfNotEditable()
Override the cursor appropriately if current node is not editable.
Definition kis_tool.cc:618
void paintToolOutline(QPainter *painter, const KisOptimizedBrushOutline &path)
Definition kis_tool.cc:589
QRectF convertToPt(const QRectF &rect)
Definition kis_tool.cc:252
KisImageWSP image() const
Definition kis_tool.cc:332
@ PAINT_MODE
Definition kis_tool.h:300
@ HOVER_MODE
Definition kis_tool.h:299
QPointF convertToPixelCoordAndSnap(KoPointerEvent *e, const QPointF &offset=QPointF(), bool useModifiers=true)
Definition kis_tool.cc:214
KisCanvas2 * canvas