qskinny/src/nodes/QskVertexHelper.h

350 lines
8.7 KiB
C++

/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#ifndef QSK_VERTEX_HELPER_H
#define QSK_VERTEX_HELPER_H
#include "QskGradient.h"
#include "QskGradientDirection.h"
#include "QskVertex.h"
#include <qmath.h>
namespace QskVertex
{
class ArcIterator
{
public:
inline ArcIterator() = default;
inline ArcIterator( int stepCount, bool inverted = false )
{
reset( stepCount, inverted );
}
void reset( int stepCount, bool inverted = false )
{
m_inverted = inverted;
if ( inverted )
{
m_cos = 1.0;
m_sin = 0.0;
}
else
{
m_cos = 0.0;
m_sin = 1.0;
}
m_stepIndex = 0;
m_stepCount = stepCount;
const auto angleStep = M_PI_2 / stepCount;
m_cosStep = qFastCos( angleStep );
m_sinStep = qFastSin( angleStep );
}
inline bool isInverted() const { return m_inverted; }
inline qreal cos() const { return m_cos; }
inline qreal sin() const { return m_inverted ? -m_sin : m_sin; }
inline int step() const { return m_stepIndex; }
inline int stepCount() const { return m_stepCount; }
inline bool isDone() const { return m_stepIndex > m_stepCount; }
inline void increment()
{
if ( ++m_stepIndex >= m_stepCount )
{
if ( m_stepIndex == m_stepCount )
{
/*
Doubles are not numerical stable and the small errors,
sum up when iterating in steps. To avoid having to deal with
fuzzy compares we manually fix cos/sin at the end.
*/
if ( m_inverted )
{
m_cos = 0.0;
m_sin = -1.0;
}
else
{
m_cos = 1.0;
m_sin = 0.0;
}
}
}
else
{
const auto cos0 = m_cos;
m_cos = m_cos * m_cosStep + m_sin * m_sinStep;
m_sin = m_sin * m_cosStep - cos0 * m_sinStep;
}
}
inline void decrement()
{
revert();
increment();
revert();
}
inline void operator++() { increment(); }
static int segmentHint( qreal radius )
{
const auto arcLength = radius * M_PI_2;
return qBound( 3, qCeil( arcLength / 3.0 ), 18 ); // every 3 pixels
}
inline void revert()
{
m_inverted = !m_inverted;
m_stepIndex = m_stepCount - m_stepIndex;
m_sin = -m_sin;
}
ArcIterator reverted() const
{
ArcIterator it = *this;
it.revert();
return it;
}
private:
qreal m_cos;
qreal m_sin;
int m_stepIndex;
qreal m_cosStep;
qreal m_sinStep;
int m_stepCount;
bool m_inverted;
};
}
namespace QskVertex
{
class ColorMap
{
public:
inline ColorMap()
: ColorMap( QskGradient() )
{
}
inline ColorMap( const QskGradient& gradient )
: m_isTransparent( !gradient.isVisible() )
, m_isMonochrome( gradient.isMonochrome() )
, m_color1( gradient.rgbStart() )
, m_color2( gradient.rgbEnd() )
{
if ( !m_isMonochrome )
{
const auto dir = gradient.linearDirection();
m_x = dir.x1();
m_y = dir.y1();
m_dx = dir.x2() - dir.x1();
m_dy = dir.y2() - dir.y1();
m_dot = m_dx * m_dx + m_dy * m_dy;
}
}
inline void setLine( qreal x1, qreal y1, qreal x2, qreal y2,
QskVertex::ColoredLine* line ) const
{
if ( m_isMonochrome )
{
line->setLine( x1, y1, x2, y2, m_color1 );
}
else
{
const auto c1 = colorAt( x1, y1 );
const auto c2 = colorAt( x2, y2 );
line->setLine( x1, y1, c1, x2, y2, c2 );
}
}
inline bool isMonochrome() const { return m_isMonochrome; }
inline bool isTransparent() const { return m_isTransparent; }
static inline bool isGradientSupported(
const QskGradient& gradient, const QRectF& rect )
{
if ( gradient.isMonochrome() )
return true;
switch( gradient.stepCount() )
{
case 0:
return true;
case 1:
{
Q_ASSERT( gradient.stretchMode() != QskGradient::StretchToSize );
return gradient.linearDirection().contains( rect );
}
default:
return false;
}
}
private:
inline QskVertex::Color colorAt( qreal x, qreal y ) const
{
return m_color1.interpolatedTo( m_color2, valueAt( x, y ) );
}
inline qreal valueAt( qreal x, qreal y ) const
{
const qreal dx = x - m_x;
const qreal dy = y - m_y;
return ( dx * m_dx + dy * m_dy ) / m_dot;
}
const bool m_isTransparent;
const bool m_isMonochrome;
qreal m_x, m_y, m_dx, m_dy, m_dot;
const QskVertex::Color m_color1;
const QskVertex::Color m_color2;
};
}
namespace QskVertex
{
class GradientIterator
{
public:
GradientIterator() = default;
inline GradientIterator( const QskVertex::Color color )
: m_color1( color )
, m_color2( color )
{
}
inline GradientIterator(
const QskVertex::Color& color1, const QskVertex::Color& color2 )
: m_color1( color1 )
, m_color2( color2 )
{
}
inline GradientIterator( const QskGradientStops& stops )
: m_stops( stops )
, m_color1( stops.first().rgb() )
, m_color2( m_color1 )
, m_pos1( stops.first().position() )
, m_pos2( m_pos1 )
, m_index( 0 )
{
}
inline void reset( const QskVertex::Color color )
{
m_index = -1;
m_color1 = m_color2 = color;
}
inline void reset( const QskVertex::Color& color1,
const QskVertex::Color& color2 )
{
m_index = -1;
m_color1 = color1;
m_color2 = color2;
}
inline void reset( const QskGradientStops& stops )
{
m_stops = stops;
m_index = 0;
m_color1 = m_color2 = stops.first().rgb();
m_pos1 = m_pos2 = stops.first().position();
}
inline qreal position() const
{
return m_pos2;
}
inline QskVertex::Color color() const
{
return m_color2;
}
inline QskVertex::Color colorAt( qreal pos ) const
{
if ( m_color1 == m_color2 )
return m_color1;
if ( m_index < 0 )
{
return m_color1.interpolatedTo( m_color2, pos );
}
else
{
if ( m_pos2 == m_pos1 )
return m_color1;
const auto r = ( pos - m_pos1 ) / ( m_pos2 - m_pos1 );
return m_color1.interpolatedTo( m_color2, r );
}
}
inline bool advance()
{
if ( m_index < 0 )
return true;
m_pos1 = m_pos2;
m_color1 = m_color2;
if ( ++m_index < m_stops.size() )
{
const auto& s = m_stops[ m_index ];
m_pos2 = s.position();
m_color2 = s.rgb();
}
return !isDone();
}
inline bool isDone() const
{
if ( m_index < 0 )
return true;
return m_index >= m_stops.size();
}
private:
QskGradientStops m_stops;
QskVertex::Color m_color1, m_color2;
qreal m_pos1 = 0.0;
qreal m_pos2 = 1.0;
int m_index = -1;
};
}
#endif