Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_multichannel_filter_base.cpp
Go to the documentation of this file.
1/*
2 * This file is part of Krita
3 *
4 * SPDX-FileCopyrightText: 2018 Jouni Pentikainen <joupent@gmail.com>
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
10
11#include <Qt>
12#include <QLayout>
13#include <QPixmap>
14#include <QPainter>
15#include <QDomDocument>
16#include <QHBoxLayout>
17#include <QMessageBox>
18#include <QRegularExpression>
19
20#include "KoChannelInfo.h"
23#include "KoColorSpace.h"
26#include "KoCompositeOp.h"
27#include "KoID.h"
28
29#include "kis_signals_blocker.h"
30
32#include "kis_config_widget.h"
35#include <kis_selection.h>
36#include <kis_paint_device.h>
38
39#include "kis_histogram.h"
40#include "kis_painter.h"
42
44
51
53{
54 Q_UNUSED(config);
55 return cs->colorModelId() == AlphaColorModelID;
56}
57
62
64 const VirtualChannelInfo::Type &channelType)
65{
66 return KisMultiChannelUtils::findChannel(virtualChannels, channelType);
67}
68
69
70KisMultiChannelFilterConfiguration::KisMultiChannelFilterConfiguration(int channelCount, const QString & name, qint32 version, KisResourcesInterfaceSP resourcesInterface)
71 : KisColorTransformationConfiguration(name, version, resourcesInterface)
72 , m_channelCount(channelCount)
73{
74}
75
78 m_channelCount(rhs.m_channelCount),
79 m_curves(rhs.m_curves),
80 m_transfers(rhs.m_transfers)
81{
82}
83
86
88{
89 m_curves.clear();
90
92
93 for (int i = 0; i < m_channelCount; ++i) {
94 m_curves.append(getDefaultCurve());
95
96 const QString name = QLatin1String("curve") + QString::number(i);
97 const QString value = m_curves.last().toString();
99 }
100
102}
103
108
110{
111 // Clean unused properties
112 if (curves.size() < m_curves.size()) {
113 for (int i = curves.size(); i < m_curves.size(); ++i) {
114 const QString name = QLatin1String("curve") + QString::number(i);
116 }
117 }
118
119 m_curves.clear();
121 m_channelCount = curves.size();
123
125
126 // Update properties for python
128
129 for (int i = 0; i < m_curves.size(); ++i) {
130 const QString name = QLatin1String("curve") + QString::number(i);
131 const QString value = m_curves[i].toString();
133 }
134}
135
141
143{
144 KIS_SAFE_ASSERT_RECOVER_RETURN(index >= 0 && index < m_curves.size());
145 m_transfers[index] = m_curves[index].uint16Transfer();
146}
147
149{
151 for (int i = 0; i < m_channelCount; i++) {
152 m_transfers[i] = m_curves[i].uint16Transfer();
153 }
154}
155
161
167
169{
170 fromXML(root);
171}
172
174{
176 quint16 numTransfers = 0;
177 quint16 numTransfersWithAlpha = 0;
178 int activeCurve = -1;
179 int version;
180 version = root.attribute("version").toInt();
181
182 QDomElement e = root.firstChild().toElement();
183 QString attributeName;
184 KisCubicCurve curve;
185 quint16 index;
186 QRegularExpression curveRegexp("curve(\\d+)");
187 QRegularExpressionMatch match;
188
189 while (!e.isNull()) {
190 if ((attributeName = e.attribute("name")) == "activeCurve") {
191 activeCurve = e.text().toInt();
192 } else if ((attributeName = e.attribute("name")) == "nTransfers") {
193 numTransfers = e.text().toUShort();
194 } else if ((attributeName = e.attribute("name")) == "nTransfersWithAlpha") {
195 numTransfersWithAlpha = e.text().toUShort();
196 } else {
197 if (attributeName.contains(curveRegexp, &match)) {
198
199 index = match.captured(1).toUShort();
200 index = qMin(index, quint16(curves.count()));
201
202 if (!e.text().isEmpty()) {
203 curve = KisCubicCurve(e.text());
204 }
205 curves.insert(index, curve);
206 }
207 }
208 e = e.nextSiblingElement();
209 }
210
215 if (numTransfersWithAlpha > numTransfers) {
216 e = root.firstChild().toElement();
217 while (!e.isNull()) {
218 if ((attributeName = e.attribute("name")) == "alphaCurve") {
219 if (!e.text().isEmpty()) {
220 curves.append(KisCubicCurve(e.text()));
221 }
222 }
223 e = e.nextSiblingElement();
224 }
225 }
226
227 //prepend empty curves for the brightness contrast filter.
228 if(getString("legacy") == "brightnesscontrast") {
229 if (getString("colorModel") == LABAColorModelID.id()) {
230 curves.append(KisCubicCurve());
231 curves.append(KisCubicCurve());
232 curves.append(KisCubicCurve());
233 } else {
234 int extraChannels = 5;
235 if (getString("colorModel") == CMYKAColorModelID.id()) {
236 extraChannels = 6;
237 } else if (getString("colorModel") == GrayAColorModelID.id()) {
238 extraChannels = 0;
239 }
240 for(int c = 0; c < extraChannels; c ++) {
241 curves.insert(0, KisCubicCurve());
242 }
243 }
244 }
245 if (!numTransfers)
246 return;
247
250 setActiveCurve(activeCurve);
251}
252
256//void KisMultiChannelFilterConfiguration::fromXML(const QString& s)
257
258void addParamNode(QDomDocument& doc,
259 QDomElement& root,
260 const QString &name,
261 const QString &value)
262{
263 QDomText text = doc.createTextNode(value);
264 QDomElement t = doc.createElement("param");
265 t.setAttribute("name", name);
266 t.appendChild(text);
267 root.appendChild(t);
268}
269
270void KisMultiChannelFilterConfiguration::toXML(QDomDocument& doc, QDomElement& root) const
271{
283 root.setAttribute("version", version());
284
285 QDomText text;
286 QDomElement t;
287
288 addParamNode(doc, root, "nTransfers", QString::number(m_channelCount));
289
290 if (m_activeCurve >= 0) {
291 // save active curve if only it has non-default value
292 addParamNode(doc, root, "activeCurve", QString::number(m_activeCurve));
293 }
294
295 KisCubicCurve curve;
296 QString paramName;
297
298 for (int i = 0; i < m_curves.size(); ++i) {
299 QString name = QLatin1String("curve") + QString::number(i);
300 QString value = m_curves[i].toString();
301
302 addParamNode(doc, root, name, value);
303 }
304}
305
307{
308 const KisMultiChannelFilterConfiguration *otherConfig = dynamic_cast<const KisMultiChannelFilterConfiguration *>(rhs);
309
310 return otherConfig
312 && m_channelCount == otherConfig->m_channelCount
313 && m_curves == otherConfig->m_curves
314 && m_transfers == otherConfig->m_transfers
315 && m_activeCurve == otherConfig->m_activeCurve;
316}
317
318void KisMultiChannelFilterConfiguration::setProperty(const QString& name, const QVariant& value)
319{
320 if (name == "nTransfers") {
321 KIS_SAFE_ASSERT_RECOVER_RETURN(value.canConvert<int>());
322
323 const qint32 newChannelCount = value.toInt();
324
325 if (newChannelCount == m_channelCount) {
326 return;
327 }
328
330
331 m_transfers.resize(newChannelCount);
332 if (newChannelCount > m_channelCount) {
333 for (qint32 i = m_channelCount; i < newChannelCount; ++i) {
334 m_curves.append(getDefaultCurve());
336
337 const QString name = QLatin1String("curve") + QString::number(i);
338 const QString value = m_curves.last().toString();
340 }
341 } else {
342 for (qint32 i = newChannelCount; i < m_channelCount; ++i) {
343 m_curves.removeLast();
344
345 const QString name = QLatin1String("curve") + QString::number(i);
347 }
348 }
349
350 m_channelCount = newChannelCount;
352
353
354 return;
355 }
356
357 if (name == "activeCurve") {
358 setActiveCurve(qBound(0, value.toInt(), m_channelCount));
359 }
360
361 int curveIndex;
362 if (!curveIndexFromCurvePropertyName(name, curveIndex) ||
363 curveIndex < 0 || curveIndex >= m_channelCount) {
364 return;
365 }
366
367 KIS_SAFE_ASSERT_RECOVER_RETURN(value.canConvert<QString>());
368
369 m_curves[curveIndex] = KisCubicCurve(value.toString());
370 updateTransfer(curveIndex);
372
373 // Query the curve instead of using the value directly, in case of not valid curve string
375}
376
377bool KisMultiChannelFilterConfiguration::curveIndexFromCurvePropertyName(const QString& name, int& curveIndex) const
378{
379 QRegularExpression rx("curve(\\d+)");
380 QRegularExpressionMatch match;
381 if (!name.contains(rx, &match)) {
382 return false;
383 }
384
385 curveIndex = match.captured(1).toUShort();
386 return true;
387}
388
390 : KisConfigWidget(parent, f)
391 , m_dev(dev)
392 , m_page(new WdgPerChannel(this))
393{
394 Q_ASSERT(m_dev);
395
396 const KoColorSpace *targetColorSpace = dev->compositionSourceColorSpace();
398}
399
405 QHBoxLayout * layout = new QHBoxLayout(this);
406 Q_CHECK_PTR(layout);
407 layout->setContentsMargins(0,0,0,0);
408 layout->addWidget(m_page);
409
410 resetCurves();
411
412 const int virtualChannelCount = m_virtualChannels.size();
413 for (int i = 0; i < virtualChannelCount; i++) {
414 const VirtualChannelInfo &info = m_virtualChannels[i];
415 m_page->cmbChannel->addItem(info.name(), i);
416 }
417
418 connect(m_page->cmbChannel, SIGNAL(activated(int)), this, SLOT(slotChannelSelected(int)));
419 connect((QObject*)(m_page->chkLogarithmic), SIGNAL(toggled(bool)), this, SLOT(logHistView()));
420 connect((QObject*)(m_page->resetButton), SIGNAL(clicked()), this, SLOT(resetCurve()));
421
422 // create the horizontal and vertical gradient labels
423 m_page->hgradient->setPixmap(createGradient(Qt::Horizontal));
424 m_page->vgradient->setPixmap(createGradient(Qt::Vertical));
425
426 // init histogram calculator
427 const KoColorSpace *targetColorSpace = m_dev->compositionSourceColorSpace();
428 QList<QString> keys =
430
431 if (keys.size() > 0) {
435 }
436
437 m_page->curveWidget->setCurve(m_curves[0]);
438 connect(m_page->curveWidget, SIGNAL(modified()), this, SLOT(slotCurveModified()));
439
440 {
441 KisSignalsBlocker b(m_page->curveWidget);
443 }
444}
445
451
453{
454 const KisPropertiesConfigurationSP &defaultConfiguration = getDefaultConfiguration();
455 const auto *defaults = dynamic_cast<const KisMultiChannelFilterConfiguration*>(defaultConfiguration.data());
456
458 m_curves = defaults->curves();
459
460 const int virtualChannelCount = m_virtualChannels.size();
461 for (int i = 0; i < virtualChannelCount; i++) {
462 const VirtualChannelInfo &info = m_virtualChannels[i];
463 m_curves[i].setName(info.name());
464 }
465}
466
468{
469 const KisMultiChannelFilterConfiguration * cfg = dynamic_cast<const KisMultiChannelFilterConfiguration *>(config.data());
470 if (!cfg) {
471 return;
472 }
473
474 if (cfg->curves().empty()) {
480 const KisPropertiesConfigurationSP &defaultConfiguration = getDefaultConfiguration();
481 const auto *defaults = dynamic_cast<const KisMultiChannelFilterConfiguration*>(defaultConfiguration.data());
483
484 if (!defaults->curves().isEmpty()) {
485 setConfiguration(defaultConfiguration);
486 return;
487 }
488 } else if (cfg->curves().size() > m_virtualChannels.size()) {
489 QMessageBox::warning(this, i18nc("@title:window", "Krita"), i18n("The current configuration was created for a different colorspace and cannot be used. All curves will be reset."));
490 warnKrita << "WARNING: trying to load a curve with invalid number of channels!";
491 warnKrita << "WARNING: expected:" << m_virtualChannels.size();
492 warnKrita << "WARNING: got:" << cfg->curves().size();
493 return;
494 } else {
495 if (cfg->curves().size() == m_virtualChannels.size()) {
496 for (int ch = 0; ch < cfg->curves().size(); ch++) {
497 m_curves[ch] = cfg->curves()[ch];
498 }
499 } else {
500 // The configuration does not cover all our channels.
501 // This happens when loading a document from an older version, which supported fewer channels.
502 // Reset to make sure the unspecified channels have their default values.
503 resetCurves();
504
505 auto compareChannels =
506 [] (const VirtualChannelInfo &lhs, const VirtualChannelInfo &rhs) -> bool {
507 return lhs.type() == rhs.type() &&
508 (lhs.type() != VirtualChannelInfo::REAL || lhs.pixelIndex() == rhs.pixelIndex());
509 };
510
511 const KoColorSpace *targetColorSpace = m_dev->compositionSourceColorSpace();
512
513
520 QVector<VirtualChannelInfo> detectedCurves = KisMultiChannelUtils::getVirtualChannels(targetColorSpace, cfg->curves().size());
521
522 for (auto detectedIt = detectedCurves.begin(); detectedIt != detectedCurves.end(); ++detectedIt) {
523 auto dstIt = std::find_if(m_virtualChannels.begin(), m_virtualChannels.end(),
524 [=] (const VirtualChannelInfo &info) {
525 return compareChannels(*detectedIt, info);
526 });
527 if (dstIt != m_virtualChannels.end()) {
528 const int srcIndex = std::distance(detectedCurves.begin(), detectedIt);
529 const int dstIndex = std::distance(m_virtualChannels.begin(), dstIt);
530 m_curves[dstIndex] = cfg->curves()[srcIndex];
531 } else {
532 warnKrita << "WARNING: failed to find mapping of the channel in the filter configuration:";
533 warnKrita << "WARNING: channel:" << ppVar(detectedIt->name()) << ppVar(detectedIt->type())<< ppVar(detectedIt->pixelIndex());
534 warnKrita << "WARNING:";
535
536 for (auto it = detectedCurves.begin(); it != detectedCurves.end(); ++it) {
537 warnKrita << "WARNING: detected channels" << std::distance(detectedCurves.begin(), it) << ":" << it->name();
538 }
539
540 for (auto it = m_virtualChannels.begin(); it != m_virtualChannels.end(); ++it) {
541 warnKrita << "WARNING: read channels" << std::distance(m_virtualChannels.begin(), it) << ":" << it->name();
542 }
543 }
544 }
545 }
546 }
547
548 const int activeChannel =
549 config->hasProperty("activeCurve") ?
550 qBound(0, config->getInt("activeCurve"), m_curves.size()) :
552
553 if (activeChannel == m_activeVChannel) {
554 m_page->curveWidget->setCurve(m_curves[m_activeVChannel]);
555 } else {
556 setActiveChannel(activeChannel);
557 }
558}
559
564
565inline QPixmap KisMultiChannelConfigWidget::createGradient(Qt::Orientation orient /*, int invert (not used yet) */)
566{
567 int width;
568 int height;
569 int *i, inc, col;
570 int x = 0, y = 0;
571
572 if (orient == Qt::Horizontal) {
573 i = &x; inc = 1; col = 0;
574 width = 256; height = 1;
575 } else {
576 i = &y; inc = -1; col = 255;
577 width = 1; height = 256;
578 }
579
580 QPixmap gradientpix(width, height);
581 QPainter p(&gradientpix);
582 p.setPen(QPen(QColor(0, 0, 0), 1, Qt::SolidLine));
583 for (; *i < 256; (*i)++, col += inc) {
584 p.setPen(QColor(col, col, col));
585 p.drawPoint(x, y);
586 }
587 return gradientpix;
588}
589
591{
592 int i;
593 int height = 256;
594 QPixmap pix(256, height);
596
597
598 bool logarithmic = m_page->chkLogarithmic->isChecked();
599
600 if (logarithmic)
602 else
604
605
606 QPalette appPalette = QApplication::palette();
607
608 pix.fill(QColor(appPalette.color(QPalette::Base)));
609
610 QPainter p(&pix);
611 p.setPen(QColor(appPalette.color(QPalette::Text)));
612 p.save();
613 p.setOpacity(0.2);
614
616
617
618 if (info.type() == VirtualChannelInfo::REAL) {
620
621 double highest = (double)m_histogram->calculations().getHighest();
622
623 qint32 bins = m_histogram->producer()->numberOfBins();
624
626 double factor = (double)height / highest;
627 for (i = 0; i < bins; ++i) {
628 p.drawLine(i, height, i, height - int(m_histogram->getValue(i) * factor));
629 }
630 } else {
631 double factor = (double)height / (double)log(highest);
632 for (i = 0; i < bins; ++i) {
633 p.drawLine(i, height, i, height - int(log((double)m_histogram->getValue(i)) * factor));
634 }
635 }
636 }
637
638 p.restore();
639
640 return pix;
641}
642
644{
645 const int virtualChannel = m_page->cmbChannel->itemData(index).toInt();
646 setActiveChannel(virtualChannel);
647}
648
657
659{
660 if (ch == m_activeVChannel) return;
663
664 m_activeVChannel = ch;
665 m_page->curveWidget->setCurve(m_curves[m_activeVChannel]);
666 m_page->curveWidget->setPixmap(getHistogram());
667
668 const int index = m_page->cmbChannel->findData(m_activeVChannel);
669 m_page->cmbChannel->setCurrentIndex(index);
670
672}
673
675{
676 m_page->curveWidget->setPixmap(getHistogram());
677}
678
680{
681 const KisPropertiesConfigurationSP &defaultConfiguration = getDefaultConfiguration();
682 const auto *defaults = dynamic_cast<const KisMultiChannelFilterConfiguration*>(defaultConfiguration.data());
684
685 auto defaultCurves = defaults->curves();
686 KIS_SAFE_ASSERT_RECOVER_RETURN(defaultCurves.size() > m_activeVChannel);
687
688 m_page->curveWidget->setCurve(defaultCurves[m_activeVChannel]);
689}
float value(const T *src, size_t ch)
const Params2D p
const KoID GrayAColorModelID("GRAYA", ki18n("Grayscale/Alpha"))
const KoID AlphaColorModelID("A", ki18n("Alpha mask"))
const KoID CMYKAColorModelID("CMYKA", ki18n("CMYK/Alpha"))
const KoID LABAColorModelID("LABA", ki18n("L*a*b*/Alpha"))
@ TO_LAB16
void sigConfigurationItemChanged()
quint32 getHighest()
This function return the highest value of the histogram.
void setChannel(qint32 channel)
quint32 getValue(quint8 i)
void setHistogramType(enumHistogramType type)
KoHistogramProducer * producer()
enumHistogramType getHistogramType()
Calculations calculations()
QPixmap createGradient(Qt::Orientation orient)
virtual void updateChannelControls()=0
KisMultiChannelConfigWidget(QWidget *parent, KisPaintDeviceSP dev, Qt::WindowFlags f=Qt::WindowFlags())
void setConfiguration(const KisPropertiesConfigurationSP config) override
virtual KisPropertiesConfigurationSP getDefaultConfiguration()=0
QVector< VirtualChannelInfo > m_virtualChannels
void setProperty(const QString &name, const QVariant &value) override
const QVector< QVector< quint16 > > & transfers() const
bool curveIndexFromCurvePropertyName(const QString &name, int &curveIndex) const
Takes a curve property name with format "curve#", where # is the index of the channel and puts the in...
void setCurves(QList< KisCubicCurve > &curves)
bool isCompatible(const KisPaintDeviceSP) const override
void fromLegacyXML(const QDomElement &root) override
KisMultiChannelFilterConfiguration(int channelCount, const QString &name, qint32 version, KisResourcesInterfaceSP resourcesInterface)
virtual KisCubicCurve getDefaultCurve()=0
const QList< KisCubicCurve > & curves() const
virtual bool compareTo(const KisPropertiesConfiguration *rhs) const override
void fromXML(const QDomElement &e) override
bool needsTransparentPixels(const KisFilterConfigurationSP config, const KoColorSpace *cs) const override
static QVector< VirtualChannelInfo > getVirtualChannels(const KoColorSpace *cs, int maxChannels=-1)
static int findChannel(const QVector< VirtualChannelInfo > &virtualChannels, const VirtualChannelInfo::Type &channelType)
KisMultiChannelFilter(const KoID &id, const QString &entry)
virtual const KoColorSpace * compositionSourceColorSpace() const
QRect exactBounds() const
virtual KoID colorModelId() const =0
virtual quint32 channelCount() const =0
T get(const QString &id) const
QList< QString > keysCompatibleWith(const KoColorSpace *colorSpace, bool isStrict=false) const
returns a list, sorted by preference: higher preference comes first
static KoHistogramProducerFactoryRegistry * instance()
virtual KoHistogramProducer * generate()=0
Factory method, generates a new KoHistogramProducer.
virtual qint32 numberOfBins()=0
Definition KoID.h:30
QString id() const
Definition KoID.cpp:63
#define KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(cond, val)
Definition kis_assert.h:129
#define KIS_SAFE_ASSERT_RECOVER_RETURN(cond)
Definition kis_assert.h:128
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
#define warnKrita
Definition kis_debug.h:87
#define ppVar(var)
Definition kis_debug.h:155
const KoID FiltersCategoryAdjustId("adjust_filters", ki18nc("The category of color adjustment filters, like levels. Verb.", "Adjust"))
@ LOGARITHMIC
void addParamNode(QDomDocument &doc, QDomElement &root, const QString &name, const QString &value)
QVector< VirtualChannelInfo > getVirtualChannels(const KoColorSpace *cs, int maxChannels, bool supportsLightness, bool supportsHue, bool supportsSaturation)
int findChannel(const QVector< VirtualChannelInfo > &virtualChannels, const VirtualChannelInfo::Type &channelType)
@ LINEAR
Definition nugrid.h:26
void setSupportsPainting(bool v)
void setColorSpaceIndependence(ColorSpaceIndependence v)
void setProperty(const QString &name, const QVariant &value) override
void invalidateColorTransformationCache()
Manually invalidate the cache. By default setProperty invalidates the cache but this method can be us...
virtual bool compareTo(const KisPropertiesConfiguration *rhs) const override
QString getString(const QString &name, const QString &def=QString()) const