diff --git a/libs/image/kis_cubic_curve.cpp b/libs/image/kis_cubic_curve.cpp index 1ba7b9efbd..1f12831310 100644 --- a/libs/image/kis_cubic_curve.cpp +++ b/libs/image/kis_cubic_curve.cpp @@ -1,506 +1,519 @@ /* * Copyright (c) 2005 C. Boemann * Copyright (c) 2009 Dmitry Kazakov * Copyright (c) 2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_cubic_curve.h" #include #include #include #include #include "kis_dom_utils.h" #include "kis_algebra_2d.h" template class KisTridiagonalSystem { /* * e.g. * |b0 c0 0 0 0| |x0| |f0| * |a0 b1 c1 0 0| |x1| |f1| * |0 a1 b2 c2 0|*|x2|=|f2| * |0 0 a2 b3 c3| |x3| |f3| * |0 0 0 a3 b4| |x4| |f4| */ public: /** * @return - vector that is storing x[] */ static QVector calculate(QList &a, QList &b, QList &c, QList &f) { QVector x; QVector alpha; QVector beta; int i; int size = b.size(); Q_ASSERT(a.size() == size - 1 && c.size() == size - 1 && f.size() == size); x.resize(size); /** * Check for special case when * order of the matrix is equal to 1 */ if (size == 1) { x[0] = f[0] / b[0]; return x; } /** * Common case */ alpha.resize(size); beta.resize(size); alpha[1] = -c[0] / b[0]; beta[1] = f[0] / b[0]; for (i = 1; i < size - 1; i++) { alpha[i+1] = -c[i] / (a[i-1] * alpha[i] + b[i]); beta[i+1] = (f[i] - a[i-1] * beta[i]) / (a[i-1] * alpha[i] + b[i]); } x.last() = (f.last() - a.last() * beta.last()) / (b.last() + a.last() * alpha.last()); for (i = size - 2; i >= 0; i--) x[i] = alpha[i+1] * x[i+1] + beta[i+1]; return x; } }; template class KisCubicSpline { /** * s[i](x)=a[i] + * b[i] * (x-x[i]) + * 1/2 * c[i] * (x-x[i])^2 + * 1/6 * d[i] * (x-x[i])^3 * * h[i]=x[i+1]-x[i] * */ protected: QList m_a; QVector m_b; QVector m_c; QVector m_d; QVector m_h; T m_begin; T m_end; int m_intervals; public: KisCubicSpline() {} KisCubicSpline(const QList &a) { createSpline(a); } /** * Create new spline and precalculate some values * for future * * @a - base points of the spline */ void createSpline(const QList &a) { int intervals = m_intervals = a.size() - 1; int i; m_begin = a.first().x(); m_end = a.last().x(); m_a.clear(); m_b.resize(intervals); m_c.clear(); m_d.resize(intervals); m_h.resize(intervals); for (i = 0; i < intervals; i++) { m_h[i] = a[i+1].x() - a[i].x(); m_a.append(a[i].y()); } m_a.append(a.last().y()); QList tri_b; QList tri_f; QList tri_a; /* equals to @tri_c */ for (i = 0; i < intervals - 1; i++) { tri_b.append(2.*(m_h[i] + m_h[i+1])); tri_f.append(6.*((m_a[i+2] - m_a[i+1]) / m_h[i+1] - (m_a[i+1] - m_a[i]) / m_h[i])); } for (i = 1; i < intervals - 1; i++) tri_a.append(m_h[i]); if (intervals > 1) { m_c = KisTridiagonalSystem::calculate(tri_a, tri_b, tri_a, tri_f); } m_c.prepend(0); m_c.append(0); for (i = 0; i < intervals; i++) m_d[i] = (m_c[i+1] - m_c[i]) / m_h[i]; for (i = 0; i < intervals; i++) m_b[i] = -0.5 * (m_c[i] * m_h[i]) - (1 / 6.0) * (m_d[i] * m_h[i] * m_h[i]) + (m_a[i+1] - m_a[i]) / m_h[i]; } /** * Get value of precalculated spline in the point @x */ T getValue(T x) const { T x0; int i = findRegion(x, x0); /* TODO: check for asm equivalent */ return m_a[i] + m_b[i] *(x - x0) + 0.5 * m_c[i] *(x - x0) *(x - x0) + (1 / 6.0)* m_d[i] *(x - x0) *(x - x0) *(x - x0); } T begin() const { return m_begin; } T end() const { return m_end; } protected: /** * findRegion - Searches for the region containing @x * @x0 - out parameter, containing beginning of the region * @return - index of the region */ int findRegion(T x, T &x0) const { int i; x0 = m_begin; for (i = 0; i < m_intervals; i++) { if (x >= x0 && x < x0 + m_h[i]) return i; x0 += m_h[i]; } if (x >= x0) { x0 -= m_h[m_intervals-1]; return m_intervals - 1; } qDebug("X value: %f\n", x); qDebug("m_begin: %f\n", m_begin); qDebug("m_end : %f\n", m_end); Q_ASSERT_X(0, "findRegion", "X value is outside regions"); /* **never reached** */ return -1; } }; static bool pointLessThan(const QPointF &a, const QPointF &b) { return a.x() < b.x(); } struct Q_DECL_HIDDEN KisCubicCurve::Data : public QSharedData { Data() { init(); } Data(const Data& data) : QSharedData() { init(); points = data.points; name = data.name; } void init() { validSpline = false; validU16Transfer = false; validFTransfer = false; } ~Data() { } mutable QString name; mutable KisCubicSpline spline; QList points; mutable bool validSpline; mutable QVector u8Transfer; mutable bool validU8Transfer; mutable QVector u16Transfer; mutable bool validU16Transfer; mutable QVector fTransfer; mutable bool validFTransfer; void updateSpline(); void keepSorted(); qreal value(qreal x); void invalidate(); template void updateTransfer(QVector<_T_>* transfer, bool& valid, _T2_ min, _T2_ max, int size); }; void KisCubicCurve::Data::updateSpline() { if (validSpline) return; validSpline = true; spline.createSpline(points); } void KisCubicCurve::Data::invalidate() { validSpline = false; validFTransfer = false; validU16Transfer = false; } void KisCubicCurve::Data::keepSorted() { std::sort(points.begin(), points.end(), pointLessThan); } qreal KisCubicCurve::Data::value(qreal x) { updateSpline(); /* Automatically extend non-existing parts of the curve * (e.g. before the first point) and cut off big y-values */ x = qBound(spline.begin(), x, spline.end()); qreal y = spline.getValue(x); return qBound(qreal(0.0), y, qreal(1.0)); } template void KisCubicCurve::Data::updateTransfer(QVector<_T_>* transfer, bool& valid, _T2_ min, _T2_ max, int size) { if (!valid || transfer->size() != size) { if (transfer->size() != size) { transfer->resize(size); } qreal end = 1.0 / (size - 1); for (int i = 0; i < size; ++i) { /* Direct uncached version */ _T2_ val = value(i * end ) * max; val = qBound(min, val, max); (*transfer)[i] = val; } valid = true; } } struct Q_DECL_HIDDEN KisCubicCurve::Private { QSharedDataPointer data; }; KisCubicCurve::KisCubicCurve() : d(new Private) { d->data = new Data; QPointF p; p.rx() = 0.0; p.ry() = 0.0; d->data->points.append(p); p.rx() = 1.0; p.ry() = 1.0; d->data->points.append(p); } KisCubicCurve::KisCubicCurve(const QList& points) : d(new Private) { d->data = new Data; d->data->points = points; d->data->keepSorted(); } KisCubicCurve::KisCubicCurve(const KisCubicCurve& curve) : d(new Private(*curve.d)) { } KisCubicCurve::~KisCubicCurve() { delete d; } KisCubicCurve& KisCubicCurve::operator=(const KisCubicCurve & curve) { if (&curve != this) { *d = *curve.d; } return *this; } bool KisCubicCurve::operator==(const KisCubicCurve& curve) const { if (d->data == curve.d->data) return true; return d->data->points == curve.d->data->points; } qreal KisCubicCurve::value(qreal x) const { qreal value = d->data->value(x); return value; } QList KisCubicCurve::points() const { return d->data->points; } void KisCubicCurve::setPoints(const QList& points) { d->data.detach(); d->data->points = points; d->data->invalidate(); } void KisCubicCurve::setPoint(int idx, const QPointF& point) { d->data.detach(); d->data->points[idx] = point; d->data->keepSorted(); d->data->invalidate(); } int KisCubicCurve::addPoint(const QPointF& point) { d->data.detach(); d->data->points.append(point); d->data->keepSorted(); d->data->invalidate(); return d->data->points.indexOf(point); } void KisCubicCurve::removePoint(int idx) { d->data.detach(); d->data->points.removeAt(idx); d->data->invalidate(); } -bool KisCubicCurve::isNull() const +bool KisCubicCurve::isIdentity() const { const QList &points = d->data->points; Q_FOREACH (const QPointF &pt, points) { if (!qFuzzyCompare(pt.x(), pt.y())) { return false; } } return true; } +bool KisCubicCurve::isConstant(qreal c) const +{ + const QList &points = d->data->points; + + Q_FOREACH (const QPointF &pt, points) { + if (!qFuzzyCompare(c, pt.y())) { + return false; + } + } + + return true; +} + const QString& KisCubicCurve::name() const { return d->data->name; } qreal KisCubicCurve::interpolateLinear(qreal normalizedValue, const QVector &transfer) { const qreal maxValue = transfer.size() - 1; const qreal bilinearX = qBound(0.0, maxValue * normalizedValue, maxValue); const qreal xFloored = std::floor(bilinearX); const qreal xCeiled = std::ceil(bilinearX); const qreal t = bilinearX - xFloored; constexpr qreal eps = 1e-6; qreal newValue = normalizedValue; if (t < eps) { newValue = transfer[int(xFloored)]; } else if (t > (1.0 - eps)) { newValue = transfer[int(xCeiled)]; } else { qreal a = transfer[int(xFloored)]; qreal b = transfer[int(xCeiled)]; newValue = a + t * (b - a); } return KisAlgebra2D::copysign(newValue, normalizedValue); } void KisCubicCurve::setName(const QString& name) { d->data->name = name; } QString KisCubicCurve::toString() const { QString sCurve; if(d->data->points.count() < 1) return sCurve; Q_FOREACH (const QPointF & pair, d->data->points) { sCurve += QString::number(pair.x()); sCurve += ','; sCurve += QString::number(pair.y()); sCurve += ';'; } return sCurve; } void KisCubicCurve::fromString(const QString& string) { QStringList data = string.split(';'); QList points; Q_FOREACH (const QString & pair, data) { if (pair.indexOf(',') > -1) { QPointF p; p.rx() = KisDomUtils::toDouble(pair.section(',', 0, 0)); p.ry() = KisDomUtils::toDouble(pair.section(',', 1, 1)); points.append(p); } } setPoints(points); } const QVector KisCubicCurve::uint16Transfer(int size) const { d->data->updateTransfer(&d->data->u16Transfer, d->data->validU16Transfer, 0x0, 0xFFFF, size); return d->data->u16Transfer; } const QVector KisCubicCurve::floatTransfer(int size) const { d->data->updateTransfer(&d->data->fTransfer, d->data->validFTransfer, 0.0, 1.0, size); return d->data->fTransfer; } diff --git a/libs/image/kis_cubic_curve.h b/libs/image/kis_cubic_curve.h index 5e763d0e94..9184fde4ee 100644 --- a/libs/image/kis_cubic_curve.h +++ b/libs/image/kis_cubic_curve.h @@ -1,83 +1,91 @@ /* * Copyright (c) 2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_CUBIC_CURVE_H_ #define _KIS_CUBIC_CURVE_H_ #include #include #include #include class QPointF; const QString DEFAULT_CURVE_STRING = "0,0;1,1;"; /** * Hold the data for a cubic curve. */ class KRITAIMAGE_EXPORT KisCubicCurve { public: KisCubicCurve(); KisCubicCurve(const QList& points); KisCubicCurve(const QVector& points); KisCubicCurve(const KisCubicCurve& curve); ~KisCubicCurve(); KisCubicCurve& operator=(const KisCubicCurve& curve); bool operator==(const KisCubicCurve& curve) const; public: qreal value(qreal x) const; QList points() const; void setPoints(const QList& points); void setPoint(int idx, const QPointF& point); /** * Add a point to the curve, the list of point is always sorted. * @return the index of the inserted point */ int addPoint(const QPointF& point); void removePoint(int idx); - bool isNull() const; + /* + * Check whether the curve maps all values to themselves. + */ + bool isIdentity() const; + + /* + * Check whether the curve maps all values to given constant. + */ + bool isConstant(qreal c) const; /** * This allows us to carry around a display name for the curve internally. It is used * currently in Sketch for perchannel, but would potentially be useful anywhere * curves are used in the UI */ void setName(const QString& name); const QString& name() const; static qreal interpolateLinear(qreal normalizedValue, const QVector &transfer); public: const QVector uint16Transfer(int size = 256) const; const QVector floatTransfer(int size = 256) const; public: QString toString() const; void fromString(const QString&); private: struct Data; struct Private; Private* const d; }; Q_DECLARE_METATYPE(KisCubicCurve) #endif diff --git a/libs/image/tests/kis_cubic_curve_test.cpp b/libs/image/tests/kis_cubic_curve_test.cpp index f33bce18fd..f9944ae33f 100644 --- a/libs/image/tests/kis_cubic_curve_test.cpp +++ b/libs/image/tests/kis_cubic_curve_test.cpp @@ -1,187 +1,187 @@ /* * Copyright (c) 2010 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_cubic_curve_test.h" #include #include "kis_cubic_curve.h" KisCubicCurveTest::KisCubicCurveTest() : pt0(0, 0), pt1(0.1, 0.0), pt2(0.5, 0.7), pt3(0.8, 0.6), pt4(0.9, 1.0), pt5(1, 1) { } void KisCubicCurveTest::testCreation() { KisCubicCurve cc1; QCOMPARE(cc1.points()[0], pt0); QCOMPARE(cc1.points()[1], pt5); QList pts; pts.push_back(pt1); pts.push_back(pt3); pts.push_back(pt2); KisCubicCurve cc2(pts); QCOMPARE(cc2.points()[0], pt1); QCOMPARE(cc2.points()[1], pt2); QCOMPARE(cc2.points()[2], pt3); } void KisCubicCurveTest::testCopy() { QList pts; pts.push_back(pt1); pts.push_back(pt2); pts.push_back(pt4); KisCubicCurve cc1(pts); KisCubicCurve cc2(cc1); QVERIFY(cc1 == cc2); QCOMPARE(cc1.points()[0], cc2.points()[0]); QCOMPARE(cc1.points()[1], cc2.points()[1]); QCOMPARE(cc1.points()[2], cc2.points()[2]); cc2.addPoint(pt3); QCOMPARE(cc1.points().size(), 3); QCOMPARE(cc2.points().size(), 4); QCOMPARE(cc1.points()[0], cc2.points()[0]); QCOMPARE(cc1.points()[1], cc2.points()[1]); QCOMPARE(cc1.points()[2], cc2.points()[3]); QCOMPARE(pt3, cc2.points()[2]); } void KisCubicCurveTest::testEdition() { KisCubicCurve cc1; cc1.addPoint(pt3); QCOMPARE(cc1.points().size(), 3); QCOMPARE(cc1.points()[0], pt0); QCOMPARE(cc1.points()[1], pt3); QCOMPARE(cc1.points()[2], pt5); cc1.setPoint(0, pt4); QCOMPARE(cc1.points().size(), 3); QCOMPARE(cc1.points()[0], pt3); QCOMPARE(cc1.points()[1], pt4); QCOMPARE(cc1.points()[2], pt5); int pos = cc1.addPoint(pt2); QCOMPARE(pos, 0); QCOMPARE(cc1.points().size(), 4); QCOMPARE(cc1.points()[0], pt2); QCOMPARE(cc1.points()[1], pt3); QCOMPARE(cc1.points()[2], pt4); QCOMPARE(cc1.points()[3], pt5); cc1.removePoint(2); QCOMPARE(cc1.points().size(), 3); QCOMPARE(cc1.points()[0], pt2); QCOMPARE(cc1.points()[1], pt3); QCOMPARE(cc1.points()[2], pt5); } void KisCubicCurveTest::testComparison() { QList pts; pts.push_back(pt1); pts.push_back(pt2); pts.push_back(pt4); KisCubicCurve cc1(pts); KisCubicCurve cc2(pts); QVERIFY(cc1 == cc2); cc2.setPoint(0, pt1); QVERIFY(cc1 == cc2); cc2.removePoint(2); QVERIFY(!(cc1 == cc2)); cc2.addPoint(pt4); QVERIFY(cc1 == cc2); cc2.addPoint(pt5); QVERIFY(!(cc1 == cc2)); QList pts2; pts2.push_back(pt1); pts2.push_back(pt2); pts2.push_back(pt3); KisCubicCurve cc3(pts2); QVERIFY(!(cc1 == cc3)); } void KisCubicCurveTest::testSerialization() { QList pts; pts.push_back(pt1); pts.push_back(pt2); pts.push_back(pt4); KisCubicCurve cc1(pts); QString s = cc1.toString(); QCOMPARE(s, QString("0.1,0;0.5,0.7;0.9,1;")); KisCubicCurve cc2; cc2.fromString(s); QVERIFY(cc1 == cc2); } void KisCubicCurveTest::testValue() { KisCubicCurve cc; for(int i = 0; i < 256; ++i) { qreal x = i/255.0; QCOMPARE(cc.value(x), x); } } void KisCubicCurveTest::testNull() { KisCubicCurve cc; - QVERIFY(cc.isNull()); + QVERIFY(cc.isIdentity()); cc.addPoint(QPointF(0.2, 0.3)); - QVERIFY(!cc.isNull()); + QVERIFY(!cc.isIdentity()); QList points; points << QPointF(); points << QPointF(0.2,0.2); points << QPointF(1.0,1.0); cc.setPoints(points); - QVERIFY(cc.isNull()); + QVERIFY(cc.isIdentity()); } void KisCubicCurveTest::testTransfer() { KisCubicCurve cc; QCOMPARE(cc.uint16Transfer().size(), 256); qreal denom = 1 / 255.0; for(int i = 0; i < 256; ++i) { QCOMPARE(cc.uint16Transfer()[i], quint16( cc.value(i * denom) * 0xFFFF) ); } QCOMPARE(cc.floatTransfer().size(), 256); for(int i = 0; i < 256; ++i) { QCOMPARE(cc.floatTransfer()[i], i * denom); } QCOMPARE(cc.uint16Transfer(1024).size(), 1024); denom = 1 / 1023.0; for(int i = 0; i < 1024; ++i) { QCOMPARE(cc.uint16Transfer(1024)[i], quint16( cc.value(i * denom) * 0xFFFF) ); } QCOMPARE(cc.floatTransfer(1024).size(), 1024); for(int i = 0; i < 1024; ++i) { QCOMPARE(cc.floatTransfer(1024)[i], i * denom); } } QTEST_MAIN(KisCubicCurveTest) diff --git a/libs/ui/widgets/kis_curve_widget.cpp b/libs/ui/widgets/kis_curve_widget.cpp index fe9c2afbe1..039cf899d2 100644 --- a/libs/ui/widgets/kis_curve_widget.cpp +++ b/libs/ui/widgets/kis_curve_widget.cpp @@ -1,515 +1,516 @@ /* * Copyright (c) 2005 C. Boemann * Copyright (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // C++ includes. #include #include // Qt includes. #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes. #include #include #include // Local includes. #include "widgets/kis_curve_widget.h" #define bounds(x,a,b) (xb ? b :x)) #define MOUSE_AWAY_THRES 15 #define POINT_AREA 1E-4 #define CURVE_AREA 1E-4 #include "kis_curve_widget_p.h" KisCurveWidget::KisCurveWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f), d(new KisCurveWidget::Private(this)) { setObjectName("KisCurveWidget"); d->m_grab_point_index = -1; d->m_readOnlyMode = false; d->m_guideVisible = false; d->m_pixmapDirty = true; d->m_pixmapCache = 0; d->setState(ST_NORMAL); d->m_intIn = 0; d->m_intOut = 0; setMouseTracking(true); setAutoFillBackground(false); setAttribute(Qt::WA_OpaquePaintEvent); setMinimumSize(150, 50); setMaximumSize(250, 250); d->setCurveModified(); setFocusPolicy(Qt::StrongFocus); } KisCurveWidget::~KisCurveWidget() { delete d->m_pixmapCache; delete d; } -void KisCurveWidget::setupInOutControls(QSpinBox *in, QSpinBox *out, int min, int max) +void KisCurveWidget::setupInOutControls(QSpinBox *in, QSpinBox *out, int inMin, int inMax, int outMin, int outMax) { d->m_intIn = in; d->m_intOut = out; if (!d->m_intIn || !d->m_intOut) return; - d->m_inOutMin = min; - d->m_inOutMax = max; - - d->m_intIn->setRange(d->m_inOutMin, d->m_inOutMax); - d->m_intOut->setRange(d->m_inOutMin, d->m_inOutMax); + d->m_inMin = inMin; + d->m_inMax = inMax; + d->m_outMin = outMin; + d->m_outMax = outMax; + d->m_intIn->setRange(d->m_inMin, d->m_inMax); + d->m_intOut->setRange(d->m_outMin, d->m_outMax); connect(d->m_intIn, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int))); connect(d->m_intOut, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int))); d->syncIOControls(); } void KisCurveWidget::dropInOutControls() { if (!d->m_intIn || !d->m_intOut) return; disconnect(d->m_intIn, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int))); disconnect(d->m_intOut, SIGNAL(valueChanged(int)), this, SLOT(inOutChanged(int))); d->m_intIn = d->m_intOut = 0; } void KisCurveWidget::inOutChanged(int) { QPointF pt; Q_ASSERT(d->m_grab_point_index >= 0); - pt.setX(d->io2sp(d->m_intIn->value())); - pt.setY(d->io2sp(d->m_intOut->value())); + pt.setX(d->io2sp(d->m_intIn->value(), d->m_inMin, d->m_inMax)); + pt.setY(d->io2sp(d->m_intOut->value(), d->m_outMin, d->m_outMax)); if (d->jumpOverExistingPoints(pt, d->m_grab_point_index)) { d->m_curve.setPoint(d->m_grab_point_index, pt); d->m_grab_point_index = d->m_curve.points().indexOf(pt); emit pointSelectedChanged(); } else pt = d->m_curve.points()[d->m_grab_point_index]; d->m_intIn->blockSignals(true); d->m_intOut->blockSignals(true); - d->m_intIn->setValue(d->sp2io(pt.x())); - d->m_intOut->setValue(d->sp2io(pt.y())); + d->m_intIn->setValue(d->sp2io(pt.x(), d->m_inMin, d->m_inMax)); + d->m_intOut->setValue(d->sp2io(pt.y(), d->m_outMin, d->m_outMax)); d->m_intIn->blockSignals(false); d->m_intOut->blockSignals(false); d->setCurveModified(); } void KisCurveWidget::reset(void) { d->m_grab_point_index = -1; emit pointSelectedChanged(); d->m_guideVisible = false; //remove total - 2 points. while (d->m_curve.points().count() - 2 ) { d->m_curve.removePoint(d->m_curve.points().count() - 2); } d->setCurveModified(); } void KisCurveWidget::setCurveGuide(const QColor & color) { d->m_guideVisible = true; d->m_colorGuide = color; } void KisCurveWidget::setPixmap(const QPixmap & pix) { d->m_pix = pix; d->m_pixmapDirty = true; d->setCurveRepaint(); } QPixmap KisCurveWidget::getPixmap() { return d->m_pix; } void KisCurveWidget::setBasePixmap(const QPixmap &pix) { d->m_pixmapBase = pix; } QPixmap KisCurveWidget::getBasePixmap() { return d->m_pixmapBase; } bool KisCurveWidget::pointSelected() const { return d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1; } void KisCurveWidget::keyPressEvent(QKeyEvent *e) { if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace) { if (d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1) { //x() find closest point to get focus afterwards double grab_point_x = d->m_curve.points()[d->m_grab_point_index].x(); int left_of_grab_point_index = d->m_grab_point_index - 1; int right_of_grab_point_index = d->m_grab_point_index + 1; int new_grab_point_index; if (fabs(d->m_curve.points()[left_of_grab_point_index].x() - grab_point_x) < fabs(d->m_curve.points()[right_of_grab_point_index].x() - grab_point_x)) { new_grab_point_index = left_of_grab_point_index; } else { new_grab_point_index = d->m_grab_point_index; } d->m_curve.removePoint(d->m_grab_point_index); d->m_grab_point_index = new_grab_point_index; emit pointSelectedChanged(); setCursor(Qt::ArrowCursor); d->setState(ST_NORMAL); } e->accept(); d->setCurveModified(); } else if (e->key() == Qt::Key_Escape && d->state() != ST_NORMAL) { d->m_curve.setPoint(d->m_grab_point_index, QPointF(d->m_grabOriginalX, d->m_grabOriginalY) ); setCursor(Qt::ArrowCursor); d->setState(ST_NORMAL); e->accept(); d->setCurveModified(); } else if ((e->key() == Qt::Key_A || e->key() == Qt::Key_Insert) && d->state() == ST_NORMAL) { /* FIXME: Lets user choose the hotkeys */ addPointInTheMiddle(); e->accept(); } else QWidget::keyPressEvent(e); } void KisCurveWidget::addPointInTheMiddle() { QPointF pt(0.5, d->m_curve.value(0.5)); if (!d->jumpOverExistingPoints(pt, -1)) return; d->m_grab_point_index = d->m_curve.addPoint(pt); emit pointSelectedChanged(); if (d->m_intIn) d->m_intIn->setFocus(Qt::TabFocusReason); d->setCurveModified(); } void KisCurveWidget::resizeEvent(QResizeEvent *e) { d->m_pixmapDirty = true; QWidget::resizeEvent(e); } void KisCurveWidget::paintEvent(QPaintEvent *) { int wWidth = width() - 1; int wHeight = height() - 1; QPainter p(this); // Antialiasing is not a good idea here, because // the grid will drift one pixel to any side due to rounding of int // FIXME: let's user tell the last word (in config) //p.setRenderHint(QPainter::Antialiasing); QPalette appPalette = QApplication::palette(); p.fillRect(rect(), appPalette.color(QPalette::Base)); // clear out previous paint call results // make the entire widget greyed out if it is disabled if (!this->isEnabled()) { p.setOpacity(0.2); } // draw background if (!d->m_pix.isNull()) { if (d->m_pixmapDirty || !d->m_pixmapCache) { delete d->m_pixmapCache; d->m_pixmapCache = new QPixmap(width(), height()); QPainter cachePainter(d->m_pixmapCache); cachePainter.scale(1.0*width() / d->m_pix.width(), 1.0*height() / d->m_pix.height()); cachePainter.drawPixmap(0, 0, d->m_pix); d->m_pixmapDirty = false; } p.drawPixmap(0, 0, *d->m_pixmapCache); } d->drawGrid(p, wWidth, wHeight); KisConfig cfg; if (cfg.antialiasCurves()) p.setRenderHint(QPainter::Antialiasing); // Draw curve. double curY; double normalizedX; int x; QPolygonF poly; p.setPen(QPen(appPalette.color(QPalette::Text), 2, Qt::SolidLine)); for (x = 0 ; x < wWidth ; x++) { normalizedX = double(x) / wWidth; curY = wHeight - d->m_curve.value(normalizedX) * wHeight; /** * Keep in mind that QLineF rounds doubles * to ints mathematically, not just rounds down * like in C */ poly.append(QPointF(x, curY)); } poly.append(QPointF(x, wHeight - d->m_curve.value(1.0) * wHeight)); p.drawPolyline(poly); QPainterPath fillCurvePath; QPolygonF fillPoly = poly; fillPoly.append(QPoint(rect().width(), rect().height())); fillPoly.append(QPointF(0,rect().height())); // add a couple points to the edges so it fills in below always QColor fillColor = appPalette.color(QPalette::Text); fillColor.setAlphaF(0.2); fillCurvePath.addPolygon(fillPoly); p.fillPath(fillCurvePath, fillColor); // Drawing curve handles. double curveX; double curveY; if (!d->m_readOnlyMode) { for (int i = 0; i < d->m_curve.points().count(); ++i) { curveX = d->m_curve.points().at(i).x(); curveY = d->m_curve.points().at(i).y(); if (i == d->m_grab_point_index) { p.setPen(QPen(appPalette.color(QPalette::Text), 6, Qt::SolidLine)); p.drawEllipse(QRectF(curveX * wWidth - 2, wHeight - 2 - curveY * wHeight, 4, 4)); } else { p.setPen(QPen(appPalette.color(QPalette::Text), 2, Qt::SolidLine)); p.drawEllipse(QRectF(curveX * wWidth - 3, wHeight - 3 - curveY * wHeight, 6, 6)); } } } // add border around widget to help contain everything QPainterPath widgetBoundsPath; widgetBoundsPath.addRect(rect()); p.strokePath(widgetBoundsPath, appPalette.color(QPalette::Text)); p.setOpacity(1.0); // reset to 1.0 in case we were drawing a disabled widget before } void KisCurveWidget::mousePressEvent(QMouseEvent * e) { if (d->m_readOnlyMode) return; if (e->button() != Qt::LeftButton) return; double x = e->pos().x() / (double)(width() - 1); double y = 1.0 - e->pos().y() / (double)(height() - 1); int closest_point_index = d->nearestPointInRange(QPointF(x, y), width(), height()); if (closest_point_index < 0) { QPointF newPoint(x, y); if (!d->jumpOverExistingPoints(newPoint, -1)) return; d->m_grab_point_index = d->m_curve.addPoint(newPoint); emit pointSelectedChanged(); } else { d->m_grab_point_index = closest_point_index; emit pointSelectedChanged(); } d->m_grabOriginalX = d->m_curve.points()[d->m_grab_point_index].x(); d->m_grabOriginalY = d->m_curve.points()[d->m_grab_point_index].y(); d->m_grabOffsetX = d->m_curve.points()[d->m_grab_point_index].x() - x; d->m_grabOffsetY = d->m_curve.points()[d->m_grab_point_index].y() - y; d->m_curve.setPoint(d->m_grab_point_index, QPointF(x + d->m_grabOffsetX, y + d->m_grabOffsetY)); d->m_draggedAwayPointIndex = -1; d->setState(ST_DRAG); d->setCurveModified(); } void KisCurveWidget::mouseReleaseEvent(QMouseEvent *e) { if (d->m_readOnlyMode) return; if (e->button() != Qt::LeftButton) return; setCursor(Qt::ArrowCursor); d->setState(ST_NORMAL); d->setCurveModified(); } void KisCurveWidget::mouseMoveEvent(QMouseEvent * e) { if (d->m_readOnlyMode) return; double x = e->pos().x() / (double)(width() - 1); double y = 1.0 - e->pos().y() / (double)(height() - 1); if (d->state() == ST_NORMAL) { // If no point is selected set the cursor shape if on top int nearestPointIndex = d->nearestPointInRange(QPointF(x, y), width(), height()); if (nearestPointIndex < 0) setCursor(Qt::ArrowCursor); else setCursor(Qt::CrossCursor); } else { // Else, drag the selected point bool crossedHoriz = e->pos().x() - width() > MOUSE_AWAY_THRES || e->pos().x() < -MOUSE_AWAY_THRES; bool crossedVert = e->pos().y() - height() > MOUSE_AWAY_THRES || e->pos().y() < -MOUSE_AWAY_THRES; bool removePoint = (crossedHoriz || crossedVert); if (!removePoint && d->m_draggedAwayPointIndex >= 0) { // point is no longer dragged away so reinsert it QPointF newPoint(d->m_draggedAwayPoint); d->m_grab_point_index = d->m_curve.addPoint(newPoint); d->m_draggedAwayPointIndex = -1; } if (removePoint && (d->m_draggedAwayPointIndex >= 0)) return; setCursor(Qt::CrossCursor); x += d->m_grabOffsetX; y += d->m_grabOffsetY; double leftX; double rightX; if (d->m_grab_point_index == 0) { leftX = 0.0; if (d->m_curve.points().count() > 1) rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA; else rightX = 1.0; } else if (d->m_grab_point_index == d->m_curve.points().count() - 1) { leftX = d->m_curve.points()[d->m_grab_point_index - 1].x() + POINT_AREA; rightX = 1.0; } else { Q_ASSERT(d->m_grab_point_index > 0 && d->m_grab_point_index < d->m_curve.points().count() - 1); // the 1E-4 addition so we can grab the dot later. leftX = d->m_curve.points()[d->m_grab_point_index - 1].x() + POINT_AREA; rightX = d->m_curve.points()[d->m_grab_point_index + 1].x() - POINT_AREA; } x = bounds(x, leftX, rightX); y = bounds(y, 0., 1.); d->m_curve.setPoint(d->m_grab_point_index, QPointF(x, y)); if (removePoint && d->m_curve.points().count() > 2) { d->m_draggedAwayPoint = d->m_curve.points()[d->m_grab_point_index]; d->m_draggedAwayPointIndex = d->m_grab_point_index; d->m_curve.removePoint(d->m_grab_point_index); d->m_grab_point_index = bounds(d->m_grab_point_index, 0, d->m_curve.points().count() - 1); emit pointSelectedChanged(); } d->setCurveModified(); } } KisCubicCurve KisCurveWidget::curve() { return d->m_curve; } void KisCurveWidget::setCurve(KisCubicCurve inlist) { d->m_curve = inlist; d->m_grab_point_index = qBound(0, d->m_grab_point_index, d->m_curve.points().count() - 1); emit pointSelectedChanged(); d->setCurveModified(); } void KisCurveWidget::leaveEvent(QEvent *) { } diff --git a/libs/ui/widgets/kis_curve_widget.h b/libs/ui/widgets/kis_curve_widget.h index 4ee4330fa2..a0301fbe78 100644 --- a/libs/ui/widgets/kis_curve_widget.h +++ b/libs/ui/widgets/kis_curve_widget.h @@ -1,160 +1,161 @@ /* * Copyright (c) 2005 C. Boemann * Copyright (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_CURVE_WIDGET_H #define KIS_CURVE_WIDGET_H // Qt includes. #include #include #include #include #include #include #include #include #include #include class QSpinBox; class KisCubicCurve; /** * KisCurveWidget is a widget that shows a single curve that can be edited * by the user. The user can grab the curve and move it; this creates * a new control point. Control points can be deleted by selecting a point * and pressing the delete key. * * (From: http://techbase.kde.org/Projects/Widgets_and_Classes#KisCurveWidget) * KisCurveWidget allows editing of spline based y=f(x) curves. Handy for cases * where you want the user to control such things as tablet pressure * response, color transformations, acceleration by time, aeroplane lift *by angle of attack. */ class KRITAUI_EXPORT KisCurveWidget : public QWidget { Q_OBJECT Q_PROPERTY(bool pointSelected READ pointSelected NOTIFY pointSelectedChanged); public: friend class CurveEditorItem; /** * Create a new curve widget with a default curve, that is a straight * line from bottom-left to top-right. */ KisCurveWidget(QWidget *parent = 0, Qt::WindowFlags f = 0); ~KisCurveWidget() override; /** * Reset the curve to the default shape */ void reset(void); /** * Enable the guide and set the guide color to the specified color. * * XXX: it seems that the guide feature isn't actually implemented yet? */ void setCurveGuide(const QColor & color); /** * Set a background pixmap. The background pixmap will be drawn under * the grid and the curve. * * XXX: or is the pixmap what is drawn to the left and bottom of the curve * itself? */ void setPixmap(const QPixmap & pix); QPixmap getPixmap(); void setBasePixmap(const QPixmap & pix); QPixmap getBasePixmap(); /** * Whether or not there is a point selected * This does NOT include the first and last points */ bool pointSelected() const; Q_SIGNALS: /** * Emitted whenever a control point has changed position. */ void modified(void); /** * Emitted whenever the status of whether a control point is selected or not changes */ void pointSelectedChanged(); protected Q_SLOTS: void inOutChanged(int); protected: void keyPressEvent(QKeyEvent *) override; void paintEvent(QPaintEvent *) override; void mousePressEvent(QMouseEvent * e) override; void mouseReleaseEvent(QMouseEvent * e) override; void mouseMoveEvent(QMouseEvent * e) override; void leaveEvent(QEvent *) override; void resizeEvent(QResizeEvent *e) override; public: /** * @return get a list with all defined points. If you want to know what the * y value for a given x is on the curve defined by these points, use getCurveValue(). * @see getCurveValue */ KisCubicCurve curve(); /** * Replace the current curve with a curve specified by the curve defined by the control * points in @param inlist. */ void setCurve(KisCubicCurve inlist); /** * Connect/disconnect external spinboxes to the curve - * @min/@max - is the range for their values + * @inMin/@inMax - is the range for input values + * @outMin/@outMax - is the range for output values */ - void setupInOutControls(QSpinBox *in, QSpinBox *out, int min, int max); + void setupInOutControls(QSpinBox *in, QSpinBox *out, int inMin, int inMax, int outMin, int outMax); void dropInOutControls(); /** * Handy function that creates new point in the middle * of the curve and sets focus on the m_intIn field, * so the user can move this point anywhere in a moment */ void addPointInTheMiddle(); private: class Private; Private * const d; }; #endif /* KIS_CURVE_WIDGET_H */ diff --git a/libs/ui/widgets/kis_curve_widget_p.h b/libs/ui/widgets/kis_curve_widget_p.h index 1288e5d91d..c31b452b7c 100644 --- a/libs/ui/widgets/kis_curve_widget_p.h +++ b/libs/ui/widgets/kis_curve_widget_p.h @@ -1,269 +1,271 @@ /* * Copyright (c) 2005 C. Boemann * Copyright (c) 2009 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_CURVE_WIDGET_P_H_ #define _KIS_CURVE_WIDGET_P_H_ #include #include #include enum enumState { ST_NORMAL, ST_DRAG }; /** * Private members for KisCurveWidget class */ class Q_DECL_HIDDEN KisCurveWidget::Private { KisCurveWidget *m_curveWidget; public: Private(KisCurveWidget *parent); /* Dragging variables */ int m_grab_point_index; double m_grabOffsetX; double m_grabOffsetY; double m_grabOriginalX; double m_grabOriginalY; QPointF m_draggedAwayPoint; int m_draggedAwayPointIndex; bool m_readOnlyMode; bool m_guideVisible; QColor m_colorGuide; /* The curve itself */ bool m_splineDirty; KisCubicCurve m_curve; QPixmap m_pix; QPixmap m_pixmapBase; bool m_pixmapDirty; QPixmap *m_pixmapCache; /* In/Out controls */ QSpinBox *m_intIn; QSpinBox *m_intOut; /* Working range of them */ - int m_inOutMin; - int m_inOutMax; + int m_inMin; + int m_inMax; + int m_outMin; + int m_outMax; /** * State functions. * At the moment used only for dragging. */ enumState m_state; inline void setState(enumState st); inline enumState state() const; /*** Internal routines ***/ /** * Common update routines */ void setCurveModified(); void setCurveRepaint(); /** * Convert working range of * In/Out controls to normalized * range of spline (and reverse) */ - double io2sp(int x); - int sp2io(double x); + double io2sp(int x, int min, int max); + int sp2io(double x, int min, int max); /** * Check whether newly created/moved point @pt doesn't overlap * with any of existing ones from @m_points and adjusts its coordinates. * @skipIndex is the index of the point, that shouldn't be taken * into account during the search * (e.g. because it's @pt itself) * * Returns false in case the point can't be placed anywhere * without overlapping */ bool jumpOverExistingPoints(QPointF &pt, int skipIndex); /** * Synchronize In/Out spinboxes with the curve */ void syncIOControls(); /** * Find the nearest point to @pt from m_points */ int nearestPointInRange(QPointF pt, int wWidth, int wHeight) const; /** * Nothing to be said! =) */ inline void drawGrid(QPainter &p, int wWidth, int wHeight); }; KisCurveWidget::Private::Private(KisCurveWidget *parent) { m_curveWidget = parent; } -double KisCurveWidget::Private::io2sp(int x) +double KisCurveWidget::Private::io2sp(int x, int min, int max) { - int rangeLen = m_inOutMax - m_inOutMin; - return double(x - m_inOutMin) / rangeLen; + int rangeLen = max - min; + return double(x - min) / rangeLen; } -int KisCurveWidget::Private::sp2io(double x) +int KisCurveWidget::Private::sp2io(double x, int min, int max) { - int rangeLen = m_inOutMax - m_inOutMin; - return int(x*rangeLen + 0.5) + m_inOutMin; + int rangeLen = max - min; + return int(x*rangeLen + 0.5) + min; } bool KisCurveWidget::Private::jumpOverExistingPoints(QPointF &pt, int skipIndex) { Q_FOREACH (const QPointF &it, m_curve.points()) { if (m_curve.points().indexOf(it) == skipIndex) continue; if (fabs(it.x() - pt.x()) < POINT_AREA) pt.rx() = pt.x() >= it.x() ? it.x() + POINT_AREA : it.x() - POINT_AREA; } return (pt.x() >= 0 && pt.x() <= 1.); } int KisCurveWidget::Private::nearestPointInRange(QPointF pt, int wWidth, int wHeight) const { double nearestDistanceSquared = 1000; int nearestIndex = -1; int i = 0; Q_FOREACH (const QPointF & point, m_curve.points()) { double distanceSquared = (pt.x() - point.x()) * (pt.x() - point.x()) + (pt.y() - point.y()) * (pt.y() - point.y()); if (distanceSquared < nearestDistanceSquared) { nearestIndex = i; nearestDistanceSquared = distanceSquared; } ++i; } if (nearestIndex >= 0) { if (fabs(pt.x() - m_curve.points()[nearestIndex].x()) *(wWidth - 1) < 5 && fabs(pt.y() - m_curve.points()[nearestIndex].y()) *(wHeight - 1) < 5) { return nearestIndex; } } return -1; } #define div2_round(x) (((x)+1)>>1) #define div4_round(x) (((x)+2)>>2) void KisCurveWidget::Private::drawGrid(QPainter &p, int wWidth, int wHeight) { /** * Hint: widget size should conform * formula 4n+5 to draw grid correctly * without curious shifts between * spline and it caused by rounding * * That is not mandatory but desirable */ QPalette appPalette = QApplication::palette(); p.setPen(QPen(appPalette.color(QPalette::Background), 1, Qt::SolidLine)); p.drawLine(div4_round(wWidth), 0, div4_round(wWidth), wHeight); p.drawLine(div2_round(wWidth), 0, div2_round(wWidth), wHeight); p.drawLine(div4_round(3*wWidth), 0, div4_round(3*wWidth), wHeight); p.drawLine(0, div4_round(wHeight), wWidth, div4_round(wHeight)); p.drawLine(0, div2_round(wHeight), wWidth, div2_round(wHeight)); p.drawLine(0, div4_round(3*wHeight), wWidth, div4_round(3*wHeight)); } void KisCurveWidget::Private::syncIOControls() { if (!m_intIn || !m_intOut) return; bool somethingSelected = (m_grab_point_index >= 0); m_intIn->setEnabled(somethingSelected); m_intOut->setEnabled(somethingSelected); if (m_grab_point_index >= 0) { m_intIn->blockSignals(true); m_intOut->blockSignals(true); - m_intIn->setValue(sp2io(m_curve.points()[m_grab_point_index].x())); - m_intOut->setValue(sp2io(m_curve.points()[m_grab_point_index].y())); + m_intIn->setValue(sp2io(m_curve.points()[m_grab_point_index].x(), m_inMin, m_inMax)); + m_intOut->setValue(sp2io(m_curve.points()[m_grab_point_index].y(), m_outMin, m_outMax)); m_intIn->blockSignals(false); m_intOut->blockSignals(false); } else { /*FIXME: Ideally, these controls should hide away now */ } } void KisCurveWidget::Private::setCurveModified() { syncIOControls(); m_splineDirty = true; m_curveWidget->update(); m_curveWidget->emit modified(); } void KisCurveWidget::Private::setCurveRepaint() { m_curveWidget->update(); } void KisCurveWidget::Private::setState(enumState st) { m_state = st; } enumState KisCurveWidget::Private::state() const { return m_state; } #endif /* _KIS_CURVE_WIDGET_P_H_ */ diff --git a/plugins/color/colorspaceextensions/extensions_plugin.cc b/plugins/color/colorspaceextensions/extensions_plugin.cc index ba962eb837..ee445b5742 100644 --- a/plugins/color/colorspaceextensions/extensions_plugin.cc +++ b/plugins/color/colorspaceextensions/extensions_plugin.cc @@ -1,60 +1,61 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; version 2 * of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "extensions_plugin.h" #include #include #include #include "kis_hsv_adjustment.h" #include "kis_dodgemidtones_adjustment.h" #include "kis_dodgehighlights_adjustment.h" #include "kis_dodgeshadows_adjustment.h" #include "kis_burnmidtones_adjustment.h" #include "kis_burnhighlights_adjustment.h" #include "kis_burnshadows_adjustment.h" #include "kis_color_balance_adjustment.h" #include "kis_desaturate_adjustment.h" K_PLUGIN_FACTORY_WITH_JSON(ExtensionsPluginFactory, "krita_colorspaces_extensions_plugin.json", registerPlugin();) ExtensionsPlugin::ExtensionsPlugin(QObject *parent, const QVariantList &) { Q_UNUSED(parent); KoColorTransformationFactoryRegistry::addColorTransformationFactory(new KisHSVAdjustmentFactory); + KoColorTransformationFactoryRegistry::addColorTransformationFactory(new KisHSVCurveAdjustmentFactory); KoColorTransformationFactoryRegistry::addColorTransformationFactory(new KisDodgeMidtonesAdjustmentFactory); KoColorTransformationFactoryRegistry::addColorTransformationFactory(new KisDodgeHighlightsAdjustmentFactory); KoColorTransformationFactoryRegistry::addColorTransformationFactory(new KisDodgeShadowsAdjustmentFactory); KoColorTransformationFactoryRegistry::addColorTransformationFactory(new KisBurnMidtonesAdjustmentFactory); KoColorTransformationFactoryRegistry::addColorTransformationFactory(new KisBurnHighlightsAdjustmentFactory); KoColorTransformationFactoryRegistry::addColorTransformationFactory(new KisBurnShadowsAdjustmentFactory); KoColorTransformationFactoryRegistry::addColorTransformationFactory(new KisColorBalanceAdjustmentFactory); KoColorTransformationFactoryRegistry::addColorTransformationFactory(new KisDesaturateAdjustmentFactory); } ExtensionsPlugin::~ExtensionsPlugin() { } #include "extensions_plugin.moc" diff --git a/plugins/color/colorspaceextensions/kis_hsv_adjustment.cpp b/plugins/color/colorspaceextensions/kis_hsv_adjustment.cpp index 4cb50fca9d..03e4083354 100644 --- a/plugins/color/colorspaceextensions/kis_hsv_adjustment.cpp +++ b/plugins/color/colorspaceextensions/kis_hsv_adjustment.cpp @@ -1,428 +1,676 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; version 2 * of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_hsv_adjustment.h" #include #ifdef HAVE_OPENEXR #include #endif +#include + #include #include #include #include #include #include #include #include #define SCALE_TO_FLOAT( v ) KoColorSpaceMaths< _channel_type_, float>::scaleToA( v ) #define SCALE_FROM_FLOAT( v ) KoColorSpaceMaths< float, _channel_type_>::scaleToA( v ) template void clamp(float* r, float* g, float* b); #define FLOAT_CLAMP( v ) *v = (*v < 0.0) ? 0.0 : ( (*v>1.0) ? 1.0 : *v ) template<> void clamp(float* r, float* g, float* b) { FLOAT_CLAMP(r); FLOAT_CLAMP(g); FLOAT_CLAMP(b); } template<> void clamp(float* r, float* g, float* b) { FLOAT_CLAMP(r); FLOAT_CLAMP(g); FLOAT_CLAMP(b); } #ifdef HAVE_OPENEXR template<> void clamp(float* r, float* g, float* b) { Q_UNUSED(r); Q_UNUSED(g); Q_UNUSED(b); } #endif template<> void clamp(float* r, float* g, float* b) { Q_UNUSED(r); Q_UNUSED(g); Q_UNUSED(b); } template class KisHSVAdjustment : public KoColorTransformation { typedef traits RGBTrait; typedef typename RGBTrait::Pixel RGBPixel; public: KisHSVAdjustment() : m_adj_h(0.0), m_adj_s(0.0), m_adj_v(0.0), m_lumaRed(0.0), m_lumaGreen(0.0), m_lumaBlue(0.0), m_type(0), m_colorize(false) { } public: void transform(const quint8 *srcU8, quint8 *dstU8, qint32 nPixels) const override { //if (m_model="RGBA" || m_colorize) { - /*It'd be nice to have LCH automatically selector for LAB in the future, but I don't know how to select LAB + /*It'd be nice to have LCH automatically selector for LAB in the future, but I don't know how to select LAB * */ const RGBPixel* src = reinterpret_cast(srcU8); RGBPixel* dst = reinterpret_cast(dstU8); float h, s, v; float r = 0.0; float g = 0.0; float b = 0.0; qreal lumaR, lumaG, lumaB; //Default to rec 709 when there's no coefficients given// if (m_lumaRed<=0 || m_lumaGreen<=0 || m_lumaBlue<=0) { lumaR = 0.2126; lumaG = 0.7152; lumaB = 0.0722; } else { lumaR = m_lumaRed; lumaG = m_lumaGreen; lumaB = m_lumaBlue; } while (nPixels > 0) { if (m_colorize) { h = m_adj_h * 360; if (h >= 360.0) h = 0; s = m_adj_s; r = SCALE_TO_FLOAT(src->red); g = SCALE_TO_FLOAT(src->green); b = SCALE_TO_FLOAT(src->blue); float luminance = r * lumaR + g * lumaG + b * lumaB; if (m_adj_v > 0) { luminance *= (1.0 - m_adj_v); luminance += 1.0 - (1.0 - m_adj_v); } else if (m_adj_v < 0 ){ luminance *= (m_adj_v + 1.0); } v = luminance; HSLToRGB(h, s, v, &r, &g, &b); } else { if (m_type == 0) { RGBToHSV(SCALE_TO_FLOAT(src->red), SCALE_TO_FLOAT(src->green), SCALE_TO_FLOAT(src->blue), &h, &s, &v); h += m_adj_h * 180; if (h > 360) h -= 360; if (h < 0) h += 360; s += m_adj_s; v += m_adj_v; HSVToRGB(h, s, v, &r, &g, &b); } else if (m_type == 1) { RGBToHSL(SCALE_TO_FLOAT(src->red), SCALE_TO_FLOAT(src->green), SCALE_TO_FLOAT(src->blue), &h, &s, &v); h += m_adj_h * 180; if (h > 360) h -= 360; if (h < 0) h += 360; s *= (m_adj_s + 1.0); if (s < 0.0) s = 0.0; if (s > 1.0) s = 1.0; if (m_adj_v < 0) v *= (m_adj_v + 1.0); else v += (m_adj_v * (1.0 - v)); HSLToRGB(h, s, v, &r, &g, &b); } else if (m_type == 2) { qreal red = SCALE_TO_FLOAT(src->red); qreal green = SCALE_TO_FLOAT(src->green); qreal blue = SCALE_TO_FLOAT(src->blue); qreal hue, sat, intensity; RGBToHCI(red, green, blue, &hue, &sat, &intensity); hue *=360.0; hue += m_adj_h * 180; //if (intensity+m_adj_v>1.0){hue+=180.0;} if (hue < 0) hue += 360; hue = fmod(hue, 360.0); sat *= (m_adj_s + 1.0); //sat = qBound(0.0, sat, 1.0); - + intensity += (m_adj_v); HCIToRGB(hue/360.0, sat, intensity, &red, &green, &blue); r = red; g = green; b = blue; } else if (m_type == 3) { qreal red = SCALE_TO_FLOAT(src->red); qreal green = SCALE_TO_FLOAT(src->green); qreal blue = SCALE_TO_FLOAT(src->blue); qreal hue, sat, luma; RGBToHCY(red, green, blue, &hue, &sat, &luma, lumaR, lumaG, lumaB); hue *=360.0; hue += m_adj_h * 180; //if (luma+m_adj_v>1.0){hue+=180.0;} if (hue < 0) hue += 360; hue = fmod(hue, 360.0); sat *= (m_adj_s + 1.0); //sat = qBound(0.0, sat, 1.0); luma += m_adj_v; HCYToRGB(hue/360.0, sat, luma, &red, &green, &blue, lumaR, lumaG, lumaB); r = red; g = green; b = blue; - + } else if (m_type == 4) { qreal red = SCALE_TO_FLOAT(src->red); qreal green = SCALE_TO_FLOAT(src->green); qreal blue = SCALE_TO_FLOAT(src->blue); qreal y, cb, cr; RGBToYUV(red, green, blue, &y, &cb, &cr, lumaR, lumaG, lumaB); cb *= (m_adj_h + 1.0); //cb = qBound(0.0, cb, 1.0); cr *= (m_adj_s + 1.0); //cr = qBound(0.0, cr, 1.0); y += (m_adj_v); YUVToRGB(y, cb, cr, &red, &green, &blue, lumaR, lumaG, lumaB); r = red; g = green; b = blue; } else { Q_ASSERT_X(false, "", "invalid type"); } } clamp< _channel_type_ >(&r, &g, &b); dst->red = SCALE_FROM_FLOAT(r); dst->green = SCALE_FROM_FLOAT(g); dst->blue = SCALE_FROM_FLOAT(b); dst->alpha = src->alpha; --nPixels; ++src; ++dst; } /*} else if (m_model="LABA"){ const LABPixel* src = reinterpret_cast(srcU8); LABPixel* dst = reinterpret_cast(dstU8); qreal lightness = SCALE_TO_FLOAT(src->L); qreal a = SCALE_TO_FLOAT(src->a); qreal b = SCALE_TO_FLOAT(src->b); qreal L, C, H; - + while (nPixels > 0) { if (m_type = 4) { a *= (m_adj_h + 1.0); a = qBound(0.0, a, 1.0); b *= (m_adj_s + 1.0); b = qBound(0.0, b, 1.0); if (m_adj_v < 0) lightness *= (m_adj_v + 1.0); else lightness += (m_adj_v * (1.0 - lightness)); } else {//lch LABToLCH(lightness, a, b, &L, &C, &H); H *=360; H += m_adj_h * 180; if (H > 360) h -= 360; if (H < 0) h += 360; C += m_adj_s; C = qBound(0.0,C,1.0); L += m_adj_v; L = qBound(0.0,L,1.0); LCHToLAB(L, C, H/360.0, &lightness, &a, &b); } clamp< _channel_type_ >(&lightness, &a, &b); dst->L = SCALE_FROM_FLOAT(lightness); dst->a = SCALE_FROM_FLOAT(a); dst->b = SCALE_FROM_FLOAT(b); dst->alpha = src->alpha; --nPixels; ++src; ++dst; } }*/ } QList parameters() const override { QList list; list << "h" << "s" << "v" << "type" << "colorize" << "lumaRed" << "lumaGreen"<< "lumaBlue"; return list; } int parameterId(const QString& name) const override { if (name == "h") { return 0; } else if (name == "s") { return 1; } else if (name == "v") { return 2; } else if (name == "type") { return 3; } else if (name == "colorize") { return 4; } else if (name == "lumaRed") { return 5; } else if (name == "lumaGreen") { return 6; } else if (name == "lumaBlue") { return 7; } return -1; } - + /** * name - "h", "s" or "v" * (h)ue in range <-1.0, 1.0> ( for user, show as -180, 180 or 0, 360 for colorize) * (s)aturation in range <-1.0, 1.0> ( for user, show -100, 100, or 0, 100 for colorize) * (v)alue in range <-1.0, 1.0> (for user, show -100, 100) * type: 0:HSV, 1:HSL, 2:HSI, 3:HSY, 4:YUV * m_colorize: Use colorize formula instead * luma Red/Green/Blue: Used for luma calculations. */ void setParameter(int id, const QVariant& parameter) override { switch(id) { case 0: m_adj_h = parameter.toDouble(); break; case 1: m_adj_s = parameter.toDouble(); break; case 2: m_adj_v = parameter.toDouble(); break; case 3: m_type = parameter.toInt(); break; case 4: m_colorize = parameter.toBool(); break; case 5: m_lumaRed = parameter.toDouble(); break; case 6: m_lumaGreen = parameter.toDouble(); break; case 7: m_lumaBlue = parameter.toDouble(); break; default: KIS_ASSERT_RECOVER_NOOP(false && "Unknown parameter ID. Ignored!"); ; } } private: double m_adj_h, m_adj_s, m_adj_v; qreal m_lumaRed, m_lumaGreen, m_lumaBlue; int m_type; bool m_colorize; }; +template +class KisHSVCurveAdjustment : public KoColorTransformation +{ + typedef traits RGBTrait; + typedef typename RGBTrait::Pixel RGBPixel; + +public: + KisHSVCurveAdjustment() : + m_lumaRed(0.0), + m_lumaGreen(0.0), + m_lumaBlue(0.0) + {} + + QList parameters() const override + { + QList list; + list << "curve" << "channel" << "driverChannel" << "relative" << "lumaRed" << "lumaGreen"<< "lumaBlue"; + return list; + } + + int parameterId(const QString& name) const override + { + if (name == "curve") { + return PAR_CURVE; + } else if (name == "channel") { + return PAR_CHANNEL; + } else if (name == "driverChannel") { + return PAR_DRIVER_CHANNEL; + } else if (name == "relative") { + return PAR_RELATIVE; + } else if (name == "lumaRed") { + return PAR_LUMA_R; + } else if (name == "lumaGreen") { + return PAR_LUMA_G; + } else if (name == "lumaBlue") { + return PAR_LUMA_B; + } + return -1; + } + + /** + * curve: adjustment curve as QVector + * channel: which channel to adjust. See KisHSVCurve::ColorChannel. + * driverChannel: which channel to use as source for adjustments. + * relative: + * false: use curve for direct lookup. + * true: add adjustment to original. In this mode, the curve range is mapped to -1.0 to 1.0 + * luma Red/Green/Blue: Used for luma calculations. + */ + void setParameter(int id, const QVariant& parameter) override + { + switch(id) + { + case PAR_CURVE: + m_curve = parameter.value>(); + break; + case PAR_CHANNEL: + case PAR_DRIVER_CHANNEL: { + int channel = parameter.toInt(); + KIS_ASSERT_RECOVER_RETURN(0 <= channel && channel < KisHSVCurve::ChannelCount && "Invalid channel. Ignored!"); + + if (id == PAR_CHANNEL) { + m_channel = channel; + } else { + m_driverChannel = channel; + } + } break; + case PAR_RELATIVE: + m_relative = parameter.toBool(); + break; + case PAR_LUMA_R: + m_lumaRed = parameter.toDouble(); + break; + case PAR_LUMA_G: + m_lumaGreen = parameter.toDouble(); + break; + case PAR_LUMA_B: + m_lumaBlue = parameter.toDouble(); + break; + default: + KIS_ASSERT_RECOVER_NOOP(false && "Unknown parameter ID. Ignored!"); + } + } + + void transform(const quint8 *srcU8, quint8 *dstU8, qint32 nPixels) const override + { + const RGBPixel* src = reinterpret_cast(srcU8); + RGBPixel* dst = reinterpret_cast(dstU8); + float max = m_curve.size() - 1; + + int driverChannel = m_relative ? m_driverChannel : m_channel; + + float component[KisHSVCurve::ChannelCount]; + + // Aliases for convenience + float &h = component[KisHSVCurve::Hue]; + float &s = component[KisHSVCurve::Saturation]; + float &v = component[KisHSVCurve::Value]; + float &r = component[KisHSVCurve::Red]; + float &g = component[KisHSVCurve::Green]; + float &b = component[KisHSVCurve::Blue]; + float &a = component[KisHSVCurve::Alpha]; + + while (nPixels > 0) { + r = SCALE_TO_FLOAT(src->red); + g = SCALE_TO_FLOAT(src->green); + b = SCALE_TO_FLOAT(src->blue); + a = SCALE_TO_FLOAT(src->alpha); + + RGBToHSV(r, g, b, &h, &s, &v); + + // Normalize hue to 0.0 to 1.0 range + h /= 360.0f; + + float adjustment = lookupComponent(component[driverChannel], max); + + if (m_relative) { + // Curve uses range 0.0 to 1.0, but for adjustment we need -1.0 to 1.0 + adjustment = 2.0f * adjustment - 1.0f; + + if (m_channel == KisHSVCurve::AllColors) { + r += adjustment; + g += adjustment; + b += adjustment; + } else { + component[m_channel] += adjustment; + } + } else { + if (m_channel == KisHSVCurve::AllColors) { + r = b = g = adjustment; + } else { + component[m_channel] = adjustment; + } + } + + h *= 360.0f; + if (h > 360) h -= 360; + if (h < 0) h += 360; + + if (m_channel >= KisHSVCurve::Hue) { + HSVToRGB(h, s, v, &r, &g, &b); + } + + clamp< _channel_type_ >(&r, &g, &b); + FLOAT_CLAMP(&a); + + dst->red = SCALE_FROM_FLOAT(r); + dst->green = SCALE_FROM_FLOAT(g); + dst->blue = SCALE_FROM_FLOAT(b); + dst->alpha = SCALE_FROM_FLOAT(a); + + --nPixels; + ++src; + ++dst; + } + } + + const float SCALE_FROM_16BIT = 1.0f / 0xFFFF; + float lookupComponent(float x, float max) const + { + // No curve for this component? Pass through unmodified + if (max < 2) return x; + if (x < 0) return m_curve[0]; + + float lookup = x * max; + float base = floor(lookup); + float offset = lookup - base; + + if (base >= max) { + base = max - 1.0f; + offset = 1.0f; + } + int index = (int)base; + + return ((1.0f - offset) * m_curve[index] + + offset * m_curve[index + 1]) * SCALE_FROM_16BIT; + } + + +private: + enum ParameterID + { + PAR_CURVE, + PAR_CHANNEL, + PAR_DRIVER_CHANNEL, + PAR_RELATIVE, + PAR_LUMA_R, + PAR_LUMA_G, + PAR_LUMA_B, + }; + + QVector m_curve; + int m_channel = 0; + int m_driverChannel = 0; + bool m_relative = false; + + /* Note: the filter currently only supports HSV, so these are + * unused, but will be needed once HSL, etc. + */ + qreal m_lumaRed, m_lumaGreen, m_lumaBlue; +}; + KisHSVAdjustmentFactory::KisHSVAdjustmentFactory() : KoColorTransformationFactory("hsv_adjustment") { } QList< QPair< KoID, KoID > > KisHSVAdjustmentFactory::supportedModels() const { QList< QPair< KoID, KoID > > l; l.append(QPair< KoID, KoID >(RGBAColorModelID , Integer8BitsColorDepthID)); l.append(QPair< KoID, KoID >(RGBAColorModelID , Integer16BitsColorDepthID)); l.append(QPair< KoID, KoID >(RGBAColorModelID , Float16BitsColorDepthID)); l.append(QPair< KoID, KoID >(RGBAColorModelID , Float32BitsColorDepthID)); return l; } KoColorTransformation* KisHSVAdjustmentFactory::createTransformation(const KoColorSpace* colorSpace, QHash parameters) const { KoColorTransformation * adj; if (colorSpace->colorModelId() != RGBAColorModelID) { dbgKrita << "Unsupported color space " << colorSpace->id() << " in KisHSVAdjustmentFactory::createTransformation"; return 0; } if (colorSpace->colorDepthId() == Integer8BitsColorDepthID) { adj = new KisHSVAdjustment< quint8, KoBgrTraits < quint8 > >(); } else if (colorSpace->colorDepthId() == Integer16BitsColorDepthID) { adj = new KisHSVAdjustment< quint16, KoBgrTraits < quint16 > >(); } #ifdef HAVE_OPENEXR else if (colorSpace->colorDepthId() == Float16BitsColorDepthID) { adj = new KisHSVAdjustment< half, KoRgbTraits < half > >(); } #endif else if (colorSpace->colorDepthId() == Float32BitsColorDepthID) { adj = new KisHSVAdjustment< float, KoRgbTraits < float > >(); } else { dbgKrita << "Unsupported color space " << colorSpace->id() << " in KisHSVAdjustmentFactory::createTransformation"; return 0; } adj->setParameters(parameters); return adj; } + + +KisHSVCurveAdjustmentFactory::KisHSVCurveAdjustmentFactory() + : KoColorTransformationFactory("hsv_curve_adjustment") +{ +} + +QList< QPair< KoID, KoID > > KisHSVCurveAdjustmentFactory::supportedModels() const +{ + QList< QPair< KoID, KoID > > l; + l.append(QPair< KoID, KoID >(RGBAColorModelID , Integer8BitsColorDepthID)); + l.append(QPair< KoID, KoID >(RGBAColorModelID , Integer16BitsColorDepthID)); + l.append(QPair< KoID, KoID >(RGBAColorModelID , Float16BitsColorDepthID)); + l.append(QPair< KoID, KoID >(RGBAColorModelID , Float32BitsColorDepthID)); + return l; +} + +KoColorTransformation* KisHSVCurveAdjustmentFactory::createTransformation(const KoColorSpace* colorSpace, QHash parameters) const +{ + KoColorTransformation * adj; + if (colorSpace->colorModelId() != RGBAColorModelID) { + dbgKrita << "Unsupported color space " << colorSpace->id() << " in KisHSVCurveAdjustmentFactory::createTransformation"; + return 0; + } + if (colorSpace->colorDepthId() == Integer8BitsColorDepthID) { + adj = new KisHSVCurveAdjustment< quint8, KoBgrTraits < quint8 > >(); + } else if (colorSpace->colorDepthId() == Integer16BitsColorDepthID) { + adj = new KisHSVCurveAdjustment< quint16, KoBgrTraits < quint16 > >(); + } +#ifdef HAVE_OPENEXR + else if (colorSpace->colorDepthId() == Float16BitsColorDepthID) { + adj = new KisHSVCurveAdjustment< half, KoRgbTraits < half > >(); + } +#endif + else if (colorSpace->colorDepthId() == Float32BitsColorDepthID) { + adj = new KisHSVCurveAdjustment< float, KoRgbTraits < float > >(); + } else { + dbgKrita << "Unsupported color space " << colorSpace->id() << " in KisHSVCurveAdjustmentFactory::createTransformation"; + return 0; + } + adj->setParameters(parameters); + return adj; + +} diff --git a/plugins/color/colorspaceextensions/kis_hsv_adjustment.h b/plugins/color/colorspaceextensions/kis_hsv_adjustment.h index b46d4b01f7..561ce04df0 100644 --- a/plugins/color/colorspaceextensions/kis_hsv_adjustment.h +++ b/plugins/color/colorspaceextensions/kis_hsv_adjustment.h @@ -1,37 +1,63 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; version 2 * of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_HSV_ADJUSTMENT_H_ #define _KIS_HSV_ADJUSTMENT_H_ #include "KoColorTransformationFactory.h" class KisHSVAdjustmentFactory : public KoColorTransformationFactory { public: KisHSVAdjustmentFactory(); QList< QPair< KoID, KoID > > supportedModels() const override; KoColorTransformation* createTransformation(const KoColorSpace* colorSpace, QHash parameters) const override; }; +class KisHSVCurveAdjustmentFactory : public KoColorTransformationFactory +{ +public: + + KisHSVCurveAdjustmentFactory(); + + QList< QPair< KoID, KoID > > supportedModels() const override; + + KoColorTransformation* createTransformation(const KoColorSpace* colorSpace, QHash parameters) const override; + +}; + +namespace KisHSVCurve { + enum ColorChannel { + Red = 0, + Green = 1, + Blue = 2, + Alpha = 3, + AllColors = 4, + Hue = 5, + Saturation = 6, + Value = 7, + ChannelCount + }; +} + #endif diff --git a/plugins/extensions/pykrita/sip/krita/KisCubicCurve.sip b/plugins/extensions/pykrita/sip/krita/KisCubicCurve.sip index 08a5edd98c..1b3d956923 100644 --- a/plugins/extensions/pykrita/sip/krita/KisCubicCurve.sip +++ b/plugins/extensions/pykrita/sip/krita/KisCubicCurve.sip @@ -1,19 +1,20 @@ class KisCubicCurve { %TypeHeaderCode #include "kis_cubic_curve.h" %End public: KisCubicCurve(); qreal value(qreal x) const; QList points() const; void setPoints(const QList& points); void setPoint(int idx, const QPointF& point); int addPoint(const QPointF& point); void removePoint(int idx); - bool isNull() const; + bool isIdentity() const; + bool isConstant(qreal x) const; void setName(const QString& name); const QString& name() const; QString toString() const; void fromString(const QString&); }; diff --git a/plugins/filters/colorsfilters/CMakeLists.txt b/plugins/filters/colorsfilters/CMakeLists.txt index 90784342c7..2d5e7be9dd 100644 --- a/plugins/filters/colorsfilters/CMakeLists.txt +++ b/plugins/filters/colorsfilters/CMakeLists.txt @@ -1,19 +1,21 @@ set(kritacolorsfilters_SOURCES colorsfilters.cpp kis_hsv_adjustment_filter.cpp virtual_channel_info.cpp + kis_multichannel_filter_base.cpp kis_perchannel_filter.cpp + kis_cross_channel_filter.cpp kis_color_balance_filter.cpp kis_desaturate_filter.cpp ) ki18n_wrap_ui(kritacolorsfilters_SOURCES wdg_perchannel.ui wdg_color_balance.ui wdg_hsv_adjustment.ui wdg_desaturate.ui ) add_library(kritacolorsfilters MODULE ${kritacolorsfilters_SOURCES}) target_link_libraries(kritacolorsfilters kritaui) install(TARGETS kritacolorsfilters DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) diff --git a/plugins/filters/colorsfilters/colorsfilters.cpp b/plugins/filters/colorsfilters/colorsfilters.cpp index 1091a1c697..51635d0ed3 100644 --- a/plugins/filters/colorsfilters/colorsfilters.cpp +++ b/plugins/filters/colorsfilters/colorsfilters.cpp @@ -1,173 +1,175 @@ /* * This file is part of Krita * * Copyright (c) 2004 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "colorsfilters.h" #include #include #include #include #include #include #include #include #include #include "KoBasicHistogramProducers.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_hsv_adjustment_filter.h" #include "kis_perchannel_filter.h" +#include "kis_cross_channel_filter.h" #include "kis_color_balance_filter.h" #include "kis_desaturate_filter.h" K_PLUGIN_FACTORY_WITH_JSON(ColorsFiltersFactory, "kritacolorsfilter.json", registerPlugin();) ColorsFilters::ColorsFilters(QObject *parent, const QVariantList &) : QObject(parent) { KisFilterRegistry * manager = KisFilterRegistry::instance(); manager->add(new KisAutoContrast()); manager->add(new KisPerChannelFilter()); + manager->add(new KisCrossChannelFilter()); manager->add(new KisDesaturateFilter()); manager->add(new KisHSVAdjustmentFilter()); manager->add(new KisColorBalanceFilter()); } ColorsFilters::~ColorsFilters() { } //================================================================== KisAutoContrast::KisAutoContrast() : KisFilter(id(), categoryAdjust(), i18n("&Auto Contrast")) { setSupportsPainting(false); setSupportsThreading(false); setSupportsAdjustmentLayers(false); setColorSpaceIndependence(TO_LAB16); setShowConfigurationWidget(false); } void KisAutoContrast::processImpl(KisPaintDeviceSP device, const QRect& applyRect, const KisFilterConfigurationSP config, KoUpdater* progressUpdater) const { Q_ASSERT(device != 0); Q_UNUSED(config); // initialize KoHistogramProducer *producer = new KoGenericLabHistogramProducer(); KisHistogram histogram(device, applyRect, producer, LINEAR); int minvalue = int(255 * histogram.calculations().getMin() + 0.5); int maxvalue = int(255 * histogram.calculations().getMax() + 0.5); if (maxvalue > 255) maxvalue = 255; histogram.setChannel(0); int twoPercent = int(0.005 * histogram.calculations().getCount()); int pixCount = 0; int binnum = 0; while (binnum < histogram.producer()->numberOfBins()) { pixCount += histogram.getValue(binnum); if (pixCount > twoPercent) { minvalue = binnum; break; } binnum++; } pixCount = 0; binnum = histogram.producer()->numberOfBins() - 1; while (binnum > 0) { pixCount += histogram.getValue(binnum); if (pixCount > twoPercent) { maxvalue = binnum; break; } binnum--; } // build the transferfunction int diff = maxvalue - minvalue; quint16* transfer = new quint16[256]; for (int i = 0; i < 255; i++) transfer[i] = 0xFFFF; if (diff != 0) { for (int i = 0; i < minvalue; i++) transfer[i] = 0x0; for (int i = minvalue; i < maxvalue; i++) { qint32 val = int((0xFFFF * (i - minvalue)) / diff); if (val > 0xFFFF) val = 0xFFFF; if (val < 0) val = 0; transfer[i] = val; } for (int i = maxvalue; i < 256; i++) transfer[i] = 0xFFFF; } // apply KoColorTransformation *adj = device->colorSpace()->createBrightnessContrastAdjustment(transfer); KisSequentialIteratorProgress it(device, applyRect, progressUpdater); quint32 npix = it.nConseqPixels(); while(it.nextPixels(npix)) { // adjust npix = it.nConseqPixels(); adj->transform(it.oldRawData(), it.rawData(), npix); } delete[] transfer; delete adj; } #include "colorsfilters.moc" diff --git a/plugins/filters/colorsfilters/kis_cross_channel_filter.cpp b/plugins/filters/colorsfilters/kis_cross_channel_filter.cpp new file mode 100644 index 0000000000..f1f05dd48f --- /dev/null +++ b/plugins/filters/colorsfilters/kis_cross_channel_filter.cpp @@ -0,0 +1,269 @@ +/* + * This file is part of Krita + * + * Copyright (c) 2018 Jouni Pentikainen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "kis_cross_channel_filter.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "KoChannelInfo.h" +#include "KoBasicHistogramProducers.h" +#include "KoColorModelStandardIds.h" +#include "KoColorSpace.h" +#include "KoColorTransformation.h" +#include "KoCompositeColorTransformation.h" +#include "KoCompositeOp.h" +#include "KoID.h" + +#include "kis_signals_blocker.h" + +#include "kis_bookmarked_configuration_manager.h" +#include "kis_config_widget.h" +#include +#include +#include +#include +#include + +#include "kis_histogram.h" +#include "kis_painter.h" +#include "widgets/kis_curve_widget.h" + +#include "../../color/colorspaceextensions/kis_hsv_adjustment.h" + +// KisCrossChannelFilterConfiguration + +KisCrossChannelFilterConfiguration::KisCrossChannelFilterConfiguration(int channelCount) + : KisMultiChannelFilterConfiguration(channelCount, "crosschannel", 1) +{ + init(); + m_driverChannels.resize(channelCount); +} + +KisCrossChannelFilterConfiguration::~KisCrossChannelFilterConfiguration() +{} + +const QVector KisCrossChannelFilterConfiguration::driverChannels() const +{ + return m_driverChannels; +} + +void KisCrossChannelFilterConfiguration::setDriverChannels(QVector driverChannels) +{ + KIS_SAFE_ASSERT_RECOVER_RETURN(driverChannels.size() == m_curves.size()); + m_driverChannels = driverChannels; +} + +void KisCrossChannelFilterConfiguration::fromXML(const QDomElement& root) +{ + KisMultiChannelFilterConfiguration::fromXML(root); + + m_driverChannels.resize(m_curves.size()); + + QRegExp rx("driver(\\d+)"); + for (QDomElement e = root.firstChildElement(); !e.isNull(); e = e.nextSiblingElement()) { + const QString attributeName = e.attribute("name"); + + if (rx.exactMatch(attributeName)) { + int channel = rx.cap(1).toUShort(); + int driver = KisDomUtils::toInt(e.text()); + + if (0 <= channel && channel < m_driverChannels.size()) { + m_driverChannels[channel] = driver; + } + } + } +} + +void KisCrossChannelFilterConfiguration::toXML(QDomDocument& doc, QDomElement& root) const +{ + KisMultiChannelFilterConfiguration::toXML(doc, root); + + for (int i = 0; i < m_driverChannels.size(); i++) { + QDomElement param = doc.createElement("param"); + param.setAttribute("name", QString("driver%1").arg(i)); + + QDomText text = doc.createTextNode(KisDomUtils::toString(m_driverChannels[i])); + param.appendChild(text); + + root.appendChild(param); + } +} + +KisCubicCurve KisCrossChannelFilterConfiguration::getDefaultCurve() +{ + const QList points { QPointF(0.0f, 0.5f), QPointF(1.0f, 0.5f) }; + return KisCubicCurve(points); +} + + +KisCrossChannelConfigWidget::KisCrossChannelConfigWidget(QWidget * parent, KisPaintDeviceSP dev, Qt::WindowFlags f) + : KisMultiChannelConfigWidget(parent, dev, f) +{ + const int virtualChannelCount = m_virtualChannels.size(); + m_driverChannels.resize(virtualChannelCount); + + init(); + + for (int i = 0; i < virtualChannelCount; i++) { + const VirtualChannelInfo &info = m_virtualChannels[i]; + + if (info.type() == VirtualChannelInfo::ALL_COLORS) { + continue; + } + + m_page->cmbDriverChannel->addItem(info.name(), i); + } + + connect(m_page->cmbDriverChannel, SIGNAL(activated(int)), this, SLOT(slotDriverChannelSelected(int))); +} + +// KisCrossChannelConfigWidget + +KisCrossChannelConfigWidget::~KisCrossChannelConfigWidget() +{} + +void KisCrossChannelConfigWidget::setConfiguration(const KisPropertiesConfigurationSP config) +{ + const auto *cfg = dynamic_cast(config.data()); + m_driverChannels = cfg->driverChannels(); + + KisMultiChannelConfigWidget::setConfiguration(config); +} + +KisPropertiesConfigurationSP KisCrossChannelConfigWidget::configuration() const +{ + auto *cfg = new KisCrossChannelFilterConfiguration(m_virtualChannels.count()); + KisPropertiesConfigurationSP cfgSP = cfg; + + m_curves[m_activeVChannel] = m_page->curveWidget->curve(); + cfg->setCurves(m_curves); + cfg->setDriverChannels(m_driverChannels); + + return cfgSP; +} + +void KisCrossChannelConfigWidget::updateChannelControls() +{ + m_page->curveWidget->setupInOutControls(m_page->intIn, m_page->intOut, 0, 100, -100, 100); + + const int index = m_page->cmbDriverChannel->findData(m_driverChannels[m_activeVChannel]); + m_page->cmbDriverChannel->setCurrentIndex(index); +} + + +KisPropertiesConfigurationSP KisCrossChannelConfigWidget::getDefaultConfiguration() +{ + return new KisCrossChannelFilterConfiguration(m_virtualChannels.size()); +} + +void KisCrossChannelConfigWidget::slotDriverChannelSelected(int index) +{ + const int channel = m_page->cmbDriverChannel->itemData(index).toInt(); + + KIS_SAFE_ASSERT_RECOVER_RETURN(0 <= channel && channel < m_virtualChannels.size()); + m_driverChannels[m_activeVChannel] = channel; + + updateChannelControls(); +} + +// KisCrossChannelFilter + +KisCrossChannelFilter::KisCrossChannelFilter() : KisMultiChannelFilter(id(), i18n("&Cross-channel adjustment curves...")) +{} + +KisCrossChannelFilter::~KisCrossChannelFilter() +{} + +KisConfigWidget * KisCrossChannelFilter::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev) const +{ + return new KisCrossChannelConfigWidget(parent, dev); +} + +KisFilterConfigurationSP KisCrossChannelFilter::factoryConfiguration() const +{ + return new KisCrossChannelFilterConfiguration(0); +} + +int mapChannel(const VirtualChannelInfo &channel) { + switch (channel.type()) { + case VirtualChannelInfo::REAL: { + int pixelIndex = channel.pixelIndex(); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(0 <= pixelIndex && pixelIndex < 4, 0); + return pixelIndex; + } + case VirtualChannelInfo::ALL_COLORS: + return KisHSVCurve::AllColors; + case VirtualChannelInfo::HUE: + return KisHSVCurve::Hue; + case VirtualChannelInfo::SATURATION: + return KisHSVCurve::Saturation; + case VirtualChannelInfo::LIGHTNESS: + return KisHSVCurve::Value; + }; + + KIS_SAFE_ASSERT_RECOVER_NOOP(false); + return 0; +} + +KoColorTransformation* KisCrossChannelFilter::createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const +{ + const KisCrossChannelFilterConfiguration* configBC = + dynamic_cast(config.data()); + Q_ASSERT(configBC); + + const QVector > &originalTransfers = configBC->transfers(); + const QList &curves = configBC->curves(); + const QVector &drivers = configBC->driverChannels(); + + const QVector virtualChannels = KisMultiChannelFilter::getVirtualChannels(cs); + + if (originalTransfers.size() != int(virtualChannels.size())) { + // We got an illegal number of colorchannels :( + return 0; + } + + QVector transforms; + // Channel order reversed in order to adjust saturation before hue. This allows mapping grays to colors. + for (int i = virtualChannels.size() - 1; i >= 0; i--) { + if (!curves[i].isConstant(0.5)) { + int channel = mapChannel(virtualChannels[i]); + int driverChannel = mapChannel(virtualChannels[drivers[i]]); + QHash params; + params["channel"] = channel; + params["driverChannel"] = driverChannel; + params["curve"] = QVariant::fromValue(originalTransfers[i]); + params["relative"] = true; + params["lumaRed"] = cs->lumaCoefficients()[0]; + params["lumaGreen"] = cs->lumaCoefficients()[1]; + params["lumaBlue"] = cs->lumaCoefficients()[2]; + + transforms << cs->createColorTransformation("hsv_curve_adjustment", params); + } + } + + return KoCompositeColorTransformation::createOptimizedCompositeTransform(transforms); +} diff --git a/plugins/filters/colorsfilters/kis_cross_channel_filter.h b/plugins/filters/colorsfilters/kis_cross_channel_filter.h new file mode 100644 index 0000000000..c7b69b0242 --- /dev/null +++ b/plugins/filters/colorsfilters/kis_cross_channel_filter.h @@ -0,0 +1,101 @@ +/* + * This file is part of Krita + * + * Copyright (c) 2018 Jouni Pentikainen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef _KIS_CROSSCHANNEL_FILTER_H_ +#define _KIS_CROSSCHANNEL_FILTER_H_ + +#include +#include + +#include +#include +#include +#include +#include "ui_wdg_perchannel.h" + +#include "virtual_channel_info.h" + +#include "kis_multichannel_filter_base.h" + +/** + * Filter which applies a relative adjustment to a (virtual) color channel based on the value of another. + * The amount of adjustment for a given input is controlled by a user-defined curve. + */ +class KisCrossChannelFilter : public KisMultiChannelFilter +{ +public: + KisCrossChannelFilter(); + ~KisCrossChannelFilter() override; + + KisConfigWidget * createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev) const override; + KisFilterConfigurationSP factoryConfiguration() const override; + + KoColorTransformation* createTransformation(const KoColorSpace *cs, const KisFilterConfigurationSP config) const override; + + static inline KoID id() { + return KoID("crosschannel", i18n("Cross-channel color adjustment")); + } +}; + +class KisCrossChannelFilterConfiguration : public KisMultiChannelFilterConfiguration +{ +public: + KisCrossChannelFilterConfiguration(int n); + ~KisCrossChannelFilterConfiguration() override; + + const QVector driverChannels() const; + + void setDriverChannels(QVector driverChannels); + using KisFilterConfiguration::fromXML; + + using KisFilterConfiguration::toXML; + void fromXML(const QDomElement& e) override; + void toXML(QDomDocument& doc, QDomElement& root) const override; + + KisCubicCurve getDefaultCurve() override; + +private: + QVector m_driverChannels; +}; + +class KisCrossChannelConfigWidget : public KisMultiChannelConfigWidget +{ + Q_OBJECT + +public: + KisCrossChannelConfigWidget(QWidget * parent, KisPaintDeviceSP dev, Qt::WindowFlags f = 0); + ~KisCrossChannelConfigWidget() override; + + void setConfiguration(const KisPropertiesConfigurationSP config) override; + KisPropertiesConfigurationSP configuration() const override; + +protected: + void updateChannelControls() override; + + virtual KisPropertiesConfigurationSP getDefaultConfiguration() override; + +private Q_SLOTS: + void slotDriverChannelSelected(int index); + +private: + QVector m_driverChannels; +}; + +#endif diff --git a/plugins/filters/colorsfilters/kis_perchannel_filter.cpp b/plugins/filters/colorsfilters/kis_multichannel_filter_base.cpp similarity index 50% copy from plugins/filters/colorsfilters/kis_perchannel_filter.cpp copy to plugins/filters/colorsfilters/kis_multichannel_filter_base.cpp index 0a0fd8d5f8..91fc2ad928 100644 --- a/plugins/filters/colorsfilters/kis_perchannel_filter.cpp +++ b/plugins/filters/colorsfilters/kis_multichannel_filter_base.cpp @@ -1,639 +1,496 @@ /* * This file is part of Krita * - * Copyright (c) 2005 C. Boemann + * Copyright (c) 2018 Jouni Pentikainen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "kis_perchannel_filter.h" +#include "kis_multichannel_filter_base.h" #include #include #include #include #include #include #include #include #include "KoChannelInfo.h" #include "KoBasicHistogramProducers.h" #include "KoColorModelStandardIds.h" #include "KoColorSpace.h" #include "KoColorTransformation.h" #include "KoCompositeColorTransformation.h" #include "KoCompositeOp.h" #include "KoID.h" #include "kis_signals_blocker.h" #include "kis_bookmarked_configuration_manager.h" #include "kis_config_widget.h" #include #include #include #include #include "kis_histogram.h" #include "kis_painter.h" #include "widgets/kis_curve_widget.h" -QVector getVirtualChannels(const KoColorSpace *cs) +KisMultiChannelFilter::KisMultiChannelFilter(const KoID& id, const QString &entry) + : KisColorTransformationFilter(id, categoryAdjust(), entry) +{ + setSupportsPainting(true); + setColorSpaceIndependence(TO_LAB16); +} + +bool KisMultiChannelFilter::needsTransparentPixels(const KisFilterConfigurationSP config, const KoColorSpace *cs) const +{ + Q_UNUSED(config); + return cs->colorModelId() == AlphaColorModelID; +} + +QVector KisMultiChannelFilter::getVirtualChannels(const KoColorSpace *cs) { const bool supportsLightness = cs->colorModelId() != LABAColorModelID && cs->colorModelId() != GrayAColorModelID && cs->colorModelId() != GrayColorModelID && cs->colorModelId() != AlphaColorModelID; + const bool supportsHue = supportsLightness; + const bool supportSaturation = supportsLightness; + QVector vchannels; QList sortedChannels = KoChannelInfo::displayOrderSorted(cs->channels()); if (supportsLightness) { vchannels << VirtualChannelInfo(VirtualChannelInfo::ALL_COLORS, -1, 0, cs); } Q_FOREACH (KoChannelInfo *channel, sortedChannels) { int pixelIndex = KoChannelInfo::displayPositionToChannelIndex(channel->displayPosition(), sortedChannels); vchannels << VirtualChannelInfo(VirtualChannelInfo::REAL, pixelIndex, channel, cs); } - if (supportsLightness) { - vchannels << VirtualChannelInfo(VirtualChannelInfo::LIGHTNESS, -1, 0, cs); - } - - return vchannels; -} - -KisPerChannelConfigWidget::KisPerChannelConfigWidget(QWidget * parent, KisPaintDeviceSP dev, Qt::WindowFlags f) - : KisConfigWidget(parent, f), m_histogram(0) -{ - Q_ASSERT(dev); - m_page = new WdgPerChannel(this); - - QHBoxLayout * layout = new QHBoxLayout(this); - Q_CHECK_PTR(layout); - layout->setContentsMargins(0,0,0,0); - layout->addWidget(m_page); - - m_dev = dev; - m_activeVChannel = 0; - const KoColorSpace *targetColorSpace = dev->compositionSourceColorSpace(); - - // fill in the channel chooser, in the display order, but store the pixel index as well. - - m_virtualChannels = getVirtualChannels(targetColorSpace); - const int virtualChannelCount = m_virtualChannels.size(); - - KisPerChannelFilterConfiguration::initDefaultCurves(m_curves, - virtualChannelCount); - for (int i = 0; i < virtualChannelCount; i++) { - const VirtualChannelInfo &info = m_virtualChannels[i]; - - m_page->cmbChannel->addItem(info.name(), info.pixelIndex()); - m_curves[i].setName(info.name()); - } - - connect(m_page->cmbChannel, SIGNAL(activated(int)), this, SLOT(setActiveChannel(int))); - connect((QObject*)(m_page->chkLogarithmic), SIGNAL(toggled(bool)), this, SLOT(logHistView())); - connect((QObject*)(m_page->resetButton), SIGNAL(clicked()), this, SLOT(resetCurve())); - - // create the horizontal and vertical gradient labels - m_page->hgradient->setPixmap(createGradient(Qt::Horizontal)); - m_page->vgradient->setPixmap(createGradient(Qt::Vertical)); - - // init histogram calculator - QList keys = - KoHistogramProducerFactoryRegistry::instance()->keysCompatibleWith(targetColorSpace); - - if(keys.size() > 0) { - KoHistogramProducerFactory *hpf; - hpf = KoHistogramProducerFactoryRegistry::instance()->get(keys.at(0)); - m_histogram = new KisHistogram(m_dev, m_dev->exactBounds(), hpf->generate(), LINEAR); - } - - connect(m_page->curveWidget, SIGNAL(modified()), this, SIGNAL(sigConfigurationItemChanged())); - - m_page->curveWidget->setupInOutControls(m_page->intIn, m_page->intOut, 0, 100); - - { - KisSignalsBlocker b(m_page->curveWidget); - m_page->curveWidget->setCurve(m_curves[0]); - setActiveChannel(0); + if (supportsHue) { + vchannels << VirtualChannelInfo(VirtualChannelInfo::HUE, -1, 0, cs); } -} -KisPerChannelConfigWidget::~KisPerChannelConfigWidget() -{ - delete m_histogram; -} - -inline QPixmap KisPerChannelConfigWidget::createGradient(Qt::Orientation orient /*, int invert (not used yet) */) -{ - int width; - int height; - int *i, inc, col; - int x = 0, y = 0; - - if (orient == Qt::Horizontal) { - i = &x; inc = 1; col = 0; - width = 256; height = 1; - } else { - i = &y; inc = -1; col = 255; - width = 1; height = 256; + if (supportSaturation) { + vchannels << VirtualChannelInfo(VirtualChannelInfo::SATURATION, -1, 0, cs); } - QPixmap gradientpix(width, height); - QPainter p(&gradientpix); - p.setPen(QPen(QColor(0, 0, 0), 1, Qt::SolidLine)); - for (; *i < 256; (*i)++, col += inc) { - p.setPen(QColor(col, col, col)); - p.drawPoint(x, y); - } - return gradientpix; -} - -inline QPixmap KisPerChannelConfigWidget::getHistogram() -{ - int i; - int height = 256; - QPixmap pix(256, height); - KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_histogram, pix); - - - bool logarithmic = m_page->chkLogarithmic->isChecked(); - - if (logarithmic) - m_histogram->setHistogramType(LOGARITHMIC); - else - m_histogram->setHistogramType(LINEAR); - - - QPalette appPalette = QApplication::palette(); - - pix.fill(QColor(appPalette.color(QPalette::Base))); - - QPainter p(&pix); - p.setPen(QColor(appPalette.color(QPalette::Text))); - p.save(); - p.setOpacity(0.2); - - const VirtualChannelInfo &info = m_virtualChannels[m_activeVChannel]; - - - if (info.type() == VirtualChannelInfo::REAL) { - m_histogram->setChannel(info.pixelIndex()); - - double highest = (double)m_histogram->calculations().getHighest(); - - qint32 bins = m_histogram->producer()->numberOfBins(); - - if (m_histogram->getHistogramType() == LINEAR) { - double factor = (double)height / highest; - for (i = 0; i < bins; ++i) { - p.drawLine(i, height, i, height - int(m_histogram->getValue(i) * factor)); - } - } else { - double factor = (double)height / (double)log(highest); - for (i = 0; i < bins; ++i) { - p.drawLine(i, height, i, height - int(log((double)m_histogram->getValue(i)) * factor)); - } - } + if (supportsLightness) { + vchannels << VirtualChannelInfo(VirtualChannelInfo::LIGHTNESS, -1, 0, cs); } - p.restore(); - - return pix; + return vchannels; } -#define BITS_PER_BYTE 8 -#define pwr2(p) (1<curveWidget->curve(); - - m_activeVChannel = ch; - m_page->curveWidget->setCurve(m_curves[m_activeVChannel]); - m_page->curveWidget->setPixmap(getHistogram()); - m_page->cmbChannel->setCurrentIndex(m_activeVChannel); - - - // Getting range accepted by channel - VirtualChannelInfo ¤tVChannel = m_virtualChannels[m_activeVChannel]; - - KoChannelInfo::enumChannelValueType valueType = currentVChannel.valueType(); - - int order = BITS_PER_BYTE * currentVChannel.channelSize(); - int maxValue = pwr2(order); - int min; - int max; - - m_page->curveWidget->dropInOutControls(); - - switch (valueType) { - case KoChannelInfo::UINT8: - case KoChannelInfo::UINT16: - case KoChannelInfo::UINT32: - m_shift = 0; - m_scale = double(maxValue); - min = 0; - max = maxValue - 1; - break; - case KoChannelInfo::INT8: - case KoChannelInfo::INT16: - m_shift = 0.5; - m_scale = double(maxValue); - min = -maxValue / 2; - max = maxValue / 2 - 1; - break; - case KoChannelInfo::FLOAT16: - case KoChannelInfo::FLOAT32: - case KoChannelInfo::FLOAT64: - default: - m_shift = 0; - m_scale = 100.0; - //Hack Alert: should be changed to float - min = 0; - max = 100; - break; - } - - m_page->curveWidget->setupInOutControls(m_page->intIn, m_page->intOut, min, max); + m_transfers.resize(m_channelCount); } +KisMultiChannelFilterConfiguration::~KisMultiChannelFilterConfiguration() +{} -KisPropertiesConfigurationSP KisPerChannelConfigWidget::configuration() const -{ - int numChannels = m_virtualChannels.size(); - KisPropertiesConfigurationSP cfg = new KisPerChannelFilterConfiguration(numChannels); - - KIS_ASSERT_RECOVER(m_activeVChannel < m_curves.size()) { return cfg; } - - m_curves[m_activeVChannel] = m_page->curveWidget->curve(); - static_cast(cfg.data())->setCurves(m_curves); - - return cfg; -} - -void KisPerChannelConfigWidget::setConfiguration(const KisPropertiesConfigurationSP config) +void KisMultiChannelFilterConfiguration::init() { - const KisPerChannelFilterConfiguration * cfg = dynamic_cast(config.data()); - if (!cfg) - return; - - if (cfg->curves().size() == 0) { - /** - * HACK ALERT: our configuration factory generates - * default configuration with nTransfers==0. - * Catching it here. Just reset all the transfers. - */ - - const int virtualChannelCount = m_virtualChannels.size(); - KisPerChannelFilterConfiguration::initDefaultCurves(m_curves, - virtualChannelCount); - - for (int i = 0; i < virtualChannelCount; i++) { - const VirtualChannelInfo &info = m_virtualChannels[i]; - m_curves[i].setName(info.name()); - } - - } else if (cfg->curves().size() != int(m_virtualChannels.size())) { - warnKrita << "WARNING: trying to load a curve with incorrect number of channels!"; - warnKrita << "WARNING: expected:" << m_virtualChannels.size(); - warnKrita << "WARNING: got:" << cfg->curves().size(); - return; - } else { - for (int ch = 0; ch < cfg->curves().size(); ch++) - m_curves[ch] = cfg->curves()[ch]; + m_curves.clear(); + for (int i = 0; i < m_channelCount; ++i) { + m_curves.append(getDefaultCurve()); } - - // HACK: we save the previous curve in setActiveChannel, so just copy it - m_page->curveWidget->setCurve(m_curves[m_activeVChannel]); - - setActiveChannel(0); -} - - -KisPerChannelFilterConfiguration::KisPerChannelFilterConfiguration(int nCh) - : KisColorTransformationConfiguration("perchannel", 1) -{ - initDefaultCurves(m_curves, nCh); updateTransfers(); } -KisPerChannelFilterConfiguration::~KisPerChannelFilterConfiguration() +bool KisMultiChannelFilterConfiguration::isCompatible(const KisPaintDeviceSP dev) const { + return (int)dev->compositionSourceColorSpace()->channelCount() == m_channelCount; } -bool KisPerChannelFilterConfiguration::isCompatible(const KisPaintDeviceSP dev) const -{ - return (int)dev->compositionSourceColorSpace()->channelCount() == m_curves.size(); -} - -void KisPerChannelFilterConfiguration::setCurves(QList &curves) +void KisMultiChannelFilterConfiguration::setCurves(QList &curves) { m_curves.clear(); m_curves = curves; + m_channelCount = curves.size(); updateTransfers(); } -void KisPerChannelFilterConfiguration::initDefaultCurves(QList &curves, int nCh) -{ - curves.clear(); - for (int i = 0; i < nCh; i++) { - curves.append(KisCubicCurve()); - } -} - -void KisPerChannelFilterConfiguration::updateTransfers() +void KisMultiChannelFilterConfiguration::updateTransfers() { - m_transfers.resize(m_curves.size()); - for (int i = 0; i < m_curves.size(); i++) { + m_transfers.resize(m_channelCount); + for (int i = 0; i < m_channelCount; i++) { m_transfers[i] = m_curves[i].uint16Transfer(); } } const QVector >& -KisPerChannelFilterConfiguration::transfers() const +KisMultiChannelFilterConfiguration::transfers() const { return m_transfers; } const QList& -KisPerChannelFilterConfiguration::curves() const +KisMultiChannelFilterConfiguration::curves() const { return m_curves; } -void KisPerChannelFilterConfiguration::fromLegacyXML(const QDomElement& root) +void KisMultiChannelFilterConfiguration::fromLegacyXML(const QDomElement& root) { fromXML(root); } -void KisPerChannelFilterConfiguration::fromXML(const QDomElement& root) +void KisMultiChannelFilterConfiguration::fromXML(const QDomElement& root) { QList curves; quint16 numTransfers = 0; int version; - version = root.attribute("version").toInt(); + version = root.attribute("version").toInt(); QDomElement e = root.firstChild().toElement(); QString attributeName; KisCubicCurve curve; quint16 index; while (!e.isNull()) { if ((attributeName = e.attribute("name")) == "nTransfers") { numTransfers = e.text().toUShort(); } else { QRegExp rx("curve(\\d+)"); + if (rx.indexIn(attributeName, 0) != -1) { index = rx.cap(1).toUShort(); index = qMin(index, quint16(curves.count())); if (!e.text().isEmpty()) { curve.fromString(e.text()); } curves.insert(index, curve); } } e = e.nextSiblingElement(); } //prepend empty curves for the brightness contrast filter. if(getString("legacy") == "brightnesscontrast") { if (getString("colorModel") == LABAColorModelID.id()) { curves.append(KisCubicCurve()); curves.append(KisCubicCurve()); curves.append(KisCubicCurve()); } else { int extraChannels = 5; if (getString("colorModel") == CMYKAColorModelID.id()) { extraChannels = 6; } else if (getString("colorModel") == GrayAColorModelID.id()) { extraChannels = 0; } for(int c = 0; c < extraChannels; c ++) { curves.insert(0, KisCubicCurve()); } } } if (!numTransfers) return; setVersion(version); setCurves(curves); } /** * Inherited from KisPropertiesConfiguration */ -//void KisPerChannelFilterConfiguration::fromXML(const QString& s) +//void KisMultiChannelFilterConfiguration::fromXML(const QString& s) void addParamNode(QDomDocument& doc, QDomElement& root, const QString &name, const QString &value) { QDomText text = doc.createTextNode(value); QDomElement t = doc.createElement("param"); t.setAttribute("name", name); t.appendChild(text); root.appendChild(t); } -void KisPerChannelFilterConfiguration::toXML(QDomDocument& doc, QDomElement& root) const +void KisMultiChannelFilterConfiguration::toXML(QDomDocument& doc, QDomElement& root) const { /** * * 3 * 0,0;0.5,0.5;1,1; * 0,0;1,1; * 0,0;1,1; * */ root.setAttribute("version", version()); QDomText text; QDomElement t; - addParamNode(doc, root, "nTransfers", QString::number(m_curves.size())); + addParamNode(doc, root, "nTransfers", QString::number(m_channelCount)); KisCubicCurve curve; QString paramName; for (int i = 0; i < m_curves.size(); ++i) { QString name = QLatin1String("curve") + QString::number(i); QString value = m_curves[i].toString(); addParamNode(doc, root, name, value); } } +KisMultiChannelConfigWidget::KisMultiChannelConfigWidget(QWidget * parent, KisPaintDeviceSP dev, Qt::WindowFlags f) + : KisConfigWidget(parent, f) + , m_dev(dev) + , m_page(new WdgPerChannel(this)) +{ + Q_ASSERT(m_dev); + + const KoColorSpace *targetColorSpace = dev->compositionSourceColorSpace(); + m_virtualChannels = KisMultiChannelFilter::getVirtualChannels(targetColorSpace); +} + /** - * Inherited from KisPropertiesConfiguration + * Initialize the dialog. + * Note: m_virtualChannels must be populated before calling this */ -//QString KisPerChannelFilterConfiguration::toXML() +void KisMultiChannelConfigWidget::init() { + QHBoxLayout * layout = new QHBoxLayout(this); + Q_CHECK_PTR(layout); + layout->setContentsMargins(0,0,0,0); + layout->addWidget(m_page); + resetCurves(); -KisPerChannelFilter::KisPerChannelFilter() : KisColorTransformationFilter(id(), categoryAdjust(), i18n("&Color Adjustment curves...")) -{ - setShortcut(QKeySequence(Qt::CTRL + Qt::Key_M)); - setSupportsPainting(true); - setColorSpaceIndependence(TO_LAB16); + const int virtualChannelCount = m_virtualChannels.size(); + for (int i = 0; i < virtualChannelCount; i++) { + const VirtualChannelInfo &info = m_virtualChannels[i]; + m_page->cmbChannel->addItem(info.name(), i); + } + + connect(m_page->cmbChannel, SIGNAL(activated(int)), this, SLOT(slotChannelSelected(int))); + connect((QObject*)(m_page->chkLogarithmic), SIGNAL(toggled(bool)), this, SLOT(logHistView())); + connect((QObject*)(m_page->resetButton), SIGNAL(clicked()), this, SLOT(resetCurve())); + + // create the horizontal and vertical gradient labels + m_page->hgradient->setPixmap(createGradient(Qt::Horizontal)); + m_page->vgradient->setPixmap(createGradient(Qt::Vertical)); + + // init histogram calculator + const KoColorSpace *targetColorSpace = m_dev->compositionSourceColorSpace(); + QList keys = + KoHistogramProducerFactoryRegistry::instance()->keysCompatibleWith(targetColorSpace); + + if (keys.size() > 0) { + KoHistogramProducerFactory *hpf; + hpf = KoHistogramProducerFactoryRegistry::instance()->get(keys.at(0)); + m_histogram = new KisHistogram(m_dev, m_dev->exactBounds(), hpf->generate(), LINEAR); + } + + connect(m_page->curveWidget, SIGNAL(modified()), this, SIGNAL(sigConfigurationItemChanged())); + + { + KisSignalsBlocker b(m_page->curveWidget); + m_page->curveWidget->setCurve(m_curves[0]); + setActiveChannel(0); + } } -KisConfigWidget * KisPerChannelFilter::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev) const +KisMultiChannelConfigWidget::~KisMultiChannelConfigWidget() { - return new KisPerChannelConfigWidget(parent, dev); + delete m_histogram; } -KisFilterConfigurationSP KisPerChannelFilter::factoryConfiguration() const +void KisMultiChannelConfigWidget::resetCurves() { - return new KisPerChannelFilterConfiguration(0); + const KisPropertiesConfigurationSP &defaultConfiguration = getDefaultConfiguration(); + const auto *defaults = dynamic_cast(defaultConfiguration.data()); + + KIS_SAFE_ASSERT_RECOVER_RETURN(defaults); + m_curves = defaults->curves(); + + const int virtualChannelCount = m_virtualChannels.size(); + for (int i = 0; i < virtualChannelCount; i++) { + const VirtualChannelInfo &info = m_virtualChannels[i]; + m_curves[i].setName(info.name()); + } } -KoColorTransformation* KisPerChannelFilter::createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const +void KisMultiChannelConfigWidget::setConfiguration(const KisPropertiesConfigurationSP config) { - const KisPerChannelFilterConfiguration* configBC = - dynamic_cast(config.data()); // Somehow, this shouldn't happen - Q_ASSERT(configBC); + const KisMultiChannelFilterConfiguration * cfg = dynamic_cast(config.data()); + if (!cfg) { + return; + } - const QVector > &originalTransfers = configBC->transfers(); - const QList &originalCurves = configBC->curves(); + if (cfg->curves().empty()) { + /** + * HACK ALERT: our configuration factory generates + * default configuration with nTransfers==0. + * Catching it here. Just set everything to defaults instead. + */ + const KisPropertiesConfigurationSP &defaultConfiguration = getDefaultConfiguration(); + const auto *defaults = dynamic_cast(defaultConfiguration.data()); + KIS_SAFE_ASSERT_RECOVER_RETURN(defaults); - /** - * TODO: What about the order of channels? (DK) - * - * Virtual channels are sorted in display order, does Lcms accepts - * transforms in display order? Why on Earth it works?! Is it - * documented anywhere? - */ - const QVector virtualChannels = getVirtualChannels(cs); + if (!defaults->curves().isEmpty()) { + setConfiguration(defaultConfiguration); + return; + } + } else if (cfg->curves().size() != int(m_virtualChannels.size())) { + warnKrita << "WARNING: trying to load a curve with incorrect number of channels!"; + warnKrita << "WARNING: expected:" << m_virtualChannels.size(); + warnKrita << "WARNING: got:" << cfg->curves().size(); + return; + } else { + for (int ch = 0; ch < cfg->curves().size(); ch++) { + m_curves[ch] = cfg->curves()[ch]; + } + } + + // HACK: we save the previous curve in setActiveChannel, so just copy it + m_page->curveWidget->setCurve(m_curves[m_activeVChannel]); - if (originalTransfers.size() != int(virtualChannels.size())) { - // We got an illegal number of colorchannels :( - return 0; + setActiveChannel(0); +} + +inline QPixmap KisMultiChannelConfigWidget::createGradient(Qt::Orientation orient /*, int invert (not used yet) */) +{ + int width; + int height; + int *i, inc, col; + int x = 0, y = 0; + + if (orient == Qt::Horizontal) { + i = &x; inc = 1; col = 0; + width = 256; height = 1; + } else { + i = &y; inc = -1; col = 255; + width = 1; height = 256; } - bool colorsNull = true; - bool lightnessNull = true; - bool allColorsNull = true; - int alphaIndexInReal = -1; + QPixmap gradientpix(width, height); + QPainter p(&gradientpix); + p.setPen(QPen(QColor(0, 0, 0), 1, Qt::SolidLine)); + for (; *i < 256; (*i)++, col += inc) { + p.setPen(QColor(col, col, col)); + p.drawPoint(x, y); + } + return gradientpix; +} - QVector > realTransfers; - QVector lightnessTransfer; - QVector allColorsTransfer; +inline QPixmap KisMultiChannelConfigWidget::getHistogram() +{ + int i; + int height = 256; + QPixmap pix(256, height); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_histogram, pix); - for (int i = 0; i < virtualChannels.size(); i++) { - if (virtualChannels[i].type() == VirtualChannelInfo::REAL) { - realTransfers << originalTransfers[i]; - if (virtualChannels[i].isAlpha()) { - alphaIndexInReal = realTransfers.size() - 1; - } + bool logarithmic = m_page->chkLogarithmic->isChecked(); - if (colorsNull && !originalCurves[i].isNull()) { - colorsNull = false; - } - } else if (virtualChannels[i].type() == VirtualChannelInfo::LIGHTNESS) { - KIS_ASSERT_RECOVER_NOOP(lightnessTransfer.isEmpty()); - lightnessTransfer = originalTransfers[i]; + if (logarithmic) + m_histogram->setHistogramType(LOGARITHMIC); + else + m_histogram->setHistogramType(LINEAR); - if (lightnessNull && !originalCurves[i].isNull()) { - lightnessNull = false; - } - } else if (virtualChannels[i].type() == VirtualChannelInfo::ALL_COLORS) { - KIS_ASSERT_RECOVER_NOOP(allColorsTransfer.isEmpty()); - allColorsTransfer = originalTransfers[i]; - if (allColorsNull && !originalCurves[i].isNull()) { - allColorsNull = false; - } - } - } + QPalette appPalette = QApplication::palette(); - KoColorTransformation *lightnessTransform = 0; - KoColorTransformation *allColorsTransform = 0; - KoColorTransformation *colorTransform = 0; - - if (!colorsNull) { - const quint16** transfers = new const quint16*[realTransfers.size()]; - for(int i = 0; i < realTransfers.size(); ++i) { - transfers[i] = realTransfers[i].constData(); - - /** - * createPerChannelAdjustment() expects alpha channel to - * be the last channel in the list, so just it here - */ - KIS_ASSERT_RECOVER_NOOP(i != alphaIndexInReal || - alphaIndexInReal == (realTransfers.size() - 1)); - } + pix.fill(QColor(appPalette.color(QPalette::Base))); - colorTransform = cs->createPerChannelAdjustment(transfers); - delete [] transfers; - } + QPainter p(&pix); + p.setPen(QColor(appPalette.color(QPalette::Text))); + p.save(); + p.setOpacity(0.2); - if (!lightnessNull) { - lightnessTransform = cs->createBrightnessContrastAdjustment(lightnessTransfer.constData()); - } + const VirtualChannelInfo &info = m_virtualChannels[m_activeVChannel]; - if (!allColorsNull) { - const quint16** allColorsTransfers = new const quint16*[realTransfers.size()]; - for(int i = 0; i < realTransfers.size(); ++i) { - allColorsTransfers[i] = (i != alphaIndexInReal) ? - allColorsTransfer.constData() : 0; - - /** - * createPerChannelAdjustment() expects alpha channel to - * be the last channel in the list, so just it here - */ - KIS_ASSERT_RECOVER_NOOP(i != alphaIndexInReal || - alphaIndexInReal == (realTransfers.size() - 1)); - } - allColorsTransform = cs->createPerChannelAdjustment(allColorsTransfers); - delete[] allColorsTransfers; + if (info.type() == VirtualChannelInfo::REAL) { + m_histogram->setChannel(info.pixelIndex()); + + double highest = (double)m_histogram->calculations().getHighest(); + + qint32 bins = m_histogram->producer()->numberOfBins(); + + if (m_histogram->getHistogramType() == LINEAR) { + double factor = (double)height / highest; + for (i = 0; i < bins; ++i) { + p.drawLine(i, height, i, height - int(m_histogram->getValue(i) * factor)); + } + } else { + double factor = (double)height / (double)log(highest); + for (i = 0; i < bins; ++i) { + p.drawLine(i, height, i, height - int(log((double)m_histogram->getValue(i)) * factor)); + } + } } - QVector allTransforms; - allTransforms << colorTransform; - allTransforms << allColorsTransform; - allTransforms << lightnessTransform; + p.restore(); - return KoCompositeColorTransformation::createOptimizedCompositeTransform(allTransforms); + return pix; } -bool KisPerChannelFilter::needsTransparentPixels(const KisFilterConfigurationSP config, const KoColorSpace *cs) const +void KisMultiChannelConfigWidget::slotChannelSelected(int index) { - Q_UNUSED(config); - return cs->colorModelId() == AlphaColorModelID; + const int virtualChannel = m_page->cmbChannel->itemData(index).toInt(); + setActiveChannel(virtualChannel); } -void KisPerChannelConfigWidget::logHistView() +void KisMultiChannelConfigWidget::setActiveChannel(int ch) +{ + m_curves[m_activeVChannel] = m_page->curveWidget->curve(); + + m_activeVChannel = ch; + m_page->curveWidget->setCurve(m_curves[m_activeVChannel]); + m_page->curveWidget->setPixmap(getHistogram()); + + const int index = m_page->cmbChannel->findData(m_activeVChannel); + m_page->cmbChannel->setCurrentIndex(index); + + updateChannelControls(); +} + +void KisMultiChannelConfigWidget::logHistView() { m_page->curveWidget->setPixmap(getHistogram()); } -void KisPerChannelConfigWidget::resetCurve() +void KisMultiChannelConfigWidget::resetCurve() { - m_page->curveWidget->reset(); + const KisPropertiesConfigurationSP &defaultConfiguration = getDefaultConfiguration(); + const auto *defaults = dynamic_cast(defaultConfiguration.data()); + KIS_SAFE_ASSERT_RECOVER_RETURN(defaults); + + auto defaultCurves = defaults->curves(); + KIS_SAFE_ASSERT_RECOVER_RETURN(defaultCurves.size() > m_activeVChannel); + + m_page->curveWidget->setCurve(defaultCurves[m_activeVChannel]); } diff --git a/plugins/filters/colorsfilters/kis_perchannel_filter.h b/plugins/filters/colorsfilters/kis_multichannel_filter_base.h similarity index 61% copy from plugins/filters/colorsfilters/kis_perchannel_filter.h copy to plugins/filters/colorsfilters/kis_multichannel_filter_base.h index 3d6c6503e9..773c926304 100644 --- a/plugins/filters/colorsfilters/kis_perchannel_filter.h +++ b/plugins/filters/colorsfilters/kis_multichannel_filter_base.h @@ -1,139 +1,134 @@ /* * This file is part of Krita * - * Copyright (c) 2004 Cyrille Berger + * Copyright (c) 2018 Jouni Pentikainen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#ifndef _KIS_PERCHANNEL_FILTER_H_ -#define _KIS_PERCHANNEL_FILTER_H_ +#ifndef _KIS_MULTICHANNEL_FILTER_BASE_H_ +#define _KIS_MULTICHANNEL_FILTER_BASE_H_ #include #include #include #include #include #include #include "ui_wdg_perchannel.h" #include "virtual_channel_info.h" - -class WdgPerChannel : public QWidget, public Ui::WdgPerChannel +/** + * Base class for filters which use curves to operate on multiple channels. + */ +class KisMultiChannelFilter : public KisColorTransformationFilter { - Q_OBJECT - public: - WdgPerChannel(QWidget *parent) : QWidget(parent) { - setupUi(this); - } + bool needsTransparentPixels(const KisFilterConfigurationSP config, const KoColorSpace *cs) const override; + + static QVector getVirtualChannels(const KoColorSpace *cs); + +protected: + KisMultiChannelFilter(const KoID &id, const QString &entry); }; -class KisPerChannelFilterConfiguration - : public KisColorTransformationConfiguration +/** + * Base class for configurations of KisMultiChannelFilter subclasses + */ +class KisMultiChannelFilterConfiguration : public KisColorTransformationConfiguration { public: - KisPerChannelFilterConfiguration(int n); - ~KisPerChannelFilterConfiguration() override; + KisMultiChannelFilterConfiguration(int channelCount, const QString & name, qint32 version); + ~KisMultiChannelFilterConfiguration() override; using KisFilterConfiguration::fromXML; using KisFilterConfiguration::toXML; using KisFilterConfiguration::fromLegacyXML; void fromLegacyXML(const QDomElement& root) override; void fromXML(const QDomElement& e) override; void toXML(QDomDocument& doc, QDomElement& root) const override; void setCurves(QList &curves) override; - static inline void initDefaultCurves(QList &curves, int nCh); bool isCompatible(const KisPaintDeviceSP) const override; const QVector >& transfers() const; const QList& curves() const override; -private: + +protected: + int m_channelCount; QList m_curves; + QVector> m_transfers; -private: + void init(); void updateTransfers(); -private: - QVector > m_transfers; -}; + virtual KisCubicCurve getDefaultCurve() = 0; +}; -/** - * This class is generic for filters that affect channel separately - */ -class KisPerChannelFilter - : public KisColorTransformationFilter +class WdgPerChannel : public QWidget, public Ui::WdgPerChannel { -public: - KisPerChannelFilter(); -public: - KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev) const override; - KisFilterConfigurationSP factoryConfiguration() const override; - - KoColorTransformation* createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const override; - - bool needsTransparentPixels(const KisFilterConfigurationSP config, const KoColorSpace *cs) const override; + Q_OBJECT - static inline KoID id() { - return KoID("perchannel", i18n("Color Adjustment")); +public: + WdgPerChannel(QWidget *parent) : QWidget(parent) { + setupUi(this); } -private: }; -class KisPerChannelConfigWidget : public KisConfigWidget +/** + * Base class for configuration widgets of KisMultiChannelFilter subclasses + */ +class KisMultiChannelConfigWidget : public KisConfigWidget { Q_OBJECT public: - KisPerChannelConfigWidget(QWidget * parent, KisPaintDeviceSP dev, Qt::WindowFlags f = 0); - ~KisPerChannelConfigWidget() override; + KisMultiChannelConfigWidget(QWidget * parent, KisPaintDeviceSP dev, Qt::WindowFlags f = 0); + ~KisMultiChannelConfigWidget() override; void setConfiguration(const KisPropertiesConfigurationSP config) override; - KisPropertiesConfigurationSP configuration() const override; -private Q_SLOTS: - virtual void setActiveChannel(int ch); +protected Q_SLOTS: void logHistView(); void resetCurve(); + void slotChannelSelected(int index); +protected: + void init(); + void resetCurves(); -private: - - QVector m_virtualChannels; - int m_activeVChannel; - + virtual void updateChannelControls() = 0; + virtual KisPropertiesConfigurationSP getDefaultConfiguration() = 0; - // private routines inline QPixmap getHistogram(); inline QPixmap createGradient(Qt::Orientation orient /*, int invert (not used now) */); - // members + QVector m_virtualChannels; + int m_activeVChannel = 0; + mutable QList m_curves; + WdgPerChannel * m_page; KisPaintDeviceSP m_dev; KisHistogram *m_histogram; - mutable QList m_curves; - // scales for displaying color numbers - double m_scale; - double m_shift; - bool checkReset; +private: + void setActiveChannel(int ch); }; #endif diff --git a/plugins/filters/colorsfilters/kis_perchannel_filter.cpp b/plugins/filters/colorsfilters/kis_perchannel_filter.cpp index 0a0fd8d5f8..31fa994c1d 100644 --- a/plugins/filters/colorsfilters/kis_perchannel_filter.cpp +++ b/plugins/filters/colorsfilters/kis_perchannel_filter.cpp @@ -1,639 +1,317 @@ /* * This file is part of Krita * * Copyright (c) 2005 C. Boemann * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kis_perchannel_filter.h" #include #include #include #include #include #include #include #include #include "KoChannelInfo.h" #include "KoBasicHistogramProducers.h" #include "KoColorModelStandardIds.h" #include "KoColorSpace.h" #include "KoColorTransformation.h" #include "KoCompositeColorTransformation.h" #include "KoCompositeOp.h" #include "KoID.h" #include "kis_signals_blocker.h" #include "kis_bookmarked_configuration_manager.h" #include "kis_config_widget.h" #include #include #include #include #include "kis_histogram.h" #include "kis_painter.h" #include "widgets/kis_curve_widget.h" -QVector getVirtualChannels(const KoColorSpace *cs) -{ - const bool supportsLightness = - cs->colorModelId() != LABAColorModelID && - cs->colorModelId() != GrayAColorModelID && - cs->colorModelId() != GrayColorModelID && - cs->colorModelId() != AlphaColorModelID; - - QVector vchannels; - - QList sortedChannels = - KoChannelInfo::displayOrderSorted(cs->channels()); - - if (supportsLightness) { - vchannels << VirtualChannelInfo(VirtualChannelInfo::ALL_COLORS, -1, 0, cs); - } - - Q_FOREACH (KoChannelInfo *channel, sortedChannels) { - int pixelIndex = KoChannelInfo::displayPositionToChannelIndex(channel->displayPosition(), sortedChannels); - vchannels << VirtualChannelInfo(VirtualChannelInfo::REAL, pixelIndex, channel, cs); - } - - if (supportsLightness) { - vchannels << VirtualChannelInfo(VirtualChannelInfo::LIGHTNESS, -1, 0, cs); - } - - return vchannels; -} +#include "../../color/colorspaceextensions/kis_hsv_adjustment.h" KisPerChannelConfigWidget::KisPerChannelConfigWidget(QWidget * parent, KisPaintDeviceSP dev, Qt::WindowFlags f) - : KisConfigWidget(parent, f), m_histogram(0) + : KisMultiChannelConfigWidget(parent, dev, f) { - Q_ASSERT(dev); - m_page = new WdgPerChannel(this); - - QHBoxLayout * layout = new QHBoxLayout(this); - Q_CHECK_PTR(layout); - layout->setContentsMargins(0,0,0,0); - layout->addWidget(m_page); - - m_dev = dev; - m_activeVChannel = 0; - const KoColorSpace *targetColorSpace = dev->compositionSourceColorSpace(); - - // fill in the channel chooser, in the display order, but store the pixel index as well. - - m_virtualChannels = getVirtualChannels(targetColorSpace); - const int virtualChannelCount = m_virtualChannels.size(); - - KisPerChannelFilterConfiguration::initDefaultCurves(m_curves, - virtualChannelCount); - for (int i = 0; i < virtualChannelCount; i++) { - const VirtualChannelInfo &info = m_virtualChannels[i]; - - m_page->cmbChannel->addItem(info.name(), info.pixelIndex()); - m_curves[i].setName(info.name()); - } + init(); - connect(m_page->cmbChannel, SIGNAL(activated(int)), this, SLOT(setActiveChannel(int))); - connect((QObject*)(m_page->chkLogarithmic), SIGNAL(toggled(bool)), this, SLOT(logHistView())); - connect((QObject*)(m_page->resetButton), SIGNAL(clicked()), this, SLOT(resetCurve())); - - // create the horizontal and vertical gradient labels - m_page->hgradient->setPixmap(createGradient(Qt::Horizontal)); - m_page->vgradient->setPixmap(createGradient(Qt::Vertical)); - - // init histogram calculator - QList keys = - KoHistogramProducerFactoryRegistry::instance()->keysCompatibleWith(targetColorSpace); - - if(keys.size() > 0) { - KoHistogramProducerFactory *hpf; - hpf = KoHistogramProducerFactoryRegistry::instance()->get(keys.at(0)); - m_histogram = new KisHistogram(m_dev, m_dev->exactBounds(), hpf->generate(), LINEAR); - } - - connect(m_page->curveWidget, SIGNAL(modified()), this, SIGNAL(sigConfigurationItemChanged())); - - m_page->curveWidget->setupInOutControls(m_page->intIn, m_page->intOut, 0, 100); - - { - KisSignalsBlocker b(m_page->curveWidget); - m_page->curveWidget->setCurve(m_curves[0]); - setActiveChannel(0); - } + // These are not used by this filter, + // but the dialog is shared with KisCrossChannelFilter + m_page->lblDriverChannel->hide(); + m_page->cmbDriverChannel->hide(); } KisPerChannelConfigWidget::~KisPerChannelConfigWidget() -{ - delete m_histogram; -} - -inline QPixmap KisPerChannelConfigWidget::createGradient(Qt::Orientation orient /*, int invert (not used yet) */) -{ - int width; - int height; - int *i, inc, col; - int x = 0, y = 0; - - if (orient == Qt::Horizontal) { - i = &x; inc = 1; col = 0; - width = 256; height = 1; - } else { - i = &y; inc = -1; col = 255; - width = 1; height = 256; - } - - QPixmap gradientpix(width, height); - QPainter p(&gradientpix); - p.setPen(QPen(QColor(0, 0, 0), 1, Qt::SolidLine)); - for (; *i < 256; (*i)++, col += inc) { - p.setPen(QColor(col, col, col)); - p.drawPoint(x, y); - } - return gradientpix; -} - -inline QPixmap KisPerChannelConfigWidget::getHistogram() -{ - int i; - int height = 256; - QPixmap pix(256, height); - KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(m_histogram, pix); - - - bool logarithmic = m_page->chkLogarithmic->isChecked(); - - if (logarithmic) - m_histogram->setHistogramType(LOGARITHMIC); - else - m_histogram->setHistogramType(LINEAR); - - - QPalette appPalette = QApplication::palette(); - - pix.fill(QColor(appPalette.color(QPalette::Base))); - - QPainter p(&pix); - p.setPen(QColor(appPalette.color(QPalette::Text))); - p.save(); - p.setOpacity(0.2); - - const VirtualChannelInfo &info = m_virtualChannels[m_activeVChannel]; - - - if (info.type() == VirtualChannelInfo::REAL) { - m_histogram->setChannel(info.pixelIndex()); - - double highest = (double)m_histogram->calculations().getHighest(); - - qint32 bins = m_histogram->producer()->numberOfBins(); - - if (m_histogram->getHistogramType() == LINEAR) { - double factor = (double)height / highest; - for (i = 0; i < bins; ++i) { - p.drawLine(i, height, i, height - int(m_histogram->getValue(i) * factor)); - } - } else { - double factor = (double)height / (double)log(highest); - for (i = 0; i < bins; ++i) { - p.drawLine(i, height, i, height - int(log((double)m_histogram->getValue(i)) * factor)); - } - } - } - - p.restore(); - - return pix; -} +{} #define BITS_PER_BYTE 8 #define pwr2(p) (1<curveWidget->curve(); - - m_activeVChannel = ch; - m_page->curveWidget->setCurve(m_curves[m_activeVChannel]); - m_page->curveWidget->setPixmap(getHistogram()); - m_page->cmbChannel->setCurrentIndex(m_activeVChannel); - - // Getting range accepted by channel VirtualChannelInfo ¤tVChannel = m_virtualChannels[m_activeVChannel]; KoChannelInfo::enumChannelValueType valueType = currentVChannel.valueType(); int order = BITS_PER_BYTE * currentVChannel.channelSize(); int maxValue = pwr2(order); int min; int max; m_page->curveWidget->dropInOutControls(); switch (valueType) { case KoChannelInfo::UINT8: case KoChannelInfo::UINT16: case KoChannelInfo::UINT32: - m_shift = 0; - m_scale = double(maxValue); min = 0; max = maxValue - 1; break; case KoChannelInfo::INT8: case KoChannelInfo::INT16: - m_shift = 0.5; - m_scale = double(maxValue); min = -maxValue / 2; max = maxValue / 2 - 1; break; case KoChannelInfo::FLOAT16: case KoChannelInfo::FLOAT32: case KoChannelInfo::FLOAT64: default: - m_shift = 0; - m_scale = 100.0; //Hack Alert: should be changed to float min = 0; max = 100; break; } - m_page->curveWidget->setupInOutControls(m_page->intIn, m_page->intOut, min, max); + m_page->curveWidget->setupInOutControls(m_page->intIn, m_page->intOut, min, max, min, max); } -KisPropertiesConfigurationSP KisPerChannelConfigWidget::configuration() const +KisPropertiesConfigurationSP KisPerChannelConfigWidget::configuration() const { int numChannels = m_virtualChannels.size(); KisPropertiesConfigurationSP cfg = new KisPerChannelFilterConfiguration(numChannels); KIS_ASSERT_RECOVER(m_activeVChannel < m_curves.size()) { return cfg; } m_curves[m_activeVChannel] = m_page->curveWidget->curve(); static_cast(cfg.data())->setCurves(m_curves); return cfg; } -void KisPerChannelConfigWidget::setConfiguration(const KisPropertiesConfigurationSP config) +KisPropertiesConfigurationSP KisPerChannelConfigWidget::getDefaultConfiguration() { - const KisPerChannelFilterConfiguration * cfg = dynamic_cast(config.data()); - if (!cfg) - return; - - if (cfg->curves().size() == 0) { - /** - * HACK ALERT: our configuration factory generates - * default configuration with nTransfers==0. - * Catching it here. Just reset all the transfers. - */ - - const int virtualChannelCount = m_virtualChannels.size(); - KisPerChannelFilterConfiguration::initDefaultCurves(m_curves, - virtualChannelCount); - - for (int i = 0; i < virtualChannelCount; i++) { - const VirtualChannelInfo &info = m_virtualChannels[i]; - m_curves[i].setName(info.name()); - } - - } else if (cfg->curves().size() != int(m_virtualChannels.size())) { - warnKrita << "WARNING: trying to load a curve with incorrect number of channels!"; - warnKrita << "WARNING: expected:" << m_virtualChannels.size(); - warnKrita << "WARNING: got:" << cfg->curves().size(); - return; - } else { - for (int ch = 0; ch < cfg->curves().size(); ch++) - m_curves[ch] = cfg->curves()[ch]; - } - - // HACK: we save the previous curve in setActiveChannel, so just copy it - m_page->curveWidget->setCurve(m_curves[m_activeVChannel]); - - setActiveChannel(0); + return new KisPerChannelFilterConfiguration(m_virtualChannels.size()); } - -KisPerChannelFilterConfiguration::KisPerChannelFilterConfiguration(int nCh) - : KisColorTransformationConfiguration("perchannel", 1) +KisPerChannelFilterConfiguration::KisPerChannelFilterConfiguration(int channelCount) + : KisMultiChannelFilterConfiguration(channelCount, "perchannel", 1) { - initDefaultCurves(m_curves, nCh); - updateTransfers(); + init(); } KisPerChannelFilterConfiguration::~KisPerChannelFilterConfiguration() { } -bool KisPerChannelFilterConfiguration::isCompatible(const KisPaintDeviceSP dev) const -{ - return (int)dev->compositionSourceColorSpace()->channelCount() == m_curves.size(); -} - -void KisPerChannelFilterConfiguration::setCurves(QList &curves) -{ - m_curves.clear(); - m_curves = curves; - - updateTransfers(); -} - -void KisPerChannelFilterConfiguration::initDefaultCurves(QList &curves, int nCh) -{ - curves.clear(); - for (int i = 0; i < nCh; i++) { - curves.append(KisCubicCurve()); - } -} - -void KisPerChannelFilterConfiguration::updateTransfers() -{ - m_transfers.resize(m_curves.size()); - for (int i = 0; i < m_curves.size(); i++) { - m_transfers[i] = m_curves[i].uint16Transfer(); - } -} - -const QVector >& -KisPerChannelFilterConfiguration::transfers() const +KisCubicCurve KisPerChannelFilterConfiguration::getDefaultCurve() { - return m_transfers; + return KisCubicCurve(); } -const QList& -KisPerChannelFilterConfiguration::curves() const -{ - return m_curves; -} - -void KisPerChannelFilterConfiguration::fromLegacyXML(const QDomElement& root) -{ - fromXML(root); -} - -void KisPerChannelFilterConfiguration::fromXML(const QDomElement& root) -{ - QList curves; - quint16 numTransfers = 0; - int version; - version = root.attribute("version").toInt(); - - QDomElement e = root.firstChild().toElement(); - QString attributeName; - KisCubicCurve curve; - quint16 index; - while (!e.isNull()) { - if ((attributeName = e.attribute("name")) == "nTransfers") { - numTransfers = e.text().toUShort(); - } else { - QRegExp rx("curve(\\d+)"); - if (rx.indexIn(attributeName, 0) != -1) { - - index = rx.cap(1).toUShort(); - index = qMin(index, quint16(curves.count())); - - if (!e.text().isEmpty()) { - curve.fromString(e.text()); - } - curves.insert(index, curve); - } - } - e = e.nextSiblingElement(); - } - - //prepend empty curves for the brightness contrast filter. - if(getString("legacy") == "brightnesscontrast") { - if (getString("colorModel") == LABAColorModelID.id()) { - curves.append(KisCubicCurve()); - curves.append(KisCubicCurve()); - curves.append(KisCubicCurve()); - } else { - int extraChannels = 5; - if (getString("colorModel") == CMYKAColorModelID.id()) { - extraChannels = 6; - } else if (getString("colorModel") == GrayAColorModelID.id()) { - extraChannels = 0; - } - for(int c = 0; c < extraChannels; c ++) { - curves.insert(0, KisCubicCurve()); - } - } - } - if (!numTransfers) - return; - - setVersion(version); - setCurves(curves); -} - -/** - * Inherited from KisPropertiesConfiguration - */ -//void KisPerChannelFilterConfiguration::fromXML(const QString& s) - -void addParamNode(QDomDocument& doc, - QDomElement& root, - const QString &name, - const QString &value) -{ - QDomText text = doc.createTextNode(value); - QDomElement t = doc.createElement("param"); - t.setAttribute("name", name); - t.appendChild(text); - root.appendChild(t); -} +// KisPerChannelFilter -void KisPerChannelFilterConfiguration::toXML(QDomDocument& doc, QDomElement& root) const -{ - /** - * - * 3 - * 0,0;0.5,0.5;1,1; - * 0,0;1,1; - * 0,0;1,1; - * - */ - - root.setAttribute("version", version()); - - QDomText text; - QDomElement t; - - addParamNode(doc, root, "nTransfers", QString::number(m_curves.size())); - - KisCubicCurve curve; - QString paramName; - - for (int i = 0; i < m_curves.size(); ++i) { - QString name = QLatin1String("curve") + QString::number(i); - QString value = m_curves[i].toString(); - - addParamNode(doc, root, name, value); - } -} - -/** - * Inherited from KisPropertiesConfiguration - */ -//QString KisPerChannelFilterConfiguration::toXML() - - -KisPerChannelFilter::KisPerChannelFilter() : KisColorTransformationFilter(id(), categoryAdjust(), i18n("&Color Adjustment curves...")) +KisPerChannelFilter::KisPerChannelFilter() : KisMultiChannelFilter(id(), i18n("&Color Adjustment curves...")) { setShortcut(QKeySequence(Qt::CTRL + Qt::Key_M)); - setSupportsPainting(true); - setColorSpaceIndependence(TO_LAB16); } KisConfigWidget * KisPerChannelFilter::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev) const { return new KisPerChannelConfigWidget(parent, dev); } KisFilterConfigurationSP KisPerChannelFilter::factoryConfiguration() const { return new KisPerChannelFilterConfiguration(0); } KoColorTransformation* KisPerChannelFilter::createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const { const KisPerChannelFilterConfiguration* configBC = dynamic_cast(config.data()); // Somehow, this shouldn't happen Q_ASSERT(configBC); const QVector > &originalTransfers = configBC->transfers(); const QList &originalCurves = configBC->curves(); /** * TODO: What about the order of channels? (DK) * * Virtual channels are sorted in display order, does Lcms accepts * transforms in display order? Why on Earth it works?! Is it * documented anywhere? */ - const QVector virtualChannels = getVirtualChannels(cs); + const QVector virtualChannels = KisMultiChannelFilter::getVirtualChannels(cs); if (originalTransfers.size() != int(virtualChannels.size())) { // We got an illegal number of colorchannels :( return 0; } bool colorsNull = true; + bool hueNull = true; + bool saturationNull = true; bool lightnessNull = true; bool allColorsNull = true; int alphaIndexInReal = -1; QVector > realTransfers; + QVector hueTransfer; + QVector saturationTransfer; QVector lightnessTransfer; QVector allColorsTransfer; for (int i = 0; i < virtualChannels.size(); i++) { if (virtualChannels[i].type() == VirtualChannelInfo::REAL) { realTransfers << originalTransfers[i]; if (virtualChannels[i].isAlpha()) { alphaIndexInReal = realTransfers.size() - 1; } - if (colorsNull && !originalCurves[i].isNull()) { + if (colorsNull && !originalCurves[i].isIdentity()) { colorsNull = false; } + } else if (virtualChannels[i].type() == VirtualChannelInfo::HUE) { + KIS_ASSERT_RECOVER_NOOP(hueTransfer.isEmpty()); + hueTransfer = originalTransfers[i]; + + if (hueNull && !originalCurves[i].isIdentity()) { + hueNull = false; + } + } else if (virtualChannels[i].type() == VirtualChannelInfo::SATURATION) { + KIS_ASSERT_RECOVER_NOOP(saturationTransfer.isEmpty()); + saturationTransfer = originalTransfers[i]; + + if (saturationNull && !originalCurves[i].isIdentity()) { + saturationNull = false; + } } else if (virtualChannels[i].type() == VirtualChannelInfo::LIGHTNESS) { KIS_ASSERT_RECOVER_NOOP(lightnessTransfer.isEmpty()); lightnessTransfer = originalTransfers[i]; - if (lightnessNull && !originalCurves[i].isNull()) { + if (lightnessNull && !originalCurves[i].isIdentity()) { lightnessNull = false; } } else if (virtualChannels[i].type() == VirtualChannelInfo::ALL_COLORS) { KIS_ASSERT_RECOVER_NOOP(allColorsTransfer.isEmpty()); allColorsTransfer = originalTransfers[i]; - if (allColorsNull && !originalCurves[i].isNull()) { + if (allColorsNull && !originalCurves[i].isIdentity()) { allColorsNull = false; } } } + KoColorTransformation *hueTransform = 0; + KoColorTransformation *saturationTransform = 0; KoColorTransformation *lightnessTransform = 0; KoColorTransformation *allColorsTransform = 0; KoColorTransformation *colorTransform = 0; if (!colorsNull) { const quint16** transfers = new const quint16*[realTransfers.size()]; for(int i = 0; i < realTransfers.size(); ++i) { transfers[i] = realTransfers[i].constData(); /** * createPerChannelAdjustment() expects alpha channel to * be the last channel in the list, so just it here */ KIS_ASSERT_RECOVER_NOOP(i != alphaIndexInReal || alphaIndexInReal == (realTransfers.size() - 1)); } colorTransform = cs->createPerChannelAdjustment(transfers); delete [] transfers; } + if (!hueNull) { + QHash params; + params["curve"] = QVariant::fromValue(hueTransfer); + params["channel"] = KisHSVCurve::Hue; + params["relative"] = false; + params["lumaRed"] = cs->lumaCoefficients()[0]; + params["lumaGreen"] = cs->lumaCoefficients()[1]; + params["lumaBlue"] = cs->lumaCoefficients()[2]; + + hueTransform = cs->createColorTransformation("hsv_curve_adjustment", params); + } + + if (!saturationNull) { + QHash params; + params["curve"] = QVariant::fromValue(saturationTransfer); + params["channel"] = KisHSVCurve::Saturation; + params["relative"] = false; + params["lumaRed"] = cs->lumaCoefficients()[0]; + params["lumaGreen"] = cs->lumaCoefficients()[1]; + params["lumaBlue"] = cs->lumaCoefficients()[2]; + + saturationTransform = cs->createColorTransformation("hsv_curve_adjustment", params); + } + if (!lightnessNull) { lightnessTransform = cs->createBrightnessContrastAdjustment(lightnessTransfer.constData()); } if (!allColorsNull) { const quint16** allColorsTransfers = new const quint16*[realTransfers.size()]; for(int i = 0; i < realTransfers.size(); ++i) { allColorsTransfers[i] = (i != alphaIndexInReal) ? allColorsTransfer.constData() : 0; /** * createPerChannelAdjustment() expects alpha channel to * be the last channel in the list, so just it here */ KIS_ASSERT_RECOVER_NOOP(i != alphaIndexInReal || alphaIndexInReal == (realTransfers.size() - 1)); } allColorsTransform = cs->createPerChannelAdjustment(allColorsTransfers); delete[] allColorsTransfers; } QVector allTransforms; allTransforms << colorTransform; allTransforms << allColorsTransform; + allTransforms << hueTransform; + allTransforms << saturationTransform; allTransforms << lightnessTransform; return KoCompositeColorTransformation::createOptimizedCompositeTransform(allTransforms); } - -bool KisPerChannelFilter::needsTransparentPixels(const KisFilterConfigurationSP config, const KoColorSpace *cs) const -{ - Q_UNUSED(config); - return cs->colorModelId() == AlphaColorModelID; -} - -void KisPerChannelConfigWidget::logHistView() -{ - m_page->curveWidget->setPixmap(getHistogram()); -} - -void KisPerChannelConfigWidget::resetCurve() -{ - m_page->curveWidget->reset(); -} diff --git a/plugins/filters/colorsfilters/kis_perchannel_filter.h b/plugins/filters/colorsfilters/kis_perchannel_filter.h index 3d6c6503e9..829d6862b0 100644 --- a/plugins/filters/colorsfilters/kis_perchannel_filter.h +++ b/plugins/filters/colorsfilters/kis_perchannel_filter.h @@ -1,139 +1,81 @@ /* * This file is part of Krita * * Copyright (c) 2004 Cyrille Berger * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _KIS_PERCHANNEL_FILTER_H_ #define _KIS_PERCHANNEL_FILTER_H_ #include #include #include #include #include #include -#include "ui_wdg_perchannel.h" #include "virtual_channel_info.h" - -class WdgPerChannel : public QWidget, public Ui::WdgPerChannel -{ - Q_OBJECT - -public: - WdgPerChannel(QWidget *parent) : QWidget(parent) { - setupUi(this); - } -}; +#include "kis_multichannel_filter_base.h" class KisPerChannelFilterConfiguration - : public KisColorTransformationConfiguration + : public KisMultiChannelFilterConfiguration { public: - KisPerChannelFilterConfiguration(int n); + KisPerChannelFilterConfiguration(int channelCount); ~KisPerChannelFilterConfiguration() override; - using KisFilterConfiguration::fromXML; - using KisFilterConfiguration::toXML; - using KisFilterConfiguration::fromLegacyXML; - - void fromLegacyXML(const QDomElement& root) override; - - void fromXML(const QDomElement& e) override; - void toXML(QDomDocument& doc, QDomElement& root) const override; - - void setCurves(QList &curves) override; - static inline void initDefaultCurves(QList &curves, int nCh); - bool isCompatible(const KisPaintDeviceSP) const override; - - const QVector >& transfers() const; - const QList& curves() const override; -private: - QList m_curves; - -private: - void updateTransfers(); -private: - QVector > m_transfers; + KisCubicCurve getDefaultCurve() override; }; /** - * This class is generic for filters that affect channel separately + * This class is a filter to adjust channels independently */ -class KisPerChannelFilter - : public KisColorTransformationFilter +class KisPerChannelFilter : public KisMultiChannelFilter { public: KisPerChannelFilter(); -public: + KisConfigWidget * createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP dev) const override; - KisFilterConfigurationSP factoryConfiguration() const override; + KisFilterConfigurationSP factoryConfiguration() const override; KoColorTransformation* createTransformation(const KoColorSpace* cs, const KisFilterConfigurationSP config) const override; - bool needsTransparentPixels(const KisFilterConfigurationSP config, const KoColorSpace *cs) const override; - static inline KoID id() { return KoID("perchannel", i18n("Color Adjustment")); } -private: }; -class KisPerChannelConfigWidget : public KisConfigWidget +class KisPerChannelConfigWidget : public KisMultiChannelConfigWidget { Q_OBJECT public: KisPerChannelConfigWidget(QWidget * parent, KisPaintDeviceSP dev, Qt::WindowFlags f = 0); ~KisPerChannelConfigWidget() override; - void setConfiguration(const KisPropertiesConfigurationSP config) override; - KisPropertiesConfigurationSP configuration() const override; - -private Q_SLOTS: - virtual void setActiveChannel(int ch); - void logHistView(); - void resetCurve(); - - -private: - - QVector m_virtualChannels; - int m_activeVChannel; - - - // private routines - inline QPixmap getHistogram(); - inline QPixmap createGradient(Qt::Orientation orient /*, int invert (not used now) */); + KisPropertiesConfigurationSP configuration() const override; - // members - WdgPerChannel * m_page; - KisPaintDeviceSP m_dev; - KisHistogram *m_histogram; - mutable QList m_curves; +protected: + void updateChannelControls() override; - // scales for displaying color numbers - double m_scale; - double m_shift; - bool checkReset; + virtual KisPropertiesConfigurationSP getDefaultConfiguration() override; }; #endif diff --git a/plugins/filters/colorsfilters/virtual_channel_info.cpp b/plugins/filters/colorsfilters/virtual_channel_info.cpp index 802dadd69f..6c807d2b97 100644 --- a/plugins/filters/colorsfilters/virtual_channel_info.cpp +++ b/plugins/filters/colorsfilters/virtual_channel_info.cpp @@ -1,79 +1,87 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "virtual_channel_info.h" #include #include VirtualChannelInfo::VirtualChannelInfo() : m_type(LIGHTNESS), m_pixelIndex(-1), m_realChannelInfo(0) { } VirtualChannelInfo::VirtualChannelInfo(Type type, int pixelIndex, KoChannelInfo *realChannelInfo, const KoColorSpace *cs) : m_type(type), m_pixelIndex(pixelIndex), m_realChannelInfo(realChannelInfo) { - if (m_type == LIGHTNESS) { + if (m_type == HUE) { + m_nameOverride = i18n("Hue"); + m_valueTypeOverride = KoChannelInfo::FLOAT32; + m_channelSizeOverride = 4; + } else if (m_type == SATURATION) { + m_nameOverride = i18n("Saturation"); + m_valueTypeOverride = KoChannelInfo::FLOAT32; + m_channelSizeOverride = 4; + } else if (m_type == LIGHTNESS) { m_nameOverride = i18n("Lightness"); m_valueTypeOverride = KoChannelInfo::FLOAT32; m_channelSizeOverride = 4; } else if (m_type == ALL_COLORS) { m_nameOverride = cs->colorModelId().id(); m_valueTypeOverride = cs->channels().first()->channelValueType(); m_channelSizeOverride = cs->channels().first()->size(); } } VirtualChannelInfo::Type VirtualChannelInfo::type() const { return m_type; } KoChannelInfo* VirtualChannelInfo::channelInfo() const { return m_realChannelInfo; } QString VirtualChannelInfo::name() const { return m_type == REAL ? m_realChannelInfo->name() : m_nameOverride; } int VirtualChannelInfo::pixelIndex() const { return m_pixelIndex; } KoChannelInfo::enumChannelValueType VirtualChannelInfo::valueType() const { return m_type == REAL ? m_realChannelInfo->channelValueType() : m_valueTypeOverride; } int VirtualChannelInfo::channelSize() const { return m_type == REAL ? m_realChannelInfo->size() : m_channelSizeOverride; } bool VirtualChannelInfo::isAlpha() const { return m_type == REAL && m_realChannelInfo->channelType() == KoChannelInfo::ALPHA; } diff --git a/plugins/filters/colorsfilters/virtual_channel_info.h b/plugins/filters/colorsfilters/virtual_channel_info.h index 10839f859b..cc5dadb749 100644 --- a/plugins/filters/colorsfilters/virtual_channel_info.h +++ b/plugins/filters/colorsfilters/virtual_channel_info.h @@ -1,86 +1,88 @@ /* * Copyright (c) 2015 Dmitry Kazakov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __VIRTUAL_CHANNEL_INFO_H #define __VIRTUAL_CHANNEL_INFO_H #include class KoColorSpace; /** * This class represents a virtual channel that can have a curve in * curves filter. Vitrual channel can be of various types: * * - REAL --- represents a real color channel of the image, * like R, G, B or A * * - LIGHTNESS --- lightness virtual channel: represents L channel * of the image separation itno Lab. * * - ALL_COLORS --- represents a grouped channel, combining all the * color channels of the image. E.g. R+G+B of an RGB * image */ class VirtualChannelInfo { public: enum Type { REAL, + HUE, + SATURATION, LIGHTNESS, ALL_COLORS }; VirtualChannelInfo(); VirtualChannelInfo(Type type, int pixelIndex, KoChannelInfo *realChannelInfo, const KoColorSpace *cs); /** * \return a pointer to a KoChannelInfo structure *iff* the * channel type is 'REAL'. Returns null of all the * other types. */ KoChannelInfo* channelInfo() const; /** * Index of this channel in a pixel. * * \return -1 for all virtual channels. */ int pixelIndex() const; Type type() const; QString name() const; KoChannelInfo::enumChannelValueType valueType() const; int channelSize() const; bool isAlpha() const; private: Type m_type; int m_pixelIndex; KoChannelInfo *m_realChannelInfo; QString m_nameOverride; KoChannelInfo::enumChannelValueType m_valueTypeOverride; int m_channelSizeOverride; }; #endif /* __VIRTUAL_CHANNEL_INFO_H */ diff --git a/plugins/filters/colorsfilters/wdg_perchannel.ui b/plugins/filters/colorsfilters/wdg_perchannel.ui index 8587d1637b..53a8df10ae 100644 --- a/plugins/filters/colorsfilters/wdg_perchannel.ui +++ b/plugins/filters/colorsfilters/wdg_perchannel.ui @@ -1,401 +1,411 @@ WdgPerChannel 0 0 376 384 0 0 BrightnessCon 0 0 0 0 0 6 QLayout::SetFixedSize 0 0 0 0 16777215 16777215 Input: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 0 0 Output: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 0 0 Qt::Horizontal QSizePolicy::Expanding 20 20 Qt::Vertical QSizePolicy::Fixed 10 10 Qt::Horizontal QSizePolicy::Fixed 20 20 Qt::Horizontal QSizePolicy::Fixed 20 20 QLayout::SetDefaultConstraint 0 0 0 256 256 16777215 16777215 QFrame::Panel QFrame::Sunken 0 0 0 0 0 1 0 256 256 16777215 16777215 1 0 256 20 16777215 20 QFrame::Panel QFrame::Sunken true 0 0 20 256 20 16777215 QFrame::Panel QFrame::Sunken true - - - 6 - - + + + + + Logarithmic + + + + + + + + + + + 0 + 0 + + + + Reset + + + + 0 0 Channel: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - + Qt::Horizontal 10 10 - - - - Logarithmic - - + + - - - - - 0 - 0 - - + + - Reset + Driver channel + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter KisIntParseSpinBox QSpinBox
kis_int_parse_spin_box.h
KisCurveWidget
widgets/kis_curve_widget.h
cmbChannel intIn intOut