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 }