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:
parent
a327084c3f
commit
bc066c8103
|
@ -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 );
|
||||||
|
|
|
@ -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 } );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 << ')';
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 );
|
||||||
|
|
|
@ -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 );
|
||||||
|
|
|
@ -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& );
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue