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 <QRegExp>
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 QRegExp rx("driver(\\d+)");
112 for (QDomElement e = root.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) {
113 const QString attributeName = e.attribute("name");
114
115 if (rx.exactMatch(attributeName)) {
116 int channel = rx.cap(1).toUShort();
117 int driver = KisDomUtils::toInt(e.text());
118
119 if (0 <= channel && channel < driverChannels.size()) {
120 driverChannels[channel] = driver;
121 }
122 }
123 }
124
126}
127
128void KisCrossChannelFilterConfiguration::toXML(QDomDocument& doc, QDomElement& root) const
129{
131
132 for (int i = 0; i < m_driverChannels.size(); i++) {
133 QDomElement param = doc.createElement("param");
134 param.setAttribute("name", QString("driver%1").arg(i));
135
136 QDomText text = doc.createTextNode(KisDomUtils::toString(m_driverChannels[i]));
137 param.appendChild(text);
138
139 root.appendChild(param);
140 }
141}
142
144{
145 const QList<QPointF> points { QPointF(0.0f, 0.5f), QPointF(1.0f, 0.5f) };
146 return KisCubicCurve(points);
147}
148
150{
151 const KisCrossChannelFilterConfiguration *otherConfig = dynamic_cast<const KisCrossChannelFilterConfiguration *>(rhs);
152
153 return otherConfig
155 && m_driverChannels == otherConfig->m_driverChannels;
156}
157
158void KisCrossChannelFilterConfiguration::setProperty(const QString& name, const QVariant& value)
159{
160 if (name == "nTransfers") {
161 KIS_SAFE_ASSERT_RECOVER_RETURN(value.canConvert<int>());
162
163 const qint32 newChannelCount = value.toInt();
164 const qint32 prevChannelCount = m_channelCount;
165
166 if (newChannelCount == prevChannelCount) {
167 return;
168 }
169
171
172 m_driverChannels.resize(newChannelCount);
173
174 if (newChannelCount > prevChannelCount) {
175 int defaultDriver = 0;
176
177 if (m_colorSpace) {
179 defaultDriver = qMax(0, KisMultiChannelFilter::findChannel(virtualChannels, VirtualChannelInfo::LIGHTNESS));
180 }
181
182 for (qint32 i = prevChannelCount; i < newChannelCount; ++i) {
183 m_driverChannels[i] = defaultDriver;
184
185 const QString name = QLatin1String("driver") + QString::number(i);
187 }
188 } else {
189 for (qint32 i = newChannelCount; i < prevChannelCount; ++i) {
190 const QString name = QLatin1String("driver") + QString::number(i);
192 }
193 }
194
195 return;
196 }
197
198 if (name == "activeCurve") {
199 setActiveCurve(qBound(0, value.toInt(), m_channelCount));
200 }
201
202 int channelIndex;
203 if (!channelIndexFromDriverPropertyName(name, channelIndex)) {
205 return;
206 }
207 if (channelIndex < 0 || channelIndex >= m_channelCount) {
208 return;
209 }
210
211 KIS_SAFE_ASSERT_RECOVER_RETURN(value.canConvert<int>());
212
213 const int driver = value.toInt();
214 m_driverChannels[channelIndex] = driver;
216}
217
218bool KisCrossChannelFilterConfiguration::channelIndexFromDriverPropertyName(const QString& name, int& driverIndex) const
219{
220 QRegExp rx("driver(\\d+)");
221 if (rx.indexIn(name, 0) == -1) {
222 return false;
223 }
224
225 driverIndex = rx.cap(1).toUShort();
226 return true;
227}
228
230 : KisMultiChannelConfigWidget(parent, dev, f)
231{
232 const int virtualChannelCount = m_virtualChannels.size();
233 m_driverChannels.resize(virtualChannelCount);
234
235 init();
236
237 for (int i = 0; i < virtualChannelCount; i++) {
238 const VirtualChannelInfo &info = m_virtualChannels[i];
239
240 if (info.type() == VirtualChannelInfo::ALL_COLORS) {
241 continue;
242 }
243
244 m_page->cmbDriverChannel->addItem(info.name(), i);
245 }
246
247 connect(m_page->cmbDriverChannel, SIGNAL(activated(int)), this, SLOT(slotDriverChannelSelected(int)));
248}
249
250// KisCrossChannelConfigWidget
251
254
256{
257 const auto *cfg = dynamic_cast<const KisCrossChannelFilterConfiguration*>(config.data());
258 KIS_ASSERT(cfg);
259
260 m_driverChannels = cfg->driverChannels();
263}
264
266{
267 // Show the first channel with a curve, or saturation by default
268
269 int initialChannel = -1;
270 for (int i = 0; i < m_virtualChannels.size(); i++) {
271 if (!m_curves[i].isConstant(0.5)) {
272 initialChannel = i;
273 break;
274 }
275 }
276
277 if (initialChannel < 0) {
279 }
280
281 return initialChannel;
282}
283
284
286{
289
290 m_curves[m_activeVChannel] = m_page->curveWidget->curve();
291 cfg->setCurves(m_curves);
292 cfg->setActiveCurve(m_activeVChannel);
293 cfg->setDriverChannels(m_driverChannels);
294
295 return cfgSP;
296}
297
299{
301 m_page->intIn, m_page->intOut, 0, 100, -100, 100));
302
303 const int index = m_page->cmbDriverChannel->findData(m_driverChannels[m_activeVChannel]);
304 m_page->cmbDriverChannel->setCurrentIndex(index);
305}
306
307
312
314{
315 const int channel = m_page->cmbDriverChannel->itemData(index).toInt();
316
317 KIS_SAFE_ASSERT_RECOVER_RETURN(0 <= channel && channel < m_virtualChannels.size());
319
322}
323
324// KisCrossChannelFilter
325
326KisCrossChannelFilter::KisCrossChannelFilter() : KisMultiChannelFilter(id(), i18n("&Cross-channel adjustment curves..."))
327{}
328
331
333{
334 return new KisCrossChannelConfigWidget(parent, dev);
335}
336
338{
339 return new KisCrossChannelFilterConfiguration(0, nullptr, resourcesInterface);
340}
341
342int mapChannel(const VirtualChannelInfo &channel) {
343 switch (channel.type()) {
345 int pixelIndex = channel.pixelIndex();
346 KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(0 <= pixelIndex && pixelIndex < 4, 0);
347 return pixelIndex;
348 }
352 return KisHSVCurve::Hue;
356 return KisHSVCurve::Value;
357 };
358
360 return 0;
361}
362
364{
365 const KisCrossChannelFilterConfiguration* configBC =
366 dynamic_cast<const KisCrossChannelFilterConfiguration*>(config.data());
367 Q_ASSERT(configBC);
368
369 const QVector<QVector<quint16> > &originalTransfers = configBC->transfers();
370 const QList<KisCubicCurve> &curves = configBC->curves();
371 const QVector<int> &drivers = configBC->driverChannels();
372
373 const QVector<VirtualChannelInfo> virtualChannels =
374 KisMultiChannelFilter::getVirtualChannels(cs, originalTransfers.size());
375
376 if (originalTransfers.size() > int(virtualChannels.size())) {
377 // We got an illegal number of colorchannels :(
378 return 0;
379 }
380
382 // Channel order reversed in order to adjust saturation before hue. This allows mapping grays to colors.
383 for (int i = virtualChannels.size() - 1; i >= 0; i--) {
384 if (!curves[i].isConstant(0.5)) {
385 int channel = mapChannel(virtualChannels[i]);
386 int driverChannel = mapChannel(virtualChannels[drivers[i]]);
387 QHash<QString, QVariant> params;
388 params["channel"] = channel;
389 params["driverChannel"] = driverChannel;
390 params["curve"] = QVariant::fromValue(originalTransfers[i]);
391 params["relative"] = true;
392 params["lumaRed"] = cs->lumaCoefficients()[0];
393 params["lumaGreen"] = cs->lumaCoefficients()[1];
394 params["lumaBlue"] = cs->lumaCoefficients()[2];
395
396 transforms << cs->createColorTransformation("hsv_curve_adjustment", params);
397 }
398 }
399
401}
float value(const T *src, size_t ch)
KisCurveWidgetControlsManager< QSpinBox > KisCurveWidgetControlsManagerInt
connect(this, SIGNAL(optionsChanged()), this, SLOT(saveOptions()))
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)