rendering border/filling together

This commit is contained in:
Uwe Rathmann 2024-07-25 18:29:37 +02:00
parent a54afb5305
commit 708e68045b
1 changed files with 124 additions and 164 deletions

View File

@ -37,45 +37,40 @@ namespace
, m_border( border )
, m_rx( 0.5 * ( rect.width() - m_thickness ) )
, m_ry( 0.5 * ( rect.height() - m_thickness ) )
, m_offsetOut( 0.5 * m_thickness )
, m_offsetIn( m_offsetOut - border )
, m_offsetToBorder( 0.5 * m_thickness - border )
, m_aspectRatio( m_rx / m_ry )
, m_cx( rect.x() + 0.5 * rect.width() )
, m_cy( rect.y() + 0.5 * rect.height() )
{
}
inline void setLine( const qreal radians,
const QskVertex::Color color, QskVertex::ColoredLine& line ) const
inline void setLinesAt( const qreal radians,
const QskVertex::Color fillColor, const QskVertex::Color borderColor,
QskVertex::ColoredLine* fill, QskVertex::ColoredLine* outerBorder,
QskVertex::ColoredLine* innerBorder ) const
{
const auto cos = qFastCos( radians );
const auto sin = qFastSin( radians );
const auto x = m_cx + m_rx * cos;
const auto y = m_cy - m_ry * sin;
const auto v = normalVector( cos, sin ) * m_offsetIn;
line.setLine( x + v.x(), y - v.y(), x - v.x(), y + v.y(), color );
}
inline void setBorderLines( const qreal radians,
const QskVertex::Color color, QskVertex::ColoredLine& outer,
QskVertex::ColoredLine& inner ) const
{
const auto cos = qFastCos( radians );
const auto sin = qFastSin( radians );
const auto x = m_cx + m_rx * cos;
const auto y = m_cy - m_ry * sin;
const auto v = normalVector( cos, sin );
const auto v1 = v * m_offsetOut;
const auto v2 = v * m_offsetIn;
const QPointF p0( m_cx + m_rx * cos, m_cy - m_ry * sin );
outer.setLine( x + v1.x(), y - v1.y(), x + v2.x(), y - v2.y(), color );
inner.setLine( x - v1.x(), y + v1.y(), x - v2.x(), y + v2.y(), color );
const auto v1 = v * m_offsetToBorder;
const auto p1 = p0 + v1;
const auto p2 = p0 - v1;
if ( fill )
fill->setLine( p1, p2, fillColor );
if ( outerBorder )
{
const auto v2 = v * m_border;
outerBorder->setLine( p1 + v2, p1, borderColor );
innerBorder->setLine( p2 - v2, p2, borderColor );
}
}
inline void setClosingBorderLines( const QSGGeometry::ColoredPoint2D& pos,
@ -108,6 +103,9 @@ namespace
also be found ( first quadrant ) by:
atan2( tan( angle ), h / w );
Note: we return the vector mirrored vertically, so that it
matches the coordinate system used by Qt.
*/
if ( qFuzzyIsNull( cos ) )
@ -117,7 +115,7 @@ namespace
const qreal t = 1.0 / qSqrt( 1.0 + m * m );
const auto dx = ( cos >= 0.0 ) ? t : -t;
return { dx, m * dx };
return { dx, -m * dx };
}
const qreal m_thickness;
@ -126,8 +124,8 @@ namespace
// radii t the middle of the arc
const qreal m_rx, m_ry;
// distances between the middle and inner/outer of the border
const qreal m_offsetOut, m_offsetIn;
// distances between the middle and the beginning of the border
const qreal m_offsetToBorder;
const qreal m_aspectRatio; // m_rx / m_ry
@ -149,33 +147,37 @@ namespace
, m_ry3( m_ry1 - m_sy * ( thickness - border ) )
, m_rx4( m_rx1 - m_sx * thickness )
, m_ry4( m_ry1 - m_sy * thickness )
, m_cx( rect.x() + m_rx1 )
, m_cy( rect.y() + m_ry1 )
, m_center( rect.x() + m_rx1, rect.y() + m_ry1 )
{
}
inline void setLine( const qreal radians,
const QskVertex::Color color, QskVertex::ColoredLine& line ) const
inline void setLinesAt( const qreal radians,
const QskVertex::Color fillColor, const QskVertex::Color borderColor,
QskVertex::ColoredLine* fill, QskVertex::ColoredLine* outer,
QskVertex::ColoredLine* inner ) const
{
const auto cos = qFastCos( radians );
const auto sin = qFastSin( radians );
const QPointF v( qFastCos( radians ), -qFastSin( radians ) );
line.setLine( m_cx + m_rx2 * cos, m_cy - m_ry2 * sin,
m_cx + m_rx3 * cos, m_cy - m_ry3 * sin, color );
const auto x2 = m_center.x() + m_rx2 * v.x();
const auto y2 = m_center.y() + m_ry2 * v.y();
const auto x3 = m_center.x() + m_rx3 * v.x();
const auto y3 = m_center.y() + m_ry3 * v.y();
if ( fill )
fill->setLine( x2, y2, x3, y3, fillColor );
if ( outer )
{
const auto x1 = m_center.x() + m_rx1 * v.x();
const auto y1 = m_center.y() + m_ry1 * v.y();
const auto x4 = m_center.x() + m_rx4 * v.x();
const auto y4 = m_center.y() + m_ry4 * v.y();
outer->setLine( x1, y1, x2, y2, borderColor );
inner->setLine( x4, y4, x3, y3, borderColor );
}
inline void setBorderLines( const qreal radians,
const QskVertex::Color color, QskVertex::ColoredLine& outer,
QskVertex::ColoredLine& inner ) const
{
const auto cos = qFastCos( radians );
const auto sin = qFastSin( radians );
outer.setLine( m_cx + m_rx1 * cos, m_cy - m_ry1 * sin,
m_cx + m_rx2 * cos, m_cy - m_ry2 * sin, color );
inner.setLine( m_cx + m_rx4 * cos, m_cy - m_ry4 * sin,
m_cx + m_rx3 * cos, m_cy - m_ry3 * sin, color );
}
inline void setClosingBorderLines( const QSGGeometry::ColoredPoint2D& pos,
@ -201,7 +203,7 @@ namespace
const qreal m_rx1, m_ry1, m_rx2, m_ry2, m_rx3, m_ry3, m_rx4, m_ry4;
// center point
const qreal m_cx, m_cy;
const QPointF m_center;
};
}
@ -216,17 +218,15 @@ namespace
int fillCount() const;
int borderCount() const;
int setFillLines( qreal thickness, qreal border, QskVertex::ColoredLine* ) const;
int setBorderLines( qreal thickness, qreal border, QskVertex::ColoredLine* ) const;
void setStripLines( const qreal thickness, const qreal border,
QskVertex::ColoredLine* fillLines, QskVertex::ColoredLine* borderLines ) const;
private:
int arcLineCount() const;
template< class LineStroker >
int renderFillLines( const LineStroker&, QskVertex::ColoredLine* ) const;
template< class LineStroker >
int renderBorderLines( const LineStroker&, QskVertex::ColoredLine* ) const;
void renderStripLines( const LineStroker&,
QskVertex::ColoredLine*, QskVertex::ColoredLine* ) const;
const QRectF& m_rect;
@ -245,7 +245,7 @@ namespace
: m_rect( rect )
, m_radians1( qDegreesToRadians( metrics.startAngle() ) )
, m_radians2( qDegreesToRadians( metrics.endAngle() ) )
, m_radial( qFuzzyCompare( rect.width(), rect.height() ) ? true : radial )
, m_radial( radial )
, m_closed( metrics.isClosed() )
, m_gradient( gradient )
, m_borderColor( borderColor )
@ -271,28 +271,29 @@ namespace
return arcLineCount() + m_gradient.stepCount() - 1;
}
inline int ArcStroker::setFillLines( const qreal thickness,
const qreal border, QskVertex::ColoredLine* lines ) const
void ArcStroker::setStripLines( const qreal thickness, const qreal border,
QskVertex::ColoredLine* fillLines, QskVertex::ColoredLine* borderLines ) const
{
if ( m_radial )
{
const RadialStroker lineStroker( m_rect, thickness, border );
return renderFillLines( lineStroker, lines );
renderStripLines( lineStroker, fillLines, borderLines );
}
else
{
const OrthogonalStroker lineStroker( m_rect, thickness, border );
return renderFillLines( lineStroker, lines );
renderStripLines( lineStroker, fillLines, borderLines );
}
}
template< class LineStroker >
inline int ArcStroker::renderFillLines(
const LineStroker& stroker, QskVertex::ColoredLine* lines ) const
void ArcStroker::renderStripLines( const LineStroker& lineStroker,
QskVertex::ColoredLine* fillLines, QskVertex::ColoredLine* borderLines ) const
{
auto l = lines;
QskBoxRenderer::GradientIterator it;
if ( fillLines )
{
if ( m_gradient.stepCount() <= 1 )
{
it.reset( m_gradient.rgbStart(), m_gradient.rgbEnd() );
@ -302,11 +303,29 @@ namespace
it.reset( m_gradient.stops() );
it.advance(); // the first stop is always covered by the contour
}
}
const auto count = arcLineCount();
const auto radiansSpan = m_radians2 - m_radians1;
const qreal stepMax = count - 1;
const auto stepSize = ( m_radians2 - m_radians1 ) / stepMax;
const auto stepSize = radiansSpan / stepMax;
auto l = fillLines;
auto outer = borderLines;
auto inner = borderLines;
if ( borderLines )
{
outer = borderLines;
if ( !m_closed )
outer += 3;
inner = outer + count;
if ( !m_closed )
inner += 3;
}
for ( int i = 0; i < count; i++ )
{
@ -314,17 +333,30 @@ namespace
while( !it.isDone() && ( it.position() < progress ) )
{
const auto radians = m_radians1 + it.position() * ( m_radians2 - m_radians1 );
stroker.setLine( radians, it.color(), *l++ );
const auto radians = m_radians1 + it.position() * radiansSpan;
lineStroker.setLinesAt( radians, it.color(), m_borderColor,
l++, nullptr, nullptr );
it.advance();
}
const auto radians = m_radians1 + i * stepSize;
const auto color = it.colorAt( progress );
stroker.setLine( m_radians1 + i * stepSize, color, *l++ );
lineStroker.setLinesAt( radians, color, m_borderColor,
l ? l++ : nullptr,
outer ? outer + i : nullptr,
inner ? inner + count - 1 - i : nullptr
);
}
return l - lines;
if ( borderLines && !m_closed )
{
const auto sign = ( radiansSpan > 0.0 ) ? 1.0 : -1.0;
lineStroker.setClosingBorderLines( inner[count - 1].p1, outer, sign, m_borderColor );
lineStroker.setClosingBorderLines( outer[count - 1].p1, inner, sign, m_borderColor );
}
}
int ArcStroker::borderCount() const
@ -338,55 +370,6 @@ namespace
return count;
}
int ArcStroker::setBorderLines( const qreal thickness, const qreal border,
QskVertex::ColoredLine* lines ) const
{
if ( m_radial )
{
const RadialStroker stroker( m_rect, thickness, border );
return renderBorderLines( stroker, lines );
}
else
{
const OrthogonalStroker stroker( m_rect, thickness, border );
return renderBorderLines( stroker, lines );
}
}
template< class LineStroker >
inline int ArcStroker::renderBorderLines( const LineStroker& stroker,
QskVertex::ColoredLine* lines ) const
{
const auto count = arcLineCount();
const qreal stepMax = count - 1;
const auto stepSize = ( m_radians2 - m_radians1 ) / stepMax;
auto outer = lines;
if ( !m_closed )
outer += 3;
auto inner = outer + count;
if ( !m_closed )
inner += 3;
for ( int i = 0; i < count; i++ )
{
stroker.setBorderLines( m_radians1 + i * stepSize,
m_borderColor, outer[i], inner[count - 1 - i] );
}
if ( !m_closed )
{
const auto sign = ( stepSize > 0.0 ) ? 1.0 : -1.0;
stroker.setClosingBorderLines( inner[count - 1].p1, outer, sign, m_borderColor );
stroker.setClosingBorderLines( outer[count - 1].p1, inner, sign, m_borderColor );
}
return 2 * ( count + ( m_closed ? 0 : 3 ) );
}
}
bool QskArcRenderer::isGradientSupported( const QskGradient& gradient )
@ -407,6 +390,15 @@ void QskArcRenderer::renderArc( const QRectF& rect, const QskArcMetrics& metrics
bool radial, qreal borderWidth, const QskGradient& gradient,
const QColor& borderColor, QSGGeometry& geometry )
{
if ( !radial )
{
if ( qskFuzzyCompare( rect.width(), rect.height() ) )
radial = false;
if ( gradient.isMonochrome() && borderWidth <= 0.0 )
radial = false;
}
geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
ArcStroker stroker( rect, metrics, radial, gradient,
@ -420,44 +412,12 @@ void QskArcRenderer::renderArc( const QRectF& rect, const QskArcMetrics& metrics
lineCount++; // connecting line
const auto lines = qskAllocateColoredLines( geometry, lineCount );
if ( lines == nullptr )
return;
if ( fillCount )
if ( lines )
{
const auto effectiveCount = stroker.setFillLines(
metrics.thickness(), borderWidth, lines );
const auto fillLines = fillCount ? lines : nullptr;
const auto borderLines = borderCount ? lines + lineCount - borderCount : nullptr;
if ( effectiveCount > fillCount )
{
qFatal( "geometry: allocated memory exceeded: %d vs. %d",
effectiveCount, fillCount );
}
if ( effectiveCount < fillCount )
{
/*
Gradient or contour lines might be at the same position
and we end up with less lines, than expected.
*/
for ( int i = effectiveCount; i < fillCount; i++ )
lines[i] = lines[i - 1];
}
}
if ( borderCount > 0 )
{
auto borderLines = lines + lineCount - borderCount;
const auto effectiveCount = stroker.setBorderLines(
metrics.thickness(), borderWidth, borderLines );
if ( borderCount != effectiveCount )
{
qFatal( "geometry: allocated memory does not match: %d vs. %d",
effectiveCount, borderCount );
}
stroker.setStripLines( metrics.thickness(), borderWidth, fillLines, borderLines );
if ( fillCount && borderCount )
{