diff --git a/plugins/color/colorspaceextensions/kis_hsv_adjustment.cpp b/plugins/color/colorspaceextensions/kis_hsv_adjustment.cpp index b8f2e89b26..dc453316f7 100644 --- a/plugins/color/colorspaceextensions/kis_hsv_adjustment.cpp +++ b/plugins/color/colorspaceextensions/kis_hsv_adjustment.cpp @@ -1,622 +1,635 @@ /* * 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" << "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 == "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 * 0 = hue, 1 = saturation, 2 = value * 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: { int channel = parameter.toInt(); - KIS_ASSERT_RECOVER_RETURN(0 <= channel && channel < 3 && "Channel must be 0, 1 or 2. Ignored!"); + KIS_ASSERT_RECOVER_RETURN(0 <= channel && channel < KisHSVCurve::ChannelCount && "Invalid channel. Ignored!"); m_channel = channel; } 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 r = 0.0; - float g = 0.0; - float b = 0.0; - float max = m_curve.size() - 1; - float component[3]; - float &hue = component[0]; + + 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) { - RGBToHSV( - SCALE_TO_FLOAT(src->red), SCALE_TO_FLOAT(src->green), SCALE_TO_FLOAT(src->blue), - &component[0], &component[1], &component[2] - ); + 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 - hue /= 360.0f; + h /= 360.0f; component[m_channel] = lookupComponent(component[m_channel], max); - hue *= 360.0f; - if (hue > 360) hue -= 360; - if (hue < 0) hue += 360; + h *= 360.0f; + if (h > 360) h -= 360; + if (h < 0) h += 360; - HSVToRGB(component[0], component[1], component[2], &r, &g, &b); + 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 = src->alpha; + 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 modified + // 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_LUMA_R, PAR_LUMA_G, PAR_LUMA_B, }; QVector m_curve; int m_channel; /* 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 9ec664a677..561ce04df0 100644 --- a/plugins/color/colorspaceextensions/kis_hsv_adjustment.h +++ b/plugins/color/colorspaceextensions/kis_hsv_adjustment.h @@ -1,50 +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/filters/colorsfilters/CMakeLists.txt b/plugins/filters/colorsfilters/CMakeLists.txt index 90784342c7..69a7f66f4b 100644 --- a/plugins/filters/colorsfilters/CMakeLists.txt +++ b/plugins/filters/colorsfilters/CMakeLists.txt @@ -1,19 +1,20 @@ set(kritacolorsfilters_SOURCES colorsfilters.cpp kis_hsv_adjustment_filter.cpp virtual_channel_info.cpp + kis_multichannel_filter_base.cpp kis_perchannel_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/kis_multichannel_filter_base.cpp b/plugins/filters/colorsfilters/kis_multichannel_filter_base.cpp new file mode 100644 index 0000000000..91fc2ad928 --- /dev/null +++ b/plugins/filters/colorsfilters/kis_multichannel_filter_base.cpp @@ -0,0 +1,496 @@ +/* + * 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_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" + +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 (supportsHue) { + vchannels << VirtualChannelInfo(VirtualChannelInfo::HUE, -1, 0, cs); + } + + if (supportSaturation) { + vchannels << VirtualChannelInfo(VirtualChannelInfo::SATURATION, -1, 0, cs); + } + + if (supportsLightness) { + vchannels << VirtualChannelInfo(VirtualChannelInfo::LIGHTNESS, -1, 0, cs); + } + + return vchannels; +} + + +KisMultiChannelFilterConfiguration::KisMultiChannelFilterConfiguration(int channelCount, const QString & name, qint32 version) + : KisColorTransformationConfiguration(name, version) + , m_channelCount(channelCount) +{ + m_transfers.resize(m_channelCount); +} + +KisMultiChannelFilterConfiguration::~KisMultiChannelFilterConfiguration() +{} + +void KisMultiChannelFilterConfiguration::init() +{ + m_curves.clear(); + for (int i = 0; i < m_channelCount; ++i) { + m_curves.append(getDefaultCurve()); + } + updateTransfers(); +} + +bool KisMultiChannelFilterConfiguration::isCompatible(const KisPaintDeviceSP dev) const +{ + return (int)dev->compositionSourceColorSpace()->channelCount() == m_channelCount; +} + +void KisMultiChannelFilterConfiguration::setCurves(QList &curves) +{ + m_curves.clear(); + m_curves = curves; + m_channelCount = curves.size(); + + updateTransfers(); +} + +void KisMultiChannelFilterConfiguration::updateTransfers() +{ + m_transfers.resize(m_channelCount); + for (int i = 0; i < m_channelCount; i++) { + m_transfers[i] = m_curves[i].uint16Transfer(); + } +} + +const QVector >& +KisMultiChannelFilterConfiguration::transfers() const +{ + return m_transfers; +} + +const QList& +KisMultiChannelFilterConfiguration::curves() const +{ + return m_curves; +} + +void KisMultiChannelFilterConfiguration::fromLegacyXML(const QDomElement& root) +{ + fromXML(root); +} + +void KisMultiChannelFilterConfiguration::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 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 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_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); +} + +/** + * Initialize the dialog. + * Note: m_virtualChannels must be populated before calling this + */ +void KisMultiChannelConfigWidget::init() { + QHBoxLayout * layout = new QHBoxLayout(this); + Q_CHECK_PTR(layout); + layout->setContentsMargins(0,0,0,0); + layout->addWidget(m_page); + + resetCurves(); + + 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); + } +} + +KisMultiChannelConfigWidget::~KisMultiChannelConfigWidget() +{ + delete m_histogram; +} + +void KisMultiChannelConfigWidget::resetCurves() +{ + 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()); + } +} + +void KisMultiChannelConfigWidget::setConfiguration(const KisPropertiesConfigurationSP config) +{ + const KisMultiChannelFilterConfiguration * cfg = dynamic_cast(config.data()); + if (!cfg) { + return; + } + + 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); + + 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]); + + 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; + } + + 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 KisMultiChannelConfigWidget::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; +} + +void KisMultiChannelConfigWidget::slotChannelSelected(int index) +{ + const int virtualChannel = m_page->cmbChannel->itemData(index).toInt(); + setActiveChannel(virtualChannel); +} + +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 KisMultiChannelConfigWidget::resetCurve() +{ + 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 d1c2d825da..e620213683 100644 --- a/plugins/filters/colorsfilters/kis_perchannel_filter.cpp +++ b/plugins/filters/colorsfilters/kis_perchannel_filter.cpp @@ -1,694 +1,310 @@ /* * 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; - - 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 (supportsHue) { - vchannels << VirtualChannelInfo(VirtualChannelInfo::HUE, -1, 0, cs); - } - - if (supportSaturation) { - vchannels << VirtualChannelInfo(VirtualChannelInfo::SATURATION, -1, 0, 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()); - } - - 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); - } + init(); } 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); } -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); -} +// KisPerChannelFilter -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); -} - -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()) { colorsNull = false; } } else if (virtualChannels[i].type() == VirtualChannelInfo::HUE) { KIS_ASSERT_RECOVER_NOOP(hueTransfer.isEmpty()); hueTransfer = originalTransfers[i]; if (hueNull && !originalCurves[i].isNull()) { hueNull = false; } } else if (virtualChannels[i].type() == VirtualChannelInfo::SATURATION) { KIS_ASSERT_RECOVER_NOOP(saturationTransfer.isEmpty()); saturationTransfer = originalTransfers[i]; if (saturationNull && !originalCurves[i].isNull()) { saturationNull = false; } } else if (virtualChannels[i].type() == VirtualChannelInfo::LIGHTNESS) { KIS_ASSERT_RECOVER_NOOP(lightnessTransfer.isEmpty()); lightnessTransfer = originalTransfers[i]; 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; } } } 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"] = 0; + params["channel"] = KisHSVCurve::Hue; 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"] = 1; + params["channel"] = KisHSVCurve::Saturation; 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