Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_cross_channel_filter.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 <QRegularExpression>
18
19#include "KoChannelInfo.h"
22#include "KoColorSpace.h"
25#include "KoCompositeOp.h"
26#include "KoID.h"
27
28#include "kis_signals_blocker.h"
29
31#include "kis_config_widget.h"
33#include <kis_selection.h>
34#include <kis_paint_device.h>
37
38#include "kis_histogram.h"
39#include "kis_painter.h"
42
44
45// KisCrossChannelFilterConfiguration
46
48 : KisMultiChannelFilterConfiguration(channelCount, "crosschannel", 1, resourcesInterface)
49 , m_colorSpace(cs)
50{
51 init();
52
53 int defaultDriver = 0;
54
55 if (cs) {
57 defaultDriver = qMax(0, KisMultiChannelFilter::findChannel(virtualChannels, VirtualChannelInfo::LIGHTNESS));
58 }
59
60 m_driverChannels.fill(defaultDriver, channelCount);
61}
62
68
71
76
81
83{
85
86 // Clean unused properties
87 if (driverChannels.size() < m_driverChannels.size()) {
88 for (int i = driverChannels.size(); i < m_driverChannels.size(); ++i) {
89 const QString name = QLatin1String("driver") + QString::number(i);
91 }
92 }
93
95
96 // Update properties for python
97 for (int i = 0; i < m_driverChannels.size(); ++i) {
98 const QString name = QLatin1String("driver") + QString::number(i);
99 const int value = m_driverChannels[i];
101 }
102}
103
105{
107
109 driverChannels.resize(m_curves.size());
110
111 QRegularExpression rx("^driver(\\d+)$");
112 QRegularExpressionMatch match;
113 for (QDomElement e = root.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
114 const QString attributeName = e.attribute("name");
115
116 if (attributeName.contains(rx, &match)) {
117 int channel = match.captured(1).toUShort();
118 int driver = KisDomUtils::toInt(e.text());
119
120 if (0 <= channel && channel < driverChannels.size()) {
121 driverChannels[channel] = driver;
122 }
123 }
124 }
125
127}
128
129void KisCrossChannelFilterConfiguration::toXML(QDomDocument& doc, QDomElement& root) const
130{
132
133 for (int i = 0; i < m_driverChannels.size(); i++) {
134 QDomElement param = doc.createElement("param");
135 param.setAttribute("name", QString("driver%1").arg(i));
136
137 QDomText text = doc.createTextNode(KisDomUtils::toString(m_driverChannels[i]));
138 param.appendChild(text);
139
140 root.appendChild(param);
141 }
142}
143
145{
146 const QList<QPointF> points { QPointF(0.0f, 0.5f), QPointF(1.0f, 0.5f) };
147 return KisCubicCurve(points);
148}
149
151{
152 const KisCrossChannelFilterConfiguration *otherConfig = dynamic_cast<const KisCrossChannelFilterConfiguration *>(rhs);
153
154 return otherConfig
156 && m_driverChannels == otherConfig->m_driverChannels;
157}
158
159void KisCrossChannelFilterConfiguration::setProperty(const QString& name, const QVariant& value)
160{
161 if (name == "nTransfers") {
162 KIS_SAFE_ASSERT_RECOVER_RETURN(value.canConvert<int>());
163
164 const qint32 newChannelCount = value.toInt();
165 const qint32 prevChannelCount = m_channelCount;
166
167 if (newChannelCount == prevChannelCount) {
168 return;
169 }
170
172
173 m_driverChannels.resize(newChannelCount);
174
175 if (newChannelCount > prevChannelCount) {
176 int defaultDriver = 0;
177
178 if (m_colorSpace) {
180 defaultDriver = qMax(0, KisMultiChannelFilter::findChannel(virtualChannels, VirtualChannelInfo::LIGHTNESS));
181 }
182
183 for (qint32 i = prevChannelCount; i < newChannelCount; ++i) {
184 m_driverChannels[i] = defaultDriver;
185
186 const QString name = QLatin1String("driver") + QString::number(i);
188 }
189 } else {
190 for (qint32 i = newChannelCount; i < prevChannelCount; ++i) {
191 const QString name = QLatin1String("driver") + QString::number(i);
193 }
194 }
195
196 return;
197 }
198
199 if (name == "activeCurve") {
200 setActiveCurve(qBound(0, value.toInt(), m_channelCount));
201 }
202
203 int channelIndex;
204 if (!channelIndexFromDriverPropertyName(name, channelIndex)) {
206 return;
207 }
208 if (channelIndex < 0 || channelIndex >= m_channelCount) {
209 return;
210 }
211
212 KIS_SAFE_ASSERT_RECOVER_RETURN(value.canConvert<int>());
213
214 const int driver = value.toInt();
215 m_driverChannels[channelIndex] = driver;
217}
218
219bool KisCrossChannelFilterConfiguration::channelIndexFromDriverPropertyName(const QString& name, int& driverIndex) const
220{
221 QRegularExpression rx("driver(\\d+)");
222 QRegularExpressionMatch match;
223 if (!name.contains(rx, &match)) {
224 return false;
225 }
226
227 driverIndex = match.captured(1).toUShort();
228 return true;
229}
230
232 : KisMultiChannelConfigWidget(parent, dev, f)
233{
234 const int virtualChannelCount = m_virtualChannels.size();
235 m_driverChannels.resize(virtualChannelCount);
236
237 init();
238
239 for (int i = 0; i < virtualChannelCount; i++) {
240 const VirtualChannelInfo &info = m_virtualChannels[i];
241
242 if (info.type() == VirtualChannelInfo::ALL_COLORS) {
243 continue;
244 }
245
246 m_page->cmbDriverChannel->addItem(info.name(), i);
247 }
248
249 connect(m_page->cmbDriverChannel, SIGNAL(activated(int)), this, SLOT(slotDriverChannelSelected(int)));
250}
251
252// KisCrossChannelConfigWidget
253
256
258{
259 const auto *cfg = dynamic_cast<const KisCrossChannelFilterConfiguration*>(config.data());
260 KIS_ASSERT(cfg);
261
262 m_driverChannels = cfg->driverChannels();
265}
266
268{
269 // Show the first channel with a curve, or saturation by default
270
271 int initialChannel = -1;
272 for (int i = 0; i < m_virtualChannels.size(); i++) {
273 if (!m_curves[i].isConstant(0.5)) {
274 initialChannel = i;
275 break;
276 }
277 }
278
279 if (initialChannel < 0) {
281 }
282
283 return initialChannel;
284}
285
286
288{
291
292 m_curves[m_activeVChannel] = m_page->curveWidget->curve();
293 cfg->setCurves(m_curves);
294 cfg->setActiveCurve(m_activeVChannel);
295 cfg->setDriverChannels(m_driverChannels);
296
297 return cfgSP;
298}
299
301{
303 m_page->intIn, m_page->intOut, 0, 100, -100, 100));
304
305 const int index = m_page->cmbDriverChannel->findData(m_driverChannels[m_activeVChannel]);
306 m_page->cmbDriverChannel->setCurrentIndex(index);
307}
308
309
314
316{
317 const int channel = m_page->cmbDriverChannel->itemData(index).toInt();
318
319 KIS_SAFE_ASSERT_RECOVER_RETURN(0 <= channel && channel < m_virtualChannels.size());
321
324}
325
326// KisCrossChannelFilter
327
328KisCrossChannelFilter::KisCrossChannelFilter() : KisMultiChannelFilter(id(), i18n("&Cross-channel adjustment curves..."))
329{}
330
333
335{
336 return new KisCrossChannelConfigWidget(parent, dev);
337}
338
340{
341 return new KisCrossChannelFilterConfiguration(0, nullptr, resourcesInterface);
342}
343
344int mapChannel(const VirtualChannelInfo &channel) {
345 switch (channel.type()) {
347 int pixelIndex = channel.pixelIndex();
348 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(0 <= pixelIndex && pixelIndex < 4, 0);
349 return pixelIndex;
350 }
354 return KisHSVCurve::Hue;
358 return KisHSVCurve::Value;
359 };
360
362 return 0;
363}
364
366{
367 const KisCrossChannelFilterConfiguration* configBC =
368 dynamic_cast<const KisCrossChannelFilterConfiguration*>(config.data());
369 Q_ASSERT(configBC);
370
371 const QVector<QVector<quint16> > &originalTransfers = configBC->transfers();
372 const QList<KisCubicCurve> &curves = configBC->curves();
373 const QVector<int> &drivers = configBC->driverChannels();
374
375 const QVector<VirtualChannelInfo> virtualChannels =
376 KisMultiChannelFilter::getVirtualChannels(cs, originalTransfers.size());
377
378 if (originalTransfers.size() > int(virtualChannels.size())) {
379 // We got an illegal number of colorchannels :(
380 return 0;
381 }
382
384 // Channel order reversed in order to adjust saturation before hue. This allows mapping grays to colors.
385 for (int i = virtualChannels.size() - 1; i >= 0; i--) {
386 if (!curves[i].isConstant(0.5)) {
387 int channel = mapChannel(virtualChannels[i]);
388 int driverChannel = mapChannel(virtualChannels[drivers[i]]);
389 QHash<QString, QVariant> params;
390 params["channel"] = channel;
391 params["driverChannel"] = driverChannel;
392 params["curve"] = QVariant::fromValue(originalTransfers[i]);
393 params["relative"] = true;
394 params["lumaRed"] = cs->lumaCoefficients()[0];
395 params["lumaGreen"] = cs->lumaCoefficients()[1];
396 params["lumaBlue"] = cs->lumaCoefficients()[2];
397
398 transforms << cs->createColorTransformation("hsv_curve_adjustment", params);
399 }
400 }
401
403}
float value(const T *src, size_t ch)
KisCurveWidgetControlsManager< QSpinBox > KisCurveWidgetControlsManagerInt
void sigConfigurationItemChanged()
KisCrossChannelConfigWidget(QWidget *parent, KisPaintDeviceSP dev, Qt::WindowFlags f=Qt::WindowFlags())
void setConfiguration(const KisPropertiesConfigurationSP config) override
KisPropertiesConfigurationSP configuration() const override
virtual KisPropertiesConfigurationSP getDefaultConfiguration() override
QScopedPointer< KisCurveWidgetControlsManagerInt > m_curveControlsManager
KisFilterConfigurationSP clone() const override
virtual bool compareTo(const KisPropertiesConfiguration *rhs) const override
void setProperty(const QString &name, const QVariant &value) override
void fromXML(const QDomElement &e) override
const QVector< int > driverChannels() const
KisCrossChannelFilterConfiguration(int channelCount, const KoColorSpace *cs, KisResourcesInterfaceSP resourcesInterface)
bool channelIndexFromDriverPropertyName(const QString &name, int &channelIndex) const
Takes a driver property name with format "driver#", where # is the index of the channel and puts the ...
void setDriverChannels(QVector< int > driverChannels)
KisConfigWidget * createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev, bool useForMasks) const override
KoColorTransformation * createTransformation(const KoColorSpace *cs, const KisFilterConfigurationSP config) const override
KisFilterConfigurationSP factoryConfiguration(KisResourcesInterfaceSP resourcesInterface) const override
static KisResourcesInterfaceSP instance()
void setConfiguration(const KisPropertiesConfigurationSP config) override
QVector< VirtualChannelInfo > m_virtualChannels
void setProperty(const QString &name, const QVariant &value) override
const QVector< QVector< quint16 > > & transfers() const
const QList< KisCubicCurve > & curves() const
virtual bool compareTo(const KisPropertiesConfiguration *rhs) const override
void fromXML(const QDomElement &e) override
static QVector< VirtualChannelInfo > getVirtualChannels(const KoColorSpace *cs, int maxChannels=-1)
static int findChannel(const QVector< VirtualChannelInfo > &virtualChannels, const VirtualChannelInfo::Type &channelType)
const KoColorSpace * colorSpace() const
QVector< qreal > lumaCoefficients
KoColorTransformation * createColorTransformation(const QString &id, const QHash< QString, QVariant > &parameters) const
#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_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
int mapChannel(const VirtualChannelInfo &channel)
int toInt(const QString &str, bool *ok=nullptr)
QString toString(const QString &value)
void setProperty(const QString &name, const QVariant &value) override
static KoColorTransformation * createOptimizedCompositeTransform(const QVector< KoColorTransformation * > transforms)