From 36f088a263006393bdf66f4ea5504ad1e88ecae7 Mon Sep 17 00:00:00 2001 From: Uwe Rathmann Date: Mon, 3 Jun 2024 18:23:43 +0200 Subject: [PATCH] preperations for non monochrome gradients --- playground/shadows/ArcPage.cpp | 35 ----- playground/shadows/ShadowedArc.cpp | 5 +- src/nodes/QskArcNode.cpp | 15 ++- src/nodes/QskArcRenderNode.cpp | 2 +- src/nodes/QskArcRenderer.cpp | 203 +++++++++++++---------------- src/nodes/QskArcRenderer.h | 13 +- 6 files changed, 106 insertions(+), 167 deletions(-) diff --git a/playground/shadows/ArcPage.cpp b/playground/shadows/ArcPage.cpp index a833e2fa..e8618e04 100644 --- a/playground/shadows/ArcPage.cpp +++ b/playground/shadows/ArcPage.cpp @@ -46,31 +46,6 @@ namespace addItem( slider, 1, 1); } - { - auto slider = new Slider( "Spread Radius", -10, 50, 1, arc->spreadRadius() ); - connect( slider, &Slider::valueChanged, arc, &ShadowedArc::setSpreadRadius ); - - addItem( slider, 2, 0 ); - } - { - auto slider = new Slider( "Blur Radius", 0, 50, 1, arc->blurRadius() ); - connect( slider, &Slider::valueChanged, arc, &ShadowedArc::setBlurRadius ); - - addItem( slider, 2, 1 ); - } - { - auto slider = new Slider( "Offset X", -50, 50, 1, arc->offsetX() ); - connect( slider, &Slider::valueChanged, arc, &ShadowedArc::setOffsetX ); - - addItem( slider, 3, 0 ); - - } - { - auto slider = new Slider( "Offset Y", -50, 50, 1, arc->offsetY() ); - connect( slider, &Slider::valueChanged, arc, &ShadowedArc::setOffsetY ); - - addItem( slider, 3, 1 ); - } } }; } @@ -87,18 +62,8 @@ ArcPage::ArcPage( QQuickItem* parent ) arc->setSpanAngle( 270.0 ); arc->setThickness( 10.0 ); - arc->setFillColor( Qt::red ); - arc->setBorderWidth( 2.0 ); arc->setBorderColor( Qt::darkBlue ); - -#if 0 - arc->setShadowColor( Qt::black ); - arc->setSpreadRadius( 0.0 ); - arc->setBlurRadius( 4.0 ); - arc->setOffsetX( 2.0 ); - arc->setOffsetY( 2.0 ); -#endif } auto panel = new ControlPanel( arc ); diff --git a/playground/shadows/ShadowedArc.cpp b/playground/shadows/ShadowedArc.cpp index c237065d..0afbf91e 100644 --- a/playground/shadows/ShadowedArc.cpp +++ b/playground/shadows/ShadowedArc.cpp @@ -101,7 +101,10 @@ ShadowedArc::ShadowedArc( QQuickItem* parent ) setArcMetrics( { 0.0, 360.0, 1.0, Qt::RelativeSize } ); - setFillColor( Qt::darkRed ); + //setFillColor( Qt::darkRed ); + + const QskGradient gradient( Qt::darkRed, Qt::darkBlue ); + setGradientHint( Arc, gradient ); setBorderWidth( 0 ); setBorderColor( Qt::gray ); diff --git a/src/nodes/QskArcNode.cpp b/src/nodes/QskArcNode.cpp index 1fe2862d..895affc5 100644 --- a/src/nodes/QskArcNode.cpp +++ b/src/nodes/QskArcNode.cpp @@ -97,23 +97,22 @@ QskArcNode::~QskArcNode() } void QskArcNode::setArcData( const QRectF& rect, - const QskArcMetrics& arcMetrics, const QskGradient& fillGradient ) + const QskArcMetrics& arcMetrics, const QskGradient& gradient ) { - setArcData( rect, arcMetrics, 0.0, QColor(), fillGradient, {}, {} ); + setArcData( rect, arcMetrics, 0.0, QColor(), gradient, {}, {} ); } void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics, - const qreal borderWidth, const QColor& borderColor, const QskGradient& fillGradient ) + const qreal borderWidth, const QColor& borderColor, const QskGradient& gradient ) { - setArcData( rect, arcMetrics, borderWidth, borderColor, fillGradient, {}, {} ); + setArcData( rect, arcMetrics, borderWidth, borderColor, gradient, {}, {} ); } void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics, - const qreal borderWidth, const QColor& borderColor, const QskGradient& fillGradient, + const qreal borderWidth, const QColor& borderColor, const QskGradient& gradient, const QColor& shadowColor, const QskShadowMetrics& shadowMetrics ) { const auto metricsArc = qskEffectiveMetrics( arcMetrics, rect ); - const auto gradient = qskEffectiveGradient( fillGradient, metricsArc ); auto shadowNode = static_cast< QskArcShadowNode* >( QskSGNode::findChildNode( this, ShadowRole ) ); @@ -197,7 +196,9 @@ void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics pathNode = new QskShapeNode; QskSGNode::setNodeRole( pathNode, PathRole ); } - pathNode->updateNode( path, QTransform(), arcRect, gradient ); + + pathNode->updateNode( path, QTransform(), arcRect, + qskEffectiveGradient( gradient, metricsArc ) ); } } else diff --git a/src/nodes/QskArcRenderNode.cpp b/src/nodes/QskArcRenderNode.cpp index e29dee39..a7043802 100644 --- a/src/nodes/QskArcRenderNode.cpp +++ b/src/nodes/QskArcRenderNode.cpp @@ -106,7 +106,7 @@ void QskArcRenderNode::updateNode( else { QskArcRenderer::renderFillGeometry( - rect, metrics, *geometry() ); + rect, metrics, borderWidth, gradient, *geometry() ); } markDirty( QSGNode::DirtyGeometry ); diff --git a/src/nodes/QskArcRenderer.cpp b/src/nodes/QskArcRenderer.cpp index 755ffed3..f14b2157 100644 --- a/src/nodes/QskArcRenderer.cpp +++ b/src/nodes/QskArcRenderer.cpp @@ -25,29 +25,6 @@ static inline QskVertex::ColoredLine* qskAllocateColoredLines( return reinterpret_cast< QskVertex::ColoredLine* >( geometry.vertexData() ); } -static inline QPointF qskThicknessVector( qreal w, qreal h, qreal cos, qreal sin ) -{ - // normalized normal vector of the tangent at a specific angle - - if ( qFuzzyIsNull( cos ) ) - return { cos, sin }; - - const qreal m = ( w / h ) * ( sin / cos ); - - /* - We know: x² + y² = 1.0 and y = m * x; - - => x² + m² * x² = 1.0; - => x = 1.0 / sqrt( 1.0 + m² ); - */ - const qreal t = 1.0 / qSqrt( 1.0 + m * m ); - - const auto x = ( cos >= 0.0 ) ? t : -t; - const auto y = m * x; - - return { x, y }; -} - namespace { /* @@ -115,10 +92,10 @@ namespace namespace { - class Stroker + class EllipseStroker { public: - Stroker( const QRectF&, const QskArcMetrics& metrics, qreal borderWidth ); + EllipseStroker( const QRectF&, const QskArcMetrics&, qreal borderWidth ); int fillCount() const; int borderCount() const; @@ -129,35 +106,43 @@ namespace private: int arcLineCount( qreal radians ) const; - const QRectF& m_rect; - const QskArcMetrics& m_metrics; - const qreal m_borderWidth; + // radius + const qreal m_rx; + const qreal m_ry; + + // center + const qreal m_cx; + const qreal m_cy; + + const qreal m_radians1; + const qreal m_radians2; + const bool m_inverted; + + const qreal m_extent; }; - Stroker::Stroker( const QRectF& rect, - const QskArcMetrics& metrics, qreal borderWidth ) - : m_rect( rect ) - , m_metrics( metrics ) - , m_borderWidth( borderWidth ) + EllipseStroker::EllipseStroker( const QRectF& rect, + const QskArcMetrics& m, qreal borderWidth ) + : m_rx( 0.5 * rect.width() ) + , m_ry( 0.5 * rect.height() ) + , m_cx( rect.x() + m_rx ) + , m_cy( rect.y() + m_ry ) + , m_radians1( qDegreesToRadians( qMin( m.startAngle(), m.endAngle() ) ) ) + , m_radians2( qDegreesToRadians( qMax( m.startAngle(), m.endAngle() ) ) ) + , m_inverted( m.spanAngle() >= 0.0 ) + , m_extent( 0.5 * ( m.thickness() - borderWidth ) ) { - Q_ASSERT( metrics.sizeMode() == Qt::AbsoluteSize ); } - int Stroker::fillCount() const + int EllipseStroker::fillCount() const { int n = 0; - qreal radians1 = qDegreesToRadians( m_metrics.startAngle() ); - qreal radians2 = qDegreesToRadians( m_metrics.endAngle() ); - - if ( radians2 < radians1 ) - qSwap( radians1, radians2 ); - - for ( auto r = qFloor( radians1 / M_PI_2 ) * M_PI_2; - r < radians2; r += M_PI_2 ) + for ( auto r = qFloor( m_radians1 / M_PI_2 ) * M_PI_2; + r < m_radians2; r += M_PI_2 ) { - const auto r1 = qMax( r, radians1 ); - const auto r2 = qMin( r + M_PI_2, radians2 ); + const auto r1 = qMax( r, m_radians1 ); + const auto r2 = qMin( r + M_PI_2, m_radians2 ); n += arcLineCount( r2 - r1 ); } @@ -165,27 +150,24 @@ namespace return n; } - int Stroker::arcLineCount( qreal radians ) const + int EllipseStroker::arcLineCount( qreal radians ) const { Q_ASSERT( radians >= 0.0 ); // not very sophisticated - TODO ... - const auto radius = 0.5 * qMax( m_rect.width(), m_rect.height() ); - + const auto radius = qMax( m_rx, m_ry ); + const auto count = qCeil( ( radius * radians ) / 3.0 ); return qBound( 3, count, 40 ); } - int Stroker::borderCount() const + int EllipseStroker::borderCount() const { - if ( m_metrics.isNull() ) - return 0; - return 0; } - int Stroker::setBorderLines( QskVertex::ColoredLine* lines, + int EllipseStroker::setBorderLines( QskVertex::ColoredLine* lines, const QskVertex::Color color ) const { Q_UNUSED( lines ); @@ -194,43 +176,59 @@ namespace return 0; } - int Stroker::setFillLines( QskVertex::ColoredLine* lines, + int EllipseStroker::setFillLines( QskVertex::ColoredLine* lines, const QskVertex::Color color ) const { - qreal radians1 = qDegreesToRadians( m_metrics.startAngle() ); - qreal radians2 = qDegreesToRadians( m_metrics.endAngle() ); - - if ( m_metrics.spanAngle() < 0.0 ) - std::swap( radians1, radians2 ); - - const qreal t = 0.5 * m_metrics.thickness(); - const qreal w = 0.5 * m_rect.width() - t; - const qreal h = 0.5 * m_rect.height() - t; - - const auto center = m_rect.center(); + const auto w = m_rx - m_extent; + const auto h = m_ry - m_extent; + const auto aspectRatio = w / h; auto l = lines; - for ( auto r = qFloor( radians1 / M_PI_2 ) * M_PI_2; - r < radians2; r += M_PI_2 ) + for ( auto r = qFloor( m_radians1 / M_PI_2 ) * M_PI_2; + r < m_radians2; r += M_PI_2 ) { - const auto r1 = qMax( r, radians1 ); - const auto r2 = qMin( r + M_PI_2, radians2 ); + const auto r1 = qMax( r, m_radians1 ); + const auto r2 = qMin( r + M_PI_2, m_radians2 ); const auto lineCount = arcLineCount( r2 - r1 ); for ( AngleIterator it( r1, r2, lineCount ); !it.isDone(); ++it ) { - const auto x = center.x() + w * it.cos(); - const auto y = center.y() - h * it.sin(); + qreal dx, dy; /* The inner/outer points are found by shifting along the normal vector of the tangent at the ellipse point. */ - const auto v = t * qskThicknessVector( w, h, it.cos(), it.sin() ); - l++->setLine( x + v.x(), y - v.y(), x - v.x(), y + v.y(), color ); + if ( qFuzzyIsNull( it.cos() ) ) + { + dx = 0.0; + dy = ( it.sin() > 0.0 ) ? m_extent : -m_extent; + } + else + { + // expanding orthogonally to the ellipse tangent + + /* + m = w / h * tan( angle ) + y = m * x; + x² + y² = dt + + => x = dt / sqrt( 1.0 + m² ); + */ + 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(); + + l++->setLine( x + dx, y - dy, x - dx, y + dy, color ); } } @@ -238,32 +236,6 @@ namespace } } -void QskArcRenderer::renderFillGeometry( const QRectF& rect, - const QskArcMetrics& metrics, qreal borderWidth, QSGGeometry& geometry ) -{ - geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); - - Stroker stroker( rect, metrics, borderWidth ); - - const auto lineCount = stroker.fillCount(); - - const auto lines = qskAllocateColoredLines( geometry, lineCount ); - if ( lines ) - { - const auto effectiveCount = stroker.setFillLines( lines, QColor( Qt::darkRed ) ); - if ( lineCount != effectiveCount ) - { - qWarning() << lineCount << effectiveCount; - } - } -} - -void QskArcRenderer::renderFillGeometry( const QRectF& rect, - const QskArcMetrics& metrics, QSGGeometry& geometry ) -{ - renderFillGeometry( rect, metrics, 0.0, geometry ); -} - bool QskArcRenderer::isGradientSupported( const QskGradient& gradient ) { if ( gradient.isVisible() && !gradient.isMonochrome() ) @@ -272,22 +244,27 @@ bool QskArcRenderer::isGradientSupported( const QskGradient& gradient ) return true; } -void QskArcRenderer::renderArc( const QRectF& rect, - const QskArcMetrics& metrics, qreal borderWidth, const QColor& borderColor, +void QskArcRenderer::renderFillGeometry( const QRectF& rect, + const QskArcMetrics& metrics, qreal borderWidth, const QskGradient& gradient, QSGGeometry& geometry ) { - Q_UNUSED( rect ); - Q_UNUSED( metrics ); - Q_UNUSED( borderWidth ); - Q_UNUSED( borderColor ); - Q_UNUSED( gradient ); - Q_UNUSED( geometry ); -} + geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); -void QskArcRenderer::renderArc( const QRectF& rect, const QskArcMetrics& metrics, - const QskGradient& gradient, QSGGeometry& geometry ) -{ - renderArc( rect, metrics, 0, QColor(), gradient, geometry ); + EllipseStroker stroker( rect, metrics, borderWidth ); + + const auto lineCount = stroker.fillCount(); + + const auto lines = qskAllocateColoredLines( geometry, lineCount ); + if ( lines ) + { + const QskVertex::Color color = gradient.rgbStart(); + + const auto effectiveCount = stroker.setFillLines( lines, color ); + if ( lineCount != effectiveCount ) + { + qWarning() << lineCount << effectiveCount; + } + } } void QskArcRenderer::renderBorder( const QRectF& rect, const QskArcMetrics& metrics, @@ -295,7 +272,7 @@ void QskArcRenderer::renderBorder( const QRectF& rect, const QskArcMetrics& metr { geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip ); - Stroker stroker( rect, metrics, borderWidth ); + EllipseStroker stroker( rect, metrics, borderWidth ); const auto lineCount = stroker.borderCount(); diff --git a/src/nodes/QskArcRenderer.h b/src/nodes/QskArcRenderer.h index dc7e7375..d4ab18d3 100644 --- a/src/nodes/QskArcRenderer.h +++ b/src/nodes/QskArcRenderer.h @@ -31,24 +31,17 @@ namespace QskArcRenderer QSK_EXPORT void renderFillGeometry( const QRectF&, const QskArcMetrics&, qreal borderWidth, QSGGeometry& ); - QSK_EXPORT void renderFillGeometry( const QRectF&, - const QskArcMetrics&, QSGGeometry& ); - /* Filling the geometry with color information: see QSGGeometry::defaultAttributes_ColoredPoint2D() */ QSK_EXPORT bool isGradientSupported( const QskGradient& ); - QSK_EXPORT void renderArc( const QRectF&, - const QskArcMetrics&, qreal borderWidth, const QColor& borderColor, - const QskGradient&, QSGGeometry& ); - - QSK_EXPORT void renderArc( const QRectF&, const QskArcMetrics&, - const QskGradient&, QSGGeometry& ); - QSK_EXPORT void renderBorder( const QRectF&, const QskArcMetrics&, qreal borderWidth, const QColor& borderColor, QSGGeometry& ); + + QSK_EXPORT void renderFillGeometry( const QRectF&, const QskArcMetrics&, + qreal borderWidth, const QskGradient&, QSGGeometry& ); } #endif