File indexing completed on 2025-02-09 05:13:13

0001 /*
0002  *  SPDX-FileCopyrightText: 1999 Matthias Elter <me@kde.org>
0003  *  SPDX-FileCopyrightText: 2002 Patrick Julien <freak@codepimps.org>
0004  *  SPDX-FileCopyrightText: 2010 Lukáš Tvrdý <lukast.dev@gmail.com>
0005  *  SPDX-FileCopyrightText: 2018 Emmet & Eoin O'Neill <emmetoneill.pdx@gmail.com>
0006  *
0007  *  SPDX-License-Identifier: GPL-2.0-or-later
0008  */
0009 
0010 #include "kis_tool_colorsampler.h"
0011 
0012 #include <kis_cursor.h>
0013 #include <kis_canvas2.h>
0014 #include <KoCanvasBase.h>
0015 #include <KoResourceServerProvider.h>
0016 #include <kis_canvas_resource_provider.h>
0017 #include <KisSpinBoxI18nHelper.h>
0018 #include <KisTagFilterResourceProxyModel.h>
0019 #include <KisResourceTypes.h>
0020 #include <KisViewManager.h>
0021 #include "kis_display_color_converter.h"
0022 #include "kis_tool_utils.h"
0023 
0024 
0025 namespace
0026 {
0027 // GUI ComboBox index constants
0028 const int SAMPLE_MERGED = 0;
0029 }
0030 
0031 KisToolColorSampler::KisToolColorSampler(KoCanvasBase *canvas)
0032     : KisTool(canvas, KisCursor::samplerCursor()),
0033       m_config(new KisToolUtils::ColorSamplerConfig),
0034       m_helper(dynamic_cast<KisCanvas2*>(canvas))
0035 {
0036     setObjectName("tool_colorsampler");
0037     connect(&m_helper, SIGNAL(sigRequestCursor(QCursor)), this, SLOT(slotColorPickerRequestedCursor(QCursor)));
0038     connect(&m_helper, SIGNAL(sigRequestCursorReset()), this, SLOT(slotColorPickerRequestedCursorReset()));
0039     connect(&m_helper, SIGNAL(sigRequestUpdateOutline()), this, SLOT(slotColorPickerRequestedOutlineUpdate()));
0040     connect(&m_helper, SIGNAL(sigRawColorSelected(KoColor)), this, SLOT(slotColorPickerSelectedColor(KoColor)));
0041     connect(&m_helper, SIGNAL(sigFinalColorSelected(KoColor)), this, SLOT(slotColorPickerSelectionFinished(KoColor)));
0042 }
0043 
0044 KisToolColorSampler::~KisToolColorSampler()
0045 {
0046     if (m_isActivated) {
0047         m_config->save();
0048     }
0049 }
0050 
0051 void KisToolColorSampler::slotColorPickerRequestedCursor(const QCursor &cursor)
0052 {
0053     useCursor(cursor);
0054 }
0055 
0056 void KisToolColorSampler::slotColorPickerRequestedCursorReset()
0057 {
0058     /// we explicitly avoid resetting the cursor style
0059     /// to avoid blinking of the cursor
0060 }
0061 
0062 void KisToolColorSampler::slotColorPickerRequestedOutlineUpdate()
0063 {
0064     requestUpdateOutline(m_outlineDocPoint, 0);
0065 }
0066 
0067 void KisToolColorSampler::slotColorPickerSelectedColor(const KoColor &color)
0068 {
0069     /**
0070      * Please remember that m_sampledColor also have the alpha
0071      * of the picked color!
0072      */
0073     m_sampledColor = color;
0074     displaySampledColor(m_sampledColor);
0075 }
0076 
0077 void KisToolColorSampler::slotColorPickerSelectionFinished(const KoColor &color)
0078 {
0079     Q_UNUSED(color);
0080 
0081     if (m_config->addColorToCurrentPalette) {
0082         KisSwatch swatch;
0083         swatch.setColor(color);
0084         // We don't ask for a name, too intrusive here
0085 
0086         QModelIndex idx = m_tagFilterProxyModel->index(m_optionsWidget->cmbPalette->currentIndex(), 0);
0087         KoColorSetSP palette = qSharedPointerDynamicCast<KoColorSet>(m_tagFilterProxyModel->resourceForIndex(idx));
0088 
0089         if (palette) {
0090             KisSwatchGroup::SwatchInfo info =
0091                     palette->getClosestSwatchInfo(color);
0092 
0093             if (info.swatch.color() != color) {
0094                 palette->addSwatch(swatch);
0095                 if (!KoResourceServerProvider::instance()->paletteServer()->updateResource(palette)) {
0096                     KisCanvas2 *canvas = dynamic_cast<KisCanvas2*>(this->canvas());
0097                     KIS_ASSERT(canvas);
0098                     canvas->viewManager()->showFloatingMessage(i18n("Cannot write to palette file %1. Maybe it is read-only.", palette->filename()), koIcon("object-locked"));
0099                 }
0100             }
0101         }
0102     }
0103 }
0104 
0105 void KisToolColorSampler::paint(QPainter &gc, const KoViewConverter &converter)
0106 {
0107     m_helper.paint(gc, converter);
0108 }
0109 
0110 void KisToolColorSampler::activate(const QSet<KoShape*> &shapes)
0111 {
0112 
0113     m_isActivated = true;
0114     m_config->load();
0115 
0116     updateOptionWidget();
0117 
0118     KisTool::activate(shapes);
0119 }
0120 
0121 void KisToolColorSampler::deactivate()
0122 {
0123     m_config->save();
0124 
0125     m_isActivated = false;
0126     KisTool::deactivate();
0127 }
0128 
0129 void KisToolColorSampler::beginPrimaryAction(KoPointerEvent *event)
0130 {
0131     m_helper.setUpdateGlobalColor(m_config->updateColor);
0132     m_helper.activate(!m_config->sampleMerged, m_config->toForegroundColor);
0133     m_helper.startAction(event->point, m_config->radius, m_config->blend);
0134     requestUpdateOutline(event->point, event);
0135 
0136     setMode(KisTool::PAINT_MODE);
0137 }
0138 
0139 void KisToolColorSampler::mouseMoveEvent(KoPointerEvent *event){
0140     KisTool::mouseMoveEvent(event);
0141 }
0142 
0143 void KisToolColorSampler::continuePrimaryAction(KoPointerEvent *event)
0144 {
0145     CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
0146 
0147     m_helper.continueAction(event->point);
0148     requestUpdateOutline(event->point, event);
0149 }
0150 
0151 void KisToolColorSampler::endPrimaryAction(KoPointerEvent *event)
0152 {
0153     CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE);
0154 
0155     m_helper.endAction();
0156     m_helper.deactivate();
0157     requestUpdateOutline(event->point, event);
0158 
0159 }
0160 void KisToolColorSampler::activatePrimaryAction()
0161 {
0162     /**
0163      * We explicitly avoid calling KisTool::activatePrimaryAction()
0164      * here, because it resets the cursor, causing cursor blinking
0165      */
0166     m_helper.updateCursor(!m_config->sampleMerged, m_config->toForegroundColor);
0167 }
0168 
0169 void KisToolColorSampler::deactivatePrimaryAction()
0170 {
0171     /**
0172      * We explicitly avoid calling KisTool::endPrimaryAction()
0173      * here, because it resets the cursor, causing cursor blinking
0174      */
0175 }
0176 
0177 void KisToolColorSampler::requestUpdateOutline(const QPointF &outlineDocPoint, const KoPointerEvent *event)
0178 {
0179     Q_UNUSED(event);
0180 
0181     KisConfig cfg(true);
0182 
0183     QRectF colorPreviewDocUpdateRect;
0184 
0185     qreal zoomX;
0186     qreal zoomY;
0187     canvas()->viewConverter()->zoom(&zoomX, &zoomY);
0188     qreal xoffset = 2.0/zoomX;
0189     qreal yoffset = 2.0/zoomY;
0190 
0191     m_outlineDocPoint = outlineDocPoint;
0192 
0193     colorPreviewDocUpdateRect = m_helper.colorPreviewDocRect(m_outlineDocPoint);
0194 
0195     if (!colorPreviewDocUpdateRect.isEmpty()) {
0196         colorPreviewDocUpdateRect = colorPreviewDocUpdateRect.adjusted(-xoffset,-yoffset,xoffset,yoffset);
0197     }
0198 
0199     if (!m_oldColorPreviewUpdateRect.isEmpty()){
0200         canvas()->updateCanvas(m_oldColorPreviewUpdateRect);
0201     }
0202 
0203     if (!colorPreviewDocUpdateRect.isEmpty()){
0204         canvas()->updateCanvas(colorPreviewDocUpdateRect);
0205     }
0206 
0207     m_oldColorPreviewUpdateRect = colorPreviewDocUpdateRect;
0208 }
0209 
0210 
0211 struct SampledChannel {
0212     QString name;
0213     QString valueText;
0214 };
0215 
0216 void KisToolColorSampler::displaySampledColor(const KoColor &color)
0217 {
0218     if (color.data() && m_optionsWidget) {
0219 
0220         const QList<KoChannelInfo *> channels = color.colorSpace()->channels();
0221         m_optionsWidget->listViewChannels->clear();
0222 
0223         QVector<SampledChannel> sampledChannels;
0224         for (int i = 0; i < channels.count(); ++i) {
0225             sampledChannels.append(SampledChannel());
0226         }
0227 
0228         for (int i = 0; i < channels.count(); ++i) {
0229 
0230             SampledChannel pc;
0231             pc.name = channels[i]->name();
0232 
0233             if (m_config->normaliseValues) {
0234                 pc.valueText = color.colorSpace()->normalisedChannelValueText(color.data(), i);
0235             } else {
0236                 pc.valueText = color.colorSpace()->channelValueText(color.data(), i);
0237             }
0238 
0239             sampledChannels[channels[i]->displayPosition()] = pc;
0240 
0241         }
0242 
0243         Q_FOREACH (const SampledChannel &pc, sampledChannels) {
0244             QTreeWidgetItem *item = new QTreeWidgetItem(m_optionsWidget->listViewChannels);
0245             item->setText(0, pc.name);
0246             item->setText(1, pc.valueText);
0247         }
0248 
0249 
0250         if (qEnvironmentVariableIsSet("KRITA_DEBUG_DISPLAY_COLOR")) {
0251             KisCanvas2 *kritaCanvas = dynamic_cast<KisCanvas2*>(canvas());
0252             KIS_ASSERT(kritaCanvas);
0253             KoColor newColor = kritaCanvas->displayColorConverter()->applyDisplayFiltering(color, Float32BitsColorDepthID);
0254             KIS_SAFE_ASSERT_RECOVER_RETURN(newColor.colorSpace()->colorModelId() == RGBAColorModelID);
0255 
0256             QVector<float> values(4);
0257             newColor.colorSpace()->normalisedChannelsValue(newColor.data(), values);
0258 
0259             for (int i = 0; i < values.size(); i++) {
0260                 QTreeWidgetItem *item = new QTreeWidgetItem(m_optionsWidget->listViewChannels);
0261                 item->setText(0, QString("DisplayCh%1").arg(i));
0262                 item->setText(1, QString::number(values[i]));
0263             }
0264         }
0265     }
0266 }
0267 
0268 QWidget* KisToolColorSampler::createOptionWidget()
0269 {
0270     m_optionsWidget = new ColorSamplerOptionsWidget(0);
0271     m_optionsWidget->setObjectName(toolId() + " option widget");
0272     m_optionsWidget->listViewChannels->setSortingEnabled(false);
0273 
0274     // See https://bugs.kde.org/show_bug.cgi?id=316896
0275     QWidget *specialSpacer = new QWidget(m_optionsWidget);
0276     specialSpacer->setObjectName("SpecialSpacer");
0277     specialSpacer->setFixedSize(0, 0);
0278     m_optionsWidget->layout()->addWidget(specialSpacer);
0279 
0280     // Initialize blend KisSliderSpinBox
0281     m_optionsWidget->blend->setRange(0,100);
0282     KisSpinBoxI18nHelper::setText(m_optionsWidget->blend,
0283                                   i18nc("{n} is the number value, % is the percent sign", "{n}%"));
0284 
0285     updateOptionWidget();
0286 
0287     connect(m_optionsWidget->cbUpdateCurrentColor, SIGNAL(toggled(bool)), SLOT(slotSetUpdateColor(bool)));
0288     connect(m_optionsWidget->cbNormaliseValues, SIGNAL(toggled(bool)), SLOT(slotSetNormaliseValues(bool)));
0289     connect(m_optionsWidget->cbPalette, SIGNAL(toggled(bool)),
0290             SLOT(slotSetAddPalette(bool)));
0291     connect(m_optionsWidget->radius, SIGNAL(valueChanged(int)),
0292             SLOT(slotChangeRadius(int)));
0293     connect(m_optionsWidget->blend, SIGNAL(valueChanged(int)),
0294             SLOT(slotChangeBlend(int)));
0295     connect(m_optionsWidget->cmbSources, SIGNAL(currentIndexChanged(int)),
0296             SLOT(slotSetColorSource(int)));
0297 
0298     m_tagFilterProxyModel = new KisTagFilterResourceProxyModel(ResourceType::Palettes, this);
0299     m_optionsWidget->cmbPalette->setModel(m_tagFilterProxyModel);
0300     m_optionsWidget->cmbPalette->setModelColumn(KisAbstractResourceModel::Name);
0301     m_tagFilterProxyModel->sort(Qt::DisplayRole);
0302 
0303 
0304     KConfigGroup config =  KSharedConfig::openConfig()->group(toolId());
0305     QString paletteName = config.readEntry("ColorSamplerPalette", "");
0306     if (!paletteName.isEmpty()) {
0307         for (int i = 0; i < m_tagFilterProxyModel->rowCount(); i++) {
0308             QModelIndex idx = m_tagFilterProxyModel->index(i, 0);
0309             QString name = m_tagFilterProxyModel->data(idx, Qt::UserRole + KisAbstractResourceModel::Name).toString();
0310             if (name == paletteName) {
0311                 m_optionsWidget->cmbPalette->setCurrentIndex(i);
0312                 break;
0313             }
0314         }
0315     }
0316 
0317     connect(m_optionsWidget->cmbPalette, SIGNAL(currentIndexChanged(int)), SLOT(slotChangePalette(int)));
0318 
0319     return m_optionsWidget;
0320 }
0321 
0322 void KisToolColorSampler::updateOptionWidget()
0323 {
0324     if (!m_optionsWidget) return;
0325 
0326     m_optionsWidget->cbNormaliseValues->setChecked(m_config->normaliseValues);
0327     m_optionsWidget->cbUpdateCurrentColor->setChecked(m_config->updateColor);
0328     m_optionsWidget->cmbSources->setCurrentIndex(SAMPLE_MERGED + !m_config->sampleMerged);
0329     m_optionsWidget->cbPalette->setChecked(m_config->addColorToCurrentPalette);
0330     m_optionsWidget->radius->setValue(m_config->radius);
0331     m_optionsWidget->blend->setValue(m_config->blend);
0332 }
0333 
0334 void KisToolColorSampler::slotSetUpdateColor(bool state)
0335 {
0336     m_config->updateColor = state;
0337 }
0338 
0339 void KisToolColorSampler::slotSetNormaliseValues(bool state)
0340 {
0341     m_config->normaliseValues = state;
0342     displaySampledColor(m_sampledColor);
0343 }
0344 
0345 void KisToolColorSampler::slotSetAddPalette(bool state)
0346 {
0347     m_config->addColorToCurrentPalette = state;
0348 }
0349 
0350 void KisToolColorSampler::slotChangeRadius(int value)
0351 {
0352     m_config->radius = value;
0353 }
0354 
0355 void KisToolColorSampler::slotChangeBlend(int value)
0356 {
0357     m_config->blend = value;
0358 }
0359 
0360 void KisToolColorSampler::slotSetColorSource(int value)
0361 {
0362     m_config->sampleMerged = value == SAMPLE_MERGED;
0363 }
0364 
0365 void KisToolColorSampler::slotChangePalette(int)
0366 {
0367     QString paletteName = m_optionsWidget->cmbPalette->currentData(Qt::UserRole + KisAbstractResourceModel::Name).toString();
0368     KConfigGroup config =  KSharedConfig::openConfig()->group(toolId());
0369     config.writeEntry("ColorSamplerPalette", paletteName);
0370 }