qskinny/src/graphic/QskGraphic.cpp
Peter Hartmann f8983afa17 QskGraphic: Fix scale factors
We need to know the bounding rect of the graphic to know by how much
we can scale our shape, so we need to pass that to the scaling
methods.
E.g. when there are two shapes whose pen sizes overlap the path rect
left and right, we would calculate wrong scale factors without the
grapics' bounding rect.

Resolves #250
2023-01-30 17:11:28 +01:00

1233 lines
32 KiB
C++

/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the QSkinny License, Version 1.0
*****************************************************************************/
#include "QskGraphic.h"
#include "QskColorFilter.h"
#include "QskGraphicPaintEngine.h"
#include "QskPainterCommand.h"
#include <qguiapplication.h>
#include <qimage.h>
#include <qmath.h>
#include <qpaintengine.h>
#include <qpainter.h>
#include <qpainterpath.h>
#include <qpixmap.h>
#include <qhashfunctions.h>
QSK_QT_PRIVATE_BEGIN
#include <private/qpainter_p.h>
#include <private/qpaintengineex_p.h>
QSK_QT_PRIVATE_END
static inline qreal qskDevicePixelRatio()
{
return qGuiApp ? qGuiApp->devicePixelRatio() : 1.0;
}
static bool qskHasScalablePen( const QPainter* painter )
{
const QPen pen = painter->pen();
bool scalablePen = false;
if ( pen.style() != Qt::NoPen && pen.brush().style() != Qt::NoBrush )
{
scalablePen = !pen.isCosmetic();
}
return scalablePen;
}
static QRectF qskStrokedPathRect(
const QPainter* painter, const QPainterPath& path )
{
const auto pen = painter->pen();
QPainterPathStroker stroker;
stroker.setWidth( pen.widthF() );
stroker.setCapStyle( pen.capStyle() );
stroker.setJoinStyle( pen.joinStyle() );
stroker.setMiterLimit( pen.miterLimit() );
QRectF rect;
if ( qskHasScalablePen( painter ) )
{
const QPainterPath stroke = stroker.createStroke( path );
rect = painter->transform().map( stroke ).boundingRect();
}
else
{
QPainterPath mappedPath = painter->transform().map( path );
mappedPath = stroker.createStroke( mappedPath );
rect = mappedPath.boundingRect();
}
return rect;
}
static inline void qskExecCommand(
QPainter* painter, const QskPainterCommand& cmd,
const QskColorFilter& colorFilter,
QskGraphic::RenderHints renderHints,
const QTransform& transform,
const QTransform* initialTransform )
{
switch ( cmd.type() )
{
case QskPainterCommand::Path:
{
bool doMap = false;
if ( painter->transform().isScaling() )
{
if ( painter->pen().isCosmetic() )
{
// OpenGL2 seems to be buggy for cosmetic pens.
// It interpolates curves in too rough steps then
doMap = painter->paintEngine()->type() == QPaintEngine::OpenGL2;
}
else
{
doMap = renderHints.testFlag( QskGraphic::RenderPensUnscaled );
}
}
if ( doMap )
{
const QTransform tr = painter->transform();
painter->resetTransform();
QPainterPath path = tr.map( *cmd.path() );
if ( initialTransform )
{
painter->setTransform( *initialTransform );
path = initialTransform->inverted().map( path );
}
painter->drawPath( path );
painter->setTransform( tr );
}
else
{
painter->drawPath( *cmd.path() );
}
break;
}
case QskPainterCommand::Pixmap:
{
const auto data = cmd.pixmapData();
painter->drawPixmap( data->rect, data->pixmap, data->subRect );
break;
}
case QskPainterCommand::Image:
{
const auto data = cmd.imageData();
painter->drawImage( data->rect, data->image,
data->subRect, data->flags );
break;
}
case QskPainterCommand::State:
{
const auto data = cmd.stateData();
if ( data->flags & QPaintEngine::DirtyPen )
painter->setPen( colorFilter.substituted( data->pen ) );
if ( data->flags & QPaintEngine::DirtyBrush )
painter->setBrush( colorFilter.substituted( data->brush ) );
if ( data->flags & QPaintEngine::DirtyBrushOrigin )
painter->setBrushOrigin( data->brushOrigin );
if ( data->flags & QPaintEngine::DirtyFont )
painter->setFont( data->font );
if ( data->flags & QPaintEngine::DirtyBackground )
{
painter->setBackgroundMode( data->backgroundMode );
painter->setBackground( colorFilter.substituted( data->backgroundBrush ) );
}
if ( data->flags & QPaintEngine::DirtyTransform )
{
painter->setTransform( data->transform * transform );
}
if ( data->flags & QPaintEngine::DirtyClipEnabled )
painter->setClipping( data->isClipEnabled );
if ( data->flags & QPaintEngine::DirtyClipRegion )
{
painter->setClipRegion( data->clipRegion,
data->clipOperation );
}
if ( data->flags & QPaintEngine::DirtyClipPath )
{
painter->setClipPath( data->clipPath, data->clipOperation );
}
if ( data->flags & QPaintEngine::DirtyHints )
{
#if 1
auto& state = QPainterPrivate::get( painter )->state;
state->renderHints = data->renderHints;
// to trigger internal updates we have to set at least one flag
const auto hint = QPainter::SmoothPixmapTransform;
painter->setRenderHint( hint, data->renderHints.testFlag( hint ) );
#else
for ( int i = 0; i < 8; i++ )
{
const auto hint = static_cast< QPainter::RenderHint >( 1 << i );
painter->setRenderHint( hint, data->renderHints.testFlag( hint ) );
}
#endif
}
if ( data->flags & QPaintEngine::DirtyCompositionMode )
painter->setCompositionMode( data->compositionMode );
if ( data->flags & QPaintEngine::DirtyOpacity )
painter->setOpacity( data->opacity );
break;
}
default:
break;
}
}
/*
To avoid subobject-linkage warnings, when including the source code in
svg2qvg we don't use an anonymous namespace here
*/
namespace QskGraphicPrivate
{
class PathInfo
{
public:
PathInfo()
: m_scalablePen( false )
{
// QVector needs a default constructor
}
PathInfo( const QRectF& pointRect,
const QRectF& boundingRect, bool scalablePen )
: m_pointRect( pointRect )
, m_boundingRect( boundingRect )
, m_scalablePen( scalablePen )
{
}
inline QRectF scaledBoundingRect( qreal sx, qreal sy, bool scalePens ) const
{
if ( sx == 1.0 && sy == 1.0 )
return m_boundingRect;
QTransform transform;
transform.scale( sx, sy );
QRectF rect;
if ( scalePens && m_scalablePen )
{
rect = transform.mapRect( m_boundingRect );
}
else
{
rect = transform.mapRect( m_pointRect );
const qreal l = qAbs( m_pointRect.left() - m_boundingRect.left() );
const qreal r = qAbs( m_pointRect.right() - m_boundingRect.right() );
const qreal t = qAbs( m_pointRect.top() - m_boundingRect.top() );
const qreal b = qAbs( m_pointRect.bottom() - m_boundingRect.bottom() );
rect.adjust( -l, -t, r, b );
}
return rect;
}
inline double scaleFactorX( const QRectF& pathRect,
const QRectF& targetRect, const QRectF& graphicBoundingRect, bool scalePens ) const
{
if ( pathRect.width() <= 0.0 )
return 0.0;
const QPointF p0 = m_pointRect.center();
const auto p = pathRect.united( m_boundingRect );
const qreal l = qAbs( p.left() - p0.x() );
const qreal r = qAbs( p.right() - p0.x() );
const double w = 2.0 * qMin( l, r ) *
targetRect.width() / graphicBoundingRect.width();
double sx;
if ( scalePens && m_scalablePen )
{
sx = w / m_boundingRect.width();
}
else
{
const qreal pw = qMax(
qAbs( m_boundingRect.left() - m_pointRect.left() ),
qAbs( m_boundingRect.right() - m_pointRect.right() ) );
sx = ( w - 2 * pw ) / m_pointRect.width();
}
return sx;
}
inline double scaleFactorY( const QRectF& pathRect,
const QRectF& targetRect, const QRectF& graphicBoundingRect, bool scalePens ) const
{
if ( pathRect.height() <= 0.0 )
return 0.0;
const QPointF p0 = m_pointRect.center();
const auto p = pathRect.united( m_boundingRect );
const qreal t = qAbs( p.top() - p0.y() );
const qreal b = qAbs( p.bottom() - p0.y() );
const double h = 2.0 * qMin( t, b ) *
targetRect.height() / graphicBoundingRect.height();
double sy;
if ( scalePens && m_scalablePen )
{
sy = h / m_boundingRect.height();
}
else
{
const double pw =
qMax( qAbs( m_boundingRect.top() - m_pointRect.top() ),
qAbs( m_boundingRect.bottom() - m_pointRect.bottom() ) );
sy = ( h - 2 * pw ) / m_pointRect.height();
}
return sy;
}
private:
QRectF m_pointRect;
QRectF m_boundingRect;
bool m_scalablePen;
};
}
class QskGraphic::PrivateData : public QSharedData
{
public:
PrivateData()
: commandTypes( 0 )
, renderHints( 0 )
{
}
PrivateData( const PrivateData& other )
: QSharedData( other )
, defaultSize( other.defaultSize )
, commands( other.commands )
, pathInfos( other.pathInfos )
, boundingRect( other.boundingRect )
, pointRect( other.pointRect )
, modificationId( other.modificationId )
, commandTypes( other.commandTypes )
, renderHints( other.renderHints )
{
}
inline bool operator==( const PrivateData& other ) const
{
return ( modificationId == other.modificationId ) &&
( renderHints == other.renderHints ) &&
( defaultSize == other.defaultSize );
}
inline void addCommand( const QskPainterCommand& command )
{
commands += command;
static QAtomicInteger< quint64 > nextId( 1 );
modificationId = nextId.fetchAndAddRelaxed( 1 );
}
QSizeF defaultSize;
QVector< QskPainterCommand > commands;
QVector< QskGraphicPrivate::PathInfo > pathInfos;
QRectF boundingRect = { 0.0, 0.0, -1.0, -1.0 };
QRectF pointRect = { 0.0, 0.0, -1.0, -1.0 };
quint64 modificationId = 0;
uint commandTypes : 4;
uint renderHints : 4;
};
QskGraphic::QskGraphic()
: m_data( new PrivateData() )
, m_paintEngine( nullptr )
{
}
QskGraphic::QskGraphic( const QskGraphic& other )
: QPaintDevice()
, m_data( other.m_data )
, m_paintEngine( nullptr )
{
}
QskGraphic::QskGraphic( QskGraphic&& other )
: m_data( std::move( other.m_data ) )
, m_paintEngine( nullptr )
{
}
QskGraphic::~QskGraphic()
{
delete m_paintEngine;
}
QskGraphic& QskGraphic::operator=( const QskGraphic& other )
{
delete m_paintEngine;
m_data = other.m_data;
m_paintEngine = nullptr;
return *this;
}
QskGraphic& QskGraphic::operator=( QskGraphic&& other )
{
m_data = std::move( other.m_data );
m_paintEngine = nullptr;
return *this;
}
bool QskGraphic::operator==( const QskGraphic& other ) const
{
return *m_data == *other.m_data;
}
QPaintEngine* QskGraphic::paintEngine() const
{
if ( m_paintEngine == nullptr )
m_paintEngine = new QskGraphicPaintEngine();
return m_paintEngine;
}
int QskGraphic::metric( PaintDeviceMetric deviceMetric ) const
{
int value = 0;
switch ( deviceMetric )
{
case PdmWidth:
{
value = qCeil( defaultSize().width() );
break;
}
case PdmHeight:
{
value = qCeil( defaultSize().height() );
break;
}
case PdmNumColors:
{
value = 0xffffffff;
break;
}
case PdmDepth:
{
value = 32;
break;
}
case PdmPhysicalDpiX:
case PdmPhysicalDpiY:
case PdmDpiY:
case PdmDpiX:
{
value = 72;
break;
}
case PdmWidthMM:
{
value = qRound( metric( PdmWidth ) * 25.4 / metric( PdmDpiX ) );
break;
}
case PdmHeightMM:
{
value = qRound( metric( PdmHeight ) * 25.4 / metric( PdmDpiY ) );
break;
}
case PdmDevicePixelRatio:
{
value = 1.0;
break;
}
case PdmDevicePixelRatioScaled:
{
value = metric( PdmDevicePixelRatio ) * devicePixelRatioFScale();
break;
}
}
return value;
}
void QskGraphic::reset()
{
m_data->commands.clear();
m_data->pathInfos.clear();
m_data->commandTypes = 0;
m_data->boundingRect = QRectF( 0.0, 0.0, -1.0, -1.0 );
m_data->pointRect = QRectF( 0.0, 0.0, -1.0, -1.0 );
m_data->defaultSize = QSizeF();
m_data->modificationId = 0;
delete m_paintEngine;
m_paintEngine = nullptr;
}
bool QskGraphic::isNull() const
{
return m_data->commands.isEmpty();
}
bool QskGraphic::isEmpty() const
{
return m_data->boundingRect.isEmpty();
}
QskGraphic::CommandTypes QskGraphic::commandTypes() const
{
return static_cast< CommandTypes >( m_data->commandTypes );
}
void QskGraphic::setRenderHint( RenderHint hint, bool on )
{
if ( on )
m_data->renderHints |= hint;
else
m_data->renderHints &= ~hint;
}
bool QskGraphic::testRenderHint( RenderHint hint ) const
{
return m_data->renderHints & hint;
}
QskGraphic::RenderHints QskGraphic::renderHints() const
{
return static_cast< QskGraphic::RenderHints >( m_data->renderHints );
}
QRectF QskGraphic::boundingRect() const
{
if ( m_data->boundingRect.width() < 0 )
return QRectF();
return m_data->boundingRect;
}
QRectF QskGraphic::controlPointRect() const
{
if ( m_data->pointRect.width() < 0 )
return QRectF();
return m_data->pointRect;
}
QRectF QskGraphic::scaledBoundingRect( qreal sx, qreal sy ) const
{
if ( sx == 1.0 && sy == 1.0 )
return m_data->boundingRect;
const bool scalePens = !( m_data->renderHints & RenderPensUnscaled );
QTransform transform;
transform.scale( sx, sy );
QRectF rect = transform.mapRect( m_data->pointRect );
for ( const auto& info : qAsConst( m_data->pathInfos ) )
rect |= info.scaledBoundingRect( sx, sy, scalePens );
return rect;
}
QSize QskGraphic::sizeMetrics() const
{
const QSizeF sz = defaultSize();
return QSize( qCeil( sz.width() ), qCeil( sz.height() ) );
}
void QskGraphic::setDefaultSize( const QSizeF& size )
{
const double w = qMax( qreal( 0.0 ), size.width() );
const double h = qMax( qreal( 0.0 ), size.height() );
m_data->defaultSize = QSizeF( w, h );
}
QSizeF QskGraphic::defaultSize() const
{
if ( !m_data->defaultSize.isEmpty() )
return m_data->defaultSize;
return boundingRect().size();
}
qreal QskGraphic::heightForWidth( qreal width ) const
{
const auto sz = defaultSize();
if ( sz.isEmpty() )
return 0;
return sz.height() * width / sz.width();
}
qreal QskGraphic::widthForHeight( qreal height ) const
{
const auto sz = defaultSize();
if ( sz.isEmpty() )
return 0;
return sz.width() * height / sz.height();
}
qreal QskGraphic::aspectRatio() const
{
const auto sz = defaultSize();
if ( sz.isEmpty() )
return 1.0;
return sz.width() / sz.height();
}
void QskGraphic::render( QPainter* painter ) const
{
render( painter, QskColorFilter() );
}
void QskGraphic::render( QPainter* painter,
const QskColorFilter& colorFilter, QTransform* initialTransform ) const
{
if ( isNull() )
return;
const int numCommands = m_data->commands.size();
const auto commands = m_data->commands.constData();
const auto transform = painter->transform();
const QskGraphic::RenderHints renderHints( m_data->renderHints );
painter->save();
for ( int i = 0; i < numCommands; i++ )
{
qskExecCommand( painter, commands[ i ], colorFilter,
renderHints, transform, initialTransform );
}
painter->restore();
}
void QskGraphic::render( QPainter* painter, const QSizeF& size,
Qt::AspectRatioMode aspectRatioMode ) const
{
const QRectF r( 0.0, 0.0, size.width(), size.height() );
render( painter, r, aspectRatioMode );
}
void QskGraphic::render( QPainter* painter, const QRectF& rect,
Qt::AspectRatioMode aspectRatioMode ) const
{
render( painter, rect, QskColorFilter(), aspectRatioMode );
}
void QskGraphic::render( QPainter* painter, const QRectF& rect,
const QskColorFilter& colorFilter, Qt::AspectRatioMode aspectRatioMode ) const
{
if ( isEmpty() || rect.isEmpty() )
return;
qreal sx = 1.0;
qreal sy = 1.0;
if ( m_data->pointRect.width() > 0.0 )
sx = rect.width() / m_data->pointRect.width();
if ( m_data->pointRect.height() > 0.0 )
sy = rect.height() / m_data->pointRect.height();
const bool scalePens = !( m_data->renderHints & RenderPensUnscaled );
for ( const auto& info : qAsConst( m_data->pathInfos ) )
{
const qreal ssx = info.scaleFactorX( m_data->pointRect,
rect, m_data->boundingRect, scalePens );
if ( ssx > 0.0 )
sx = qMin( sx, ssx );
const qreal ssy = info.scaleFactorY( m_data->pointRect,
rect, m_data->boundingRect, scalePens );
if ( ssy > 0.0 )
sy = qMin( sy, ssy );
}
if ( aspectRatioMode == Qt::KeepAspectRatio )
{
sx = sy = qMin( sx, sy );
}
else if ( aspectRatioMode == Qt::KeepAspectRatioByExpanding )
{
sx = sy = qMax( sx, sy );
}
const auto& br = m_data->boundingRect;
const auto rc = rect.center();
QTransform tr;
tr.translate(
rc.x() - 0.5 * sx * br.width(),
rc.y() - 0.5 * sy * br.height() );
tr.scale( sx, sy );
tr.translate( -br.x(), -br.y() );
const auto transform = painter->transform();
painter->setTransform( tr, true );
if ( !scalePens && transform.isScaling() )
{
/*
We don't want to scale pens according to sx/sy,
but we want to apply the initial scaling from the
painter transformation.
*/
QTransform initialTransform;
initialTransform.scale( transform.m11(), transform.m22() );
render( painter, colorFilter, &initialTransform );
}
else
{
render( painter, colorFilter, nullptr );
}
painter->setTransform( transform );
}
void QskGraphic::render( QPainter* painter,
const QPointF& pos, Qt::Alignment alignment ) const
{
QRectF r( pos, defaultSize() );
if ( alignment & Qt::AlignLeft )
{
r.moveLeft( pos.x() );
}
else if ( alignment & Qt::AlignHCenter )
{
r.moveCenter( QPointF( pos.x(), r.center().y() ) );
}
else if ( alignment & Qt::AlignRight )
{
r.moveRight( pos.x() );
}
if ( alignment & Qt::AlignTop )
{
r.moveTop( pos.y() );
}
else if ( alignment & Qt::AlignVCenter )
{
r.moveCenter( QPointF( r.center().x(), pos.y() ) );
}
else if ( alignment & Qt::AlignBottom )
{
r.moveBottom( pos.y() );
}
render( painter, r );
}
QPixmap QskGraphic::toPixmap( qreal devicePixelRatio ) const
{
if ( isNull() )
return QPixmap();
if ( devicePixelRatio <= 0.0 )
devicePixelRatio = qskDevicePixelRatio();
const QSizeF sz = defaultSize();
const int w = qCeil( sz.width() * devicePixelRatio );
const int h = qCeil( sz.height() * devicePixelRatio );
QPixmap pixmap( w, h );
pixmap.setDevicePixelRatio( devicePixelRatio );
pixmap.fill( Qt::transparent );
const QRectF r( 0.0, 0.0, sz.width(), sz.height() );
QPainter painter( &pixmap );
render( &painter, r, Qt::KeepAspectRatio );
painter.end();
return pixmap;
}
QPixmap QskGraphic::toPixmap( const QSize& size,
Qt::AspectRatioMode aspectRatioMode, qreal devicePixelRatio ) const
{
if ( devicePixelRatio <= 0.0 )
devicePixelRatio = qskDevicePixelRatio();
const int w = qCeil( size.width() * devicePixelRatio );
const int h = qCeil( size.height() * devicePixelRatio );
QPixmap pixmap( w, h );
pixmap.setDevicePixelRatio( devicePixelRatio );
pixmap.fill( Qt::transparent );
const QRect r( 0, 0, size.width(), size.height() );
QPainter painter( &pixmap );
render( &painter, r, aspectRatioMode );
painter.end();
return pixmap;
}
QImage QskGraphic::toImage( const QSize& size,
Qt::AspectRatioMode aspectRatioMode, qreal devicePixelRatio ) const
{
if ( devicePixelRatio <= 0.0 )
devicePixelRatio = qskDevicePixelRatio();
const int w = qCeil( size.width() * devicePixelRatio );
const int h = qCeil( size.height() * devicePixelRatio );
QImage image( w, h, QImage::Format_ARGB32_Premultiplied );
image.setDevicePixelRatio( devicePixelRatio );
image.fill( 0 );
const QRect r( 0, 0, size.width(), size.height() );
QPainter painter( &image );
render( &painter, r, aspectRatioMode );
painter.end();
return image;
}
QImage QskGraphic::toImage( qreal devicePixelRatio ) const
{
if ( isNull() )
return QImage();
if ( devicePixelRatio <= 0.0 )
devicePixelRatio = qskDevicePixelRatio();
const QSizeF sz = defaultSize();
const int w = qCeil( sz.width() * devicePixelRatio );
const int h = qCeil( sz.height() * devicePixelRatio );
QImage image( w, h, QImage::Format_ARGB32 );
image.setDevicePixelRatio( devicePixelRatio );
image.fill( 0 );
const QRect r( 0, 0, sz.width(), sz.height() );
QPainter painter( &image );
render( &painter, r, Qt::KeepAspectRatio );
painter.end();
return image;
}
void QskGraphic::drawPath( const QPainterPath& path )
{
const auto painter = paintEngine()->painter();
if ( painter == nullptr )
return;
m_data->addCommand( QskPainterCommand( path ) );
m_data->commandTypes |= QskGraphic::VectorData;
if ( !path.isEmpty() )
{
const auto scaledPath = painter->transform().map( path );
QRectF pointRect = scaledPath.boundingRect();
QRectF boundingRect = pointRect;
if ( painter->pen().style() != Qt::NoPen &&
painter->pen().brush().style() != Qt::NoBrush )
{
boundingRect = qskStrokedPathRect( painter, path );
}
updateControlPointRect( pointRect );
updateBoundingRect( boundingRect );
m_data->pathInfos += QskGraphicPrivate::PathInfo( pointRect,
boundingRect, qskHasScalablePen( painter ) );
}
}
void QskGraphic::drawPixmap( const QRectF& rect,
const QPixmap& pixmap, const QRectF& subRect )
{
const auto painter = paintEngine()->painter();
if ( painter == nullptr )
return;
m_data->addCommand( QskPainterCommand( rect, pixmap, subRect ) );
m_data->commandTypes |= QskGraphic::RasterData;
const QRectF r = painter->transform().mapRect( rect );
updateControlPointRect( r );
updateBoundingRect( r );
}
void QskGraphic::drawImage( const QRectF& rect, const QImage& image,
const QRectF& subRect, Qt::ImageConversionFlags flags )
{
const auto painter = paintEngine()->painter();
if ( painter == nullptr )
return;
m_data->addCommand( QskPainterCommand( rect, image, subRect, flags ) );
m_data->commandTypes |= QskGraphic::RasterData;
const QRectF r = painter->transform().mapRect( rect );
updateControlPointRect( r );
updateBoundingRect( r );
}
void QskGraphic::updateState( const QPaintEngineState& state )
{
m_data->addCommand( QskPainterCommand( state ) );
if ( state.state() & QPaintEngine::DirtyTransform )
{
if ( !( m_data->commandTypes & QskGraphic::Transformation ) )
{
/*
QTransform::isScaling() returns true for all type
of transformations beside simple translations
even if it is f.e a rotation
*/
if ( state.transform().isScaling() )
m_data->commandTypes |= QskGraphic::Transformation;
}
}
}
void QskGraphic::updateBoundingRect( const QRectF& rect )
{
QRectF br = rect;
if ( const auto painter = paintEngine()->painter() )
{
if ( painter->hasClipping() )
{
QRectF cr = painter->clipRegion().boundingRect();
cr = painter->transform().mapRect( cr );
br &= cr;
}
}
if ( m_data->boundingRect.width() < 0 )
m_data->boundingRect = br;
else
m_data->boundingRect |= br;
}
void QskGraphic::updateControlPointRect( const QRectF& rect )
{
if ( m_data->pointRect.width() < 0.0 )
m_data->pointRect = rect;
else
m_data->pointRect |= rect;
}
const QVector< QskPainterCommand >& QskGraphic::commands() const
{
return m_data->commands;
}
void QskGraphic::setCommands( const QVector< QskPainterCommand >& commands )
{
reset();
const int numCommands = commands.size();
if ( numCommands <= 0 )
return;
// to calculate a proper bounding rectangle we don't simply copy
// the commands.
const auto cmds = commands.constData();
const QskColorFilter noFilter;
const QTransform noTransform;
QPainter painter( this );
for ( int i = 0; i < numCommands; i++ )
{
qskExecCommand( &painter, cmds[ i ],
noFilter, RenderHints(), noTransform, nullptr );
}
painter.end();
}
quint64 QskGraphic::modificationId() const
{
return m_data->modificationId;
}
QskHashValue QskGraphic::hash( QskHashValue seed ) const
{
auto hash = qHash( m_data->renderHints, seed );
hash = qHash( m_data->defaultSize.width(), hash );
hash = qHash( m_data->defaultSize.height(), hash );
return qHash( m_data->modificationId, hash );
}
QskGraphic QskGraphic::fromImage( const QImage& image )
{
QskGraphic graphic;
if ( !image.isNull() )
{
QPainter painter( &graphic );
painter.drawImage( 0, 0, image );
painter.end();
}
return graphic;
}
QskGraphic QskGraphic::fromPixmap( const QPixmap& pixmap )
{
QskGraphic graphic;
if ( !pixmap.isNull() )
{
QPainter painter( &graphic );
painter.drawPixmap( 0, 0, pixmap );
painter.end();
}
return graphic;
}
QskGraphic QskGraphic::fromPixmapAsImage( const QPixmap& pixmap )
{
/*
Using QPainter::drawPixmap in the scene graph thread leads
to warnings about "not being safe". This is probably not critical
for Qt/Quick as the main thread is waiting, when updating the
scene graph nodes.
It needs to be checked, what is going on, when
using the X11 paint engine, where QPixmap/QImage are more different.
TODO ...
*/
return fromImage( pixmap.toImage() );
}
QskGraphic QskGraphic::fromGraphic(
const QskGraphic& graphic, const QskColorFilter& colorFilter )
{
if ( colorFilter.isIdentity() )
return graphic;
QskGraphic recoloredGraphic;
QPainter painter( &recoloredGraphic );
graphic.render( &painter, colorFilter );
painter.end();
return recoloredGraphic;
}
#ifndef QT_NO_DEBUG_STREAM
#include <qdebug.h>
QDebug operator<<( QDebug debug, const QskGraphic& graphic )
{
QDebugStateSaver saver( debug );
debug << "Graphic" << '(';
debug << "\n boundingRect:" << graphic.boundingRect();
debug << "\n controlPointsRect:" << graphic.boundingRect();
debug << "\n commandTypes:" << graphic.commandTypes();
for ( const auto& command : graphic.commands() )
{
switch ( command.type() )
{
case QskPainterCommand::Path:
{
const auto& path = *command.path();
debug << "\n Path(" << path.elementCount() << ")";
const char* types[] = { "MoveTo", "LineTo", "CurveTo", "CurveToData" };
for ( int i = 0; i < path.elementCount(); i++ )
{
debug << "\n ";
const auto el = path.elementAt(i);
debug << types[ el.type] << el.x << el.y;
}
break;
}
case QskPainterCommand::Pixmap:
{
const auto& pixmapData = *command.pixmapData();
debug << "\n Pixmap:";
debug << "\n " << pixmapData.pixmap;
debug << "\n Rect:" << pixmapData.rect;
debug << "\n SubRect:" << pixmapData.subRect;
break;
}
case QskPainterCommand::Image:
{
const auto& imageData = *command.imageData();
debug << "\n Image:";
debug << "\n " << imageData.image;
debug << "\n ConversionFlags" << imageData.flags;
debug << "\n Rect:" << imageData.rect;
debug << "\n SubRect:" << imageData.subRect;
break;
}
case QskPainterCommand::State:
{
const auto& stateData = *command.stateData();
const auto flags = stateData.flags;
debug << "\n State:";
const char indent[] = "\n ";
if ( flags & QPaintEngine::DirtyPen )
{
debug << indent << "Pen:" << stateData.pen;
}
if ( flags & QPaintEngine::DirtyBrush )
{
debug << indent << "Brush:" << stateData.brush;
}
if ( flags & QPaintEngine::DirtyBrushOrigin )
{
debug << indent << "BrushOrigin:" << stateData.brushOrigin;
}
if ( flags & QPaintEngine::DirtyFont )
{
debug << indent << "Font:" << stateData.font;
}
if ( flags & QPaintEngine::DirtyBackground )
{
debug << indent << "Background:"
<< stateData.backgroundMode
<< stateData.backgroundBrush;
}
if ( flags & QPaintEngine::DirtyTransform )
{
debug << indent << "Transform: " << stateData.transform;
}
if ( flags & QPaintEngine::DirtyClipEnabled )
{
debug << indent << "ClipEnabled: " << stateData.isClipEnabled;
}
if ( flags & QPaintEngine::DirtyClipRegion )
{
debug << indent << "ClipRegion: " << stateData.clipOperation;
}
if ( flags & QPaintEngine::DirtyClipPath )
{
debug << indent << "ClipPath:" << stateData.clipOperation;
}
if ( flags & QPaintEngine::DirtyHints )
{
debug << indent << "RenderHints:" << stateData.renderHints;
}
if ( flags & QPaintEngine::DirtyCompositionMode )
{
debug << indent << "CompositionMode:" << stateData.compositionMode;
}
if ( flags & QPaintEngine::DirtyOpacity )
{
debug << indent << "Opacity:" << stateData.opacity;
}
break;
}
default:
break;
}
}
debug << "\n)";
return debug;
}
#endif
#include "moc_QskGraphic.cpp"