Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_custom_brush_widget.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2005 Bart Coppens <kde@bartcoppens.be>
3 * SPDX-FileCopyrightText: 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
4 * SPDX-FileCopyrightText: 2013 Somsubhra Bairi <somsubhra.bairi@gmail.com>
5 * SPDX-FileCopyrightText: 2026 Freya Lupen <penguinflyer2222@gmail.com>
6 *
7 * SPDX-License-Identifier: GPL-2.0-or-later
8 */
9
11
12#include <QImage>
13#include <QPixmap>
14#include <QPushButton>
15#include <QVBoxLayout>
16#include <QComboBox>
17#include <QLabel>
18#include <QDialogButtonBox>
19
20#include <kstandardguiitem.h>
21
22#include <KoResourcePaths.h>
23#include "KisResourceTypes.h"
24#include "kis_image.h"
25#include "kis_paint_device.h"
26#include "kis_gbr_brush.h"
28#include "kis_paint_layer.h"
29#include <kis_selection.h>
30#include <KoProperties.h>
31#include "kis_iterator_ng.h"
32#include "KisImageBarrierLock.h"
34#include "kis_clipboard.h"
35#include "KisMainWindow.h"
36#include "KisViewManager.h"
37#include "KisPart.h"
40#include "kis_config_widget.h"
41#include "KisDocument.h"
43
44
45const QString TEMPORARY_BRUSH_NAME = "Temporary custom brush";
46
51
56
57const QString KEY_NAME = "name";
58const QString KEY_MASK = "mask";
59const QString KEY_PRESERVE_ALPHA = "preserveAlpha";
60const QString KEY_BRUSH_STYLE = "brushStyle";
61
62KisCustomBrushWidget::KisCustomBrushWidget(QWidget *parent, const QString& caption)
63 : QDialog(parent)
64{
65 setWindowTitle(caption);
66
67 QVBoxLayout* layout = new QVBoxLayout();
68 QHBoxLayout* hLayout = new QHBoxLayout();
69
70 m_preview = new QLabel();
71 m_preview->setFixedSize(110, 110);
72 m_preview->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
73 m_preview->setScaledContents(false);
74 m_preview->setFrameStyle(QFrame::StyledPanel | QFrame::Plain);
75 m_preview->setAlignment(Qt::AlignCenter);
76 m_preview->setWordWrap(true);
77 hLayout->addWidget(m_preview);
78
79 QLabel* sourceLabel = new QLabel(i18nc("@label:listbox image source", "Source:"));
80 hLayout->addWidget(sourceLabel);
81
82 QComboBox* sourceBox = new QComboBox();
83 sourceBox->addItem(i18nc("@item:inlistbox", "Image"), Source::Image);
84 sourceBox->addItem(i18nc("@item:inlistbox", "Clipboard"), Source::Clipboard);
85 hLayout->addWidget(sourceBox);
86
87 QPushButton* reloadButton = new QPushButton(i18nc("@action:button reload image", "Reload"));
88 hLayout->addWidget(reloadButton);
89
90 layout->addLayout(hLayout);
91
92 const QString mimetype = "image/x-gimp-brush";
95 m_brushWidget = filter->createConfigurationWidget(0, 0, mimetype.toLatin1());
96 connect(m_brushWidget, SIGNAL(sigConfigurationUpdated()), this, SLOT(slotUpdateBrush()));
97
98 const QString animMimetype = "image/x-gimp-brush-animated";
101 m_animBrushWidget = animFilter->createConfigurationWidget(0, 0, animMimetype.toLatin1());
102 connect(m_animBrushWidget, SIGNAL(sigConfigurationUpdated()), this, SLOT(slotUpdateBrush()));
103 layout->addWidget(m_animBrushWidget);
104
106
108
109 m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel|QDialogButtonBox::Save);
110 KGuiItem::assign(m_buttonBox->button(QDialogButtonBox::Save), KStandardGuiItem::save());
111 KGuiItem::assign(m_buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel());
112 layout->addWidget(m_buttonBox);
113 connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
114 connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
115
116 setLayout(layout);
117
118 connect(this, SIGNAL(accepted()), SLOT(slotAddPredefined()));
119 connect(reloadButton, SIGNAL(clicked()), this, SLOT(slotReload()));
120 connect(sourceBox, SIGNAL(activated(int)), this, SLOT(slotSourceChanged(int)));
121
122 loadImage();
123}
124
128
130{
131 if (m_useClipboard) { // Can't be animated
132 return m_brushWidget;
133 } else {
134 return m_animBrushWidget;
135 }
136}
137
139{
140 if (m_useClipboard) { // Can't be animated
141 return m_animBrushWidget;
142 } else {
143 return m_brushWidget;
144 }
145}
146
148{
150 layout()->replaceWidget(otherBrushWidget(), currentBrushWidget());
151 otherBrushWidget()->hide();
152 currentBrushWidget()->show();
153}
154
159
161{
162 const QString name = currentBrushWidget()->configuration()->getString(KEY_NAME);
163 const QString suffix = currentBrushStyle() == BrushStyle::Regular ? ".gbr" : ".gih";
164 // Export to a temp location, the resource system doesn't like importing from the resource folder
165 return QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/" + name.split(" ").join("_") + suffix;
166}
167
169{
171 if (!mainWin) { return nullptr; }
172 KisViewManager *viewManager = mainWin->viewManager();
173 if (!viewManager) { return nullptr; }
174
175 // Animated uses the view for the layer (frames) count.
176 m_animBrushWidget->setView(viewManager);
177 return viewManager->image();
178}
179
181{
182 bool useClipboard = source == Source::Clipboard;
183 if (m_useClipboard == useClipboard) { return; }
184
185 m_useClipboard = useClipboard;
186
188
189 if (m_useClipboard) {
190 // Clipboard doesn't support animated.
192 cfg->setProperty(KEY_BRUSH_STYLE, BrushStyle::Regular);
194 }
195
196 slotReload();
197}
198
200{
201 int brushStyle = currentBrushStyle();
202 if (brushStyle != m_oldConfig->getInt(KEY_BRUSH_STYLE)) {
203 loadImage();
204 }
205 updatePreviewImage(false);
206
208
210}
211
213{
214 int brushStyle = currentBrushStyle();
215 bool useColorMask = currentBrushWidget()->configuration()->getBool(KEY_MASK);
216 bool preserveAlpha = currentBrushWidget()->configuration()->getBool(KEY_PRESERVE_ALPHA);
217 bool needsUpdate = updateDev || (brushStyle != m_oldConfig->getInt(KEY_BRUSH_STYLE)) ||
218 (useColorMask != m_oldConfig->getInt(KEY_MASK)) || (preserveAlpha != m_oldConfig->getInt(KEY_PRESERVE_ALPHA));
219 if (!needsUpdate) {
220 return;
221 }
222
223 // Create a temporary brush to generate a preview image
225 QRect rc;
227 dev = m_dev;
228 rc = m_devRect;
229 } else {
230 if (!m_devices.isEmpty() && !m_devices[0].isEmpty()) {
231 dev = m_devices[0][0];
232 rc = QRect(QPoint(0,0), m_imageSize);
233 }
234 }
235
236 QImage brushImage;
237 if (dev) {
238 KisBrushSP brush = KisBrushSP(new KisGbrBrush(dev, rc.x(), rc.y(), rc.width(), rc.height()));
239
240 if (useColorMask) {
241 static_cast<KisGbrBrush*>(brush.data())->makeMaskImage(preserveAlpha);
242 static_cast<KisGbrBrush*>(brush.data())->setBrushApplication(preserveAlpha ? LIGHTNESSMAP : ALPHAMASK);
243 } else {
244 static_cast<KisGbrBrush*>(brush.data())->setBrushApplication(IMAGESTAMP);
245 }
246 brushImage = brush->brushTipImage();
247 }
248
249 if (brushImage.isNull()) {
250 m_preview->setText(i18nc("@info empty image", "Empty"));
251 } else {
252 int w = m_preview->size().width() - m_preview->frameWidth() * 2;
253 brushImage = brushImage.scaled(w, w, Qt::KeepAspectRatio);
254 m_preview->setPixmap(QPixmap::fromImage(brushImage));
255 }
256}
257
259{
260 m_dev = nullptr;
262 loadImage();
263}
264
266{
267 if (!m_rServer) { return; }
268
269 const QString name = currentBrushWidget()->configuration()->getString(KEY_NAME);
270 int brushStyle = currentBrushStyle();
271 bool needsUpdate = (name != m_oldConfig->getString(KEY_NAME) || brushStyle != m_oldConfig->getInt(KEY_BRUSH_STYLE));
272 if (!needsUpdate) { return; }
273
274 const QString suffix = brushStyle == BrushStyle::Regular ? ".gbr" : ".gih";
275 QString exportFilename = m_rServer->saveLocation() + "/" + name.split(" ").join("_") + suffix;
276 if (QFileInfo(exportFilename).exists()) {
277 m_buttonBox->button(QDialogButtonBox::Save)->setText(i18nc("@action:button", "Overwrite"));
278 } else {
279 m_buttonBox->button(QDialogButtonBox::Save)->setText(i18nc("@action:button", "Save"));
280 }
281}
282
284{
285 if (!m_rServer || !(m_dev || !m_devices.isEmpty())) {
286 close();
287 return;
288 }
289
291 // Don't show a second export dialog when calling export later.
292 doc->setFileBatchMode(true);
293 KisImageSP image;
294
296 image = new KisImage(0, m_devRect.width(), m_devRect.height(), m_dev->colorSpace(), TEMPORARY_BRUSH_NAME);
298 image->addNode(node);
299 } else /*if (currentBrushStyle() == BrushStyle::Animated)*/ {
300 image = new KisImage(0, m_imageSize.width(), m_imageSize.height(), m_imageColorSpace, TEMPORARY_BRUSH_NAME);
301 Q_FOREACH(KisPaintDeviceSP dev, m_devices[0]) {
303 image->addNode(node);
304 }
305 }
306
307 doc->setCurrentImage(image);
308
309 const QByteArray mimetype = currentBrushStyle() == BrushStyle::Regular ? "image/x-gimp-brush" : "image/x-gimp-brush-animated";
310 QString path = fileName();
311 bool success = doc->exportDocumentSync(path, mimetype, currentBrushWidget()->configuration());
312 doc->deleteLater();
313
314 // Add it to the brush server, so that it automatically gets to the mediators, and
315 // so to the other brush choosers can pick it up, if they want to
316 if (success) {
318 if (resource) {
319 Q_EMIT sigNewPredefinedBrush(resource);
320 }
321 }
322
323 close();
324}
325
327{
329 KisImageWSP image;
330 if (!m_dev) {
331 if (m_useClipboard) {
333 if (clipboard->hasImage()) {
334 m_dev = clipboard->clip(QRect(), false);
335 if (m_dev) {
337 }
338 }
339 } else if ((image = currentImage())) {
340 // create copy of the data
341 image->barrierLock();
342 m_dev = new KisPaintDevice(*image->projection());
343 m_devRect = QRect(QPoint(0,0), image->size());
344 image->unlock();
345
346 KisSelectionSP selection = image->globalSelection();
347 if (selection) {
348 // apply selection mask
349 QRect r = selection->selectedExactRect();
350
351 KisHLineIteratorSP pixelIt = m_dev->createHLineIteratorNG(r.x(), r.top(), r.width());
352 KisHLineConstIteratorSP maskIt = selection->projection()->createHLineIteratorNG(r.x(), r.top(), r.width());
353
354 for (qint32 y = r.top(); y <= r.bottom(); ++y) {
355 do {
356 m_dev->colorSpace()->applyAlphaU8Mask(pixelIt->rawData(), maskIt->oldRawData(), 1);
357 } while (pixelIt->nextPixel() && maskIt->nextPixel());
358
359 pixelIt->nextRow();
360 maskIt->nextRow();
361 }
362
363 m_dev->crop(r);
364 m_devRect = r;
365 }
366 }
367 updatePreviewImage(true);
368 }
369 } else /*if (currentBrushStyle() == BrushStyle::Animated)*/ {
370 KisImageWSP image;
371 if (m_devices.isEmpty() && (image = currentImage())) {
372 // For each layer in the current image, create a new image, and add it to the list
373 m_devices.clear();
375
376 KisImageReadOnlyBarrierLock lock(image);
377
378 m_imageSize = image->size();
379 m_imageColorSpace = image->colorSpace();
380
381 // We only loop over the rootLayer. Since we actually should have a layer selection
382 // list, no need to elaborate on that here and now
383 KoProperties properties;
384 properties.setProperty("visible", true);
385 QList<KisNodeSP> layers = image->root()->childNodes(QStringList("KisLayer"), properties);
386 Q_FOREACH (KisNodeSP node, layers) {
387 m_devices[0].append(node->projection().data());
388 }
389 }
390 updatePreviewImage(true);
391 }
392
393 m_buttonBox->button(QDialogButtonBox::Save)->setEnabled(m_dev || !m_devices.isEmpty());
394}
KisMagneticGraph::vertex_descriptor source(typename KisMagneticGraph::edge_descriptor e, KisMagneticGraph g)
QList< QString > QStringList
const QString KEY_MASK
const QString KEY_BRUSH_STYLE
const QString KEY_PRESERVE_ALPHA
const QString KEY_NAME
const quint8 OPACITY_OPAQUE_U8
virtual const quint8 * oldRawData() const =0
virtual bool nextPixel()=0
static KisBrushServerProvider * instance()
KoResourceServer< KisBrush > * brushServer()
static KisClipboard * instance()
bool hasImage() const
virtual void setView(KisViewManager *view)
virtual KisPropertiesConfigurationSP configuration() const =0
virtual void setConfiguration(const KisPropertiesConfigurationSP config)=0
KisPropertiesConfigurationSP m_oldConfig
void sigNewPredefinedBrush(KoResourceSP)
KisConfigWidget * m_animBrushWidget
const KoColorSpace * m_imageColorSpace
QDialogButtonBox * m_buttonBox
KisConfigWidget * currentBrushWidget()
KisConfigWidget * m_brushWidget
KoResourceServer< KisBrush > * m_rServer
KisCustomBrushWidget(QWidget *parent, const QString &caption)
void updatePreviewImage(bool updateDev)
QVector< QVector< KisPaintDevice * > > m_devices
KisConfigWidget * otherBrushWidget()
void setFileBatchMode(const bool batchMode)
bool exportDocumentSync(const QString &path, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration=0)
void setCurrentImage(KisImageSP image, bool forceInitialUpdate=true, KisNodeSP preActivatedNode=nullptr)
virtual void nextRow()=0
const KoColorSpace * colorSpace() const
void unlock()
Definition kis_image.cc:831
void barrierLock(bool readOnly=false)
Wait until all the queued background jobs are completed and lock the image.
Definition kis_image.cc:782
KisPaintDeviceSP projection() const
QSize size() const
Definition kis_image.h:550
KisSelectionSP globalSelection() const
Definition kis_image.cc:721
static KisImportExportFilter * filterForMimeType(const QString &mimetype, Direction direction)
filterForMimeType loads the relevant import/export plugin and returns it. The caller is responsible f...
Main window for Krita.
KisViewManager * viewManager
void crop(qint32 x, qint32 y, qint32 w, qint32 h)
QRect exactBounds() const
KisHLineIteratorSP createHLineIteratorNG(qint32 x, qint32 y, qint32 w)
const KoColorSpace * colorSpace() const
static KisPart * instance()
Definition KisPart.cpp:130
KisMainWindow * currentMainwindow() const
Definition KisPart.cpp:457
KisDocument * createTemporaryDocument() const
Definition KisPart.cpp:234
static KoResourceSP importResourceFileWithUserInput(QWidget *widgetParent, QString storageLocation, QString resourceType, QString resourceFilepath)
KisImageWSP image() const
Return the image this view is displaying.
virtual void applyAlphaU8Mask(quint8 *pixels, const quint8 *alpha, qint32 nPixels) const =0
void setProperty(const QString &name, const QVariant &value)
QString saveLocation()
Returns path where to save user defined and imported resources to.
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
QSharedPointer< KisBrush > KisBrushSP
Definition kis_brush.h:49
@ ALPHAMASK
Definition kis_brush.h:39
@ IMAGESTAMP
Definition kis_brush.h:40
@ LIGHTNESSMAP
Definition kis_brush.h:41
const QString KEY_MASK
const QString KEY_BRUSH_STYLE
const QString TEMPORARY_BRUSH_NAME
const QString KEY_PRESERVE_ALPHA
const QString KEY_NAME
const QString Brushes
virtual KisPaintDeviceSP projection() const =0
bool addNode(KisNodeSP node, KisNodeSP parent=KisNodeSP(), KisNodeAdditionFlags flags=KisNodeAdditionFlag::None)
QList< KisNodeSP > childNodes(const QStringList &nodeTypes, const KoProperties &properties) const
Definition kis_node.cpp:439
KisPixelSelectionSP projection() const
QRect selectedExactRect() const
Slow, but exact way of determining the rectangle that encloses the selection.