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 <QRegExp>
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 QRegExp curveRegexp("curve(\\d+)");
187
188 while (!e.isNull()) {
189 if ((attributeName = e.attribute("name")) == "activeCurve") {
190 activeCurve = e.text().toInt();
191 } else if ((attributeName = e.attribute("name")) == "nTransfers") {
192 numTransfers = e.text().toUShort();
193 } else if ((attributeName = e.attribute("name")) == "nTransfersWithAlpha") {
194 numTransfersWithAlpha = e.text().toUShort();
195 } else {
196 if (curveRegexp.indexIn(attributeName, 0) != -1) {
197
198 index = curveRegexp.cap(1).toUShort();
199 index = qMin(index, quint16(curves.count()));
200
201 if (!e.text().isEmpty()) {
202 curve = KisCubicCurve(e.text());
203 }
204 curves.insert(index, curve);
205 }
206 }
207 e = e.nextSiblingElement();
208 }
209
214 if (numTransfersWithAlpha > numTransfers) {
215 e = root.firstChild().toElement();
216 while (!e.isNull()) {
217 if ((attributeName = e.attribute("name")) == "alphaCurve") {
218 if (!e.text().isEmpty()) {
219 curves.append(KisCubicCurve(e.text()));
220 }
221 }
222 e = e.nextSiblingElement();
223 }
224 }
225
226 //prepend empty curves for the brightness contrast filter.
227 if(getString("legacy") == "brightnesscontrast") {
228 if (getString("colorModel") == LABAColorModelID.id()) {
229 curves.append(KisCubicCurve());
230 curves.append(KisCubicCurve());
231 curves.append(KisCubicCurve());
232 } else {
233 int extraChannels = 5;
234 if (getString("colorModel") == CMYKAColorModelID.id()) {
235 extraChannels = 6;
236 } else if (getString("colorModel") == GrayAColorModelID.id()) {
237 extraChannels = 0;
238 }
239 for(int c = 0; c < extraChannels; c ++) {
240 curves.insert(0, KisCubicCurve());
241 }
242 }
243 }
244 if (!numTransfers)
245 return;
246
249 setActiveCurve(activeCurve);
250}
251
255//void KisMultiChannelFilterConfiguration::fromXML(const QString& s)
256
257void addParamNode(QDomDocument& doc,
258 QDomElement& root,
259 const QString &name,
260 const QString &value)
261{
262 QDomText text = doc.createTextNode(value);
263 QDomElement t = doc.createElement("param");
264 t.setAttribute("name", name);
265 t.appendChild(text);
266 root.appendChild(t);
267}
268
269void KisMultiChannelFilterConfiguration::toXML(QDomDocument& doc, QDomElement& root) const
270{
282 root.setAttribute("version", version());
283
284 QDomText text;
285 QDomElement t;
286
287 addParamNode(doc, root, "nTransfers", QString::number(m_channelCount));
288
289 if (m_activeCurve >= 0) {
290 // save active curve if only it has non-default value
291 addParamNode(doc, root, "activeCurve", QString::number(m_activeCurve));
292 }
293
294 KisCubicCurve curve;
295 QString paramName;
296
297 for (int i = 0; i < m_curves.size(); ++i) {
298 QString name = QLatin1String("curve") + QString::number(i);
299 QString value = m_curves[i].toString();
300
301 addParamNode(doc, root, name, value);
302 }
303}
304
306{
307 const KisMultiChannelFilterConfiguration *otherConfig = dynamic_cast<const KisMultiChannelFilterConfiguration *>(rhs);
308
309 return otherConfig
311 && m_channelCount == otherConfig->m_channelCount
312 && m_curves == otherConfig->m_curves
313 && m_transfers == otherConfig->m_transfers
314 && m_activeCurve == otherConfig->m_activeCurve;
315}
316
317void KisMultiChannelFilterConfiguration::setProperty(const QString& name, const QVariant& value)
318{
319 if (name == "nTransfers") {
320 KIS_SAFE_ASSERT_RECOVER_RETURN(value.canConvert<int>());
321
322 const qint32 newChannelCount = value.toInt();
323
324 if (newChannelCount == m_channelCount) {
325 return;
326 }
327
329
330 m_transfers.resize(newChannelCount);
331 if (newChannelCount > m_channelCount) {
332 for (qint32 i = m_channelCount; i < newChannelCount; ++i) {
333 m_curves.append(getDefaultCurve());
335
336 const QString name = QLatin1String("curve") + QString::number(i);
337 const QString value = m_curves.last().toString();
339 }
340 } else {
341 for (qint32 i = newChannelCount; i < m_channelCount; ++i) {
342 m_curves.removeLast();
343
344 const QString name = QLatin1String("curve") + QString::number(i);
346 }
347 }
348
349 m_channelCount = newChannelCount;
351
352
353 return;
354 }
355
356 if (name == "activeCurve") {
357 setActiveCurve(qBound(0, value.toInt(), m_channelCount));
358 }
359
360 int curveIndex;
361 if (!curveIndexFromCurvePropertyName(name, curveIndex) ||
362 curveIndex < 0 || curveIndex >= m_channelCount) {
363 return;
364 }
365
366 KIS_SAFE_ASSERT_RECOVER_RETURN(value.canConvert<QString>());
367
368 m_curves[curveIndex] = KisCubicCurve(value.toString());
369 updateTransfer(curveIndex);
371
372 // Query the curve instead of using the value directly, in case of not valid curve string
374}
375
376bool KisMultiChannelFilterConfiguration::curveIndexFromCurvePropertyName(const QString& name, int& curveIndex) const
377{
378 QRegExp rx("curve(\\d+)");
379 if (rx.indexIn(name, 0) == -1) {
380 return false;
381 }
382
383 curveIndex = rx.cap(1).toUShort();
384 return true;
385}
386
388 : KisConfigWidget(parent, f)
389 , m_dev(dev)
390 , m_page(new WdgPerChannel(this))
391{
392 Q_ASSERT(m_dev);
393
394 const KoColorSpace *targetColorSpace = dev->compositionSourceColorSpace();
396}
397
403 QHBoxLayout * layout = new QHBoxLayout(this);
404 Q_CHECK_PTR(layout);
405 layout->setContentsMargins(0,0,0,0);
406 layout->addWidget(m_page);
407
408 resetCurves();
409
410 const int virtualChannelCount = m_virtualChannels.size();
411 for (int i = 0; i < virtualChannelCount; i++) {
412 const VirtualChannelInfo &info = m_virtualChannels[i];
413 m_page->cmbChannel->addItem(info.name(), i);
414 }
415
416 connect(m_page->cmbChannel, SIGNAL(activated(int)), this, SLOT(slotChannelSelected(int)));
417 connect((QObject*)(m_page->chkLogarithmic), SIGNAL(toggled(bool)), this, SLOT(logHistView()));
418 connect((QObject*)(m_page->resetButton), SIGNAL(clicked()), this, SLOT(resetCurve()));
419
420 // create the horizontal and vertical gradient labels
421 m_page->hgradient->setPixmap(createGradient(Qt::Horizontal));
422 m_page->vgradient->setPixmap(createGradient(Qt::Vertical));
423
424 // init histogram calculator
425 const KoColorSpace *targetColorSpace = m_dev->compositionSourceColorSpace();
426 QList<QString> keys =
428
429 if (keys.size() > 0) {
433 }
434
435 m_page->curveWidget->setCurve(m_curves[0]);
436 connect(m_page->curveWidget, SIGNAL(modified()), this, SLOT(slotCurveModified()));
437
438 {
439 KisSignalsBlocker b(m_page->curveWidget);
441 }
442}
443
449
451{
452 const KisPropertiesConfigurationSP &defaultConfiguration = getDefaultConfiguration();
453 const auto *defaults = dynamic_cast<const KisMultiChannelFilterConfiguration*>(defaultConfiguration.data());
454
456 m_curves = defaults->curves();
457
458 const int virtualChannelCount = m_virtualChannels.size();
459 for (int i = 0; i < virtualChannelCount; i++) {
460 const VirtualChannelInfo &info = m_virtualChannels[i];
461 m_curves[i].setName(info.name());
462 }
463}
464
466{
467 const KisMultiChannelFilterConfiguration * cfg = dynamic_cast<const KisMultiChannelFilterConfiguration *>(config.data());
468 if (!cfg) {
469 return;
470 }
471
472 if (cfg->curves().empty()) {
478 const KisPropertiesConfigurationSP &defaultConfiguration = getDefaultConfiguration();
479 const auto *defaults = dynamic_cast<const KisMultiChannelFilterConfiguration*>(defaultConfiguration.data());
481
482 if (!defaults->curves().isEmpty()) {
483 setConfiguration(defaultConfiguration);
484 return;
485 }
486 } else if (cfg->curves().size() > m_virtualChannels.size()) {
487 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."));
488 warnKrita << "WARNING: trying to load a curve with invalid number of channels!";
489 warnKrita << "WARNING: expected:" << m_virtualChannels.size();
490 warnKrita << "WARNING: got:" << cfg->curves().size();
491 return;
492 } else {
493 if (cfg->curves().size() == m_virtualChannels.size()) {
494 for (int ch = 0; ch < cfg->curves().size(); ch++) {
495 m_curves[ch] = cfg->curves()[ch];
496 }
497 } else {
498 // The configuration does not cover all our channels.
499 // This happens when loading a document from an older version, which supported fewer channels.
500 // Reset to make sure the unspecified channels have their default values.
501 resetCurves();
502
503 auto compareChannels =
504 [] (const VirtualChannelInfo &lhs, const VirtualChannelInfo &rhs) -> bool {
505 return lhs.type() == rhs.type() &&
506 (lhs.type() != VirtualChannelInfo::REAL || lhs.pixelIndex() == rhs.pixelIndex());
507 };
508
509 const KoColorSpace *targetColorSpace = m_dev->compositionSourceColorSpace();
510
511
518 QVector<VirtualChannelInfo> detectedCurves = KisMultiChannelUtils::getVirtualChannels(targetColorSpace, cfg->curves().size());
519
520 for (auto detectedIt = detectedCurves.begin(); detectedIt != detectedCurves.end(); ++detectedIt) {
521 auto dstIt = std::find_if(m_virtualChannels.begin(), m_virtualChannels.end(),
522 [=] (const VirtualChannelInfo &info) {
523 return compareChannels(*detectedIt, info);
524 });
525 if (dstIt != m_virtualChannels.end()) {
526 const int srcIndex = std::distance(detectedCurves.begin(), detectedIt);
527 const int dstIndex = std::distance(m_virtualChannels.begin(), dstIt);
528 m_curves[dstIndex] = cfg->curves()[srcIndex];
529 } else {
530 warnKrita << "WARNING: failed to find mapping of the channel in the filter configuration:";
531 warnKrita << "WARNING: channel:" << ppVar(detectedIt->name()) << ppVar(detectedIt->type())<< ppVar(detectedIt->pixelIndex());
532 warnKrita << "WARNING:";
533
534 for (auto it = detectedCurves.begin(); it != detectedCurves.end(); ++it) {
535 warnKrita << "WARNING: detected channels" << std::distance(detectedCurves.begin(), it) << ":" << it->name();
536 }
537
538 for (auto it = m_virtualChannels.begin(); it != m_virtualChannels.end(); ++it) {
539 warnKrita << "WARNING: read channels" << std::distance(m_virtualChannels.begin(), it) << ":" << it->name();
540 }
541 }
542 }
543 }
544 }
545
546 const int activeChannel =
547 config->hasProperty("activeCurve") ?
548 qBound(0, config->getInt("activeCurve"), m_curves.size()) :
550
551 if (activeChannel == m_activeVChannel) {
552 m_page->curveWidget->setCurve(m_curves[m_activeVChannel]);
553 } else {
554 setActiveChannel(activeChannel);
555 }
556}
557
562
563inline QPixmap KisMultiChannelConfigWidget::createGradient(Qt::Orientation orient /*, int invert (not used yet) */)
564{
565 int width;
566 int height;
567 int *i, inc, col;
568 int x = 0, y = 0;
569
570 if (orient == Qt::Horizontal) {
571 i = &x; inc = 1; col = 0;
572 width = 256; height = 1;
573 } else {
574 i = &y; inc = -1; col = 255;
575 width = 1; height = 256;
576 }
577
578 QPixmap gradientpix(width, height);
579 QPainter p(&gradientpix);
580 p.setPen(QPen(QColor(0, 0, 0), 1, Qt::SolidLine));
581 for (; *i < 256; (*i)++, col += inc) {
582 p.setPen(QColor(col, col, col));
583 p.drawPoint(x, y);
584 }
585 return gradientpix;
586}
587
589{
590 int i;
591 int height = 256;
592 QPixmap pix(256, height);
594
595
596 bool logarithmic = m_page->chkLogarithmic->isChecked();
597
598 if (logarithmic)
600 else
602
603
604 QPalette appPalette = QApplication::palette();
605
606 pix.fill(QColor(appPalette.color(QPalette::Base)));
607
608 QPainter p(&pix);
609 p.setPen(QColor(appPalette.color(QPalette::Text)));
610 p.save();
611 p.setOpacity(0.2);
612
614
615
616 if (info.type() == VirtualChannelInfo::REAL) {
618
619 double highest = (double)m_histogram->calculations().getHighest();
620
621 qint32 bins = m_histogram->producer()->numberOfBins();
622
624 double factor = (double)height / highest;
625 for (i = 0; i < bins; ++i) {
626 p.drawLine(i, height, i, height - int(m_histogram->getValue(i) * factor));
627 }
628 } else {
629 double factor = (double)height / (double)log(highest);
630 for (i = 0; i < bins; ++i) {
631 p.drawLine(i, height, i, height - int(log((double)m_histogram->getValue(i)) * factor));
632 }
633 }
634 }
635
636 p.restore();
637
638 return pix;
639}
640
642{
643 const int virtualChannel = m_page->cmbChannel->itemData(index).toInt();
644 setActiveChannel(virtualChannel);
645}
646
655
657{
658 if (ch == m_activeVChannel) return;
661
662 m_activeVChannel = ch;
663 m_page->curveWidget->setCurve(m_curves[m_activeVChannel]);
664 m_page->curveWidget->setPixmap(getHistogram());
665
666 const int index = m_page->cmbChannel->findData(m_activeVChannel);
667 m_page->cmbChannel->setCurrentIndex(index);
668
670}
671
673{
674 m_page->curveWidget->setPixmap(getHistogram());
675}
676
678{
679 const KisPropertiesConfigurationSP &defaultConfiguration = getDefaultConfiguration();
680 const auto *defaults = dynamic_cast<const KisMultiChannelFilterConfiguration*>(defaultConfiguration.data());
682
683 auto defaultCurves = defaults->curves();
684 KIS_SAFE_ASSERT_RECOVER_RETURN(defaultCurves.size() > m_activeVChannel);
685
686 m_page->curveWidget->setCurve(defaultCurves[m_activeVChannel]);
687}
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
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
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