QskArcRenderer using the normal vector of the tangents for expanding

the arc to the desired thickness. This matches the result of what
QPainter::drawArc does. However our implementation is much
simpler as we do not convert the arc into a sequence of
bezier curves finally running into code that has to deal with
random QPainterPath element lists.
This commit is contained in:
Uwe Rathmann 2024-05-30 19:06:02 +02:00
parent a327084c3f
commit bc066c8103
8 changed files with 172 additions and 221 deletions

View File

@ -27,7 +27,7 @@ CircularChart::CircularChart( QQuickItem* parentItem )
setGradientHint( Panel, QskGradient() ); setGradientHint( Panel, QskGradient() );
setBoxBorderMetricsHint( Panel, 0 ); setBoxBorderMetricsHint( Panel, 0 );
setArcMetricsHint( Arc, { 90.0, -360.0, 100.0, Qt::RelativeSize, true } ); setArcMetricsHint( Arc, { 90.0, -360.0, 100.0, Qt::RelativeSize } );
setGradientHint( Arc, QskRgb::toTransparent( QskRgb::LightGray, 100 ) ); setGradientHint( Arc, QskRgb::toTransparent( QskRgb::LightGray, 100 ) );
setColor( Arc | QskAspect::Border, QskRgb::LightGray ); setColor( Arc | QskAspect::Border, QskRgb::LightGray );

View File

@ -106,7 +106,7 @@ ShadowedArc::ShadowedArc( QQuickItem* parent )
setBorderWidth( 0 ); setBorderWidth( 0 );
setBorderColor( Qt::gray ); setBorderColor( Qt::gray );
setShadowColor( Qt::black ); setShadowColor( QColor() );
setShadowMetrics( { 0, 0, QPointF( 0, 0 ), Qt::AbsoluteSize } ); setShadowMetrics( { 0, 0, QPointF( 0, 0 ), Qt::AbsoluteSize } );
} }

View File

@ -51,11 +51,6 @@ void QskArcMetrics::setSizeMode( Qt::SizeMode sizeMode ) noexcept
m_relativeSize = ( sizeMode == Qt::RelativeSize ); m_relativeSize = ( sizeMode == Qt::RelativeSize );
} }
void QskArcMetrics::setProportional( bool on ) noexcept
{
m_proportional = on;
}
bool QskArcMetrics::isClosed() const bool QskArcMetrics::isClosed() const
{ {
return qAbs( m_spanAngle ) >= 360.0; return qAbs( m_spanAngle ) >= 360.0;
@ -92,7 +87,7 @@ QskArcMetrics QskArcMetrics::interpolated(
const qreal s1 = qskInterpolated( m_startAngle, to.m_startAngle, ratio ); const qreal s1 = qskInterpolated( m_startAngle, to.m_startAngle, ratio );
const qreal s2 = qskInterpolated( endAngle(), to.endAngle(), ratio ); const qreal s2 = qskInterpolated( endAngle(), to.endAngle(), ratio );
return QskArcMetrics( s1, s2 - s1, thickness, sizeMode(), to.isProportional() ); return QskArcMetrics( s1, s2 - s1, thickness, sizeMode() );
} }
QVariant QskArcMetrics::interpolate( QVariant QskArcMetrics::interpolate(
@ -119,8 +114,7 @@ QskArcMetrics QskArcMetrics::toAbsolute( qreal radius ) const noexcept
return *this; return *this;
const qreal t = qskEffectiveThickness( radius, m_thickness ); const qreal t = qskEffectiveThickness( radius, m_thickness );
return QskArcMetrics( m_startAngle, m_spanAngle, t, return QskArcMetrics( m_startAngle, m_spanAngle, t, Qt::AbsoluteSize );
Qt::AbsoluteSize, m_proportional );
} }
QPainterPath QskArcMetrics::painterPath( const QRectF& ellipseRect ) const QPainterPath QskArcMetrics::painterPath( const QRectF& ellipseRect ) const
@ -136,18 +130,7 @@ QPainterPath QskArcMetrics::painterPath( const QRectF& ellipseRect ) const
if ( t <= 0.0 || qFuzzyIsNull( m_spanAngle ) ) if ( t <= 0.0 || qFuzzyIsNull( m_spanAngle ) )
return QPainterPath(); return QPainterPath();
auto tx = t; const auto innerRect = ellipseRect.adjusted( t, t, -t, -t );
auto ty = t;
if ( m_proportional )
{
const auto sz = qMin( ellipseRect.width(), ellipseRect.height() );
tx *= ellipseRect.width() / sz;
ty *= ellipseRect.height() / sz;
}
const auto innerRect = ellipseRect.adjusted( tx, ty, -tx, -ty );
QPainterPath path; QPainterPath path;
@ -168,32 +151,18 @@ QPainterPath QskArcMetrics::painterPath( const QRectF& ellipseRect ) const
} }
else else
{ {
if ( qAbs( m_spanAngle ) >= 360.0 ) const auto t2 = 0.5 * t;
{ const auto r = ellipseRect.adjusted( t2, t2, -t2, -t2 );
path.addEllipse( ellipseRect );
QPainterPath innerPath; QPainterPath arcPath;
innerPath.addEllipse( innerRect ); arcPath.arcMoveTo( r, m_startAngle ); // replaces the dummy arcMoveTo above
path -= innerPath; arcPath.arcTo( r, m_startAngle, m_spanAngle );
}
else
{
/*
We need the end point of the inner arc to add the line that connects
the inner/outer arcs. As QPainterPath does not offer such a method
we insert a dummy arcMoveTo and grab the calculated position.
*/
path.arcMoveTo( innerRect, m_startAngle + m_spanAngle );
const auto pos = path.currentPosition();
path.arcMoveTo( ellipseRect, m_startAngle ); // replaces the dummy arcMoveTo above QPainterPathStroker stroker;
path.arcTo( ellipseRect, m_startAngle, m_spanAngle ); stroker.setCapStyle( Qt::FlatCap );
stroker.setWidth( t );
path.lineTo( pos ); path = stroker.createStroke( arcPath );
path.arcTo( innerRect, m_startAngle + m_spanAngle, -m_spanAngle );
path.closeSubpath();
}
} }
return path; return path;
@ -227,9 +196,8 @@ QskHashValue QskArcMetrics::hash( QskHashValue seed ) const noexcept
auto hash = qHash( m_thickness, seed ); auto hash = qHash( m_thickness, seed );
hash = qHash( m_startAngle, hash ); hash = qHash( m_startAngle, hash );
hash = qHash( m_spanAngle, hash ); hash = qHash( m_spanAngle, hash );
hash = qHash( m_relativeSize, hash );
return qHash( m_proportional, hash ); return qHash( m_relativeSize, hash );
} }
#ifndef QT_NO_DEBUG_STREAM #ifndef QT_NO_DEBUG_STREAM
@ -242,8 +210,7 @@ QDebug operator<<( QDebug debug, const QskArcMetrics& metrics )
debug.nospace(); debug.nospace();
debug << "QskArcMetrics" << '('; debug << "QskArcMetrics" << '(';
debug << metrics.thickness() << ',' << metrics.sizeMode() << ',' debug << metrics.thickness() << ',' << metrics.sizeMode();
<< metrics.isProportional();
debug << ",[" << metrics.startAngle() << ',' << metrics.spanAngle() << ']'; debug << ",[" << metrics.startAngle() << ',' << metrics.spanAngle() << ']';
debug << ')'; debug << ')';

View File

@ -22,16 +22,15 @@ class QSK_EXPORT QskArcMetrics
Q_PROPERTY( qreal thickness READ thickness WRITE setThickness ) Q_PROPERTY( qreal thickness READ thickness WRITE setThickness )
Q_PROPERTY( Qt::SizeMode sizeMode READ sizeMode WRITE setSizeMode ) Q_PROPERTY( Qt::SizeMode sizeMode READ sizeMode WRITE setSizeMode )
Q_PROPERTY( bool proportional READ isProportional WRITE setProportional )
public: public:
constexpr QskArcMetrics() noexcept = default; constexpr QskArcMetrics() noexcept = default;
constexpr QskArcMetrics( qreal thickness, constexpr QskArcMetrics( qreal thickness,
Qt::SizeMode = Qt::AbsoluteSize, bool proportional = false ) noexcept; Qt::SizeMode = Qt::AbsoluteSize ) noexcept;
constexpr QskArcMetrics( qreal startAngle, qreal spanAngle, qreal thickness, constexpr QskArcMetrics( qreal startAngle, qreal spanAngle, qreal thickness,
Qt::SizeMode = Qt::AbsoluteSize, bool proportional = false ) noexcept; Qt::SizeMode = Qt::AbsoluteSize ) noexcept;
bool operator==( const QskArcMetrics& ) const noexcept; bool operator==( const QskArcMetrics& ) const noexcept;
bool operator!=( const QskArcMetrics& ) const noexcept; bool operator!=( const QskArcMetrics& ) const noexcept;
@ -53,19 +52,6 @@ class QSK_EXPORT QskArcMetrics
void setThickness( qreal ) noexcept; void setThickness( qreal ) noexcept;
constexpr qreal thickness() const noexcept; constexpr qreal thickness() const noexcept;
/*
A proportional arc scales the thickness of the arc according to the
aspect ratio of the target rectangle. F.e when having a 20x10 rectangle
the thickness in west/east direction is doubled, while for a
10x20 rectangle the thickness in north/south direction is doubled.
This matches the lines that result from a filling with a conic gradient.
A non proportional arc will have a fixed thickness regardless of
the aspect ratio.
*/
void setProportional( bool ) noexcept;
constexpr bool isProportional() const noexcept;
void setSizeMode( Qt::SizeMode ) noexcept; void setSizeMode( Qt::SizeMode ) noexcept;
constexpr Qt::SizeMode sizeMode() const noexcept; constexpr Qt::SizeMode sizeMode() const noexcept;
@ -92,22 +78,20 @@ class QSK_EXPORT QskArcMetrics
qreal m_thickness = 0.0; qreal m_thickness = 0.0;
bool m_relativeSize = false; bool m_relativeSize = false;
bool m_proportional = false;
}; };
inline constexpr QskArcMetrics::QskArcMetrics( inline constexpr QskArcMetrics::QskArcMetrics(
qreal thickness, Qt::SizeMode sizeMode, bool proportional ) noexcept qreal thickness, Qt::SizeMode sizeMode ) noexcept
: QskArcMetrics( 0.0, 360.0, thickness, sizeMode, proportional ) : QskArcMetrics( 0.0, 360.0, thickness, sizeMode )
{ {
} }
inline constexpr QskArcMetrics::QskArcMetrics( qreal startAngle, qreal spanAngle, inline constexpr QskArcMetrics::QskArcMetrics( qreal startAngle, qreal spanAngle,
qreal thickness, Qt::SizeMode sizeMode, bool proportional ) noexcept qreal thickness, Qt::SizeMode sizeMode ) noexcept
: m_startAngle( startAngle ) : m_startAngle( startAngle )
, m_spanAngle( spanAngle ) , m_spanAngle( spanAngle )
, m_thickness( thickness ) , m_thickness( thickness )
, m_relativeSize( sizeMode == Qt::RelativeSize ) , m_relativeSize( sizeMode == Qt::RelativeSize )
, m_proportional( proportional )
{ {
} }
@ -117,8 +101,7 @@ inline bool QskArcMetrics::operator==(
return qskFuzzyCompare( m_thickness, other.m_thickness ) return qskFuzzyCompare( m_thickness, other.m_thickness )
&& qskFuzzyCompare( m_startAngle, other.m_startAngle ) && qskFuzzyCompare( m_startAngle, other.m_startAngle )
&& qskFuzzyCompare( m_spanAngle, other.m_spanAngle ) && qskFuzzyCompare( m_spanAngle, other.m_spanAngle )
&& ( m_relativeSize == other.m_relativeSize ) && ( m_relativeSize == other.m_relativeSize );
&& ( m_proportional == other.m_proportional );
} }
inline bool QskArcMetrics::operator!=( inline bool QskArcMetrics::operator!=(
@ -162,11 +145,6 @@ inline constexpr Qt::SizeMode QskArcMetrics::sizeMode() const noexcept
return m_relativeSize ? Qt::RelativeSize : Qt::AbsoluteSize; return m_relativeSize ? Qt::RelativeSize : Qt::AbsoluteSize;
} }
inline constexpr bool QskArcMetrics::isProportional() const noexcept
{
return m_proportional;
}
#ifndef QT_NO_DEBUG_STREAM #ifndef QT_NO_DEBUG_STREAM
class QDebug; class QDebug;

View File

@ -16,15 +16,21 @@
#include <qpainterpath.h> #include <qpainterpath.h>
#define ARC_RENDERER // #define ARC_BORDER_NODE
#define ARC_FILL_NODE
#ifdef ARC_RENDERER #ifdef ARC_BORDER_NODE
using BorderNode = QskArcRenderNode; using BorderNode = QskArcRenderNode;
#else #else
#include <qpen.h> #include <qpen.h>
using BorderNode = QskStrokeNode; using BorderNode = QskStrokeNode;
#endif #endif
#ifdef ARC_FILL_NODE
using FillNode = QskArcRenderNode;
#else
using FillNode = QskShapeNode;
#endif
namespace namespace
{ {
@ -114,7 +120,7 @@ void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics
auto shadowNode = static_cast< QskArcShadowNode* >( auto shadowNode = static_cast< QskArcShadowNode* >(
QskSGNode::findChildNode( this, ShadowRole ) ); QskSGNode::findChildNode( this, ShadowRole ) );
auto fillNode = static_cast< QskShapeNode* >( auto fillNode = static_cast< FillNode* >(
QskSGNode::findChildNode( this, FillRole ) ); QskSGNode::findChildNode( this, FillRole ) );
auto borderNode = static_cast< BorderNode* >( auto borderNode = static_cast< BorderNode* >(
@ -168,11 +174,15 @@ void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics
{ {
if ( fillNode == nullptr ) if ( fillNode == nullptr )
{ {
fillNode = new QskShapeNode; fillNode = new FillNode;
QskSGNode::setNodeRole( fillNode, FillRole ); QskSGNode::setNodeRole( fillNode, FillRole );
} }
#ifdef ARC_FILL_NODE
fillNode->updateNode( arcRect, metricsArc, gradient );
#else
fillNode->updateNode( path, QTransform(), arcRect, gradient ); fillNode->updateNode( path, QTransform(), arcRect, gradient );
#endif
} }
else else
{ {
@ -188,9 +198,8 @@ void QskArcNode::setArcData( const QRectF& rect, const QskArcMetrics& arcMetrics
QskSGNode::setNodeRole( borderNode, BorderRole ); QskSGNode::setNodeRole( borderNode, BorderRole );
} }
#ifdef ARC_RENDERER #ifdef ARC_BORDER_NODE
borderNode->updateNode( arcRect, metricsArc, borderWidth, borderNode->updateNode( arcRect, metricsArc, borderWidth, borderColor );
borderColor, gradient );
#else #else
QPen pen( borderColor, borderWidth ); QPen pen( borderColor, borderWidth );
pen.setCapStyle( Qt::FlatCap ); pen.setCapStyle( Qt::FlatCap );

View File

@ -49,6 +49,18 @@ QskArcRenderNode::QskArcRenderNode()
setFlag( QSGNode::OwnsMaterial, false ); setFlag( QSGNode::OwnsMaterial, false );
} }
void QskArcRenderNode::updateNode( const QRectF& rect,
const QskArcMetrics& metrics, const QskGradient& gradient )
{
updateNode( rect, metrics, 0.0, QColor(), gradient );
}
void QskArcRenderNode::updateNode( const QRectF& rect,
const QskArcMetrics& metrics, qreal borderWidth, const QColor& borderColor )
{
updateNode( rect, metrics, borderWidth, borderColor, QskGradient() );
}
void QskArcRenderNode::updateNode( void QskArcRenderNode::updateNode(
const QRectF& rect, const QskArcMetrics& metrics, qreal borderWidth, const QRectF& rect, const QskArcMetrics& metrics, qreal borderWidth,
const QColor& borderColor, const QskGradient& gradient ) const QColor& borderColor, const QskGradient& gradient )
@ -86,8 +98,16 @@ void QskArcRenderNode::updateNode(
{ {
d->hash = hash; d->hash = hash;
QskArcRenderer::renderBorder( if ( borderWidth > 0.0 )
rect, metrics, borderWidth, borderColor, *geometry() ); {
QskArcRenderer::renderBorder(
rect, metrics, borderWidth, borderColor, *geometry() );
}
else
{
QskArcRenderer::renderFillGeometry(
rect, metrics, *geometry() );
}
markDirty( QSGNode::DirtyGeometry ); markDirty( QSGNode::DirtyGeometry );
markDirty( QSGNode::DirtyMaterial ); markDirty( QSGNode::DirtyMaterial );

View File

@ -21,6 +21,10 @@ class QSK_EXPORT QskArcRenderNode : public QSGGeometryNode
public: public:
QskArcRenderNode(); QskArcRenderNode();
void updateNode( const QRectF&, const QskArcMetrics&, const QskGradient& );
void updateNode( const QRectF&, const QskArcMetrics&, qreal borderWidth,
const QColor& borderColor );
void updateNode( const QRectF&, const QskArcMetrics&, qreal borderWidth, void updateNode( const QRectF&, const QskArcMetrics&, qreal borderWidth,
const QColor& borderColor, const QskGradient& ); const QColor& borderColor, const QskGradient& );

View File

@ -9,10 +9,7 @@
#include "QskVertex.h" #include "QskVertex.h"
#include <qsggeometry.h> #include <qsggeometry.h>
#if 1
#include <qdebug.h> #include <qdebug.h>
#endif
static inline QskVertex::Line* qskAllocateLines( static inline QskVertex::Line* qskAllocateLines(
QSGGeometry& geometry, int lineCount ) QSGGeometry& geometry, int lineCount )
@ -28,36 +25,6 @@ static inline QskVertex::ColoredLine* qskAllocateColoredLines(
return reinterpret_cast< QskVertex::ColoredLine* >( geometry.vertexData() ); return reinterpret_cast< QskVertex::ColoredLine* >( geometry.vertexData() );
} }
static inline int qskApproximatedCircumference( const QRectF& rect )
{
const qreal a = rect.width();
const qreal b = rect.height();
const auto ratio = a / b;
if ( ratio > 0.9 || ratio < 1.1 )
return std::max( a, b ) * 2.0 * M_PI; // circle
// Srinivasa Ramanujan: https://en.wikipedia.org/wiki/Ellipse#Circumference
const qreal d1 = ( a - b );
const qreal d2 = ( a + b );
const qreal h = ( d1 * d1 ) / ( d2 * d2 );
return M_PI * d2 * ( 1.0 + ( 3 * h / ( 10 + sqrt( 4 - 3 * h ) ) ) );
}
static inline int qskStepCount( const QRectF& rect )
{
#if 0
const auto dist = 3.0;
#else
const auto dist = 20.0;
#endif
const int length = qskApproximatedCircumference( rect );
return std::max( 3, qCeil( length / dist ) );
}
namespace namespace
{ {
class AngleIterator class AngleIterator
@ -73,14 +40,13 @@ namespace
inline int step() const { return m_stepIndex; } inline int step() const { return m_stepIndex; }
inline int stepCount() const { return m_stepCount; } inline int stepCount() const { return m_stepCount; }
inline bool isDone() const { return m_stepIndex > m_stepCount; } inline bool isDone() const { return m_stepIndex >= m_stepCount; }
private: private:
double m_cos; double m_cos;
double m_sin; double m_sin;
int m_stepIndex; int m_stepIndex;
int m_stepCount; int m_stepCount;
const double m_radians1; const double m_radians1;
@ -93,7 +59,7 @@ namespace
, m_stepCount( stepCount ) , m_stepCount( stepCount )
, m_radians1( radians1 ) , m_radians1( radians1 )
, m_radians2( radians2 ) , m_radians2( radians2 )
, m_radiansStep( ( radians2 - radians1 ) / stepCount ) , m_radiansStep( ( radians2 - radians1 ) / ( stepCount - 1 ) )
{ {
m_cos = qFastCos( radians1 ); m_cos = qFastCos( radians1 );
m_sin = qFastSin( radians1 ); m_sin = qFastSin( radians1 );
@ -130,15 +96,11 @@ namespace
int borderCount() const; int borderCount() const;
int setBorderLines( QskVertex::ColoredLine*, const QskVertex::Color ) const; int setBorderLines( QskVertex::ColoredLine*, const QskVertex::Color ) const;
int setBorderLines( QskVertex::Line* ) const; int setFillLines( QskVertex::ColoredLine*, const QskVertex::Color ) const;
private: private:
int arcLineCount() const; int arcLineCount( qreal radians = 2.0 * M_PI ) const;
QLineF fillLineAt( qreal x, qreal y, qreal sin, qreal cos ) const;
void setArcLines( QskVertex::ColoredLine*, int lineCount,
const QPointF&, const QSizeF&,
const qreal radians1, const qreal radians2,
qreal arcWidth, const QskVertex::Color ) const;
const QRectF& m_rect; const QRectF& m_rect;
const QskArcMetrics& m_metrics; const QskArcMetrics& m_metrics;
@ -156,17 +118,35 @@ namespace
int Stroker::fillCount() const int Stroker::fillCount() const
{ {
return 0; // TODO 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 )
{
const auto r1 = qMax( r, radians1 );
const auto r2 = qMin( r + M_PI_2, radians2 );
n += arcLineCount( r2 - r1 );
}
return n;
} }
int Stroker::arcLineCount() const int Stroker::arcLineCount( const qreal radians ) const
{ {
if ( m_metrics.isNull() ) // not very sophisticated - TODO ...
return 0;
int n = qskStepCount( m_rect ); const auto ratio = qAbs( radians ) / ( 2.0 * M_PI );
if ( !m_metrics.isClosed() )
n = qCeil( n * qAbs( m_metrics.spanAngle() ) / 360.0 ); int n = ( m_rect.width() + m_rect.height() ) * M_PI_2;
n = qBound( 3, n, 80 );
n = qCeil( n * ratio );
return n; return n;
} }
@ -176,122 +156,115 @@ namespace
if ( m_metrics.isNull() ) if ( m_metrics.isNull() )
return 0; return 0;
return 2 * arcLineCount() + 1;
}
void Stroker::setArcLines( QskVertex::ColoredLine* lines, int lineCount,
const QPointF& center, const QSizeF& size,
const qreal radians1, const qreal radians2,
qreal arcWidth, const QskVertex::Color color ) const
{
const auto w1 = size.width();
const auto h1 = size.height();
const auto w2 = w1 - arcWidth;
const auto h2 = h1 - arcWidth;
auto l = lines;
for ( AngleIterator it( radians1, radians2, lineCount - 1 ); !it.isDone(); ++it )
{
const auto x1 = center.x() + w1 * it.cos();
const auto x2 = center.x() + w2 * it.cos();
const auto y1 = center.y() + h1 * it.sin();
const auto y2 = center.y() + h2 * it.sin();
l++->setLine( x1, y1, x2, y2, color );
}
if ( l - lines != lineCount )
qWarning() << lineCount << "->" << l - lines;
Q_ASSERT( l - lines == lineCount );
}
int Stroker::setBorderLines( QskVertex::Line* ) const
{
return 0; return 0;
} }
int Stroker::setBorderLines( QskVertex::ColoredLine* lines, int Stroker::setBorderLines( QskVertex::ColoredLine* lines,
const QskVertex::Color color ) const const QskVertex::Color color ) const
{ {
Q_UNUSED( lines );
Q_UNUSED( color );
return 0;
}
int Stroker::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 w = 0.5 * ( m_rect.width() - m_metrics.thickness() );
const qreal h = 0.5 * ( m_rect.height() - m_metrics.thickness() );
const auto center = m_rect.center(); const auto center = m_rect.center();
const qreal radians1 = qDegreesToRadians( m_metrics.startAngle() ); auto l = lines;
const qreal radians2 = qDegreesToRadians( m_metrics.endAngle() );
const int n = arcLineCount(); for ( auto r = qFloor( radians1 / M_PI_2 ) * M_PI_2;
r < radians2; r += M_PI_2 )
auto size = 0.5 * m_rect.size();
setArcLines( lines, n, center, size,
radians1, radians2, m_borderWidth, color );
const bool stretched = true;
if ( !stretched )
{ {
size.rwidth() -= m_metrics.thickness() - m_borderWidth; const auto r1 = qMax( r, radians1 );
size.rheight() -= m_metrics.thickness() - m_borderWidth; const auto r2 = qMin( r + M_PI_2, radians2 );
}
else
{
qreal tx = m_metrics.thickness();
qreal ty = m_metrics.thickness();
const qreal ratio = m_rect.width() / m_rect.height(); const auto lineCount = arcLineCount( r2 - r1 );
if ( ratio >= 1.0 ) for ( AngleIterator it( r1, r2, lineCount ); !it.isDone(); ++it )
tx *= ratio; {
else const auto line = fillLineAt( w, h, it.cos(), it.sin() );
ty /= ratio;
size.rwidth() -= tx; l++->setLine( center.x() + line.x1(), center.y() - line.y1(),
size.rheight() -= ty; center.x() + line.x2(), center.y() - line.y2(), color );
}
} }
setArcLines( lines + n + 1, n, center, size, return l - lines;
radians2, radians1, m_borderWidth, color );
lines[n] = { lines[n - 1].p2, lines[n + 1].p1 };
return 2 * n + 1;
} }
}
void QskArcRenderer::renderBorderGeometry( const QRectF& rect, inline qreal sqr( qreal x )
const QskArcMetrics& metrics, qreal borderWidth, QSGGeometry& geometry )
{
geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
Stroker stroker( rect, metrics, borderWidth );
const auto lineCount = stroker.borderCount();
const auto lines = qskAllocateLines( geometry, lineCount );
if ( lines )
{ {
const auto effectiveCount = stroker.setBorderLines( lines ); return x * x;
if ( lineCount != effectiveCount ) }
QLineF Stroker::fillLineAt( qreal w, qreal h, qreal cos, qreal sin ) const
{
const auto x = w * cos;
const auto y = h * sin;
/*
The inner/outer points are found by shifting along the
normal vector of the tangent at the ellipse point.
*/
const auto t = 0.5 * m_metrics.thickness();
if ( qFuzzyIsNull( sin ) )
{ {
qWarning() << lineCount << effectiveCount; const qreal dx = cos * t;
return QLineF( x + dx, y, x - dx, y );
} }
else if ( qFuzzyIsNull( cos ) )
{
const qreal dy = sin * t;
return QLineF( x, y + dy, x, y - dy );
}
const qreal m = qSqrt( w * w - x * x ) * ( w / h ) / x;
const auto dt = t * qSqrt( ( 1.0 / ( 1.0 + m * m ) ) );
const qreal dx = ( x >= 0 ) ? dt : -dt;
const qreal dy = m * ( ( y >= 0 ) ? dx : -dx );
const auto x1 = x + dx;
const auto y1 = y + dy;
const auto x2 = x - dx;
const auto y2 = y - dy;
return QLineF( x1, y1, x2, y2 );
} }
} }
void QskArcRenderer::renderFillGeometry( const QRectF& rect, void QskArcRenderer::renderFillGeometry( const QRectF& rect,
const QskArcMetrics& metrics, qreal borderWidth, QSGGeometry& geometry ) const QskArcMetrics& metrics, qreal borderWidth, QSGGeometry& geometry )
{ {
Q_UNUSED( rect ); geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
Q_UNUSED( geometry );
Stroker stroker( rect, metrics, borderWidth ); Stroker stroker( rect, metrics, borderWidth );
const auto lines = qskAllocateColoredLines( geometry, stroker.fillCount() ); const auto lineCount = stroker.fillCount();
const auto lines = qskAllocateColoredLines( geometry, lineCount );
if ( lines ) if ( lines )
{ {
// TODO const auto effectiveCount = stroker.setFillLines( lines, QColor( Qt::darkRed ) );
if ( lineCount != effectiveCount )
{
qWarning() << lineCount << effectiveCount;
}
} }
} }