filling with gradients seems to work

This commit is contained in:
Uwe Rathmann 2024-06-04 18:17:07 +02:00
parent bf7fe7c8f3
commit 1a08de914a
2 changed files with 103 additions and 123 deletions

View File

@ -103,8 +103,15 @@ ShadowedArc::ShadowedArc( QQuickItem* parent )
//setFillColor( Qt::darkRed ); //setFillColor( Qt::darkRed );
const QskGradient gradient( Qt::darkRed, Qt::darkYellow ); const QskGradientStops stops =
setGradientHint( Arc, gradient ); {
{ 0.1, Qt::darkRed },
{ 0.5, Qt::darkYellow },
{ 0.75, Qt::darkBlue },
{ 1.0, Qt::darkRed }
};
setGradientHint( Arc, stops );
setBorderWidth( 0 ); setBorderWidth( 0 );
setBorderColor( Qt::gray ); setBorderColor( Qt::gray );

View File

@ -26,73 +26,6 @@ static inline QskVertex::ColoredLine* qskAllocateColoredLines(
return reinterpret_cast< QskVertex::ColoredLine* >( geometry.vertexData() ); return reinterpret_cast< QskVertex::ColoredLine* >( geometry.vertexData() );
} }
namespace
{
/*
QskVertex::ArcIterator is slightly more efficient as it increments
cos/sin instead of doing the table lookups that are behind qFastCos/qFastSin.
For the moment we go with qFastCos/qFastSin, maybe later: TODO ...
*/
class AngleIterator
{
public:
AngleIterator( qreal radians1, qreal radians2, int stepCount );
inline void operator++() { increment(); }
void increment();
inline qreal cos() const { return m_cos; }
inline qreal sin() const { return m_sin; }
inline int step() const { return m_stepIndex; }
inline int stepCount() const { return m_stepCount; }
inline bool isDone() const { return m_stepIndex >= m_stepCount; }
inline qreal progress() const { return qreal( m_stepIndex ) / m_stepCount; }
private:
qreal m_cos;
qreal m_sin;
int m_stepIndex;
int m_stepCount;
const qreal m_radians1;
const qreal m_radians2;
const qreal m_radiansStep;
};
inline AngleIterator::AngleIterator( qreal radians1, qreal radians2, int stepCount )
: m_stepIndex( 0 )
, m_stepCount( stepCount )
, m_radians1( radians1 )
, m_radians2( radians2 )
, m_radiansStep( ( radians2 - radians1 ) / ( stepCount - 1 ) )
{
m_cos = qFastCos( radians1 );
m_sin = qFastSin( radians1 );
}
inline void AngleIterator::increment()
{
if ( ++m_stepIndex >= m_stepCount )
{
if ( m_stepIndex == m_stepCount )
{
m_cos = qFastCos( m_radians2 );
m_sin = qFastSin( m_radians2 );
}
}
else
{
const auto radians = m_radians1 + m_stepIndex * m_radiansStep;
m_cos = qFastCos( radians );
m_sin = qFastSin( radians );
}
}
}
namespace namespace
{ {
class EllipseStroker class EllipseStroker
@ -109,6 +42,12 @@ namespace
private: private:
int arcLineCount() const; int arcLineCount() const;
qreal radiansAt( qreal progress ) const;
void setFillLine( const qreal radians,
const QskVertex::Color, QskVertex::ColoredLine& ) const;
const qreal m_extent;
// radius // radius
const qreal m_rx; const qreal m_rx;
@ -121,34 +60,33 @@ namespace
const qreal m_radians1; const qreal m_radians1;
const qreal m_radians2; const qreal m_radians2;
const qreal m_extent;
const QskGradient& m_gradient; const QskGradient& m_gradient;
}; };
EllipseStroker::EllipseStroker( const QRectF& rect, EllipseStroker::EllipseStroker( const QRectF& rect,
const QskArcMetrics& m, qreal borderWidth, const QskGradient& gradient ) const QskArcMetrics& m, qreal borderWidth, const QskGradient& gradient )
: m_rx( 0.5 * rect.width() ) : m_extent( 0.5 * ( m.thickness() - borderWidth ) )
, m_ry( 0.5 * rect.height() ) , m_rx( 0.5 * rect.width() - m_extent )
, m_cx( rect.x() + m_rx ) , m_ry( 0.5 * rect.height() - m_extent )
, m_cy( rect.y() + m_ry ) , m_cx( rect.center().x() )
, m_cy( rect.center().y() )
, m_radians1( qDegreesToRadians( m.startAngle() ) ) , m_radians1( qDegreesToRadians( m.startAngle() ) )
, m_radians2( qDegreesToRadians( m.endAngle() ) ) , m_radians2( qDegreesToRadians( m.endAngle() ) )
, m_extent( 0.5 * ( m.thickness() - borderWidth ) )
, m_gradient( gradient ) , m_gradient( gradient )
{ {
} }
int EllipseStroker::fillCount() const int EllipseStroker::fillCount() const
{ {
return arcLineCount(); return arcLineCount() + m_gradient.stepCount() - 1;
} }
int EllipseStroker::arcLineCount() const int EllipseStroker::arcLineCount() const
{ {
// not very sophisticated - TODO ... // not very sophisticated - TODO ...
const auto radius = qMax( m_rx, m_ry ); const auto radius = qMax( m_rx, m_ry ) + m_extent; // outer border
const auto radians = qAbs( m_radians2 - m_radians1 ); const auto radians = qAbs( m_radians2 - m_radians1 );
const auto count = qCeil( ( radius * radians ) / 3.0 ); const auto count = qCeil( ( radius * radians ) / 3.0 );
@ -169,63 +107,86 @@ namespace
return 0; return 0;
} }
inline void EllipseStroker::setFillLine( const qreal radians,
const QskVertex::Color color, QskVertex::ColoredLine& line ) const
{
const auto cos = qFastCos( radians );
const auto sin = qFastSin( radians );
/*
The inner/outer points are found by shifting orthogonally along the
ellipse tangent:
m = w / h * tan( angle )
y = m * x;
x² + y² = dt
=> x = dt / sqrt( 1.0 + m² );
Note: the angle of the orthogonal vector could
also be found ( first quadrant ) by:
atan2( tan( angle ), h / w );
*/
qreal dx, dy;
if ( qFuzzyIsNull( cos ) )
{
dx = 0.0;
dy = ( sin > 0.0 ) ? m_extent : -m_extent;
}
else
{
const qreal m = ( m_rx / m_ry ) * ( sin / cos );
const qreal t = m_extent / qSqrt( 1.0 + m * m );
dx = ( cos >= 0.0 ) ? t : -t;
dy = m * dx;
}
const auto x = m_cx + m_rx * cos;
const auto y = m_cy - m_ry * sin;
line.setLine( x + dx, y - dy, x - dx, y + dy, color );
}
int EllipseStroker::setFillLines( QskVertex::ColoredLine* lines ) const int EllipseStroker::setFillLines( QskVertex::ColoredLine* lines ) const
{ {
const auto w = m_rx - m_extent;
const auto h = m_ry - m_extent;
const auto aspectRatio = w / h;
auto l = lines; auto l = lines;
const QskVertex::Color color1 = m_gradient.rgbStart(); QskBoxRenderer::GradientIterator it;
const QskVertex::Color color2 = m_gradient.rgbEnd(); if ( m_gradient.stepCount() <= 1 )
for ( AngleIterator it( m_radians1, m_radians2, arcLineCount() ); !it.isDone(); ++it )
{ {
/* it.reset( m_gradient.rgbStart(), m_gradient.rgbEnd() );
The inner/outer points are found by shifting orthogonally along the }
ellipse tangent: else
{
it.reset( m_gradient.stops() );
it.advance(); // the first stop is always covered by the contour
}
m = w / h * tan( angle ) const auto stepCount = arcLineCount();
y = m * x; const auto stepSize = ( m_radians2 - m_radians1 ) / ( stepCount - 1 );
x² + y² = dt
=> x = dt / sqrt( 1.0 + m² ); for ( int i = 0; i < stepCount; i++ )
{
const auto progress = qreal( i ) / stepCount;
Note: the angle of the orthogonal vector could for ( ; it.position() < progress; it.advance() )
also be found ( first quadrant ) by: setFillLine( radiansAt( it.position() ), it.color(), *l++ );
atan2( tan( angle ), h / w ); const auto color = it.colorAt( progress );
*/ setFillLine( m_radians1 + i * stepSize, color, *l++ );
qreal dx, dy;
if ( qFuzzyIsNull( it.cos() ) )
{
dx = 0.0;
dy = ( it.sin() > 0.0 ) ? m_extent : -m_extent;
}
else
{
const qreal m = aspectRatio * ( it.sin() / it.cos() );
const qreal t = m_extent / qSqrt( 1.0 + m * m );
dx = ( it.cos() >= 0.0 ) ? t : -t;
dy = m * dx;
}
const auto x = m_cx + w * it.cos();
const auto y = m_cy - h * it.sin();
auto color = color1;
if ( color1 != color2 )
color = color.interpolatedTo( color2, it.progress() );
l++->setLine( x + dx, y - dy, x - dx, y + dy, color );
} }
return l - lines; return l - lines;
} }
inline qreal EllipseStroker::radiansAt( qreal progress ) const
{
return m_radians1 + progress * ( m_radians2 - m_radians1 );
}
} }
bool QskArcRenderer::isGradientSupported( const QskGradient& gradient ) bool QskArcRenderer::isGradientSupported( const QskGradient& gradient )
@ -250,9 +211,21 @@ void QskArcRenderer::renderFillGeometry( const QRectF& rect,
if ( lines ) if ( lines )
{ {
const auto effectiveCount = stroker.setFillLines( lines ); const auto effectiveCount = stroker.setFillLines( lines );
if ( lineCount != effectiveCount ) if ( effectiveCount > lineCount )
{ {
qWarning() << lineCount << effectiveCount; qFatal( "geometry: allocated memory exceeded: %d vs. %d",
effectiveCount, lineCount );
}
if ( effectiveCount < lineCount )
{
/*
Gradient or contour lines might be at the same position
and we end up with less lines, than expected.
*/
for ( int i = effectiveCount; i < lineCount; i++ )
lines[i] = lines[i - 1];
} }
} }
} }