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.
This commit is contained in:
Julien Cabieces 2024-08-27 16:50:56 +02:00
parent 0b034b7e87
commit dd41e147f8
3 changed files with 76 additions and 0 deletions

View File

@ -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"]
}

View File

@ -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<QColor>() : QgsSymbolLayerUtils::decodeColor( variant1.toString() );
QColor color2 = isQColor ? variant2.value<QColor>() : 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<float>( 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<QgsExpressionFunction *> &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" ) ),

View File

@ -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();