/****************************************************************************** * QSkinny - Copyright (C) 2016 Uwe Rathmann * SPDX-License-Identifier: BSD-3-Clause *****************************************************************************/ #include "QskGraphic.h" #include "QskColorFilter.h" #include "QskGraphicPaintEngine.h" #include "QskPainterCommand.h" #include #include #include #include #include #include #include #include QSK_QT_PRIVATE_BEGIN #include 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 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"