more variations of gradient vectors supported

This commit is contained in:
Uwe Rathmann 2023-01-05 17:18:34 +01:00
parent 21e1206b2d
commit f4aaf4cc92
4 changed files with 158 additions and 237 deletions

View File

@ -69,14 +69,16 @@ bool QskBoxRenderer::isGradientSupported(
} }
case QskGradient::Linear: case QskGradient::Linear:
{ {
const auto dir = gradient.linearDirection();
if ( dir.x1() > dir.x2() || dir.y1() > dir.y2() )
return false;
if ( shape.isRectangle() ) if ( shape.isRectangle() )
{ {
// rectangles are fully supported // rectangles are fully supported
return true; return true;
} }
const auto dir = gradient.linearDirection();
if ( dir.isTilted() ) if ( dir.isTilted() )
{ {
return ( dir.x1() == 0.0 && dir.x2() == 0.0 ) return ( dir.x1() == 0.0 && dir.x2() == 0.0 )
@ -84,29 +86,31 @@ bool QskBoxRenderer::isGradientSupported(
} }
else else
{ {
// different radii at oppsoite corners are not implemented TODO ... qreal r1, r2, r3, r4;
const auto r1 = shape.radius( Qt::TopLeftCorner );
const auto r2 = shape.radius( Qt::TopRightCorner );
const auto r3 = shape.radius( Qt::BottomLeftCorner );
const auto r4 = shape.radius( Qt::BottomRightCorner );
if ( dir.isHorizontal() ) if ( dir.isHorizontal() )
{ {
if ( dir.x1() == 0.0 && dir.x2() == 1.0 ) r1 = shape.radius( Qt::TopLeftCorner ).width();
{ r2 = shape.radius( Qt::BottomLeftCorner ).width();
return ( r1.width() == r3.width() ) r3 = shape.radius( Qt::TopRightCorner ).width();
&& ( r2.width() == r4.width() ); r4 = shape.radius( Qt::BottomRightCorner ).width();
}
} }
else else
{ {
if ( dir.y1() == 0.0 && dir.y2() == 1.0 ) r1 = shape.radius( Qt::TopLeftCorner ).height();
{ r2 = shape.radius( Qt::TopRightCorner ).height();
return ( r1.height() == r2.height() ) r3 = shape.radius( Qt::BottomLeftCorner ).height();
&& ( r3.height() == r4.height() ); r4 = shape.radius( Qt::BottomRightCorner ).height();
}
} }
if ( ( r1 <= 0.0 || r2 <= 0.0 ) && ( r3 <= 0.0 || r4 <= 0.0 ) )
{
// one of the corners is not rounded
return true;
}
// different radii at opposite corners are not implemented TODO ...
return ( r1 == r2 ) && ( r3 == r4 );
} }
return false; return false;

View File

@ -217,15 +217,15 @@ namespace QskVertex
ColoredLine* fillOrdered( ContourIterator& contourIt, ColoredLine* fillOrdered( ContourIterator& contourIt,
ColorIterator& colorIt, int lineCount, ColoredLine* lines ) ColorIterator& colorIt, int lineCount, ColoredLine* lines )
{ {
/* /*
When the the vector exceeds [ 0.0, 1.0 ] we might have When the the vector exceeds [ 0.0, 1.0 ] we might have
gradient lines lying outside the contour. gradient lines lying outside the contour.
This effect could be precalculated - however we might end This effect could be precalculated - however we might end
up difficult code with potential bugs. up difficult code with potential bugs.
So we allow the allocation code to ignore the effect by So we allow the allocation code to ignore the effect by
adding duplicates of the last line. adding duplicates of the last line.
*/ */
const auto value0 = contourIt.value(); const auto value0 = contourIt.value();
ColoredLine* l = lines; ColoredLine* l = lines;

View File

@ -43,9 +43,6 @@ namespace
inline bool setGradientLine( qreal value, Color color, ColoredLine* line ) inline bool setGradientLine( qreal value, Color color, ColoredLine* line )
{ {
if ( value <= m_values[0] || value >= m_values[1] )
return false;
const auto v = m_t + value * m_dt; const auto v = m_t + value * m_dt;
if ( m_vertical ) if ( m_vertical )
@ -209,17 +206,17 @@ namespace
{ {
if( m_step == 0 || m_step == 3 ) if( m_step == 0 || m_step == 3 )
{ {
const auto& p = m_corners[m_step].pos; const auto& p = m_corners[ m_step ].pos;
line->setLine( p.x(), p.y(), p.x(), p.y(), color ); line->setLine( p.x(), p.y(), p.x(), p.y(), color );
} }
else else
{ {
const qreal m = m_v.dy / m_v.dx; const qreal m = m_v.dy / m_v.dx;
auto p1 = m_corners[m_step - 1].pos; auto p1 = m_corners[ m_step - 1 ].pos;
const auto& p2 = m_corners[m_step].pos; const auto& p2 = m_corners[ m_step ].pos;
if ( p1.x() == m_corners[m_step + 1].pos.x() ) if ( p1.x() == m_corners[ m_step + 1 ].pos.x() )
p1.ry() = p2.y() + ( p2.x() - p1.x() ) / m; p1.ry() = p2.y() + ( p2.x() - p1.x() ) / m;
else else
p1.rx() = p2.x() + ( p2.y() - p1.y() ) * m; p1.rx() = p2.x() + ( p2.y() - p1.y() ) * m;

View File

@ -152,272 +152,198 @@ namespace
Values m_outer[ 4 ]; Values m_outer[ 4 ];
}; };
class FillValues class CornerValues
{ {
public: public:
inline FillValues( const QskRoundedRectRenderer::Metrics& metrics ) inline void setCorner( Qt::Orientation orientation, bool increasing,
const QskRoundedRectRenderer::Metrics::Corner& c )
{ {
for ( int i = 0; i < 4; i++ ) if ( orientation == Qt::Horizontal )
{ {
const auto& c = metrics.corner[ i ]; m_center = c.centerX;
auto& v = m_inner[ i ]; m_radius = c.radiusInnerX;
if ( c.radiusInnerX >= 0.0 )
{
v.x0 = 0.0;
v.rx = c.radiusInnerX;
}
else
{
v.x0 = c.radiusInnerX;
v.rx = 0.0;
}
if ( c.radiusInnerY >= 0.0 )
{
v.y0 = 0.0;
v.ry = c.radiusInnerY;
}
else
{
v.y0 = c.radiusInnerY;
v.ry = 0.0;
}
} }
else
{
m_center = c.centerY;
m_radius = c.radiusInnerY;
}
const qreal f = increasing ? 1.0 : -1.0;
if ( m_radius < 0.0 )
{
m_center += m_radius * f;
m_radius = 0.0;
}
else
{
m_radius *= f;
}
stepCount = c.stepCount;
} }
inline void setAngle( qreal cos, qreal sin ) qreal valueAt( qreal fv ) const
{ {
m_inner[ 0 ].setAngle( cos, sin ); return m_center + fv * m_radius;
m_inner[ 1 ].setAngle( cos, sin );
m_inner[ 2 ].setAngle( cos, sin );
m_inner[ 3 ].setAngle( cos, sin );
} }
inline qreal dx( int pos ) const { return m_inner[ pos ].dx; } int stepCount;
inline qreal dy( int pos ) const { return m_inner[ pos ].dy; }
private: private:
class Values qreal m_center = 0.0;
{ qreal m_radius = 0.0;
public:
inline void setAngle( qreal cos, qreal sin )
{
dx = x0 + cos * rx;
dy = y0 + sin * ry;
}
qreal dx, dy;
qreal x0, y0, rx, ry;
};
Values m_inner[ 4 ];
}; };
} }
namespace namespace
{ {
class VRectEllipseIterator class HVRectEllipseIterator
{ {
public: public:
VRectEllipseIterator( const QskRoundedRectRenderer::Metrics& metrics ) HVRectEllipseIterator(
const QskRoundedRectRenderer::Metrics& metrics, const QLineF& vector )
: m_metrics( metrics ) : m_metrics( metrics )
, m_values( metrics ) , m_vertical( vector.x1() == vector.x2() )
{ {
const auto& c = metrics.corner; const auto& c = metrics.corner;
m_v[ 0 ].left = c[ TopLeft ].centerX; auto v = m_values;
m_v[ 0 ].right = c[ TopRight ].centerX;
m_v[ 0 ].y = metrics.innerQuad.top;
m_v[ 1 ] = m_v[ 0 ]; if ( m_vertical )
{
v[0].setCorner( Qt::Horizontal, false, c[TopLeft] );
v[1].setCorner( Qt::Horizontal, true, c[TopRight] );
v[2].setCorner( Qt::Vertical, false, c[TopLeft] );
m_leadingCorner = ( c[ TopLeft ].stepCount >= c[ TopRight ].stepCount ) v[3].setCorner( Qt::Horizontal, false, c[BottomLeft] );
? TopLeft : TopRight; v[4].setCorner( Qt::Horizontal, true, c[BottomRight] );
v[5].setCorner( Qt::Vertical, true, c[BottomLeft] );
m_arcIterator.reset( c[ m_leadingCorner ].stepCount, false ); m_pos0 = metrics.innerQuad.top;
} m_size = metrics.innerQuad.height;
inline bool setGradientLine( qreal value, Color color, ColoredLine* line ) m_t = m_pos0 + vector.y1() * m_size;
{ m_dt = vector.dy() * m_size;
const auto& q = m_metrics.innerQuad; }
const qreal y = q.top + value * q.height; else
{
v[0].setCorner( Qt::Vertical, false, c[TopLeft] );
v[1].setCorner( Qt::Vertical, true, c[BottomLeft] );
v[2].setCorner( Qt::Horizontal, false, c[TopLeft] );
const qreal f = ( y - m_v[ 0 ].y ) / ( m_v[ 1 ].y - m_v[ 0 ].y ); v[3].setCorner( Qt::Vertical, false, c[TopRight] );
const qreal left = m_v[ 0 ].left + f * ( m_v[ 1 ].left - m_v[ 0 ].left ); v[4].setCorner( Qt::Vertical, true, c[BottomRight] );
const qreal right = m_v[ 0 ].right + f * ( m_v[ 1 ].right - m_v[ 0 ].right ); v[5].setCorner( Qt::Horizontal, true, c[TopRight] );
line->setLine( left, y, right, y, color ); m_pos0 = metrics.innerQuad.left;
return true; m_size = metrics.innerQuad.width;
}
inline void setContourLine( Color color, ColoredLine* line ) m_t = m_pos0 + vector.x1() * m_size;
{ m_dt = vector.dx() * m_size;
line->setLine( m_v[ 1 ].left, m_v[ 1 ].y, }
m_v[ 1 ].right, m_v[ 1 ].y, color );
}
inline qreal value() const m_v1.from = v[0].valueAt( 1.0 );
{ m_v1.to = v[1].valueAt( 1.0 );
const auto& q = m_metrics.innerQuad; m_v1.pos = m_pos0;
return ( m_v[ 1 ].y - q.top ) / q.height;
m_v2 = m_v1;
const auto stepCount = qMax( v[0].stepCount, v[1].stepCount );
m_arcIterator.reset( stepCount, false );
} }
inline bool advance() inline bool advance()
{ {
const auto& centerQuad = m_metrics.centerQuad; auto v = m_values;
const auto& c = m_metrics.corner;
if ( m_arcIterator.step() == m_arcIterator.stepCount() ) if ( m_arcIterator.step() == m_arcIterator.stepCount() )
{ {
if ( m_arcIterator.isInverted() ) if ( m_arcIterator.isInverted() )
return false;
m_leadingCorner = ( c[ BottomLeft ].stepCount >= c[ BottomRight ].stepCount )
? BottomLeft : BottomRight;
m_arcIterator.reset( c[ m_leadingCorner ].stepCount, true );
if ( centerQuad.top < centerQuad.bottom )
{ {
m_v[ 0 ] = m_v[ 1 ]; // we have finished the closing "corners"
return false;
}
m_v[ 1 ].left = m_metrics.innerQuad.left; const auto stepCount = qMax( v[3].stepCount, v[4].stepCount );
m_v[ 1 ].right = m_metrics.innerQuad.right; m_arcIterator.reset( stepCount, true );
m_v[ 1 ].y = centerQuad.bottom;
const qreal pos1 = v[2].valueAt( 0.0 );
const qreal pos2 = v[5].valueAt( 0.0 );
if ( pos1 < pos2 )
{
// the real rectangle - between the rounded "corners "
m_v1 = m_v2;
m_v2.from = v[3].valueAt( 1.0 );
m_v2.to = v[4].valueAt( 1.0 );
m_v2.pos = pos2;
return true; return true;
} }
} }
m_arcIterator.increment(); m_arcIterator.increment();
m_values.setAngle( m_arcIterator.cos(), m_arcIterator.sin() );
m_v[ 0 ] = m_v[ 1 ]; m_v1 = m_v2;
if ( m_arcIterator.isInverted() ) if ( m_arcIterator.isInverted() )
{ v += 3;
m_v[ 1 ].left = c[ BottomLeft ].centerX - m_values.dx( BottomLeft );
m_v[ 1 ].right = c[ BottomRight ].centerX + m_values.dx( BottomRight ); m_v2.from = v[0].valueAt( m_arcIterator.cos() );
m_v[ 1 ].y = c[ m_leadingCorner ].centerY + m_values.dy( m_leadingCorner ); m_v2.to = v[1].valueAt( m_arcIterator.cos() );
} m_v2.pos = v[2].valueAt( m_arcIterator.sin() );
else
{
m_v[ 1 ].left = c[ TopLeft ].centerX - m_values.dx( TopLeft );
m_v[ 1 ].right = c[ TopRight ].centerX + m_values.dx( TopRight );
m_v[ 1 ].y = c[ m_leadingCorner ].centerY - m_values.dy( m_leadingCorner );
}
return true; return true;
} }
private:
const QskRoundedRectRenderer::Metrics& m_metrics;
ArcIterator m_arcIterator;
int m_leadingCorner;
FillValues m_values;
struct { qreal left, right, y; } m_v[ 2 ];
};
class HRectEllipseIterator
{
public:
HRectEllipseIterator( const QskRoundedRectRenderer::Metrics& metrics )
: m_metrics( metrics )
, m_values( metrics )
{
const auto& c = metrics.corner;
m_v[ 0 ].top = c[ TopLeft ].centerY;
m_v[ 0 ].bottom = c[ BottomLeft ].centerY;
m_v[ 0 ].x = metrics.innerQuad.left;
m_v[ 1 ] = m_v[ 0 ];
m_leadingCorner = ( c[ TopLeft ].stepCount >= c[ BottomLeft ].stepCount )
? TopLeft : BottomLeft;
m_arcIterator.reset( c[ m_leadingCorner ].stepCount, true );
}
inline bool setGradientLine( qreal value, Color color, ColoredLine* line ) inline bool setGradientLine( qreal value, Color color, ColoredLine* line )
{ {
const auto& q = m_metrics.innerQuad; const auto pos = m_t + value * m_dt;
const qreal x = q.left + value * q.width;
const qreal f = ( x - m_v[ 0 ].x ) / ( m_v[ 1 ].x - m_v[ 0 ].x ); const qreal f = ( pos - m_v1.pos ) / ( m_v2.pos - m_v1.pos );
const qreal top = m_v[ 0 ].top + f * ( m_v[ 1 ].top - m_v[ 0 ].top );
const qreal bottom = m_v[ 0 ].bottom + f * ( m_v[ 1 ].bottom - m_v[ 0 ].bottom );
line->setLine( x, top, x, bottom, color ); const qreal v1 = m_v1.from + f * ( m_v2.from - m_v1.from );
const qreal v2 = m_v1.to + f * ( m_v2.to - m_v1.to );
setLine( v1, v2, pos, color, line );
return true; return true;
} }
inline void setContourLine( Color color, ColoredLine* line ) inline void setContourLine( Color color, ColoredLine* line )
{ {
line->setLine( m_v[ 1 ].x, m_v[ 1 ].top, setLine( m_v2.from, m_v2.to, m_v2.pos, color, line );
m_v[ 1 ].x, m_v[ 1 ].bottom, color );
} }
inline qreal value() const inline qreal value() const
{ {
const auto& q = m_metrics.innerQuad; // coordinate translated into the gradiet vector
return ( m_v[ 1 ].x - q.left ) / q.width; return ( m_v2.pos - m_t ) / m_dt;
}
inline bool advance()
{
const auto& centerQuad = m_metrics.centerQuad;
const auto& c = m_metrics.corner;
if ( m_arcIterator.step() == m_arcIterator.stepCount() )
{
if ( !m_arcIterator.isInverted() )
return false;
m_leadingCorner = ( c[ TopRight ].stepCount >= c[ BottomRight ].stepCount )
? TopRight : BottomRight;
m_arcIterator.reset( c[ m_leadingCorner ].stepCount, false );
if ( centerQuad.left < centerQuad.right )
{
m_v[ 0 ] = m_v[ 1 ];
m_v[ 1 ].top = m_metrics.innerQuad.top;
m_v[ 1 ].bottom = m_metrics.innerQuad.bottom;
m_v[ 1 ].x = centerQuad.right;
return true;
}
}
m_arcIterator.increment();
m_values.setAngle( m_arcIterator.cos(), m_arcIterator.sin() );
m_v[ 0 ] = m_v[ 1 ];
if ( m_arcIterator.isInverted() )
{
m_v[ 1 ].top = c[ TopLeft ].centerY - m_values.dy( TopLeft );
m_v[ 1 ].bottom = c[ BottomLeft ].centerY + m_values.dy( BottomLeft );
m_v[ 1 ].x = c[ m_leadingCorner ].centerX - m_values.dx( m_leadingCorner );
}
else
{
m_v[ 1 ].top = c[ TopRight ].centerY - m_values.dy( TopRight );
m_v[ 1 ].bottom = c[ BottomRight ].centerY + m_values.dy( BottomRight );
m_v[ 1 ].x = c[ m_leadingCorner ].centerX + m_values.dx( m_leadingCorner );
}
return true;
} }
private: private:
inline void setLine( qreal from, qreal to, qreal pos,
Color color, ColoredLine* line )
{
if ( m_vertical )
line->setLine( from, pos, to, pos, color );
else
line->setLine( pos, from, pos, to, color );
}
const QskRoundedRectRenderer::Metrics& m_metrics; const QskRoundedRectRenderer::Metrics& m_metrics;
const bool m_vertical;
ArcIterator m_arcIterator; ArcIterator m_arcIterator;
int m_leadingCorner;
FillValues m_values; CornerValues m_values[6];
struct { qreal top, bottom, x; } m_v[ 2 ]; struct { qreal from, to, pos; } m_v1, m_v2;
qreal m_pos0, m_size;
qreal m_t, m_dt; // to translate into gradient values
}; };
} }
@ -1045,16 +971,10 @@ static inline void qskRenderFillOrdered(
implemented TODO ... implemented TODO ...
*/ */
if ( gradient.linearDirection().isHorizontal() ) const auto dir = gradient.linearDirection();
{
HRectEllipseIterator it( metrics ); HVRectEllipseIterator it( metrics, dir.vector() );
QskVertex::fillOrdered( it, gradient, lineCount, lines ); QskVertex::fillOrdered( it, gradient, lineCount,lines );
}
else
{
VRectEllipseIterator it( metrics );
QskVertex::fillOrdered( it, gradient, lineCount, lines );
}
} }
QskRoundedRectRenderer::Metrics::Metrics( const QRectF& rect, QskRoundedRectRenderer::Metrics::Metrics( const QRectF& rect,