From dd41e147f8e190cc5224a6c8c380efb5c35e51e6 Mon Sep 17 00:00:00 2001 From: Julien Cabieces Date: Tue, 27 Aug 2024 16:50:56 +0200 Subject: [PATCH] feat(Expression): Add color_mix function It takes string representation or color object as arguments so the function can work with CMYK color without changing its color type and converting it to RGB. --- resources/function_help/json/color_mix | 25 ++++++++++ src/core/expression/qgsexpressionfunction.cpp | 47 +++++++++++++++++++ tests/src/core/testqgsexpression.cpp | 4 ++ 3 files changed, 76 insertions(+) create mode 100644 resources/function_help/json/color_mix diff --git a/resources/function_help/json/color_mix b/resources/function_help/json/color_mix new file mode 100644 index 00000000000..96af85411bf --- /dev/null +++ b/resources/function_help/json/color_mix @@ -0,0 +1,25 @@ +{ + "name": "color_mix", + "type": "function", + "groups": ["Color"], + "description": "Returns a color mixing the red, green, blue, and alpha values of two provided colors based on a given ratio. Returned type is the same as color arguments, i.e. a color string representation or a color object.", + "arguments": [{ + "arg": "color1", + "description": "a color string or a color object" + }, { + "arg": "color2", + "description": "a color string or a color object" + }, { + "arg": "ratio", + "description": "a ratio" + }], + "examples": [{ + "expression": "color_mix_rgb('0,0,0','255,255,255',0.5)", + "returns": "'127,127,127,255'" + }, + { + "expression": "color_mix(color_cmykf(0.9,0.9,0.9,0.9),color_cmykf(0.1,0.1,0.1,0.1),0.5)", + "returns": "CMYKA: 0.50,0.50,0.50,0.50,1.00" + }], + "tags": ["green", "blue", "red", "alpha", "mixing", "color", "colors", "provided", "ratio"] +} diff --git a/src/core/expression/qgsexpressionfunction.cpp b/src/core/expression/qgsexpressionfunction.cpp index 5fd16e27b03..07c65464d39 100644 --- a/src/core/expression/qgsexpressionfunction.cpp +++ b/src/core/expression/qgsexpressionfunction.cpp @@ -5864,6 +5864,49 @@ static QVariant fcnColorMixRgb( const QVariantList &values, const QgsExpressionC return QgsSymbolLayerUtils::encodeColor( newColor ); } +static QVariant fcnColorMix( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) +{ + const QVariant variant1 = values.at( 0 ); + const QVariant variant2 = values.at( 1 ); + + if ( variant1.userType() != variant2.userType() ) + { + parent->setEvalErrorString( QObject::tr( "Both color arguments must have the same type (string or color object)" ) ); + return QVariant(); + } + + const bool isQColor = variant1.userType() == QMetaType::Type::QColor; + QColor color1 = isQColor ? variant1.value() : QgsSymbolLayerUtils::decodeColor( variant1.toString() ); + QColor color2 = isQColor ? variant2.value() : QgsSymbolLayerUtils::decodeColor( variant2.toString() ); + if ( ( color1.spec() == QColor::Cmyk ) != ( color2.spec() == QColor::Cmyk ) ) + { + parent->setEvalErrorString( QObject::tr( "Both color arguments must have compatible color type (CMYK or RGB/HSV/HSL)" ) ); + return QVariant(); + } + + const float ratio = static_cast( std::clamp( QgsExpressionUtils::getDoubleValue( values.at( 2 ), parent ), 0., 1. ) ); + + QColor newColor; + const float alpha = color1.alphaF() * ( 1 - ratio ) + color2.alphaF() * ratio; + if ( color1.spec() == QColor::Spec::Cmyk ) + { + float cyan = color1.cyanF() * ( 1 - ratio ) + color2.cyanF() * ratio; + float magenta = color1.magentaF() * ( 1 - ratio ) + color2.magentaF() * ratio; + float yellow = color1.yellowF() * ( 1 - ratio ) + color2.yellowF() * ratio; + float black = color1.blackF() * ( 1 - ratio ) + color2.blackF() * ratio; + newColor = QColor::fromCmykF( cyan, magenta, yellow, black, alpha ); + } + else + { + float red = color1.redF() * ( 1 - ratio ) + color2.redF() * ratio; + float green = color1.greenF() * ( 1 - ratio ) + color2.greenF() * ratio; + float blue = color1.blueF() * ( 1 - ratio ) + color2.blueF() * ratio; + newColor = QColor::fromRgbF( red, green, blue, alpha ); + } + + return isQColor ? QVariant( newColor ) : QVariant( QgsSymbolLayerUtils::encodeColor( newColor ) ); +} + static QVariant fcnColorRgb( const QVariantList &values, const QgsExpressionContext *, QgsExpression *parent, const QgsExpressionNodeFunction * ) { int red = QgsExpressionUtils::getNativeIntValue( values.at( 0 ), parent ); @@ -8430,6 +8473,10 @@ const QList &QgsExpression::Functions() << QgsExpressionFunction::Parameter( QStringLiteral( "color2" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "ratio" ) ), fcnColorMixRgb, QStringLiteral( "Color" ) ) + << new QgsStaticExpressionFunction( QStringLiteral( "color_mix" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "color1" ) ) + << QgsExpressionFunction::Parameter( QStringLiteral( "color2" ) ) + << QgsExpressionFunction::Parameter( QStringLiteral( "ratio" ) ), + fcnColorMix, QStringLiteral( "Color" ) ) << new QgsStaticExpressionFunction( QStringLiteral( "color_rgb" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "red" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "green" ) ) << QgsExpressionFunction::Parameter( QStringLiteral( "blue" ) ), diff --git a/tests/src/core/testqgsexpression.cpp b/tests/src/core/testqgsexpression.cpp index 6e30bfabffe..1cab0ef2485 100644 --- a/tests/src/core/testqgsexpression.cpp +++ b/tests/src/core/testqgsexpression.cpp @@ -1897,6 +1897,10 @@ class TestQgsExpression: public QObject QTest::newRow( "color grayscale average object" ) << "color_grayscale_average(color_rgbf(0.6,0.5,0.1))" << false << QVariant( QColor::fromRgbF( 0.4, 0.4, 0.4 ) ); QTest::newRow( "color grayscale average cmyk" ) << "color_grayscale_average(color_cmykf(0.6,0.5,0.1,0.8))" << false << QVariant( QColor::fromCmykF( 0.4, 0.4, 0.4, 0.8 ) ); QTest::newRow( "color mix rgb" ) << "color_mix_rgb('0,0,0,100','255,255,255',0.5)" << false << QVariant( "127,127,127,177" ); + QTest::newRow( "color mix" ) << "color_mix('0,0,0,100','255,255,255',0.5)" << false << QVariant( "128,128,128,178" ); + QTest::newRow( "color mix mixed types" ) << "color_mix('0,0,0,100',color_rgbf(1.0,1.0,1.0),0.5)" << true << QVariant(); + QTest::newRow( "color mix mixed color types" ) << "color_mix(color_cmykf(1.0,1.0,1.0,1.0),color_rgbf(1.0,1.0,1.0),0.5)" << true << QVariant(); + QTest::newRow( "color mix cmyk" ) << "color_mix(color_cmykf(0.9,0.9,0.9,0.9),color_cmykf(0.1,0.1,0.1,0.1),0.5)" << false << QVariant( QColor::fromCmykF( 0.5, 0.5, 0.5, 0.5 ) ); QTest::newRow( "color part bad color" ) << "color_part('notacolor','red')" << true << QVariant(); QTest::newRow( "color part bad part" ) << "color_part(color_rgb(255,127,0),'bad')" << true << QVariant();