qskinny/src/nodes/QskBoxRendererRect.cpp
2022-04-16 16:01:40 +02:00

642 lines
18 KiB
C++

/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/
#include "QskBoxBorderColors.h"
#include "QskBoxBorderMetrics.h"
#include "QskBoxRenderer.h"
#include "QskBoxRendererColorMap.h"
#include "QskFunctions.h"
#include "QskGradient.h"
#include "QskVertex.h"
using namespace QskVertex;
namespace
{
class VRectIterator
{
public:
inline VRectIterator( const QskBoxRenderer::Quad& rect )
: m_rect( rect )
, m_value( rect.top )
{
}
template< class ColorIterator >
inline void setGradientLine( const ColorIterator& it, ColoredLine* line )
{
line->setHLine( m_rect.left, m_rect.right, it.value(), it.color() );
}
template< class ColorIterator >
inline void setContourLine( const ColorIterator& it, ColoredLine* line )
{
line->setHLine( m_rect.left, m_rect.right, m_value, it.colorAt( m_value ) );
}
inline qreal value() const
{
return m_value;
}
inline bool advance()
{
if ( m_value == m_rect.top )
{
m_value = m_rect.bottom;
return true;
}
return false;
}
private:
const QskBoxRenderer::Quad& m_rect;
qreal m_value;
};
class HRectIterator
{
public:
inline HRectIterator( const QskBoxRenderer::Quad& rect )
: m_rect( rect )
, m_value( rect.left )
{
}
template< class ColorIterator >
inline void setGradientLine( const ColorIterator& it, ColoredLine* line )
{
line->setVLine( it.value(), m_rect.top, m_rect.bottom, it.color() );
}
template< class ColorIterator >
inline void setContourLine( const ColorIterator& it, ColoredLine* line )
{
line->setVLine( m_value, m_rect.top, m_rect.bottom, it.colorAt( m_value ) );
}
inline qreal value() const
{
return m_value;
}
inline bool advance()
{
if ( m_value == m_rect.left )
{
m_value = m_rect.right;
return true;
}
return false;
}
private:
const QskBoxRenderer::Quad& m_rect;
qreal m_value;
};
class DSquareIterator
{
public:
inline DSquareIterator( const QskBoxRenderer::Quad& rect )
: m_rect( rect )
, m_step( 0 )
{
Q_ASSERT( rect.width == rect.height );
}
template< class ColorIterator >
inline void setGradientLine( const ColorIterator& it, ColoredLine* line )
{
const auto v = it.value();
if ( v == 0.5 )
{
// this line is also a contour line, so we can skip it
return;
}
else if ( v < 0.5 )
{
const qreal dt = m_rect.width * 2 * v;
line->setLine( m_rect.left, m_rect.top + dt,
m_rect.left + dt, m_rect.top, it.color() );
}
else
{
const qreal dt = m_rect.width * 2 * ( v - 0.5 );
line->setLine( m_rect.left + dt, m_rect.bottom,
m_rect.right, m_rect.top + dt, it.color() );
}
}
template< class ColorIterator >
inline void setContourLine( const ColorIterator& it, ColoredLine* line )
{
const auto color = it.colorAt( value() );
const auto& r = m_rect;
switch ( m_step )
{
case 0:
{
line->setLine( r.left, r.top, r.left, r.top, color );
break;
}
case 1:
{
line->setLine( r.left, r.bottom, r.right, r.top, color );
break;
}
case 2:
{
line->setLine( r.right, r.bottom, r.right, r.bottom, color );
break;
}
}
}
inline qreal value() const
{
return m_step * 0.5;
}
inline bool advance()
{
return ++m_step <= 2;
}
private:
const QskBoxRenderer::Quad& m_rect;
int m_step;
};
class DRectIterator
{
public:
inline DRectIterator( const QskBoxRenderer::Quad& rect )
: m_rect( rect )
, m_step( 0 )
{
const qreal w = rect.width;
const qreal h = rect.height;
Q_ASSERT( w != h );
const qreal w2 = w * w;
const qreal h2 = h * h;
m_fx = ( w2 + h2 ) / w;
m_fy = ( w2 + h2 ) / h;
m_lx = m_rect.top - h2 / w;
m_ly = m_rect.left - w2 / h;
m_valueTR = w2 / ( w2 + h2 );
m_valueBL = h2 / ( w2 + h2 );
}
template< class ColorIterator >
inline void setGradientLine( const ColorIterator& it, ColoredLine* line )
{
const auto v = it.value();
const auto color = it.color();
const auto& r = m_rect;
switch ( m_step )
{
case 1:
{
const qreal dx = v * m_fx;
const qreal dy = v * m_fy;
line->setLine( r.left, r.top + dy, r.left + dx, r.top, color );
break;
}
case 2:
{
if ( r.width > r.height )
{
const qreal dx = v * m_fx;
line->setLine( m_lx + dx, r.bottom, r.left + dx, r.top, color );
}
else
{
const qreal dy = v * m_fy;
line->setLine( r.left, r.top + dy, r.right, m_ly + dy, color );
}
break;
}
case 3:
{
const qreal dx = v * m_fx;
const qreal dy = v * m_fy;
line->setLine( m_lx + dx, r.bottom, r.right, m_ly + dy, color );
break;
}
}
}
template< class ColorIterator >
inline void setContourLine( const ColorIterator& it, ColoredLine* line )
{
const auto& r = m_rect;
switch ( m_step )
{
case 0:
{
line->setLine( r.left, r.top,
r.left, r.top, it.colorAt( 0.0 ) );
break;
}
case 1:
{
if ( r.width >= r.height )
{
const qreal dx = m_valueBL * m_fx;
line->setLine( r.left, r.bottom,
r.left + dx, r.top, it.colorAt( m_valueBL ) );
}
else
{
const qreal dy = m_valueTR * m_fy;
line->setLine( r.left, r.top + dy,
r.right, r.top, it.colorAt( m_valueTR ) );
}
break;
}
case 2:
{
if ( r.width >= r.height )
{
const qreal dx = m_valueTR * m_fx;
line->setLine( r.left + dx, r.bottom,
r.right, r.top, it.colorAt( m_valueTR ) );
}
else
{
const qreal dy = m_valueBL * m_fy;
line->setLine( r.left, r.bottom,
r.right, r.top + dy, it.colorAt( m_valueBL ) );
}
break;
}
case 3:
{
line->setLine( r.right, r.bottom,
r.right, r.bottom, it.colorAt( 1.0 ) );
break;
}
default:
{
Q_ASSERT( false );
}
}
}
inline qreal value() const
{
switch ( m_step )
{
case 0:
return 0.0;
case 1:
return std::min( m_valueBL, m_valueTR );
case 2:
return std::max( m_valueBL, m_valueTR );
default:
return 1.0;
}
}
inline bool advance()
{
return ++m_step <= 3;
}
private:
const QskBoxRenderer::Quad& m_rect;
qreal m_fx, m_fy;
qreal m_lx, m_ly;
qreal m_valueTR, m_valueBL;
int m_step;
};
}
static inline void qskCreateFillOrdered( const QskBoxRenderer::Quad& rect,
const QskGradient& gradient, ColoredLine* line )
{
switch ( gradient.orientation() )
{
case QskGradient::Horizontal:
{
HRectIterator it( rect );
line = QskVertex::fillOrdered( it, rect.left, rect.right, gradient, line );
break;
}
case QskGradient::Vertical:
{
VRectIterator it( rect );
line = QskVertex::fillOrdered( it, rect.top, rect.bottom, gradient, line );
break;
}
case QskGradient::Diagonal:
{
if ( rect.width == rect.height )
{
DSquareIterator it( rect );
line = QskVertex::fillOrdered( it, 0.0, 1.0, gradient, line );
}
else
{
DRectIterator it( rect );
line = QskVertex::fillOrdered( it, 0.0, 1.0, gradient, line );
}
break;
}
}
}
template< class ColorMap, class Line >
static inline void qskCreateFillRandom(
QskGradient::Orientation orientation,
const QskBoxRenderer::Quad& r, const ColorMap& map, Line* line )
{
if ( orientation == QskGradient::Vertical )
{
( line++ )->setLine( r.left, r.top, r.right, r.top, map.colorAt( 0.0 ) );
( line++ )->setLine( r.left, r.bottom, r.right, r.bottom, map.colorAt( 1.0 ) );
}
else
{
( line++ )->setLine( r.left, r.top, r.left, r.bottom, map.colorAt( 0.0 ) );
( line++ )->setLine( r.right, r.top, r.right, r.bottom, map.colorAt( 1.0 ) );
}
}
template< class Line >
static inline void qskCreateBorderMonochrome(
const QskBoxRenderer::Quad& out, const QskBoxRenderer::Quad& in, QRgb rgb, Line* line )
{
qskCreateBorderMonochrome( out, in, Color( rgb ), line );
}
template< class Line >
static inline void qskCreateBorderMonochrome(
const QskBoxRenderer::Quad& out, const QskBoxRenderer::Quad& in, Color color, Line* line )
{
auto l = line;
( l++ )->setLine( in.right, in.bottom, out.right, out.bottom, color );
( l++ )->setLine( in.left, in.bottom, out.left, out.bottom, color );
( l++ )->setLine( in.left, in.top, out.left, out.top, color );
( l++ )->setLine( in.right, in.top, out.right, out.top, color );
*l = line[ 0 ];
}
template< class Line >
static inline void qskCreateBorder(
const QskBoxRenderer::Quad& out, const QskBoxRenderer::Quad& in,
const QskBoxBorderColors& colors, Line* line )
{
const auto& gradientLeft = colors.left();
const auto& gradientRight = colors.right();
const auto& gradientTop = colors.top();
const auto& gradientBottom = colors.bottom();
// qdebug
const qreal dx1 = in.right - in.left;
const qreal dx2 = out.right - out.left;
const qreal dy1 = in.top - in.bottom;
const qreal dy2 = out.top - out.bottom;
for( const auto& stop : qAsConst( gradientBottom.stops() ) )
{
const Color c( stop.color() );
const qreal x1 = in.right - stop.position() * dx1;
const qreal x2 = out.right - stop.position() * dx2;
const qreal y1 = in.bottom;
const qreal y2 = out.bottom;
( line++ )->setLine( x1, y1, x2, y2, c );
}
for( const auto& stop : qAsConst( gradientLeft.stops() ) )
{
const Color c( stop.color() );
const qreal x1 = in.left;
const qreal x2 = out.left;
const qreal y1 = in.bottom + stop.position() * dy1;
const qreal y2 = out.bottom + stop.position() * dy2;
( line++ )->setLine( x1, y1, x2, y2, c );
}
for( const auto& stop : qAsConst( gradientTop.stops() ) )
{
const Color c( stop.color() );
const qreal x1 = in.left + stop.position() * dx1;
const qreal x2 = out.left + stop.position() * dx2;
const qreal y1 = in.top;
const qreal y2 = out.top;
( line++ )->setLine( x1, y1, x2, y2, c );
}
for( const auto& stop : qAsConst( gradientRight.stops() ) )
{
const Color c( stop.color() );
const qreal x1 = in.right;
const qreal x2 = out.right;
// ( 1 - stop.position() ) because we want to make the gradients go
// around the border clock-wise:
const qreal y1 = in.bottom + ( 1 - stop.position() ) * dy1;
const qreal y2 = out.bottom + ( 1 - stop.position() ) * dy2;
( line++ )->setLine( x1, y1, x2, y2, c );
}
}
void QskBoxRenderer::renderRectBorder(
const QRectF& rect, const QskBoxShapeMetrics& shape,
const QskBoxBorderMetrics& border, QSGGeometry& geometry )
{
Q_UNUSED( shape )
const Quad out = rect;
const Quad in = qskValidOrEmptyInnerRect( rect, border.widths() );
if ( out == in )
{
allocateLines< Line >( geometry, 0 );
return;
}
const auto line = allocateLines< Line >( geometry, 4 + 1 );
qskCreateBorderMonochrome( out, in, Color(), line );
}
void QskBoxRenderer::renderRectFill(
const QRectF& rect, const QskBoxShapeMetrics& shape,
const QskBoxBorderMetrics& border, QSGGeometry& geometry )
{
Q_UNUSED( shape )
const Quad in = qskValidOrEmptyInnerRect( rect, border.widths() );
if ( in.isEmpty() )
{
allocateLines< Line >( geometry, 0 );
return;
}
const auto line = allocateLines< Line >( geometry, 2 );
qskCreateFillRandom( QskGradient::Vertical,
in, ColorMapSolid( Color() ), line );
}
void QskBoxRenderer::renderRect(
const QRectF& rect, const QskBoxShapeMetrics& shape,
const QskBoxBorderMetrics& border, const QskBoxBorderColors& borderColors,
const QskGradient& gradient, QSGGeometry& geometry )
{
Q_UNUSED( shape )
const Quad out = rect;
const Quad in = qskValidOrEmptyInnerRect( rect, border.widths() );
int fillLineCount = 0;
if ( !in.isEmpty() )
{
fillLineCount = gradient.stops().count();
if ( gradient.orientation() == QskGradient::Diagonal )
{
if ( in.width == in.height )
{
if ( !gradient.hasStopAt( 0.5 ) )
fillLineCount++;
}
else
{
// we might need extra lines for the corners
fillLineCount += 2;
}
}
}
int borderLineCount = 0;
if ( in != out )
{
const auto& bc = borderColors;
if ( bc.isVisible() )
{
// We can build a rectangular border from the 4 diagonal
// lines at the corners, but need an additional line
// for closing the border.
borderLineCount = 4 + 1;
if ( !bc.isMonochrome() )
{
// we might need extra lines to separate colors
// at the non closing corners
// ### As an optimization we could check orientation and colors
// to test whether colors are the same
const int additionalLines = -1
+ bc.left().stops().count() - 1
+ bc.top().stops().count() - 1
+ bc.right().stops().count() - 1
+ bc.bottom().stops().count() - 1;
borderLineCount += qMax( additionalLines, 0 );
}
}
}
auto line = allocateLines< ColoredLine >( geometry, borderLineCount + fillLineCount );
if ( fillLineCount > 0 )
{
const auto& gd = gradient;
if ( gd.isMonochrome() )
{
const ColorMapSolid colorMap( gd.startColor() );
qskCreateFillRandom( QskGradient::Vertical, in, colorMap, line );
}
else
{
bool fillRandom = gd.stops().count() <= 2;
if ( fillRandom )
{
/*
Not necessarily a requirement for being ordered,
but we didn't implement a random fill algo for
diagonal gradients yet.
*/
fillRandom = gd.orientation() != QskGradient::Diagonal;
}
if ( fillRandom )
{
const ColorMapGradient colorMap( gd.startColor(), gd.endColor() );
qskCreateFillRandom( gd.orientation(), in, colorMap, line );
}
else
{
qskCreateFillOrdered( in, gd, line );
}
}
}
if ( borderLineCount > 0 )
{
const auto& bc = borderColors;
auto fillLines = line + fillLineCount;
if ( bc.isMonochrome() )
{
const auto rgb = bc.left().startColor().rgba();
qskCreateBorderMonochrome( rect, in, rgb, fillLines );
}
else
{
qskCreateBorder( rect, in, bc, fillLines );
}
}
}
void QskBoxRenderer::renderRectFill( const QskBoxRenderer::Quad& rect,
const QskGradient& gradient, QskVertex::ColoredLine* line )
{
qskCreateFillOrdered( rect, gradient, line );
}