6#include "QskArcRenderer.h"
7#include "QskArcMetrics.h"
8#include "QskGradient.h"
10#include "QskVertexHelper.h"
11#include "QskRgbValue.h"
13#include <qsggeometry.h>
16 QSGGeometry& geometry,
int lineCount )
18 geometry.allocate( 2 * lineCount );
23 QSGGeometry& geometry,
int lineCount )
25 geometry.allocate( 2 * lineCount );
31 template<
class Line >
32 class OrthogonalStroker
35 OrthogonalStroker(
const QRectF& rect, qreal thickness, qreal border )
36 : m_thickness( thickness )
38 , m_rx( 0.5 * ( rect.width() - m_thickness ) )
39 , m_ry( 0.5 * ( rect.height() - m_thickness ) )
40 , m_offsetToBorder( 0.5 * m_thickness - border )
41 , m_aspectRatio( m_rx / m_ry )
42 , m_cx( rect.x() + 0.5 * rect.width() )
43 , m_cy( rect.y() + 0.5 * rect.height() )
47 inline void setLinesAt(
const qreal radians,
49 Line* fill, Line* outerBorder, Line* innerBorder )
const
51 const auto cos = qFastCos( radians );
52 const auto sin = qFastSin( radians );
54 const auto v = normalVector( cos, sin );
56 const QPointF p0( m_cx + m_rx * cos, m_cy - m_ry * sin );
58 const auto v1 = v * m_offsetToBorder;
60 const auto p1 = p0 + v1;
61 const auto p2 = p0 - v1;
64 fill->setLine( p1, p2, fillColor );
68 const auto v2 = v * m_border;
70 outerBorder->setLine( p1 + v2, p1, borderColor );
71 innerBorder->setLine( p2 - v2, p2, borderColor );
75 inline void setClosingBorderLines(
const Line& l,
78 const auto& pos = l.p1;
79 const auto& l0 = lines[0];
81 const auto dx = sign * l0.dy();
82 const auto dy = sign * l0.dx();
84 lines[-3].setLine( pos.x, pos.y, pos.x, pos.y, color );
85 lines[-2].setLine( pos.x + dx, pos.y - dy, pos.x, pos.y, color );
86 lines[-1].setLine( l0.x1() + dx, l0.y1() - dy, l0.x1(), l0.y1(), color );
90 inline QPointF normalVector(
const qreal cos,
const qreal sin )
const
111 if ( qFuzzyIsNull( cos ) )
112 return { 0.0, ( sin < 0.0 ) ? 1.0 : -1.0 };
114 const qreal m = m_aspectRatio * ( sin / cos );
115 const qreal t = 1.0 / qSqrt( 1.0 + m * m );
117 const auto dx = ( cos >= 0.0 ) ? t : -t;
118 return { dx, -m * dx };
121 const qreal m_thickness;
122 const qreal m_border;
125 const qreal m_rx, m_ry;
128 const qreal m_offsetToBorder;
130 const qreal m_aspectRatio;
133 const qreal m_cx, m_cy;
136 template<
class Line >
140 RadialStroker(
const QRectF& rect, qreal thickness, qreal border )
141 : m_sx( qMax( rect.width() / rect.height(), 1.0 ) )
142 , m_sy( qMax( rect.height() / rect.width(), 1.0 ) )
143 , m_rx1( 0.5 * rect.width() )
144 , m_ry1( 0.5 * rect.height() )
145 , m_rx2( m_rx1 - m_sx * border )
146 , m_ry2( m_ry1 - m_sy * border )
147 , m_rx3( m_rx1 - m_sx * ( thickness - border ) )
148 , m_ry3( m_ry1 - m_sy * ( thickness - border ) )
149 , m_rx4( m_rx1 - m_sx * thickness )
150 , m_ry4( m_ry1 - m_sy * thickness )
151 , m_center( rect.x() + m_rx1, rect.y() + m_ry1 )
155 inline void setLinesAt(
const qreal radians,
157 Line* fill, Line* outer, Line* inner )
const
159 const QPointF v( qFastCos( radians ), -qFastSin( radians ) );
161 const auto x1 = m_center.x() + m_rx2 * v.x();
162 const auto y1 = m_center.y() + m_ry2 * v.y();
164 const auto x2 = m_center.x() + m_rx3 * v.x();
165 const auto y2 = m_center.y() + m_ry3 * v.y();
168 fill->setLine( x1, y1, x2, y2, fillColor );
172 const auto x3 = m_center.x() + m_rx1 * v.x();
173 const auto y3 = m_center.y() + m_ry1 * v.y();
175 const auto x4 = m_center.x() + m_rx4 * v.x();
176 const auto y4 = m_center.y() + m_ry4 * v.y();
178 outer->setLine( x3, y3, x1, y1, borderColor );
179 inner->setLine( x4, y4, x2, y2, borderColor );
183 inline void setClosingBorderLines(
const Line& l,
186 const auto& pos = l.p1;
189 const auto& l0 = lines[0];
191 const auto s = m_sx / m_sy;
192 const auto dx = sign * l0.dy() * s;
193 const auto dy = sign * l0.dx() / s;
195 lines[-3].setLine( pos.x, pos.y, pos.x, pos.y, color );
196 lines[-2].setLine( pos.x + dx, pos.y - dy, pos.x, pos.y, color );
197 lines[-1].setLine( l0.x1() + dx, l0.y1() - dy, l0.x1(), l0.y1(), color );
202 const qreal m_sx, m_sy;
205 const qreal m_rx1, m_ry1, m_rx2, m_ry2, m_rx3, m_ry3, m_rx4, m_ry4;
208 const QPointF m_center;
211 template<
class Line >
212 class CircularStroker
215 CircularStroker(
const QRectF& rect, qreal thickness, qreal border )
216 : m_center( rect.center() )
217 , m_radius( 0.5 * ( rect.width() - thickness ) )
218 , m_distOut( 0.5 * thickness )
219 , m_distIn( m_distOut - border )
223 inline void setLinesAt(
const qreal radians,
225 Line* fill, Line* outer, Line* inner )
const
227 const QPointF v( qFastCos( radians ), -qFastSin( radians ) );
229 const auto p0 = m_center + m_radius * v;
230 const auto dv1 = v * m_distIn;
232 const auto p1 = p0 + dv1;
233 const auto p2 = p0 - dv1;
236 fill->setLine( p1, p2, fillColor );
240 const auto dv2 = v * m_distOut;
242 const auto p3 = p0 + dv2;
243 const auto p4 = p0 - dv2;
245 outer->setLine( p3, p1, borderColor );
246 inner->setLine( p4, p2, borderColor );
250 inline void setClosingBorderLines(
const Line& l,
253 const auto& pos = l.p1;
254 const auto& l0 = lines[0];
256 const auto dx = sign * l0.dy();
257 const auto dy = sign * l0.dx();
259 lines[-3].setLine( pos.x, pos.y, pos.x, pos.y, color );
260 lines[-2].setLine( pos.x + dx, pos.y - dy, pos.x, pos.y, color );
261 lines[-1].setLine( l0.x1() + dx, l0.y1() - dy, l0.x1(), l0.y1(), color );
266 const QPointF m_center;
267 const qreal m_radius;
270 const qreal m_distOut, m_distIn;
282 int fillCount()
const;
283 int borderCount()
const;
285 template<
class Line >
286 void renderArc(
const qreal thickness,
const qreal border, Line*, Line* )
const;
289 int arcLineCount()
const;
291 template<
class LineStroker,
class Line >
292 void renderLines(
const LineStroker&, Line*, Line* )
const;
294 const QRectF& m_rect;
296 const qreal m_radians1;
297 const qreal m_radians2;
306 Renderer::Renderer(
const QRectF& rect,
const QskArcMetrics& metrics,
309 , m_radians1( qDegreesToRadians( metrics.startAngle() ) )
310 , m_radians2( qDegreesToRadians( metrics.endAngle() ) )
312 , m_closed( metrics.isClosed() )
313 , m_gradient( gradient )
314 , m_borderColor( borderColor )
318 int Renderer::arcLineCount()
const
322 const auto radius = 0.5 * qMax( m_rect.width(), m_rect.height() );
323 const auto radians = qAbs( m_radians2 - m_radians1 );
325 const auto count = qCeil( ( radius * radians ) / 3.0 );
326 return qBound( 3, count, 160 );
329 int Renderer::fillCount()
const
331 if ( !m_gradient.isVisible() )
334 return arcLineCount() + m_gradient.stepCount() - 1;
337 template<
class Line >
338 void Renderer::renderArc(
const qreal thickness,
const qreal border,
339 Line* fillLines, Line* borderLines )
const
341 if ( qskFuzzyCompare( m_rect.width(), m_rect.height() ) )
343 const CircularStroker< Line > stroker( m_rect, thickness, border );
344 renderLines( stroker, fillLines, borderLines );
348 const RadialStroker< Line > stroker( m_rect, thickness, border );
349 renderLines( stroker, fillLines, borderLines );
353 const OrthogonalStroker< Line > stroker( m_rect, thickness, border );
354 renderLines( stroker, fillLines, borderLines );
358 template<
class LineStroker,
class Line >
359 void Renderer::renderLines(
const LineStroker& lineStroker,
360 Line* fillLines, Line* borderLines )
const
362 QskVertex::GradientIterator it;
366 if ( m_gradient.stepCount() <= 1 )
368 it.reset( m_gradient.rgbStart(), m_gradient.rgbEnd() );
372 it.reset( m_gradient.stops() );
377 const auto count = arcLineCount();
378 const auto radiansSpan = m_radians2 - m_radians1;
380 const qreal stepMax = count - 1;
381 const auto stepSize = radiansSpan / stepMax;
385 auto outer = borderLines;
386 auto inner = borderLines;
394 inner = outer + count;
399 for (
int i = 0; i < count; i++ )
401 const auto progress = i / stepMax;
403 while( !it.isDone() && ( it.position() < progress ) )
405 const auto radians = m_radians1 + it.position() * radiansSpan;
406 lineStroker.setLinesAt( radians, it.color(), m_borderColor,
407 l++,
nullptr,
nullptr );
412 const auto radians = m_radians1 + i * stepSize;
413 const auto color = it.colorAt( progress );
415 lineStroker.setLinesAt( radians, color, m_borderColor,
417 outer ? outer + i : nullptr,
418 inner ? inner + count - 1 - i : nullptr
422 if ( borderLines && !m_closed )
424 const auto sign = ( radiansSpan > 0.0 ) ? 1.0 : -1.0;
426 lineStroker.setClosingBorderLines( inner[count - 1], outer, sign, m_borderColor );
427 lineStroker.setClosingBorderLines( outer[count - 1], inner, sign, m_borderColor );
431 int Renderer::borderCount()
const
433 if ( m_borderColor.a == 0 )
436 auto count = 2 * arcLineCount();
444bool QskArcRenderer::isGradientSupported(
const QRectF& rect,
447 if ( rect.isEmpty() || metrics.isNull() )
450 if ( !gradient.isVisible() || gradient.isMonochrome() )
453 switch( gradient.type() )
455 case QskGradient::Stops:
459 case QskGradient::Conic:
462 const auto direction = gradient.conicDirection();
463 if ( direction.center() == rect.center() )
465 const auto aspectRatio = rect.width() / rect.height();
466 if ( qskFuzzyCompare( direction.aspectRatio(), aspectRatio ) )
487void QskArcRenderer::setColoredBorderLines(
const QRectF& rect,
488 const QskArcMetrics& metrics,
bool radial, qreal borderWidth,
489 const QColor& borderColor, QSGGeometry& geometry )
491 geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
492 geometry.markVertexDataDirty();
494 if ( borderWidth <= 0.0 || !QskRgb::isVisible( borderColor ) )
496 qskAllocateColoredLines( geometry, 0 );
500 const Renderer renderer( rect, metrics, radial,
QskGradient(), borderColor );
502 if (
const auto lines = qskAllocateColoredLines( geometry, renderer.borderCount() ) )
504 renderer.renderArc( metrics.thickness(), borderWidth,
509void QskArcRenderer::setColoredFillLines(
const QRectF& rect,
const QskArcMetrics& metrics,
510 bool radial, qreal borderWidth,
const QskGradient& gradient, QSGGeometry& geometry )
512 geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
513 geometry.markVertexDataDirty();
515 if ( !gradient.isVisible() )
517 qskAllocateColoredLines( geometry, 0 );
521 const Renderer renderer( rect, metrics, radial, gradient, QColor( 0, 0, 0, 0 ) );
523 if (
const auto lines = qskAllocateColoredLines( geometry, renderer.fillCount() ) )
525 renderer.renderArc( metrics.thickness(), borderWidth, lines,
530void QskArcRenderer::setColoredBorderAndFillLines(
const QRectF& rect,
531 const QskArcMetrics& metrics,
bool radial, qreal borderWidth,
532 const QColor& borderColor,
const QskGradient& gradient, QSGGeometry& geometry )
534 geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
535 geometry.markVertexDataDirty();
537 const Renderer renderer( rect, metrics, radial, gradient,
538 borderColor.isValid() ? borderColor : QColor( 0, 0, 0, 0 ) );
540 const auto borderCount = renderer.borderCount();
541 const auto fillCount = renderer.fillCount();
543 auto lineCount = borderCount + fillCount;
544 if ( borderCount && fillCount )
547 const auto lines = qskAllocateColoredLines( geometry, lineCount );
550 const auto fillLines = fillCount ? lines :
nullptr;
551 const auto borderLines = borderCount ? lines + lineCount - borderCount :
nullptr;
553 renderer.renderArc( metrics.thickness(), borderWidth, fillLines, borderLines );
555 if ( fillCount && borderCount )
557 const auto idx = fillCount;
559 lines[idx].p1 = lines[idx - 1].p2;
560 lines[idx].p2 = lines[idx + 1].p1;
565void QskArcRenderer::setBorderLines(
const QRectF& rect,
566 const QskArcMetrics& metrics,
bool radial, qreal borderWidth, QSGGeometry& geometry )
568 geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
569 geometry.markVertexDataDirty();
571 if ( borderWidth <= 0.0 )
573 qskAllocateLines( geometry, 0 );
577 const Renderer renderer( rect, metrics, radial,
QskGradient(), QskRgb::Black );
579 const auto lines = qskAllocateLines( geometry, renderer.borderCount() );
583 renderer.renderArc( metrics.thickness(), borderWidth, fill, lines );
587void QskArcRenderer::setFillLines(
const QRectF& rect,
588 const QskArcMetrics& metrics,
bool radial, qreal borderWidth, QSGGeometry& geometry )
590 geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
591 geometry.markVertexDataDirty();
593 const Renderer renderer( rect, metrics, radial, QskRgb::Black, 0 );
595 const auto lines = qskAllocateLines( geometry, renderer.fillCount() );
599 renderer.renderArc( metrics.thickness(), borderWidth, lines, border );