Commit 5bc98844 authored by Jouni Pentikäinen's avatar Jouni Pentikäinen

Merge branch 'jounip/cross_curves'

parents 4163ff30 a705628c
......@@ -408,7 +408,7 @@ void KisCubicCurve::removePoint(int idx)
d->data->invalidate();
}
bool KisCubicCurve::isNull() const
bool KisCubicCurve::isIdentity() const
{
const QList<QPointF> &points = d->data->points;
......@@ -421,6 +421,19 @@ bool KisCubicCurve::isNull() const
return true;
}
bool KisCubicCurve::isConstant(qreal c) const
{
const QList<QPointF> &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;
......
......@@ -54,7 +54,15 @@ public:
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
......
......@@ -142,10 +142,10 @@ void KisCubicCurveTest::testValue()
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<QPointF> points;
points << QPointF();
......@@ -153,7 +153,7 @@ void KisCubicCurveTest::testNull()
points << QPointF(1.0,1.0);
cc.setPoints(points);
QVERIFY(cc.isNull());
QVERIFY(cc.isIdentity());
}
......
......@@ -90,7 +90,7 @@ KisCurveWidget::~KisCurveWidget()
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;
......@@ -98,12 +98,13 @@ void KisCurveWidget::setupInOutControls(QSpinBox *in, QSpinBox *out, int min, in
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)));
......@@ -128,8 +129,8 @@ void KisCurveWidget::inOutChanged(int)
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);
......@@ -142,8 +143,8 @@ void KisCurveWidget::inOutChanged(int)
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);
......
......@@ -137,9 +137,10 @@ public:
/**
* 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();
/**
......
......@@ -67,8 +67,10 @@ public:
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.
......@@ -95,8 +97,8 @@ public:
* 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);
/**
......@@ -135,16 +137,16 @@ 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;
}
......@@ -231,8 +233,8 @@ void KisCurveWidget::Private::syncIOControls()
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);
......
......@@ -39,6 +39,7 @@ 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);
......
......@@ -23,6 +23,8 @@
#include <half.h>
#endif
#include <QByteArray>
#include <kis_debug.h>
#include <klocalizedstring.h>
......@@ -101,7 +103,7 @@ public:
{
//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<const RGBPixel*>(srcU8);
RGBPixel* dst = reinterpret_cast<RGBPixel*>(dstU8);
......@@ -189,7 +191,7 @@ public:
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);
......@@ -221,7 +223,7 @@ public:
r = red;
g = green;
b = blue;
} else if (m_type == 4) {
qreal red = SCALE_TO_FLOAT(src->red);
......@@ -265,7 +267,7 @@ public:
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);
......@@ -331,7 +333,7 @@ public:
}
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)
......@@ -383,6 +385,208 @@ private:
bool m_colorize;
};
template<typename _channel_type_,typename traits>
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<QString> parameters() const override
{
QList<QString> 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<quin16>
* 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<QVector<quint16>>();
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<const RGBPixel*>(srcU8);
RGBPixel* dst = reinterpret_cast<RGBPixel*>(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<quint16> 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")
......@@ -426,3 +630,47 @@ KoColorTransformation* KisHSVAdjustmentFactory::createTransformation(const KoCol
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<QString, QVariant> 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;
}
......@@ -34,4 +34,30 @@ public:
};
class KisHSVCurveAdjustmentFactory : public KoColorTransformationFactory
{
public:
KisHSVCurveAdjustmentFactory();
QList< QPair< KoID, KoID > > supportedModels() const override;
KoColorTransformation* createTransformation(const KoColorSpace* colorSpace, QHash<QString, QVariant> 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
......@@ -11,7 +11,8 @@ public:
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;
......
......@@ -2,7 +2,9 @@ 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
)
......
......@@ -58,6 +58,7 @@
#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"
......@@ -69,6 +70,7 @@ ColorsFilters::ColorsFilters(QObject *parent, const QVariantList &)
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());
......
/*
* This file is part of Krita
*
* Copyright (c) 2018 Jouni Pentikainen <joupent@gmail.com>
*
* 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.
*