350 lines
8.7 KiB
C++
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
|