2021-09-16 10:01:53 +02:00
|
|
|
/******************************************************************************
|
|
|
|
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
|
|
|
* This file may be used under the terms of the QSkinny License, Version 1.0
|
|
|
|
*****************************************************************************/
|
|
|
|
|
2021-10-27 15:07:17 +02:00
|
|
|
#include "QskGradientStop.h"
|
2021-09-16 10:01:53 +02:00
|
|
|
#include "QskRgbValue.h"
|
|
|
|
|
|
|
|
#include <qhashfunctions.h>
|
|
|
|
#include <qvariant.h>
|
2022-10-24 16:40:47 +02:00
|
|
|
#include <qbrush.h>
|
2021-09-16 10:01:53 +02:00
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
static void qskRegisterGradientStop()
|
|
|
|
{
|
|
|
|
qRegisterMetaType< QskGradientStop >();
|
2022-03-30 18:30:22 +02:00
|
|
|
|
|
|
|
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
|
|
|
|
QMetaType::registerEqualsComparator< QskGradientStop >();
|
|
|
|
#endif
|
2021-09-16 10:01:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Q_CONSTRUCTOR_FUNCTION( qskRegisterGradientStop )
|
|
|
|
|
|
|
|
void QskGradientStop::setPosition( qreal position ) noexcept
|
|
|
|
{
|
|
|
|
m_position = position;
|
|
|
|
}
|
|
|
|
|
|
|
|
void QskGradientStop::setColor( const QColor& color ) noexcept
|
|
|
|
{
|
|
|
|
m_color = color;
|
|
|
|
}
|
|
|
|
|
2022-11-14 08:56:30 +01:00
|
|
|
void QskGradientStop::setRgb( QRgb rgb ) noexcept
|
|
|
|
{
|
|
|
|
m_color = QColor::fromRgba( rgb );
|
|
|
|
}
|
|
|
|
|
2021-09-16 10:01:53 +02:00
|
|
|
void QskGradientStop::setStop( qreal position, const QColor& color ) noexcept
|
|
|
|
{
|
|
|
|
m_position = position;
|
|
|
|
m_color = color;
|
|
|
|
}
|
|
|
|
|
2022-11-14 09:00:02 +01:00
|
|
|
void QskGradientStop::setStop( qreal position, Qt::GlobalColor color ) noexcept
|
|
|
|
{
|
|
|
|
m_position = position;
|
|
|
|
m_color = color;
|
|
|
|
}
|
|
|
|
|
2022-03-25 10:28:06 +01:00
|
|
|
QskHashValue QskGradientStop::hash( QskHashValue seed ) const noexcept
|
2021-09-16 10:01:53 +02:00
|
|
|
{
|
2022-03-25 10:28:06 +01:00
|
|
|
auto hash = qHashBits( &m_position, sizeof( m_position ), seed );
|
2021-09-16 10:01:53 +02:00
|
|
|
return qHashBits( &m_color, sizeof( m_color ), hash );
|
|
|
|
}
|
|
|
|
|
|
|
|
QColor QskGradientStop::interpolated(
|
|
|
|
const QskGradientStop& s1, const QskGradientStop& s2, qreal position ) noexcept
|
|
|
|
{
|
|
|
|
if ( s1.color() == s2.color() )
|
|
|
|
return s1.color();
|
|
|
|
|
|
|
|
auto min = &s1;
|
|
|
|
auto max = &s2;
|
|
|
|
|
|
|
|
if ( min->position() > max->position() )
|
|
|
|
std::swap( min, max );
|
|
|
|
|
|
|
|
if ( position <= min->position() )
|
|
|
|
return min->color();
|
|
|
|
|
|
|
|
if ( position >= max->position() )
|
|
|
|
return max->color();
|
|
|
|
|
|
|
|
const qreal r = ( position - min->position() ) / ( max->position() - min->position() );
|
|
|
|
return QskRgb::interpolated( min->color(), max->color(), r );
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef QT_NO_DEBUG_STREAM
|
|
|
|
|
|
|
|
#include <qdebug.h>
|
|
|
|
|
|
|
|
QDebug operator<<( QDebug debug, const QskGradientStop& stop )
|
|
|
|
{
|
2022-03-30 12:28:45 +02:00
|
|
|
QDebugStateSaver saver( debug );
|
|
|
|
debug.nospace();
|
|
|
|
|
|
|
|
debug << stop.position() << ": ";
|
|
|
|
QskRgb::debugColor( debug, stop.color() );
|
|
|
|
|
2021-09-16 10:01:53 +02:00
|
|
|
return debug;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "moc_QskGradientStop.cpp"
|
2022-10-24 16:40:47 +02:00
|
|
|
|
|
|
|
// some helper functions around QskGradientStops
|
|
|
|
|
2022-10-24 17:08:48 +02:00
|
|
|
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 );
|
|
|
|
}
|
|
|
|
|
2022-10-24 16:40:47 +02:00
|
|
|
bool qskIsVisible( const QskGradientStops& stops ) noexcept
|
|
|
|
{
|
|
|
|
for ( const auto& stop : stops )
|
|
|
|
{
|
|
|
|
const auto& c = stop.color();
|
|
|
|
if ( c.isValid() && c.alpha() > 0 )
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool qskIsMonochrome( const QskGradientStops& stops ) noexcept
|
|
|
|
{
|
|
|
|
for ( int i = 1; i < stops.size(); i++ )
|
|
|
|
{
|
|
|
|
if ( stops[ i ].color() != stops[ 0 ].color() )
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
QskGradientStops qskTransparentGradientStops( const QskGradientStops& stops, qreal ratio )
|
|
|
|
{
|
|
|
|
auto newStops = stops;
|
|
|
|
|
|
|
|
for ( auto& stop : newStops )
|
|
|
|
{
|
|
|
|
auto c = stop.color();
|
|
|
|
c.setAlpha( c.alpha() * ratio );
|
|
|
|
|
|
|
|
stop.setColor( c );
|
|
|
|
}
|
|
|
|
|
2022-10-24 17:08:48 +02:00
|
|
|
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 ) );
|
|
|
|
|
2022-10-24 16:40:47 +02:00
|
|
|
return newStops;
|
|
|
|
}
|
|
|
|
|
2022-10-24 17:08:48 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-01-10 12:17:56 +01:00
|
|
|
QskGradientStops qskRevertedGradientStops( const QskGradientStops& stops )
|
|
|
|
{
|
|
|
|
QVector< QskGradientStop > s;
|
|
|
|
s.reserve( stops.count() );
|
|
|
|
|
|
|
|
for ( auto it = stops.crbegin(); it != stops.crend(); ++it )
|
|
|
|
s += QskGradientStop( 1.0 - it->position(), it->color() );
|
|
|
|
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
2022-10-24 16:40:47 +02:00
|
|
|
QVector< QskGradientStop > qskBuildGradientStops( const QGradientStops& qtStops )
|
|
|
|
{
|
|
|
|
QVector< QskGradientStop > stops;
|
|
|
|
stops.reserve( qtStops.count() );
|
|
|
|
|
|
|
|
for ( const auto& s : qtStops )
|
|
|
|
stops += QskGradientStop( s.first, s.second );
|
|
|
|
|
|
|
|
return stops;
|
|
|
|
}
|
|
|
|
|
|
|
|
template< typename T >
|
|
|
|
static inline QVector< QskGradientStop > qskCreateStops(
|
|
|
|
const QVector< T > colors, bool discrete )
|
|
|
|
{
|
|
|
|
QVector< QskGradientStop > stops;
|
|
|
|
|
|
|
|
const auto count = colors.count();
|
|
|
|
if ( count == 0 )
|
|
|
|
return stops;
|
|
|
|
|
|
|
|
if ( count == 1 )
|
|
|
|
{
|
|
|
|
stops.reserve( 2 );
|
|
|
|
|
|
|
|
stops += QskGradientStop( 0.0, colors[0] );
|
|
|
|
stops += QskGradientStop( 1.0, colors[0] );
|
|
|
|
|
|
|
|
return stops;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( discrete )
|
|
|
|
{
|
|
|
|
const auto step = 1.0 / count;
|
|
|
|
stops.reserve( 2 * count - 2 );
|
|
|
|
|
|
|
|
stops += QskGradientStop( 0.0, colors[0] );
|
|
|
|
|
|
|
|
for ( int i = 1; i < count; i++ )
|
|
|
|
{
|
|
|
|
const qreal pos = i * step;
|
|
|
|
stops += QskGradientStop( pos, colors[i - 1] );
|
|
|
|
stops += QskGradientStop( pos, colors[i] );
|
|
|
|
}
|
|
|
|
|
|
|
|
stops += QskGradientStop( 1.0, colors[count - 1] );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
const auto step = 1.0 / ( count - 1 );
|
|
|
|
stops.reserve( count );
|
|
|
|
|
|
|
|
stops += QskGradientStop( 0.0, colors[0] );
|
|
|
|
|
|
|
|
for ( int i = 1; i < count - 1; i++ )
|
|
|
|
stops += QskGradientStop( i * step, colors[i] );
|
|
|
|
|
|
|
|
stops += QskGradientStop( 1.0, colors[count - 1] );
|
|
|
|
}
|
|
|
|
|
|
|
|
return stops;
|
|
|
|
}
|
|
|
|
|
|
|
|
QskGradientStops qskBuildGradientStops( const QVector< QRgb >& colors, bool discrete )
|
|
|
|
{
|
|
|
|
return qskCreateStops( colors, discrete );
|
|
|
|
}
|
|
|
|
|
|
|
|
QskGradientStops qskBuildGradientStops( const QVector< QColor >& colors, bool discrete )
|
|
|
|
{
|
|
|
|
return qskCreateStops( colors, discrete );
|
|
|
|
}
|
|
|
|
|
|
|
|
QGradientStops qskToQGradientStops( const QskGradientStops& stops )
|
|
|
|
{
|
2022-12-20 15:49:09 +01:00
|
|
|
QGradientStops qStops;
|
|
|
|
qStops.reserve( stops.count() );
|
2022-10-24 16:40:47 +02:00
|
|
|
|
|
|
|
for ( const auto& stop : stops )
|
2022-12-20 15:49:09 +01:00
|
|
|
{
|
|
|
|
QPair<qreal, QColor> qStop = { stop.position(), stop.color() };
|
|
|
|
|
|
|
|
if ( !qStops.isEmpty() && qStops.last().first == qStop.first )
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
QGradient removes stops at the same position. So we have to insert
|
|
|
|
an invisible dummy offset
|
|
|
|
*/
|
|
|
|
qStop.first += 0.00001;
|
|
|
|
}
|
|
|
|
|
|
|
|
qStops += qStop;
|
|
|
|
}
|
2022-10-24 16:40:47 +02:00
|
|
|
|
2022-12-20 15:49:09 +01:00
|
|
|
return qStops;
|
2022-10-24 16:40:47 +02:00
|
|
|
}
|
|
|
|
|