qskinny/src/common/QskGradient.cpp

744 lines
17 KiB
C++
Raw Normal View History

2017-07-21 16:21:34 +00:00
/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/
#include "QskGradient.h"
#include "QskRgbValue.h"
#include "QskGradientDirection.h"
2022-11-29 09:59:09 +00:00
#include "QskFunctions.h"
2017-07-21 16:21:34 +00:00
2018-08-03 06:15:28 +00:00
#include <qvariant.h>
2022-12-01 12:45:32 +00:00
#include <qdebug.h>
static void qskRegisterGradient()
{
qRegisterMetaType< QskGradient >();
2020-10-30 06:29:43 +00:00
2022-03-30 16:30:22 +00:00
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
QMetaType::registerEqualsComparator< QskGradient >();
#endif
2020-10-30 06:29:43 +00:00
QMetaType::registerConverter< QColor, QskGradient >(
[]( const QColor& color ) { return QskGradient( color ); } );
}
Q_CONSTRUCTOR_FUNCTION( qskRegisterGradient )
2021-09-17 11:35:11 +00:00
static inline bool qskIsGradientValid( const QskGradientStops& stops )
2017-07-21 16:21:34 +00:00
{
if ( stops.isEmpty() )
2017-07-21 16:21:34 +00:00
return false;
if ( stops.first().position() < 0.0 || stops.last().position() > 1.0 )
2017-07-21 16:21:34 +00:00
return false;
if ( !stops.first().color().isValid() )
return false;
for ( int i = 1; i < stops.size(); i++ )
{
2018-08-03 06:15:28 +00:00
if ( stops[ i ].position() < stops[ i - 1 ].position() )
2017-07-21 16:21:34 +00:00
return false;
2018-08-03 06:15:28 +00:00
if ( !stops[ i ].color().isValid() )
2017-07-21 16:21:34 +00:00
return false;
}
return true;
}
2022-10-24 15:08:48 +00:00
static inline bool qskCanBeInterpolated( const QskGradient& from, const QskGradient& to )
{
2022-10-24 15:08:48 +00:00
if ( from.isMonochrome() || to.isMonochrome() )
return true;
2022-10-31 13:42:08 +00:00
return from.type() == to.type();
2017-07-21 16:21:34 +00:00
}
2018-08-03 06:15:28 +00:00
QskGradient::QskGradient( const QColor& color )
2017-07-21 16:21:34 +00:00
{
setStops( color );
2017-07-21 16:21:34 +00:00
}
2022-10-31 13:42:08 +00:00
QskGradient::QskGradient( const QColor& color1, const QColor& color2 )
2020-07-31 10:42:36 +00:00
{
2022-10-31 13:42:08 +00:00
setStops( color1, color2 );
2020-07-31 10:42:36 +00:00
}
2022-10-31 13:42:08 +00:00
QskGradient::QskGradient( QGradient::Preset preset )
2020-07-31 10:42:36 +00:00
{
2022-10-31 13:42:08 +00:00
setStops( qskBuildGradientStops( QGradient( preset ).stops() ) );
2020-07-31 10:42:36 +00:00
}
2022-10-31 13:42:08 +00:00
QskGradient::QskGradient( const QVector< QskGradientStop >& stops )
{
2022-01-21 06:49:38 +00:00
setStops( stops );
}
QskGradient::QskGradient( const QGradient* qGradient )
{
if ( qGradient == nullptr )
return;
switch( qGradient->type() )
{
case QGradient::LinearGradient:
{
m_type = Linear;
const auto g = static_cast< const QLinearGradient* >( qGradient );
m_values[0] = g->start().x();
m_values[1] = g->start().y();
m_values[2] = g->finalStop().x();
m_values[3] = g->finalStop().y();
break;
}
case QGradient::RadialGradient:
{
m_type = Radial;
const auto g = static_cast< const QRadialGradient* >( qGradient );
if ( ( g->center() != g->focalPoint() ) || ( g->radius() != g->focalRadius() ) )
qWarning() << "QskGradient: extended radial gradients are not supported.";
m_values[0] = g->focalPoint().x();
m_values[1] = g->focalPoint().y();
m_values[2] = g->focalRadius();
m_values[3] = 0.0;
break;
}
case QGradient::ConicalGradient:
{
m_type = Conic;
const auto g = static_cast< const QConicalGradient* >( qGradient );
m_values[0] = g->center().x();
m_values[1] = g->center().y();
m_values[2] = g->angle();
m_values[3] = 360.0;
break;
}
default:
{
m_type = Stops;
break;
}
}
m_spread = static_cast< Spread >( qGradient->spread() );
m_stops = qskBuildGradientStops( qGradient->stops() );
}
2022-10-31 13:42:08 +00:00
QskGradient::QskGradient( const QskGradient& other ) noexcept
: m_stops( other.m_stops )
, m_values{ other.m_values[0], other.m_values[1],
other.m_values[2], other.m_values[3], }
, m_type( other.m_type )
, m_spread( other.m_spread )
, m_isDirty( other.m_isDirty )
, m_isValid( other.m_isValid )
, m_isMonchrome( other.m_isMonchrome )
, m_isVisible( other.m_isVisible )
2022-03-30 10:28:45 +00:00
{
}
2022-10-31 13:42:08 +00:00
QskGradient::~QskGradient()
2022-03-18 15:50:34 +00:00
{
}
2022-10-31 13:42:08 +00:00
QskGradient& QskGradient::operator=( const QskGradient& other ) noexcept
2017-07-21 16:21:34 +00:00
{
2022-10-31 13:42:08 +00:00
m_type = other.m_type;
m_spread = other.m_spread;
m_stops = other.m_stops;
m_values[0] = other.m_values[0];
m_values[1] = other.m_values[1];
m_values[2] = other.m_values[2];
m_values[3] = other.m_values[3];
m_isDirty = other.m_isDirty;
m_isValid = other.m_isValid;
m_isMonchrome = other.m_isMonchrome;
m_isVisible = other.m_isVisible;
return *this;
2017-07-21 16:21:34 +00:00
}
bool QskGradient::operator==( const QskGradient& other ) const noexcept
2017-07-21 16:21:34 +00:00
{
2022-10-31 13:42:08 +00:00
return ( m_type == other.m_type )
&& ( m_spread == other.m_spread )
&& ( m_values[0] == other.m_values[0] )
&& ( m_values[1] == other.m_values[1] )
&& ( m_values[2] == other.m_values[2] )
&& ( m_values[3] == other.m_values[3] )
&& ( m_stops == other.m_stops );
2017-07-21 16:21:34 +00:00
}
void QskGradient::updateStatusBits() const
2017-07-21 16:21:34 +00:00
{
// doing all bits in one loop ?
m_isValid = qskIsGradientValid( m_stops );
if ( m_isValid )
{
m_isMonchrome = qskIsMonochrome( m_stops );
m_isVisible = qskIsVisible( m_stops );
}
else
{
m_isMonchrome = true;
m_isVisible = false;
}
2022-11-29 09:59:09 +00:00
if ( m_isVisible )
{
switch( m_type )
{
case Linear:
{
m_isVisible = !( qskFuzzyCompare( m_values[0], m_values[2] )
&& qskFuzzyCompare( m_values[1], m_values[3] ) );
break;
}
case Radial:
{
m_isVisible = m_values[2] > 0.0; // radius
break;
}
case Conic:
{
m_isVisible = !qFuzzyIsNull( m_values[3] ); // spanAngle
break;
}
default:
break;
}
}
m_isDirty = false;
2017-07-21 16:21:34 +00:00
}
bool QskGradient::isValid() const noexcept
{
if ( m_isDirty )
updateStatusBits();
return m_isValid;
}
bool QskGradient::isMonochrome() const noexcept
2017-07-21 16:21:34 +00:00
{
if ( m_isDirty )
updateStatusBits();
2017-07-21 16:21:34 +00:00
return m_isMonchrome;
2017-07-21 16:21:34 +00:00
}
bool QskGradient::isVisible() const noexcept
2017-07-21 16:21:34 +00:00
{
if ( m_isDirty )
updateStatusBits();
return m_isVisible;
2017-07-21 16:21:34 +00:00
}
void QskGradient::setStops( const QColor& color )
2017-07-21 16:21:34 +00:00
{
2022-10-24 15:08:48 +00:00
m_stops = { { 0.0, color }, { 1.0, color } };
m_isDirty = true;
2017-07-21 16:21:34 +00:00
}
2022-10-24 15:08:48 +00:00
void QskGradient::setStops( const QColor& color1, const QColor& color2 )
2017-07-21 16:21:34 +00:00
{
2022-10-24 15:08:48 +00:00
m_stops = { { 0.0, color1 }, { 1.0, color2 } };
m_isDirty = true;
2017-07-21 16:21:34 +00:00
}
2022-10-31 13:42:08 +00:00
void QskGradient::setStops( QGradient::Preset preset )
{
const auto stops = qskBuildGradientStops( QGradient( preset ).stops() );
setStops( stops );
}
2021-09-17 11:35:11 +00:00
void QskGradient::setStops( const QskGradientStops& stops )
2017-07-21 16:21:34 +00:00
{
if ( !stops.isEmpty() && !qskIsGradientValid( stops ) )
2017-07-21 16:21:34 +00:00
{
qWarning( "Invalid gradient stops" );
m_stops.clear();
}
else
{
m_stops = stops;
2017-07-21 16:21:34 +00:00
}
m_isDirty = true;
2017-07-21 16:21:34 +00:00
}
int QskGradient::stepCount() const noexcept
{
if ( !isValid() )
return 0;
int steps = m_stops.count() - 1;
if ( m_stops.first().position() > 0.0 )
steps++;
if ( m_stops.last().position() < 1.0 )
steps++;
return steps;
}
2022-10-24 14:40:47 +00:00
qreal QskGradient::stopAt( int index ) const noexcept
2017-07-21 16:21:34 +00:00
{
2022-10-24 14:40:47 +00:00
if ( index < 0 || index >= m_stops.size() )
2017-07-21 16:21:34 +00:00
return -1.0;
2018-08-03 06:15:28 +00:00
return m_stops[ index ].position();
2017-07-21 16:21:34 +00:00
}
2022-10-24 14:40:47 +00:00
bool QskGradient::hasStopAt( qreal value ) const noexcept
{
// better use binary search TODO ...
for ( auto& stop : m_stops )
{
if ( stop.position() == value )
return true;
if ( stop.position() > value )
break;
}
return false;
}
QColor QskGradient::colorAt( int index ) const noexcept
2017-07-21 16:21:34 +00:00
{
if ( index >= m_stops.size() )
return QColor();
2018-08-03 06:15:28 +00:00
return m_stops[ index ].color();
2017-07-21 16:21:34 +00:00
}
void QskGradient::setAlpha( int alpha )
{
for ( auto& stop : m_stops )
{
2020-08-15 12:42:28 +00:00
auto c = stop.color();
if ( c.isValid() && c.alpha() )
{
c.setAlpha( alpha );
stop.setColor( c );
}
}
m_isDirty = true;
}
void QskGradient::setSpread( Spread spread )
2022-10-31 13:42:08 +00:00
{
m_spread = spread;
}
2020-07-31 10:42:36 +00:00
void QskGradient::reverse()
{
if ( isMonochrome() )
return;
std::reverse( m_stops.begin(), m_stops.end() );
for( auto& stop : m_stops )
stop.setPosition( 1.0 - stop.position() );
}
QskGradient QskGradient::reversed() const
{
auto gradient = *this;
gradient.reverse();
return gradient;
}
QskGradient QskGradient::extracted( qreal from, qreal to ) const
{
2022-10-24 15:08:48 +00:00
auto gradient = *this;
2020-07-31 10:42:36 +00:00
2022-10-24 15:08:48 +00:00
if ( !isValid() || ( from > to ) || ( from > 1.0 ) )
{
2022-10-24 15:08:48 +00:00
gradient.clearStops();
}
2022-10-24 15:08:48 +00:00
else if ( isMonochrome() )
{
2022-10-24 15:08:48 +00:00
from = qMax( from, 0.0 );
to = qMin( to, 1.0 );
2022-10-24 15:08:48 +00:00
const auto color = m_stops.first().color();
2022-10-24 15:08:48 +00:00
gradient.setStops( { { from, color }, { to, color } } );
}
2022-10-24 15:08:48 +00:00
else
{
2022-10-24 15:08:48 +00:00
gradient.setStops( qskExtractedGradientStops( m_stops, from, to ) );
}
2022-10-24 15:08:48 +00:00
return gradient;
}
2022-10-24 15:08:48 +00:00
QskGradient QskGradient::interpolated( const QskGradient& to, qreal ratio ) const
{
if ( !isValid() && !to.isValid() )
return to;
2022-10-24 15:08:48 +00:00
QskGradient gradient;
2022-10-24 15:08:48 +00:00
if ( qskCanBeInterpolated( *this, to ) )
{
2022-10-31 13:42:08 +00:00
// We simply interpolate stops and values
2022-10-31 13:42:08 +00:00
gradient = to;
2022-10-31 16:35:13 +00:00
const auto stops = qskInterpolatedGradientStops(
m_stops, isMonochrome(), to.m_stops, to.isMonochrome(), ratio );
gradient.setStops( stops );
2022-10-31 13:42:08 +00:00
for ( uint i = 0; i < sizeof( m_values ) / sizeof( m_values[0] ); i++ )
gradient.m_values[i] = m_values[i] + ratio * ( to.m_values[i] - m_values[i] );
}
else
{
/*
The interpolation is devided into 2 steps. First we
2022-10-24 15:08:48 +00:00
interpolate into a monochrome gradient and then
recolor the gradient towards the target gradient
This will always result in a smooth transition - even, when
interpolating between different gradient types
*/
2022-10-24 15:08:48 +00:00
const auto c = QskRgb::interpolated( startColor(), to.startColor(), 0.5 );
2022-10-24 15:08:48 +00:00
if ( ratio < 0.5 )
{
2022-10-24 15:08:48 +00:00
const auto r = 2.0 * ratio;
2022-10-31 13:42:08 +00:00
gradient = *this;
2022-10-24 15:08:48 +00:00
gradient.setStops( qskInterpolatedGradientStops( m_stops, c, r ) );
}
else
{
2022-10-24 15:08:48 +00:00
const auto r = 2.0 * ( ratio - 0.5 );
2022-10-31 13:42:08 +00:00
gradient = to;
2022-10-24 15:08:48 +00:00
gradient.setStops( qskInterpolatedGradientStops( c, to.m_stops, r ) );
}
}
2022-10-24 15:08:48 +00:00
return gradient;
}
QVariant QskGradient::interpolate(
const QskGradient& from, const QskGradient& to, qreal progress )
{
return QVariant::fromValue( from.interpolated( to, progress ) );
}
void QskGradient::clearStops()
{
if ( !m_stops.isEmpty() )
{
m_stops.clear();
m_isDirty = true;
}
}
QskHashValue QskGradient::hash( QskHashValue seed ) const
{
2022-10-31 13:42:08 +00:00
auto hash = qHashBits( &m_type, sizeof( m_type ), seed );
if ( m_type != Stops )
hash = qHashBits( m_values, sizeof( m_values ), seed );
for ( const auto& stop : m_stops )
hash = stop.hash( hash );
return hash;
}
void QskGradient::setLinearDirection( Qt::Orientation orientation )
{
setLinearDirection( QskLinearDirection( orientation ) );
}
void QskGradient::setLinearDirection( qreal x1, qreal y1, qreal x2, qreal y2 )
{
setLinearDirection( QskLinearDirection( x1, y1, x2, y2 ) );
}
void QskGradient::setLinearDirection( const QskLinearDirection& direction )
{
m_type = Linear;
m_values[0] = direction.x1();
m_values[1] = direction.y1();
m_values[2] = direction.x2();
m_values[3] = direction.y2();
}
QskLinearDirection QskGradient::linearDirection() const
{
Q_ASSERT( m_type == Linear );
if ( m_type != Linear )
return QskLinearDirection( 0.0, 0.0, 0.0, 0.0 );
return QskLinearDirection( m_values[0], m_values[1], m_values[2], m_values[3] );
}
void QskGradient::setRadialDirection( const qreal x, qreal y, qreal radius )
{
setRadialDirection( QskRadialDirection( x, y, radius ) );
}
void QskGradient::setRadialDirection( const QskRadialDirection& direction )
{
m_type = Radial;
m_values[0] = direction.center().x();
m_values[1] = direction.center().y();
m_values[2] = direction.radius();
m_values[3] = 0.0;
}
QskRadialDirection QskGradient::radialDirection() const
{
Q_ASSERT( m_type == Radial );
if ( m_type != Radial )
return QskRadialDirection( 0.5, 0.5, 0.0 );
return QskRadialDirection( m_values[0], m_values[1], m_values[2] );
}
void QskGradient::setConicDirection( qreal x, qreal y )
{
setConicDirection( QskConicDirection( x, y ) );
}
void QskGradient::setConicDirection( qreal x, qreal y,
qreal startAngle, qreal spanAngle )
{
setConicDirection( QskConicDirection( x, y, startAngle, spanAngle ) );
}
void QskGradient::setConicDirection( const QskConicDirection& direction )
{
m_type = Conic;
m_values[0] = direction.center().x();
m_values[1] = direction.center().y();
m_values[2] = direction.startAngle();
m_values[3] = direction.spanAngle();
}
QskConicDirection QskGradient::conicDirection() const
{
Q_ASSERT( m_type == Conic );
if ( m_type != Conic )
return QskConicDirection( 0.5, 0.5, 0.0, 0.0 );
return QskConicDirection( m_values[0], m_values[1], m_values[2], m_values[3] );
}
2022-11-29 09:59:09 +00:00
void QskGradient::setDirection( Type type )
{
if ( type == m_type )
return;
switch( type )
{
case Linear:
setLinearDirection( QskLinearDirection() );
break;
case Radial:
setRadialDirection( QskRadialDirection() );
break;
case Conic:
setConicDirection( QskConicDirection() );
break;
case Stops:
resetDirection();
break;
}
}
void QskGradient::resetDirection()
{
m_type = Stops;
m_values[0] = m_values[1] = m_values[2] = m_values[3] = 0.0;
}
QGradient* QskGradient::toQGradient() const
{
QGradient* qGradient = nullptr;
switch( m_type )
{
case Linear:
{
qGradient = new QLinearGradient(
m_values[0], m_values[1], m_values[2], m_values[3] );
break;
}
case Radial:
{
qGradient = new QRadialGradient(
m_values[0], m_values[1], m_values[2] );
break;
}
case Conic:
{
if ( m_values[4] != 360.0 )
{
qWarning() <<
"QskGradient: spanAngle got lost, when converting to QConicalGradient";
}
qGradient = new QConicalGradient(
m_values[0], m_values[1], m_values[2] );
break;
}
default:
{
qGradient = new QGradient();
}
}
qGradient->setSpread( static_cast< QGradient::Spread >( m_spread ) );
qGradient->setStops( qskToQGradientStops( m_stops ) );
return qGradient;
}
2017-07-21 16:21:34 +00:00
#ifndef QT_NO_DEBUG_STREAM
2018-07-19 12:10:48 +00:00
#include <qdebug.h>
2017-07-21 16:21:34 +00:00
QDebug operator<<( QDebug debug, const QskGradient& gradient )
{
2022-03-30 10:28:45 +00:00
QDebugStateSaver saver( debug );
debug.nospace();
2022-11-18 11:15:20 +00:00
debug << "QskGradient(";
2022-10-31 13:42:08 +00:00
switch ( gradient.type() )
{
case QskGradient::Linear:
{
2022-11-18 11:15:20 +00:00
debug << " L(";
2022-10-31 13:42:08 +00:00
const auto dir = gradient.linearDirection();
debug << dir.start().x() << "," << dir.start().y()
<< "," << dir.stop().x() << "," << dir.stop().y() << ")";
2022-10-31 13:42:08 +00:00
break;
}
case QskGradient::Radial:
{
2022-11-18 11:15:20 +00:00
debug << " R(";
2022-10-31 13:42:08 +00:00
const auto dir = gradient.radialDirection();
2022-10-31 13:42:08 +00:00
debug << dir.center().x() << "," << dir.center().y()
<< "," << dir.radius() << ")";
2022-10-31 13:42:08 +00:00
break;
}
case QskGradient::Conic:
{
2022-11-18 11:15:20 +00:00
debug << " C(";
2022-10-31 13:42:08 +00:00
const auto dir = gradient.conicDirection();
2022-03-30 10:28:45 +00:00
debug << dir.center().x() << "," << dir.center().y()
<< ",[" << dir.startAngle() << "," << dir.spanAngle() << "])";
2022-10-31 13:42:08 +00:00
break;
}
case QskGradient::Stops:
{
break;
}
}
2022-11-18 11:15:20 +00:00
if ( !gradient.stops().isEmpty() )
2022-03-30 10:28:45 +00:00
{
if ( gradient.isMonochrome() )
{
2022-10-31 13:42:08 +00:00
debug << ' ';
QskRgb::debugColor( debug, gradient.rgbStart() );
2022-03-30 10:28:45 +00:00
}
else
{
2022-10-31 13:42:08 +00:00
debug << " ( ";
2022-03-30 10:28:45 +00:00
2022-10-31 13:42:08 +00:00
const auto& stops = gradient.stops();
for ( int i = 0; i < stops.count(); i++ )
2022-03-30 10:28:45 +00:00
{
2022-10-31 13:42:08 +00:00
if ( i != 0 )
debug << ", ";
debug << stops[i];
2022-03-30 10:28:45 +00:00
}
2022-10-31 13:42:08 +00:00
debug << " )";
2022-03-30 10:28:45 +00:00
}
}
2022-10-31 13:42:08 +00:00
switch( gradient.spread() )
{
case QskGradient::RepeatSpread:
2022-10-31 13:42:08 +00:00
debug << " RP";
break;
case QskGradient::ReflectSpread:
2022-10-31 13:42:08 +00:00
debug << " RF";
break;
case QskGradient::PadSpread:
2022-10-31 13:42:08 +00:00
break;
}
debug << " )";
2017-07-21 16:21:34 +00:00
return debug;
}
#endif
#include "moc_QskGradient.cpp"