/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_graphic.h" #include "qwt_painter_command.h" #include #include #include #include #include #include #include static bool qwtHasScalablePen( const QPainter *painter ) { const QPen pen = painter->pen(); bool scalablePen = false; if ( pen.style() != Qt::NoPen && pen.brush().style() != Qt::NoBrush ) { scalablePen = !pen.isCosmetic(); if ( !scalablePen && pen.widthF() == 0.0 ) { const QPainter::RenderHints hints = painter->renderHints(); if ( hints.testFlag( QPainter::NonCosmeticDefaultPen ) ) scalablePen = true; } } return scalablePen; } static QRectF qwtStrokedPathRect( const QPainter *painter, const QPainterPath &path ) { QPainterPathStroker stroker; stroker.setWidth( painter->pen().widthF() ); stroker.setCapStyle( painter->pen().capStyle() ); stroker.setJoinStyle( painter->pen().joinStyle() ); stroker.setMiterLimit( painter->pen().miterLimit() ); QRectF rect; if ( qwtHasScalablePen( painter ) ) { 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 qwtExecCommand( QPainter *painter, const QwtPainterCommand &cmd, QwtGraphic::RenderHints renderHints, const QTransform &transform, const QTransform *initialTransform ) { switch( cmd.type() ) { case QwtPainterCommand::Path: { bool doMap = false; if ( renderHints.testFlag( QwtGraphic::RenderPensUnscaled ) && painter->transform().isScaling() ) { bool isCosmetic = painter->pen().isCosmetic(); if ( isCosmetic && painter->pen().widthF() == 0.0 ) { QPainter::RenderHints hints = painter->renderHints(); if ( hints.testFlag( QPainter::NonCosmeticDefaultPen ) ) isCosmetic = false; } doMap = !isCosmetic; } 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 QwtPainterCommand::Pixmap: { const QwtPainterCommand::PixmapData *data = cmd.pixmapData(); painter->drawPixmap( data->rect, data->pixmap, data->subRect ); break; } case QwtPainterCommand::Image: { const QwtPainterCommand::ImageData *data = cmd.imageData(); painter->drawImage( data->rect, data->image, data->subRect, data->flags ); break; } case QwtPainterCommand::State: { const QwtPainterCommand::StateData *data = cmd.stateData(); if ( data->flags & QPaintEngine::DirtyPen ) painter->setPen( data->pen ); if ( data->flags & QPaintEngine::DirtyBrush ) painter->setBrush( 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( 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) { const QPainter::RenderHints hints = data->renderHints; painter->setRenderHint( QPainter::Antialiasing, hints.testFlag( QPainter::Antialiasing ) ); painter->setRenderHint( QPainter::TextAntialiasing, hints.testFlag( QPainter::TextAntialiasing ) ); painter->setRenderHint( QPainter::SmoothPixmapTransform, hints.testFlag( QPainter::SmoothPixmapTransform ) ); painter->setRenderHint( QPainter::HighQualityAntialiasing, hints.testFlag( QPainter::HighQualityAntialiasing ) ); painter->setRenderHint( QPainter::NonCosmeticDefaultPen, hints.testFlag( QPainter::NonCosmeticDefaultPen ) ); } if ( data->flags & QPaintEngine::DirtyCompositionMode) painter->setCompositionMode( data->compositionMode ); if ( data->flags & QPaintEngine::DirtyOpacity) painter->setOpacity( data->opacity ); break; } default: break; } } class QwtGraphic::PathInfo { public: PathInfo(): d_scalablePen( false ) { // QVector needs a default constructor } PathInfo( const QRectF &pointRect, const QRectF &boundingRect, bool scalablePen ): d_pointRect( pointRect ), d_boundingRect( boundingRect ), d_scalablePen( scalablePen ) { } inline QRectF scaledBoundingRect( double sx, double sy, bool scalePens ) const { if ( sx == 1.0 && sy == 1.0 ) return d_boundingRect; QTransform transform; transform.scale( sx, sy ); QRectF rect; if ( scalePens && d_scalablePen ) { rect = transform.mapRect( d_boundingRect ); } else { rect = transform.mapRect( d_pointRect ); const double l = qAbs( d_pointRect.left() - d_boundingRect.left() ); const double r = qAbs( d_pointRect.right() - d_boundingRect.right() ); const double t = qAbs( d_pointRect.top() - d_boundingRect.top() ); const double b = qAbs( d_pointRect.bottom() - d_boundingRect.bottom() ); rect.adjust( -l, -t, r, b ); } return rect; } inline double scaleFactorX( const QRectF& pathRect, const QRectF &targetRect, bool scalePens ) const { if ( pathRect.width() <= 0.0 ) return 0.0; const QPointF p0 = d_pointRect.center(); const double l = qAbs( pathRect.left() - p0.x() ); const double r = qAbs( pathRect.right() - p0.x() ); const double w = 2.0 * qMin( l, r ) * targetRect.width() / pathRect.width(); double sx; if ( scalePens && d_scalablePen ) { sx = w / d_boundingRect.width(); } else { const double pw = qMax( qAbs( d_boundingRect.left() - d_pointRect.left() ), qAbs( d_boundingRect.right() - d_pointRect.right() ) ); sx = ( w - 2 * pw ) / d_pointRect.width(); } return sx; } inline double scaleFactorY( const QRectF& pathRect, const QRectF &targetRect, bool scalePens ) const { if ( pathRect.height() <= 0.0 ) return 0.0; const QPointF p0 = d_pointRect.center(); const double t = qAbs( pathRect.top() - p0.y() ); const double b = qAbs( pathRect.bottom() - p0.y() ); const double h = 2.0 * qMin( t, b ) * targetRect.height() / pathRect.height(); double sy; if ( scalePens && d_scalablePen ) { sy = h / d_boundingRect.height(); } else { const double pw = qMax( qAbs( d_boundingRect.top() - d_pointRect.top() ), qAbs( d_boundingRect.bottom() - d_pointRect.bottom() ) ); sy = ( h - 2 * pw ) / d_pointRect.height(); } return sy; } private: QRectF d_pointRect; QRectF d_boundingRect; bool d_scalablePen; }; class QwtGraphic::PrivateData { public: PrivateData(): boundingRect( 0.0, 0.0, -1.0, -1.0 ), pointRect( 0.0, 0.0, -1.0, -1.0 ), initialTransform( NULL ) { } QSizeF defaultSize; QVector commands; QVector pathInfos; QRectF boundingRect; QRectF pointRect; QwtGraphic::RenderHints renderHints; QTransform *initialTransform; }; /*! \brief Constructor Initializes a null graphic \sa isNull() */ QwtGraphic::QwtGraphic(): QwtNullPaintDevice() { setMode( QwtNullPaintDevice::PathMode ); d_data = new PrivateData; } /*! \brief Copy constructor \param other Source \sa operator=() */ QwtGraphic::QwtGraphic( const QwtGraphic &other ): QwtNullPaintDevice() { setMode( other.mode() ); d_data = new PrivateData( *other.d_data ); } //! Destructor QwtGraphic::~QwtGraphic() { delete d_data; } /*! \brief Assignment operator \param other Source \return A reference of this object */ QwtGraphic& QwtGraphic::operator=(const QwtGraphic &other) { setMode( other.mode() ); *d_data = *other.d_data; return *this; } /*! \brief Clear all stored commands \sa isNull() */ void QwtGraphic::reset() { d_data->commands.clear(); d_data->pathInfos.clear(); d_data->boundingRect = QRectF( 0.0, 0.0, -1.0, -1.0 ); d_data->pointRect = QRectF( 0.0, 0.0, -1.0, -1.0 ); d_data->defaultSize = QSizeF(); } /*! \return True, when no painter commands have been stored \sa isEmpty(), commands() */ bool QwtGraphic::isNull() const { return d_data->commands.isEmpty(); } /*! \return True, when the bounding rectangle is empty \sa boundingRect(), isNull() */ bool QwtGraphic::isEmpty() const { return d_data->boundingRect.isEmpty(); } /*! Toggle an render hint \param hint Render hint \param on true/false \sa testRenderHint(), RenderHint */ void QwtGraphic::setRenderHint( RenderHint hint, bool on ) { if ( on ) d_data->renderHints |= hint; else d_data->renderHints &= ~hint; } /*! Test a render hint \param hint Render hint \return true/false \sa setRenderHint(), RenderHint */ bool QwtGraphic::testRenderHint( RenderHint hint ) const { return d_data->renderHints.testFlag( hint ); } /*! The bounding rectangle is the controlPointRect() extended by the areas needed for rendering the outlines with unscaled pens. \return Bounding rectangle of the graphic \sa controlPointRect(), scaledBoundingRect() */ QRectF QwtGraphic::boundingRect() const { if ( d_data->boundingRect.width() < 0 ) return QRectF(); return d_data->boundingRect; } /*! The control point rectangle is the bounding rectangle of all control points of the paths and the target rectangles of the images/pixmaps. \return Control point rectangle \sa boundingRect(), scaledBoundingRect() */ QRectF QwtGraphic::controlPointRect() const { if ( d_data->pointRect.width() < 0 ) return QRectF(); return d_data->pointRect; } /*! \brief Calculate the target rectangle for scaling the graphic \param sx Horizontal scaling factor \param sy Vertical scaling factor \note In case of paths that are painted with a cosmetic pen ( see QPen::isCosmetic() ) the target rectangle is different to multiplying the bounding rectangle. \return Scaled bounding rectangle \sa boundingRect(), controlPointRect() */ QRectF QwtGraphic::scaledBoundingRect( double sx, double sy ) const { if ( sx == 1.0 && sy == 1.0 ) return d_data->boundingRect; QTransform transform; transform.scale( sx, sy ); QRectF rect = transform.mapRect( d_data->pointRect ); for ( int i = 0; i < d_data->pathInfos.size(); i++ ) { rect |= d_data->pathInfos[i].scaledBoundingRect( sx, sy, !d_data->renderHints.testFlag( RenderPensUnscaled ) ); } return rect; } //! \return Ceiled defaultSize() QSize QwtGraphic::sizeMetrics() const { const QSizeF sz = defaultSize(); return QSize( qCeil( sz.width() ), qCeil( sz.height() ) ); } /*! \brief Set a default size The default size is used in all methods rendering the graphic, where no size is explicitly specified. Assigning an empty size means, that the default size will be calculated from the bounding rectangle. The default setting is an empty size. \param size Default size \sa defaultSize(), boundingRect() */ void QwtGraphic::setDefaultSize( const QSizeF &size ) { const double w = qMax( qreal( 0.0 ), size.width() ); const double h = qMax( qreal( 0.0 ), size.height() ); d_data->defaultSize = QSizeF( w, h ); } /*! \brief Default size When a non empty size has been assigned by setDefaultSize() this size will be returned. Otherwise the default size is the size of the bounding rectangle. The default size is used in all methods rendering the graphic, where no size is explicitly specified. \return Default size \sa setDefaultSize(), boundingRect() */ QSizeF QwtGraphic::defaultSize() const { if ( !d_data->defaultSize.isEmpty() ) return d_data->defaultSize; return boundingRect().size(); } /*! \brief Replay all recorded painter commands \param painter Qt painter */ void QwtGraphic::render( QPainter *painter ) const { if ( isNull() ) return; const int numCommands = d_data->commands.size(); const QwtPainterCommand *commands = d_data->commands.constData(); const QTransform transform = painter->transform(); painter->save(); for ( int i = 0; i < numCommands; i++ ) { qwtExecCommand( painter, commands[i], d_data->renderHints, transform, d_data->initialTransform ); } painter->restore(); } /*! \brief Replay all recorded painter commands The graphic is scaled to fit into the rectangle of the given size starting at ( 0, 0 ). \param painter Qt painter \param size Size for the scaled graphic \param aspectRatioMode Mode how to scale - See Qt::AspectRatioMode */ void QwtGraphic::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 ); } /*! \brief Replay all recorded painter commands The graphic is scaled to fit into the given rectangle \param painter Qt painter \param rect Rectangle for the scaled graphic \param aspectRatioMode Mode how to scale - See Qt::AspectRatioMode */ void QwtGraphic::render( QPainter *painter, const QRectF &rect, Qt::AspectRatioMode aspectRatioMode ) const { if ( isEmpty() || rect.isEmpty() ) return; double sx = 1.0; double sy = 1.0; if ( d_data->pointRect.width() > 0.0 ) sx = rect.width() / d_data->pointRect.width(); if ( d_data->pointRect.height() > 0.0 ) sy = rect.height() / d_data->pointRect.height(); const bool scalePens = !d_data->renderHints.testFlag( RenderPensUnscaled ); for ( int i = 0; i < d_data->pathInfos.size(); i++ ) { const PathInfo info = d_data->pathInfos[i]; const double ssx = info.scaleFactorX( d_data->pointRect, rect, scalePens ); if ( ssx > 0.0 ) sx = qMin( sx, ssx ); const double ssy = info.scaleFactorY( d_data->pointRect, rect, scalePens ); if ( ssy > 0.0 ) sy = qMin( sy, ssy ); } if ( aspectRatioMode == Qt::KeepAspectRatio ) { const double s = qMin( sx, sy ); sx = s; sy = s; } else if ( aspectRatioMode == Qt::KeepAspectRatioByExpanding ) { const double s = qMax( sx, sy ); sx = s; sy = s; } QTransform tr; tr.translate( rect.center().x() - 0.5 * sx * d_data->pointRect.width(), rect.center().y() - 0.5 * sy * d_data->pointRect.height() ); tr.scale( sx, sy ); tr.translate( -d_data->pointRect.x(), -d_data->pointRect.y() ); const QTransform transform = painter->transform(); if ( !scalePens && transform.isScaling() ) { // we don't want to scale pens according to sx/sy, // but we want to apply the scaling from the // painter transformation later d_data->initialTransform = new QTransform(); d_data->initialTransform->scale( transform.m11(), transform.m22() ); } painter->setTransform( tr, true ); render( painter ); painter->setTransform( transform ); delete d_data->initialTransform; d_data->initialTransform = NULL; } /*! \brief Replay all recorded painter commands The graphic is scaled to the defaultSize() and aligned to a position. \param painter Qt painter \param pos Reference point, where to render \param alignment Flags how to align the target rectangle to pos. */ void QwtGraphic::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 ); } /*! \brief Convert the graphic to a QPixmap All pixels of the pixmap get initialized by Qt::transparent before the graphic is scaled and rendered on it. The size of the pixmap is the default size ( ceiled to integers ) of the graphic. \return The graphic as pixmap in default size \sa defaultSize(), toImage(), render() */ QPixmap QwtGraphic::toPixmap() const { if ( isNull() ) return QPixmap(); const QSizeF sz = defaultSize(); const int w = qCeil( sz.width() ); const int h = qCeil( sz.height() ); QPixmap pixmap( w, h ); 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; } /*! \brief Convert the graphic to a QPixmap All pixels of the pixmap get initialized by Qt::transparent before the graphic is scaled and rendered on it. \param size Size of the image \param aspectRatioMode Aspect ratio how to scale the graphic \return The graphic as pixmap \sa toImage(), render() */ QPixmap QwtGraphic::toPixmap( const QSize &size, Qt::AspectRatioMode aspectRatioMode ) const { QPixmap pixmap( size ); pixmap.fill( Qt::transparent ); const QRect r( 0, 0, size.width(), size.height() ); QPainter painter( &pixmap ); render( &painter, r, aspectRatioMode ); painter.end(); return pixmap; } /*! \brief Convert the graphic to a QImage All pixels of the image get initialized by 0 ( transparent ) before the graphic is scaled and rendered on it. The format of the image is QImage::Format_ARGB32_Premultiplied. \param size Size of the image \param aspectRatioMode Aspect ratio how to scale the graphic \return The graphic as image \sa toPixmap(), render() */ QImage QwtGraphic::toImage( const QSize &size, Qt::AspectRatioMode aspectRatioMode ) const { QImage image( size, QImage::Format_ARGB32_Premultiplied ); image.fill( 0 ); const QRect r( 0, 0, size.width(), size.height() ); QPainter painter( &image ); render( &painter, r, aspectRatioMode ); painter.end(); return image; } /*! \brief Convert the graphic to a QImage All pixels of the image get initialized by 0 ( transparent ) before the graphic is scaled and rendered on it. The format of the image is QImage::Format_ARGB32_Premultiplied. The size of the image is the default size ( ceiled to integers ) of the graphic. \return The graphic as image in default size \sa defaultSize(), toPixmap(), render() */ QImage QwtGraphic::toImage() const { if ( isNull() ) return QImage(); const QSizeF sz = defaultSize(); const int w = qCeil( sz.width() ); const int h = qCeil( sz.height() ); QImage image( w, h, QImage::Format_ARGB32 ); image.fill( 0 ); const QRect r( 0, 0, sz.width(), sz.height() ); QPainter painter( &image ); render( &painter, r, Qt::KeepAspectRatio ); painter.end(); return image; } /*! Store a path command in the command list \param path Painter path \sa QPaintEngine::drawPath() */ void QwtGraphic::drawPath( const QPainterPath &path ) { const QPainter *painter = paintEngine()->painter(); if ( painter == NULL ) return; d_data->commands += QwtPainterCommand( path ); if ( !path.isEmpty() ) { const QPainterPath 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 = qwtStrokedPathRect( painter, path ); } updateControlPointRect( pointRect ); updateBoundingRect( boundingRect ); d_data->pathInfos += PathInfo( pointRect, boundingRect, qwtHasScalablePen( painter ) ); } } /*! \brief Store a pixmap command in the command list \param rect target rectangle \param pixmap Pixmap to be painted \param subRect Reactangle of the pixmap to be painted \sa QPaintEngine::drawPixmap() */ void QwtGraphic::drawPixmap( const QRectF &rect, const QPixmap &pixmap, const QRectF &subRect ) { const QPainter *painter = paintEngine()->painter(); if ( painter == NULL ) return; d_data->commands += QwtPainterCommand( rect, pixmap, subRect ); const QRectF r = painter->transform().mapRect( rect ); updateControlPointRect( r ); updateBoundingRect( r ); } /*! \brief Store a image command in the command list \param rect traget rectangle \param image Image to be painted \param subRect Reactangle of the pixmap to be painted \param flags Image conversion flags \sa QPaintEngine::drawImage() */ void QwtGraphic::drawImage( const QRectF &rect, const QImage &image, const QRectF &subRect, Qt::ImageConversionFlags flags) { const QPainter *painter = paintEngine()->painter(); if ( painter == NULL ) return; d_data->commands += QwtPainterCommand( rect, image, subRect, flags ); const QRectF r = painter->transform().mapRect( rect ); updateControlPointRect( r ); updateBoundingRect( r ); } /*! \brief Store a state command in the command list \param state State to be stored \sa QPaintEngine::updateState() */ void QwtGraphic::updateState( const QPaintEngineState &state) { d_data->commands += QwtPainterCommand( state ); } void QwtGraphic::updateBoundingRect( const QRectF &rect ) { QRectF br = rect; const QPainter *painter = paintEngine()->painter(); if ( painter && painter->hasClipping() ) { QRectF cr = painter->clipRegion().boundingRect(); cr = painter->transform().mapRect( cr ); br &= cr; } if ( d_data->boundingRect.width() < 0 ) d_data->boundingRect = br; else d_data->boundingRect |= br; } void QwtGraphic::updateControlPointRect( const QRectF &rect ) { if ( d_data->pointRect.width() < 0.0 ) d_data->pointRect = rect; else d_data->pointRect |= rect; } /*! \return List of recorded paint commands \sa setCommands() */ const QVector< QwtPainterCommand > &QwtGraphic::commands() const { return d_data->commands; } /*! \brief Append paint commands \param commands Paint commands \sa commands() */ void QwtGraphic::setCommands( QVector< QwtPainterCommand > &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 QwtPainterCommand *cmds = commands.constData(); QPainter painter( this ); for ( int i = 0; i < numCommands; i++ ) qwtExecCommand( &painter, cmds[i], RenderHints(), QTransform(), NULL ); painter.end(); }