File indexing completed on 2025-03-09 04:10:06

0001 /*
0002  *  SPDX-FileCopyrightText: 2004 Adrian Page <adrian@pagenet.plus.com>
0003  *  SPDX-FileCopyrightText: 2009 Sven Langkamp <sven.langkamp@gmail.com>
0004  *  SPDX-FileCopyrightText: 2010 Cyrille Berger <cberger@cberger.net>
0005  *  SPDX-FileCopyrightText: 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
0006  *  SPDX-FileCopyrightText: 2011 Srikanth Tiyyagura <srikanth.tulasiram@gmail.com>
0007  *
0008  *  SPDX-License-Identifier: GPL-2.0-or-later
0009  */
0010 
0011 #include "kis_predefined_brush_chooser.h"
0012 
0013 #include <QtMath>
0014 #include <QLabel>
0015 #include <QLayout>
0016 #include <QCheckBox>
0017 #include <QPushButton>
0018 #include <QVBoxLayout>
0019 #include <QHBoxLayout>
0020 #include <QGridLayout>
0021 #include <QPainter>
0022 #include <QAbstractItemDelegate>
0023 #include <klocalizedstring.h>
0024 
0025 #include <KoFileDialog.h>
0026 #include <KisKineticScroller.h>
0027 
0028 #include <KisResourceItemView.h>
0029 #include <KisResourceItemChooser.h>
0030 #include <KisResourceModel.h>
0031 
0032 #include <kis_icon.h>
0033 #include "KisBrushServerProvider.h"
0034 #include "kis_algebra_2d.h"
0035 #include "kis_painting_tweaks.h"
0036 #include "kis_slider_spin_box.h"
0037 #include "krita_utils.h"
0038 #include "kis_spacing_selection_widget.h"
0039 #include "kis_signals_blocker.h"
0040 
0041 #include "kis_imagepipe_brush.h"
0042 #include "kis_custom_brush_widget.h"
0043 #include "kis_clipboard_brush_widget.h"
0044 #include <kis_image_config.h>
0045 #include <KisMimeDatabase.h>
0046 
0047 #include "kis_global.h"
0048 #include "kis_gbr_brush.h"
0049 #include "kis_png_brush.h"
0050 #include "kis_debug.h"
0051 #include "kis_image.h"
0052 #include <KisGlobalResourcesInterface.h>
0053 #include <KisResourceLoaderRegistry.h>
0054 #include <KisTagFilterResourceProxyModel.h>
0055 #include <KisStorageModel.h>
0056 #include <KisResourceUserOperations.h>
0057 #include <KisResourceThumbnailCache.h>
0058 
0059 #include <KisWidgetConnectionUtils.h>
0060 
0061 #include <lager/state.hpp>
0062 #include <kis_predefined_brush_factory.h>
0063 #include <KisPredefinedBrushModel.h>
0064 
0065 using namespace KisBrushModel;
0066 using namespace KisWidgetConnectionUtils;
0067 
0068 /// The resource item delegate for rendering the resource preview
0069 class KisBrushDelegate : public QAbstractItemDelegate
0070 {
0071 public:
0072     KisBrushDelegate(QObject * parent = 0) : QAbstractItemDelegate(parent) {}
0073     ~KisBrushDelegate() override {}
0074     /// reimplemented
0075     void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override;
0076     /// reimplemented
0077     QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex &) const override {
0078         return option.decorationSize;
0079     }
0080 };
0081 
0082 void KisBrushDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
0083 {
0084     if (! index.isValid())
0085         return;
0086 
0087     QImage thumbnail = index.data(Qt::UserRole + KisAbstractResourceModel::Thumbnail).value<QImage>();
0088 
0089     const QRect itemRect = kisGrowRect(option.rect, -1);
0090     const qreal devicePixelRatioF = painter->device()->devicePixelRatioF();
0091 
0092     const QSize hidpiSize = itemRect.size() * devicePixelRatioF;
0093     thumbnail = KisResourceThumbnailCache::instance()->getImage(index,
0094                                                                 hidpiSize,
0095                                                                 Qt::KeepAspectRatio,
0096                                                                 Qt::SmoothTransformation);
0097     thumbnail.setDevicePixelRatio(devicePixelRatioF);
0098 
0099     painter->save();
0100 
0101     const QMap<QString, QVariant> metadata =
0102         index.data(Qt::UserRole + KisAbstractResourceModel::MetaData).value<QMap<QString, QVariant>>();
0103 
0104     const bool hasImageType =
0105         metadata.value(KisBrush::brushTypeMetaDataKey,
0106                        QVariant::fromValue(false)).toBool();
0107 
0108 
0109     if (hasImageType) {
0110         KisPaintingTweaks::PenBrushSaver s(painter);
0111 
0112         const int baseSize = qCeil(itemRect.width() / 5.0);
0113         QImage brush(2 * baseSize, 2 * baseSize, QImage::Format_ARGB32);
0114         brush.fill(Qt::white);
0115         QPainter gc(&brush);
0116 
0117         gc.setPen(Qt::NoPen);
0118         gc.setBrush(QColor(200,200,200));
0119         gc.drawRect(QRect(0,0,baseSize,baseSize));
0120         gc.drawRect(QRect(baseSize,baseSize,baseSize,baseSize));
0121 
0122         painter->setBrush(brush);
0123 
0124         painter->setBrushOrigin(itemRect.topLeft());
0125         painter->drawRect(itemRect);
0126         painter->setBrush(Qt::NoBrush);
0127 
0128     } else {
0129         KisPaintingTweaks::PenBrushSaver s(painter);
0130         painter->setBrush(Qt::white);
0131         painter->setPen(Qt::NoPen);
0132         painter->drawRect(itemRect);
0133     }
0134 
0135     int dx = (itemRect.width() * devicePixelRatioF - thumbnail.width()) / 2 / devicePixelRatioF;
0136     int dy = (itemRect.height() * devicePixelRatioF - thumbnail.height()) / 2 / devicePixelRatioF;
0137     painter->drawImage(itemRect.x() + dx, itemRect.y() + dy, thumbnail);
0138 
0139     if (option.state & QStyle::State_Selected) {
0140         painter->setClipRect(option.rect);
0141         painter->setPen(QPen(option.palette.highlight(), 2.0));
0142         KritaUtils::renderExactRect(painter, itemRect);
0143         painter->setCompositionMode(QPainter::CompositionMode_HardLight);
0144         painter->setOpacity(0.65);
0145         painter->fillRect(itemRect, option.palette.highlight());
0146     }
0147 
0148     painter->restore();
0149 }
0150 
0151 struct KisPredefinedBrushChooser::Private
0152 {
0153     Private(KisPredefinedBrushModel *_model)
0154         : model(_model)
0155     {
0156     }
0157 
0158     KisPredefinedBrushModel *model;
0159 };
0160 
0161 KisPredefinedBrushChooser::KisPredefinedBrushChooser(int maxBrushSize,
0162                                                      KisPredefinedBrushModel *model,
0163                                                      QWidget *parent, const char *name)
0164     : QWidget(parent),
0165       m_d(new Private(model)),
0166       m_stampBrushWidget(0),
0167       m_clipboardBrushWidget(0)
0168 {
0169     setObjectName(name);
0170 
0171     setupUi(this);
0172 
0173     brushSizeSpinBox->setRange(0, maxBrushSize, 2);
0174     brushSizeSpinBox->setValue(5);
0175     brushSizeSpinBox->setExponentRatio(3.0);
0176     brushSizeSpinBox->setSuffix(i18n(" px"));
0177     brushSizeSpinBox->setExponentRatio(3.0);
0178 
0179     connect(m_d->model, &KisPredefinedBrushModel::brushNameChanged,
0180             brushTipNameLabel, &KSqueezedTextLabel::setText);
0181     m_d->model->LAGER_QT(brushName).nudge();
0182 
0183     connect(m_d->model, &KisPredefinedBrushModel::brushDetailsChanged,
0184             brushDetailsLabel, &QLabel::setText);
0185     m_d->model->LAGER_QT(brushDetails).nudge();
0186 
0187     connectControl(brushSizeSpinBox, m_d->model, "brushSize");
0188 
0189     brushRotationAngleSelector->setDecimals(0);
0190 
0191     connectControl(brushRotationAngleSelector, m_d->model, "angle");
0192 
0193     brushSpacingSelectionWidget->setSpacing(true, 1.0);
0194 
0195     connectControl(brushSpacingSelectionWidget, m_d->model, "aggregatedSpacing");
0196 
0197     m_itemChooser = new KisResourceItemChooser(ResourceType::Brushes, false, this);
0198     m_itemChooser->setObjectName("brush_selector");
0199 
0200     m_itemChooser->showTaggingBar(true);
0201     m_itemChooser->setRowHeight(30);
0202     m_itemChooser->setItemDelegate(new KisBrushDelegate(this));
0203     m_itemChooser->setCurrentItem(0);
0204     m_itemChooser->setSynced(true);
0205     m_itemChooser->setMinimumWidth(100);
0206     m_itemChooser->setMinimumHeight(150);
0207     m_itemChooser->showImportExportBtns(false); // turn the import and delete buttons since we want control over them
0208 
0209     presetsLayout->addWidget(m_itemChooser);
0210 
0211     connect(m_itemChooser, &KisResourceItemChooser::resourceSelected,
0212             this, &KisPredefinedBrushChooser::slotBrushSelected);
0213     connect(m_d->model, &KisPredefinedBrushModel::resourceSignatureChanged,
0214             this, &KisPredefinedBrushChooser::slotBrushPropertyChanged);
0215 
0216     slotBrushPropertyChanged(m_d->model->resourceSignature());
0217 
0218 
0219     addPresetButton->setIcon(KisIconUtils::loadIcon("list-add"));
0220     deleteBrushTipButton->setIcon(KisIconUtils::loadIcon("edit-delete"));
0221 
0222     connect(addPresetButton, SIGNAL(clicked(bool)), this, SLOT(slotImportNewBrushResource()));
0223     connect(deleteBrushTipButton, SIGNAL(clicked(bool)), this, SLOT(slotDeleteBrushResource()));
0224 
0225     stampButton->setIcon(KisIconUtils::loadIcon("list-add"));
0226     stampButton->setToolTip(i18n("Creates a brush tip from the current image selection."
0227                                "\n If no selection is present the whole image will be used."));
0228 
0229     clipboardButton->setIcon(KisIconUtils::loadIcon("list-add"));
0230     clipboardButton->setToolTip(i18n("Creates a brush tip from the image in the clipboard."));
0231 
0232     connect(stampButton, SIGNAL(clicked()), this,  SLOT(slotOpenStampBrush()));
0233     connect(clipboardButton, SIGNAL(clicked()), SLOT(slotOpenClipboardBrush()));
0234 
0235     resetBrushButton->setToolTip(i18n("Reloads Spacing from file\nSets Scale to 1.0\nSets Rotation to 0.0"));
0236     connect(resetBrushButton, SIGNAL(clicked()), SLOT(slotResetBrush()));
0237 
0238     intAdjustmentMidPoint->setRange(0, 255);
0239     intAdjustmentMidPoint->setPageStep(10);
0240     intAdjustmentMidPoint->setSingleStep(1);
0241     intAdjustmentMidPoint->setPrefix(i18nc("@label:slider", "Neutral point: "));
0242     connectControl(intAdjustmentMidPoint, m_d->model, "adjustmentMidPoint");
0243     connectControl(chkAutoMidPoint, m_d->model, "autoAdjustMidPoint");
0244 
0245     intBrightnessAdjustment->setRange(-100, 100);
0246     intBrightnessAdjustment->setPageStep(10);
0247     intBrightnessAdjustment->setSingleStep(1);
0248     intBrightnessAdjustment->setSuffix("%");
0249     intBrightnessAdjustment->setPrefix(i18nc("@label:slider", "Brightness: "));
0250     connectControl(intBrightnessAdjustment, m_d->model, "brightnessAdjustment");
0251 
0252     intContrastAdjustment->setRange(-100, 100);
0253     intContrastAdjustment->setPageStep(10);
0254     intContrastAdjustment->setSingleStep(1);
0255     intContrastAdjustment->setSuffix("%");
0256     intContrastAdjustment->setPrefix(i18nc("@label:slider", "Contrast: "));
0257     connectControl(intContrastAdjustment, m_d->model, "contrastAdjustment");
0258 
0259     btnResetAdjustments->setToolTip(i18nc("@info:tooltip", "Resets all the adjustments to default values:\n Neutral Point: 127\n Brightness: 0%\n Contrast: 0%"));
0260     connect(btnResetAdjustments, SIGNAL(clicked()), SLOT(slotResetAdjustments()));
0261 
0262     connectControlState(cmbBrushMode, m_d->model, "applicationSwitchState", "application");
0263 
0264     connect(m_d->model, &KisPredefinedBrushModel::adjustmentsEnabledChanged,
0265             intAdjustmentMidPoint, &KisSliderSpinBox::setEnabled);
0266     connect(m_d->model, &KisPredefinedBrushModel::adjustmentsEnabledChanged,
0267             intBrightnessAdjustment, &KisSliderSpinBox::setEnabled);
0268     connect(m_d->model, &KisPredefinedBrushModel::adjustmentsEnabledChanged,
0269             intContrastAdjustment, &KisSliderSpinBox::setEnabled);
0270     connect(m_d->model, &KisPredefinedBrushModel::adjustmentsEnabledChanged,
0271             chkAutoMidPoint, &KisSliderSpinBox::setEnabled);
0272     connect(m_d->model, &KisPredefinedBrushModel::adjustmentsEnabledChanged,
0273             btnResetAdjustments, &KisSliderSpinBox::setEnabled);
0274 
0275     m_d->model->LAGER_QT(adjustmentsEnabled).nudge();
0276 }
0277 
0278 KisPredefinedBrushChooser::~KisPredefinedBrushChooser()
0279 {
0280 }
0281 
0282 void KisPredefinedBrushChooser::slotResetBrush()
0283 {
0284     KisBrushSP brush = m_itemChooser->currentResource().dynamicCast<KisBrush>();
0285     if (brush) {
0286         KisBrushModel::CommonData commonData;
0287         KisBrushModel::PredefinedBrushData predefinedData;
0288 
0289         KisPredefinedBrushFactory::loadFromBrushResource(commonData, predefinedData, brush);
0290 
0291         if (m_d->model->applicationSwitchState().items.size() >= LIGHTNESSMAP) {
0292             predefinedData.application = LIGHTNESSMAP;
0293         }
0294 
0295         m_d->model->m_commonData.set(commonData);
0296         m_d->model->m_predefinedBrushData.set(predefinedData);
0297         m_d->model->m_commonBrushSizeData.set(KisPredefinedBrushModel::effectiveBrushSize(predefinedData));
0298     }
0299 }
0300 
0301 void KisPredefinedBrushChooser::slotOpenStampBrush()
0302 {
0303     if(!m_stampBrushWidget) {
0304         m_stampBrushWidget = new KisCustomBrushWidget(this, i18n("Stamp"), m_image);
0305         m_stampBrushWidget->setModal(false);
0306         connect(m_stampBrushWidget, SIGNAL(sigNewPredefinedBrush(KoResourceSP )),
0307                                     SLOT(slotNewPredefinedBrush(KoResourceSP )));
0308     } else {
0309         m_stampBrushWidget->setImage(m_image);
0310     }
0311 
0312     QDialog::DialogCode result = (QDialog::DialogCode)m_stampBrushWidget->exec();
0313 
0314     if(result) {
0315         // noop
0316     }
0317 }
0318 void KisPredefinedBrushChooser::slotOpenClipboardBrush()
0319 {
0320     if(!m_clipboardBrushWidget) {
0321         m_clipboardBrushWidget = new KisClipboardBrushWidget(this, i18n("Clipboard"), m_image);
0322         m_clipboardBrushWidget->setModal(true);
0323         connect(m_clipboardBrushWidget, SIGNAL(sigNewPredefinedBrush(KoResourceSP )),
0324                                         SLOT(slotNewPredefinedBrush(KoResourceSP )));
0325     }
0326 
0327     QDialog::DialogCode result = (QDialog::DialogCode)m_clipboardBrushWidget->exec();
0328 
0329     if(result) {
0330         // noop
0331     }
0332 }
0333 
0334 void KisPredefinedBrushChooser::slotBrushSelected(KoResourceSP resource)
0335 {
0336     KIS_SAFE_ASSERT_RECOVER_RETURN(resource);
0337 
0338     KisBrushModel::PredefinedBrushData predefinedBrushData = m_d->model->bakedOptionData();
0339     if (resource->signature() == predefinedBrushData.resourceSignature) return;
0340 
0341     KisBrushModel::CommonData commonBrushData;
0342     KisPredefinedBrushFactory::loadFromBrushResource(commonBrushData, predefinedBrushData, resource.dynamicCast<KisBrush>());
0343 
0344     predefinedBrushData.scale = 1.0;
0345     predefinedBrushData.application = KisPredefinedBrushModel::effectiveBrushApplication(predefinedBrushData, m_d->model->m_supportsHSLBrushTips.get());
0346 
0347     // TODO: check what happens when we add a new brush
0348     if (this->preserveBrushPresetSettings->isChecked()) {
0349         commonBrushData.spacing = m_d->model->spacing();
0350         commonBrushData.useAutoSpacing = m_d->model->useAutoSpacing();
0351         commonBrushData.autoSpacingCoeff = m_d->model->autoSpacingCoeff();
0352     } else {
0353         m_d->model->m_commonBrushSizeData.set(predefinedBrushData.baseSize.width());
0354     }
0355 
0356     m_d->model->m_commonData.set(commonBrushData);
0357     m_d->model->m_predefinedBrushData.set(predefinedBrushData);
0358 }
0359 
0360 void KisPredefinedBrushChooser::slotBrushPropertyChanged(KoResourceSignature signature)
0361 {
0362     auto source = KisGlobalResourcesInterface::instance()->source<KisBrush>(ResourceType::Brushes);
0363     m_itemChooser->setCurrentResource(source.bestMatch(signature.md5sum, signature.filename, signature.name));
0364 }
0365 
0366 void KisPredefinedBrushChooser::slotResetAdjustments()
0367 {
0368     m_d->model->m_predefinedBrushData.update(
0369         [] (KisBrushModel::PredefinedBrushData brush) {
0370             KisBrushModel::PredefinedBrushData defaultBrush;
0371 
0372             brush.adjustmentMidPoint = defaultBrush.adjustmentMidPoint;
0373             brush.brightnessAdjustment = defaultBrush.brightnessAdjustment;
0374             brush.contrastAdjustment = defaultBrush.contrastAdjustment;
0375             brush.autoAdjustMidPoint = defaultBrush.autoAdjustMidPoint;
0376 
0377             return brush;
0378         });
0379 }
0380 
0381 void KisPredefinedBrushChooser::slotNewPredefinedBrush(KoResourceSP resource)
0382 {
0383     m_itemChooser->setCurrentResource(resource);
0384 }
0385 
0386 void KisPredefinedBrushChooser::setImage(KisImageWSP image)
0387 {
0388     m_image = image;
0389 }
0390 
0391 lager::reader<bool> KisPredefinedBrushChooser::lightnessModeEnabled() const
0392 {
0393     return m_d->model->LAGER_QT(lightnessModeEnabled);
0394 }
0395 
0396 void KisPredefinedBrushChooser::slotImportNewBrushResource() {
0397     // reflects m_itemChooser->slotButtonClicked(KisResourceItemChooser::Button_Import)
0398     // but adds the .abr files support, as it was in Krita 4
0399     QStringList mimeTypes = KisResourceLoaderRegistry::instance()->mimeTypes(ResourceType::Brushes);
0400     QString abrMimeType = "image/x-adobe-brushlibrary";
0401     mimeTypes.append(abrMimeType);
0402     KoFileDialog dialog(0, KoFileDialog::OpenFiles, "OpenDocument");
0403     dialog.setMimeTypeFilters(mimeTypes);
0404     dialog.setCaption(i18nc("@title:window", "Choose File to Add"));
0405     Q_FOREACH(const QString &filename, dialog.filenames()) {
0406         if (QFileInfo(filename).exists() && QFileInfo(filename).isReadable()) {
0407             if (KisMimeDatabase::mimeTypeForFile(filename).contains(abrMimeType)) {
0408                 KisStorageModel::instance()->importStorage(filename, KisStorageModel::None);
0409             } else {
0410                 KisResourceUserOperations::importResourceFileWithUserInput(this, "", ResourceType::Brushes, filename);
0411             }
0412         }
0413     }
0414     m_itemChooser->tagFilterModel()->sort(Qt::DisplayRole);
0415 }
0416 
0417 void KisPredefinedBrushChooser::slotDeleteBrushResource() {
0418     m_itemChooser->slotButtonClicked(KisResourceItemChooser::Button_Remove);
0419 }