From 1f28eec9dee5aeb232626393a07eb65dddb8600b Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Mon, 24 Oct 2022 17:08:48 +0200 Subject: [PATCH] working towards improved QskGradient --- src/common/QskGradient.cpp | 291 ++++++--------------------------- src/common/QskGradient.h | 9 +- src/common/QskGradientStop.cpp | 175 ++++++++++++++++++++ src/common/QskGradientStop.h | 16 ++ 4 files changed, 246 insertions(+), 245 deletions(-) diff --git a/src/common/QskGradient.cpp b/src/common/QskGradient.cpp index 50e675a1..961d03a8 100644 --- a/src/common/QskGradient.cpp +++ b/src/common/QskGradient.cpp @@ -52,108 +52,12 @@ static inline bool qskIsGradientValid( const QskGradientStops& stops ) return true; } -static inline QColor qskInterpolated( - const QskGradientStop& s1, const QskGradientStop& s2, qreal pos ) +static inline bool qskCanBeInterpolated( const QskGradient& from, const QskGradient& to ) { - if ( s1.color() == s2.color() ) - return s1.color(); + if ( from.isMonochrome() || to.isMonochrome() ) + return true; - const qreal ratio = ( pos - s1.position() ) / ( s2.position() - s1.position() ); - return QskRgb::interpolated( s1.color(), s2.color(), ratio ); -} - -static inline bool qskComparePositions( - const QskGradientStops& s1, const QskGradientStops& s2 ) -{ - if ( s1.count() != s2.count() ) - return false; - - // the first is always at 0.0, the last at 1.0 - for ( int i = 1; i < s1.count() - 1; i++ ) - { - if ( s1[ i ].position() != s2[ i ].position() ) - return false; - } - - return true; -} - -static inline QskGradientStops qskExpandedStops( - const QskGradientStops& s1, const QskGradientStops& s2 ) -{ - // expand s1 by stops matching to the positions from s2 - - if ( qskComparePositions( s1, s2 ) ) - return s1; - - QskGradientStops stops; - - stops += s1.first(); - - int i = 1, j = 1; - while ( ( i < s1.count() - 1 ) || ( j < s2.count() - 1 ) ) - { - if ( s1[ i ].position() < s2[ j ].position() ) - { - stops += s1[ i++ ]; - } - else - { - const qreal pos = s2[ j++ ].position(); - stops += QskGradientStop( pos, qskInterpolated( s1[ i - 1 ], s1[ i ], pos ) ); - } - } - - stops += s1.last(); - - return stops; -} - -static inline QskGradientStops qskExtractedStops( - const QskGradientStops& stops, qreal from, qreal to ) -{ - QskGradientStops extracted; - - if ( from == to ) - extracted.reserve( 2 ); - else - extracted.reserve( stops.size() ); - - int i = 0; - - if ( from == 0.0 ) - { - extracted += QskGradientStop( 0.0, stops[i++].color() ); - } - else - { - for ( i = 1; i < stops.count(); i++ ) - { - if ( stops[i].position() > from ) - break; - } - - const auto color = - QskGradientStop::interpolated( stops[i - 1], stops[i], from ); - - extracted += QskGradientStop( 0.0, color ); - } - - for ( ; i < stops.count(); i++ ) - { - const auto& s = stops[i]; - - if ( s.position() >= to ) - break; - - const auto pos = ( s.position() - from ) / ( to - from ); - extracted += QskGradientStop( pos, s.color() ); - } - - const auto color = QskGradientStop::interpolated( stops[i - 1], stops[i], to ); - extracted += QskGradientStop( 1.0, color ); - - return extracted; + return from.orientation() == to.orientation(); } QskGradient::QskGradient( Orientation orientation ) noexcept @@ -271,23 +175,13 @@ void QskGradient::setOrientation( Orientation orientation ) noexcept void QskGradient::setStops( const QColor& color ) { - m_stops.clear(); - m_stops.reserve( 2 ); - - m_stops.append( QskGradientStop( 0.0, color ) ); - m_stops.append( QskGradientStop( 1.0, color ) ); - + m_stops = { { 0.0, color }, { 1.0, color } }; m_isDirty = true; } -void QskGradient::setStops( const QColor& startColor, const QColor& stopColor ) +void QskGradient::setStops( const QColor& color1, const QColor& color2 ) { - m_stops.clear(); - m_stops.reserve( 2 ); - - m_stops.append( QskGradientStop( 0.0, startColor ) ); - m_stops.append( QskGradientStop( 1.0, stopColor ) ); - + m_stops = { { 0.0, color1 }, { 1.0, color2 } }; m_isDirty = true; } @@ -377,158 +271,75 @@ QskGradient QskGradient::reversed() const QskGradient QskGradient::extracted( qreal from, qreal to ) const { - if ( from > to ) - return QskGradient( m_orientation ); + auto gradient = *this; - if ( isMonochrome() || ( from <= 0.0 && to >= 1.0 ) ) - return *this; + if ( !isValid() || ( from > to ) || ( from > 1.0 ) ) + { + gradient.clearStops(); + } + else if ( isMonochrome() ) + { + from = qMax( from, 0.0 ); + to = qMin( to, 1.0 ); - from = qMax( from, 0.0 ); - to = qMin( to, 1.0 ); + const auto color = m_stops.first().color(); - const auto stops = qskExtractedStops( m_stops, from, to ); - return QskGradient( orientation(), stops ); + gradient.setStops( { { from, color }, { to, color } } ); + } + else + { + gradient.setStops( qskExtractedGradientStops( m_stops, from, to ) ); + } + + return gradient; } -QskGradient QskGradient::interpolated( - const QskGradient& to, qreal value ) const +QskGradient QskGradient::interpolated( const QskGradient& to, qreal ratio ) const { - if ( !( isValid() && to.isValid() ) ) + if ( !isValid() && !to.isValid() ) + return to; + + QskGradient gradient; + + if ( qskCanBeInterpolated( *this, to ) ) { - if ( !isValid() && !to.isValid() ) - return to; + // We simply interpolate stops - qreal progress; - const QskGradient* gradient; + gradient.setOrientation( to.orientation() ); - if ( to.isValid() ) - { - progress = value; - gradient = &to; - } - else - { - progress = 1.0 - value; - gradient = this; - } - - /* - We interpolate as if the invalid gradient would be - a transparent version of the valid gradient - */ - - auto stops = gradient->m_stops; - for ( auto& stop : stops ) - { - auto c = stop.color(); - c.setAlpha( c.alpha() * progress ); - - stop.setColor( c ); - } - - return QskGradient( gradient->orientation(), stops ); - } - - if ( isMonochrome() && to.isMonochrome() ) - { - const auto c = QskRgb::interpolated( - m_stops[ 0 ].color(), to.m_stops[ 0 ].color(), value ); - - return QskGradient( to.orientation(), c, c ); - } - - if ( isMonochrome() ) - { - // we can ignore our stops - - const auto c = m_stops[ 0 ].color(); - - auto s2 = to.m_stops; - for ( int i = 0; i < s2.count(); i++ ) - { - const auto c2 = QskRgb::interpolated( c, s2[ i ].color(), value ); - s2[ i ].setColor( c2 ); - } - - return QskGradient( to.orientation(), s2 ); - } - - if ( to.isMonochrome() ) - { - // we can ignore the stops of to - - const auto c = to.m_stops[ 0 ].color(); - - auto s2 = m_stops; - for ( int i = 0; i < s2.count(); i++ ) - { - const auto c2 = QskRgb::interpolated( s2[ i ].color(), c, value ); - s2[ i ].setColor( c2 ); - } - - return QskGradient( orientation(), s2 ); - } - - if ( m_orientation == to.m_orientation ) - { - /* - we need to have the same number of stops - at the same positions - */ - - const auto s1 = qskExpandedStops( m_stops, to.m_stops ); - auto s2 = qskExpandedStops( to.m_stops, m_stops ); - - for ( int i = 0; i < s1.count(); i++ ) - { - const auto c2 = QskRgb::interpolated( - s1[ i ].color(), s2[ i ].color(), value ); - - s2[ i ].setColor( c2 ); - } - - return QskGradient( orientation(), s2 ); + gradient.setStops( qskInterpolatedGradientStops( + m_stops, isMonochrome(), + to.m_stops, to.isMonochrome(), ratio ) ); } else { /* The interpolation is devided into 2 steps. First we - interpolate into a monochrome gradient and then change - the orientation before we continue in direction of the - final gradient. + 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 */ - const auto c = m_stops[ 0 ].color(); + const auto c = QskRgb::interpolated( startColor(), to.startColor(), 0.5 ); - if ( value <= 0.5 ) + if ( ratio < 0.5 ) { - auto s2 = m_stops; + const auto r = 2.0 * ratio; - for ( int i = 0; i < s2.count(); i++ ) - { - const auto c2 = QskRgb::interpolated( - s2[ i ].color(), c, 2 * value ); - - s2[ i ].setColor( c2 ); - } - - return QskGradient( orientation(), s2 ); + gradient.setOrientation( orientation() ); + gradient.setStops( qskInterpolatedGradientStops( m_stops, c, r ) ); } else { - auto s2 = to.m_stops; + const auto r = 2.0 * ( ratio - 0.5 ); - for ( int i = 0; i < s2.count(); i++ ) - { - const auto c2 = QskRgb::interpolated( - c, s2[ i ].color(), 2 * ( value - 0.5 ) ); - - s2[ i ].setColor( c2 ); - } - - return QskGradient( to.orientation(), s2 ); + gradient.setOrientation( to.orientation() ); + gradient.setStops( qskInterpolatedGradientStops( c, to.m_stops, r ) ); } } + + return gradient; } QVariant QskGradient::interpolate( diff --git a/src/common/QskGradient.h b/src/common/QskGradient.h index 76120a81..de29b937 100644 --- a/src/common/QskGradient.h +++ b/src/common/QskGradient.h @@ -6,7 +6,6 @@ #ifndef QSK_GRADIENT_H #define QSK_GRADIENT_H -#include "QskGlobal.h" #include "QskGradientStop.h" #include @@ -88,14 +87,14 @@ class QSK_EXPORT QskGradient void reverse(); QskGradient reversed() const; - // all stops between [from, to] with positions streched into [0,1] - QskGradient extracted( qreal from, qreal start ) const; - QskGradient interpolated( const QskGradient&, qreal value ) const; static QVariant interpolate( const QskGradient&, const QskGradient&, qreal progress ); + // all stops between [from, to] with positions streched into [0,1] + QskGradient extracted( qreal from, qreal start ) const; + QskHashValue hash( QskHashValue seed ) const; Q_INVOKABLE qreal stopAt( int index ) const noexcept; @@ -136,7 +135,7 @@ inline QskGradient::QskGradient( QGradient::Preset preset ) inline bool QskGradient::operator!=( const QskGradient& other ) const noexcept { - return ( !( *this == other ) ); + return !( *this == other ); } inline QskGradient::Orientation QskGradient::orientation() const noexcept diff --git a/src/common/QskGradientStop.cpp b/src/common/QskGradientStop.cpp index 348b4077..a4aba6a0 100644 --- a/src/common/QskGradientStop.cpp +++ b/src/common/QskGradientStop.cpp @@ -98,6 +98,15 @@ QDebug operator<<( QDebug debug, const QskGradientStop& stop ) // some helper functions around QskGradientStops +static inline QColor qskInterpolatedColor( + const QskGradientStops& stops, int index1, int index2, qreal position ) +{ + index1 = qBound( 0, index1, stops.count() - 1 ); + index2 = qBound( 0, index2, stops.count() - 1 ); + + return QskGradientStop::interpolated( stops[ index1 ], stops[ index2 ], position ); +} + bool qskIsVisible( const QskGradientStops& stops ) noexcept { for ( const auto& stop : stops ) @@ -133,9 +142,175 @@ QskGradientStops qskTransparentGradientStops( const QskGradientStops& stops, qre stop.setColor( c ); } + return stops; +} + +QskGradientStops qskInterpolatedGradientStops( + const QskGradientStops& stops, const QColor& color, qreal ratio ) +{ + auto newStops = stops; + + for ( auto& stop : newStops ) + stop.setColor( QskRgb::interpolated( stop.color(), color, ratio ) ); + return newStops; } +QskGradientStops qskInterpolatedGradientStops( + const QColor& color, const QskGradientStops& stops, qreal ratio ) +{ + auto newStops = stops; + + for ( auto& stop : newStops ) + stop.setColor( QskRgb::interpolated( color, stop.color(), ratio ) ); + + return newStops; +} + +static QskGradientStops qskInterpolatedStops( + const QskGradientStops& from, const QskGradientStops& to, qreal ratio ) +{ + /* + We have to insert interpolated stops for all positions + that can be found in s1 and s2. So in case there is no + stop at a specific position of the other stops we + have to calculate one temporarily before interpolating. + */ + QVector< QskGradientStop > stops; + + int i = 0, j = 0; + while ( ( i < from.count() ) || ( j < to.count() ) ) + { + qreal pos; + QColor c1, c2; + + if ( i == from.count() ) + { + c1 = from.last().color(); + c2 = to[j].color(); + pos = to[j++].position(); + } + else if ( j == to.count() ) + { + c1 = from[i].color(); + c2 = to.last().color(); + pos = from[i++].position(); + } + else + { + c1 = from[i].color(); + c2 = to[j].color(); + + const qreal dpos = from[i].position() - to[j].position(); + + if ( qFuzzyIsNull( dpos ) ) + { + pos = from[i++].position(); + j++; + } + else if ( dpos < 0.0 ) + { + pos = from[i++].position(); + + if ( j > 0 ) + { + const auto& stop = to[j - 1]; + c2 = QskRgb::interpolated( stop.color(), c2, pos - stop.position() ); + } + } + else + { + pos = to[j++].position(); + + if ( i > 0 ) + { + const auto& stop = from[i - 1]; + c1 = QskRgb::interpolated( stop.color(), c1, pos - stop.position() ); + } + } + } + + stops += QskGradientStop( pos, QskRgb::interpolated( c1, c2, ratio ) ); + } + + return stops; +} + +QskGradientStops qskInterpolatedGradientStops( + const QskGradientStops& from, bool fromIsMonochrome, + const QskGradientStops& to, bool toIsMonochrome, qreal ratio ) +{ + if ( from.isEmpty() && to.isEmpty() ) + return QskGradientStops(); + + if ( from.isEmpty() ) + return qskTransparentGradientStops( to, ratio ); + + if ( to.isEmpty() ) + return qskTransparentGradientStops( from, 1.0 - ratio ); + + if ( fromIsMonochrome && toIsMonochrome ) + { + const auto c = QskRgb::interpolated( + from[ 0 ].color(), to[ 0 ].color(), ratio ); + + return { { 0.0, c }, { 1.0, c } }; + } + + if ( fromIsMonochrome ) + { + return qskInterpolatedGradientStops( from[ 0 ].color(), to, ratio ); + } + + if ( toIsMonochrome ) + { + return qskInterpolatedGradientStops( from, to[ 0 ].color(), ratio ); + } + + return qskInterpolatedStops( from, to, ratio ); +} + +QskGradientStops qskExtractedGradientStops( + const QskGradientStops& stops, qreal from, qreal to ) +{ + if ( ( from > to ) || ( from > 1.0 ) || stops.isEmpty() ) + return QskGradientStops(); + + if ( ( from <= 0.0 ) && ( to >= 1.0 ) ) + return stops; + + from = qMax( from, 0.0 ); + to = qMin( to, 1.0 ); + + QVector< QskGradientStop > extracted; + extracted.reserve( stops.count() ); + + int i = 0; + + for ( ; i < stops.count(); i++ ) + { + if ( stops[i].position() > from ) + break; + } + + extracted += QskGradientStop( 0.0, + qskInterpolatedColor( stops, i - 1, i, from ) ); + + for ( ; i < stops.count(); i++ ) + { + if ( stops[i].position() >= to ) + break; + + const auto pos = ( stops[i].position() - from ) / ( to - from ); + extracted += QskGradientStop( pos, stops[i].color() ); + } + + extracted += QskGradientStop( 1.0, + qskInterpolatedColor( stops, i, i + 1, to ) ); + + return extracted; +} + QVector< QskGradientStop > qskBuildGradientStops( const QGradientStops& qtStops ) { QVector< QskGradientStop > stops; diff --git a/src/common/QskGradientStop.h b/src/common/QskGradientStop.h index 8133f957..9b5b61e1 100644 --- a/src/common/QskGradientStop.h +++ b/src/common/QskGradientStop.h @@ -127,6 +127,22 @@ typedef QVector< QskGradientStop > QskGradientStops; QSK_EXPORT bool qskIsMonochrome( const QskGradientStops& ) noexcept; QSK_EXPORT bool qskIsVisible( const QskGradientStops& ) noexcept; +QSK_EXPORT QskGradientStops qskInterpolatedGradientStops( + const QskGradientStops&, bool, const QskGradientStops&, bool, + qreal ratio ); + +QSK_EXPORT QskGradientStops qskInterpolatedGradientStops( + const QskGradientStops&, const QColor&, qreal ratio ); + +QSK_EXPORT QskGradientStops qskInterpolatedGradientStops( + const QColor&, const QskGradientStops&, qreal ratio ); + +QSK_EXPORT QskGradientStops qskTransparentGradientStops( + const QskGradientStops&, qreal ratio ); + +QSK_EXPORT QskGradientStops qskExtractedGradientStops( + const QskGradientStops&, qreal from, qreal to ); + QSK_EXPORT QskGradientStops qskBuildGradientStops( const QVector< QRgb >&, bool discrete = false );