Skip to content
Snippets Groups Projects
qwt_plot_rasteritem.cpp 25 KiB
Newer Older
  • Learn to ignore specific revisions
  • pixhawk's avatar
    pixhawk committed
    /* -*- 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
     *****************************************************************************/
    
    
    Bryant's avatar
    Bryant committed
    #include "qwt_plot_rasteritem.h"
    #include "qwt_scale_map.h"
    #include "qwt_painter.h"
    
    pixhawk's avatar
    pixhawk committed
    #include <qapplication.h>
    #include <qdesktopwidget.h>
    #include <qpainter.h>
    
    Bryant's avatar
    Bryant committed
    #include <qpaintengine.h>
    #include <qmath.h>
    #if QT_VERSION >= 0x040400
    #include <qthread.h>
    #include <qfuture.h>
    #include <qtconcurrentrun.h>
    #endif
    #include <float.h>
    
    pixhawk's avatar
    pixhawk committed
    
    class QwtPlotRasterItem::PrivateData
    {
    public:
        PrivateData():
    
    Bryant's avatar
    Bryant committed
            alpha( -1 ),
            paintAttributes( QwtPlotRasterItem::PaintInDeviceResolution )
        {
    
    pixhawk's avatar
    pixhawk committed
            cache.policy = QwtPlotRasterItem::NoCache;
        }
    
        int alpha;
    
    
    Bryant's avatar
    Bryant committed
        QwtPlotRasterItem::PaintAttributes paintAttributes;
    
        struct ImageCache
        {
    
    pixhawk's avatar
    pixhawk committed
            QwtPlotRasterItem::CachePolicy policy;
    
    Bryant's avatar
    Bryant committed
            QRectF area;
            QSizeF size;
    
    pixhawk's avatar
    pixhawk committed
            QImage image;
        } cache;
    };
    
    
    Bryant's avatar
    Bryant committed
    
    static QRectF qwtAlignRect(const QRectF &rect)
    
    pixhawk's avatar
    pixhawk committed
    {
    
    Bryant's avatar
    Bryant committed
        QRectF r;
        r.setLeft( qRound( rect.left() ) );
        r.setRight( qRound( rect.right() ) );
        r.setTop( qRound( rect.top() ) );
        r.setBottom( qRound( rect.bottom() ) );
    
    pixhawk's avatar
    pixhawk committed
    
    
    Bryant's avatar
    Bryant committed
        return r;
    }
    
    pixhawk's avatar
    pixhawk committed
    
    
    Bryant's avatar
    Bryant committed
    static QRectF qwtStripRect(const QRectF &rect, const QRectF &area,
        const QwtScaleMap &xMap, const QwtScaleMap &yMap,
        const QwtInterval &xInterval, const QwtInterval &yInterval)
    {
        QRectF r = rect;
        if ( xInterval.borderFlags() & QwtInterval::ExcludeMinimum )
        {
            if ( area.left() <= xInterval.minValue() )
            {
                if ( xMap.isInverting() )
                    r.adjust(0, 0, -1, 0);
                else
                    r.adjust(1, 0, 0, 0);
            }
        }
    
    pixhawk's avatar
    pixhawk committed
    
    
    Bryant's avatar
    Bryant committed
        if ( xInterval.borderFlags() & QwtInterval::ExcludeMaximum )
        {
            if ( area.right() >= xInterval.maxValue() )
            {
                if ( xMap.isInverting() )
                    r.adjust(1, 0, 0, 0);
                else
                    r.adjust(0, 0, -1, 0);
            }
        }
    
    pixhawk's avatar
    pixhawk committed
    
    
    Bryant's avatar
    Bryant committed
        if ( yInterval.borderFlags() & QwtInterval::ExcludeMinimum )
        {
            if ( area.top() <= yInterval.minValue() )
            {
                if ( yMap.isInverting() )
                    r.adjust(0, 0, 0, -1);
                else
                    r.adjust(0, 1, 0, 0);
            }
        }
    
    pixhawk's avatar
    pixhawk committed
    
    
    Bryant's avatar
    Bryant committed
        if ( yInterval.borderFlags() & QwtInterval::ExcludeMaximum )
        {
            if ( area.bottom() >= yInterval.maxValue() )
            {
                if ( yMap.isInverting() )
                    r.adjust(0, 1, 0, 0);
                else
                    r.adjust(0, 0, 0, -1);
    
    pixhawk's avatar
    pixhawk committed
            }
    
    Bryant's avatar
    Bryant committed
        }
    
        return r;
    }
    
    static QImage qwtExpandImage(const QImage &image,
        const QwtScaleMap &xMap, const QwtScaleMap &yMap,
        const QRectF &area, const QRectF &area2, const QRectF &paintRect,
        const QwtInterval &xInterval, const QwtInterval &yInterval )
    {
        const QRectF strippedRect = qwtStripRect(paintRect, area2,
            xMap, yMap, xInterval, yInterval);
        const QSize sz = strippedRect.toRect().size();
    
        const int w = image.width();
        const int h = image.height();
    
        const QRectF r = QwtScaleMap::transform(xMap, yMap, area).normalized();
        const double pw = ( r.width() - 1) / w;
        const double ph = ( r.height() - 1) / h;
    
        double px0, py0;
        if ( !xMap.isInverting() )
        {
            px0 = xMap.transform( area2.left() );
            px0 = qRound( px0 );
            px0 = px0 - xMap.transform( area.left() );
        }
        else
        {
            px0 = xMap.transform( area2.right() );
            px0 = qRound( px0 );
            px0 -= xMap.transform( area.right() );
    
            px0 -= 1.0;
        }
        px0 += strippedRect.left() - paintRect.left();
    
        if ( !yMap.isInverting() )
        {
            py0 = yMap.transform( area2.top() );
            py0 = qRound( py0 );
            py0 -= yMap.transform( area.top() );
        }
        else
        {
            py0 = yMap.transform( area2.bottom() );
            py0 = qRound( py0 );
            py0 -= yMap.transform( area.bottom() );
    
            py0 -= 1.0;
        }
        py0 += strippedRect.top() - paintRect.top();
    
        QImage expanded(sz, image.format());
    
        switch( image.depth() )
        {
            case 32:
            {
                for ( int y1 = 0; y1 < h; y1++ )
                {
                    int yy1;
                    if ( y1 == 0 )
                    {
                        yy1 = 0;
                    }
                    else
                    {
                        yy1 = qRound( y1 * ph - py0 );
                        if ( yy1 < 0 )
                            yy1 = 0;
                    }
    
                    int yy2;
                    if ( y1 == h - 1 )
                    {
                        yy2 = sz.height();
                    }
                    else
                    {
                        yy2 = qRound( ( y1 + 1 ) * ph - py0 );
                        if ( yy2 > sz.height() )
                            yy2 = sz.height();
                    }
    
                    const quint32 *line1 = 
                        reinterpret_cast<const quint32 *>( image.scanLine( y1 ) );
    
                    for ( int x1 = 0; x1 < w; x1++ )
                    {
                        int xx1;
                        if ( x1 == 0 )
                        {
                            xx1 = 0;
                        }
                        else
                        {
                            xx1 = qRound( x1 * pw - px0 );
                            if ( xx1 < 0 )
                                xx1 = 0;
                        }
    
                        int xx2;
                        if ( x1 == w - 1 )
                        {
                            xx2 = sz.width();
                        }
                        else
                        {
                            xx2 = qRound( ( x1 + 1 ) * pw - px0 );
                            if ( xx2 > sz.width() )
                                xx2 = sz.width();
                        }
    
                        const quint32 rgb( line1[x1] );
                        for ( int y2 = yy1; y2 < yy2; y2++ )
                        {
                            quint32 *line2 = reinterpret_cast<quint32 *>( 
                                expanded.scanLine( y2 ) );
    
                            for ( int x2 = xx1; x2 < xx2; x2++ ) 
                                line2[x2] = rgb;
                        }       
                    }   
                }   
                break;
            }
            case 8:
            {
                for ( int y1 = 0; y1 < h; y1++ )
                {
                    int yy1;
                    if ( y1 == 0 )
                    {
                        yy1 = 0;
                    }   
                    else
                    {
                        yy1 = qRound( y1 * ph - py0 );
                        if ( yy1 < 0 )
                            yy1 = 0; 
                    }       
                    
                    int yy2;
                    if ( y1 == h - 1 )
                    {
                        yy2 = sz.height();
                    }   
                    else
                    {
                        yy2 = qRound( ( y1 + 1 ) * ph - py0 );
                        if ( yy2 > sz.height() )
                            yy2 = sz.height();
                    }
        
                    const uchar *line1 = image.scanLine( y1 );
    
                    for ( int x1 = 0; x1 < w; x1++ )
                    {
                        int xx1;
                        if ( x1 == 0 )
                        {
                            xx1 = 0;
                        }
                        else
                        {
                            xx1 = qRound( x1 * pw - px0 );
                            if ( xx1 < 0 )
                                xx1 = 0;
                        }
    
                        int xx2;
                        if ( x1 == w - 1 )
                        {
                            xx2 = sz.width();
                        }
                        else
                        {
                            xx2 = qRound( ( x1 + 1 ) * pw - px0 );
                            if ( xx2 > sz.width() )
                                xx2 = sz.width();
                        }
    
                        for ( int y2 = yy1; y2 < yy2; y2++ )
                        {
                            uchar *line2 = expanded.scanLine( y2 );
                            memset( line2 + xx1, line1[x1], xx2 - xx1 );
                        }       
                    }   
                }
                break;
            }
            default:
                expanded = image;
        }
        
        return expanded;
    }   
    
    static QRectF qwtExpandToPixels(const QRectF &rect, const QRectF &pixelRect)
    {
        const double pw = pixelRect.width();
        const double ph = pixelRect.height();
    
        const double dx1 = pixelRect.left() - rect.left();
        const double dx2 = pixelRect.right() - rect.right();
        const double dy1 = pixelRect.top() - rect.top();
        const double dy2 = pixelRect.bottom() - rect.bottom();
    
    pixhawk's avatar
    pixhawk committed
    
    
    Bryant's avatar
    Bryant committed
        QRectF r;
        r.setLeft( pixelRect.left() - qCeil( dx1 / pw ) * pw );
        r.setTop( pixelRect.top() - qCeil( dy1 / ph ) * ph );
        r.setRight( pixelRect.right() - qFloor( dx2 / pw ) * pw );
        r.setBottom( pixelRect.bottom() - qFloor( dy2 / ph ) * ph );
    
        return r;
    }
    
    static void qwtTransformMaps( const QTransform &tr,
        const QwtScaleMap &xMap, const QwtScaleMap &yMap,
        QwtScaleMap &xxMap, QwtScaleMap &yyMap )
    {
        const QPointF p1 = tr.map( QPointF( xMap.p1(), yMap.p1() ) );
        const QPointF p2 = tr.map( QPointF( xMap.p2(), yMap.p2() ) );
    
        xxMap = xMap;
        xxMap.setPaintInterval( p1.x(), p2.x() );
    
        yyMap = yMap;
        yyMap.setPaintInterval( p1.y(), p2.y() );
    }
    
    static void qwtAdjustMaps( QwtScaleMap &xMap, QwtScaleMap &yMap,
        const QRectF &area, const QRectF &paintRect)
    {
        double sx1 = area.left();
        double sx2 = area.right();
        if ( xMap.isInverting() )
            qSwap(sx1, sx2);
    
        double sy1 = area.top();
        double sy2 = area.bottom();
    
        if ( yMap.isInverting() )
            qSwap(sy1, sy2);
    
        xMap.setPaintInterval(paintRect.left(), paintRect.right());
        xMap.setScaleInterval(sx1, sx2);
    
        yMap.setPaintInterval(paintRect.top(), paintRect.bottom());
        yMap.setScaleInterval(sy1, sy2);
    }
    
    static bool qwtUseCache( QwtPlotRasterItem::CachePolicy policy,
        const QPainter *painter )
    {
        bool doCache = false;
    
        if ( policy == QwtPlotRasterItem::PaintCache )
        {
            // Caching doesn't make sense, when the item is
            // not painted to screen
    
            switch ( painter->paintEngine()->type() )
            {
                case QPaintEngine::SVG:
                case QPaintEngine::Pdf:
                case QPaintEngine::PostScript:
                case QPaintEngine::MacPrinter:
                case QPaintEngine::Picture:
                    break;
                default:;
                    doCache = true;
            }
        }
    
        return doCache;
    }
    
    static void qwtToRgba( const QImage* from, QImage* to,  
        const QRect& tile, int alpha )
    {
        const QRgb mask1 = qRgba( 0, 0, 0, alpha );
        const QRgb mask2 = qRgba( 255, 255, 255, 0 );
        const QRgb mask3 = qRgba( 0, 0, 0, 255 );
    
        const int y0 = tile.top();
        const int y1 = tile.bottom();
        const int x0 = tile.left();
        const int x1 = tile.right();
    
        if ( from->depth() == 8 )
        {
            for ( int y = y0; y <= y1; y++ )
            {
                QRgb *alphaLine = reinterpret_cast<QRgb *>( to->scanLine( y ) );
                const unsigned char *line = from->scanLine( y );
    
                for ( int x = x0; x <= x1; x++ )
                    *alphaLine++ = ( from->color( *line++ ) & mask2 ) | mask1;
            }
        }
        else if ( from->depth() == 32 )
        {
            for ( int y = y0; y <= y1; y++ )
            {
                QRgb *alphaLine = reinterpret_cast<QRgb *>( to->scanLine( y ) );
                const QRgb *line = reinterpret_cast<const QRgb *>( from->scanLine( y ) );
    
                for ( int x = x0; x <= x1; x++ )
                {
    
    pixhawk's avatar
    pixhawk committed
                    const QRgb rgb = *line++;
                    if ( rgb & mask3 ) // alpha != 0
    
    Bryant's avatar
    Bryant committed
                        *alphaLine++ = ( rgb & mask2 ) | mask1;
    
    pixhawk's avatar
    pixhawk committed
                    else
                        *alphaLine++ = rgb;
                }
            }
        }
    }
    
    //! Constructor
    
    Bryant's avatar
    Bryant committed
    QwtPlotRasterItem::QwtPlotRasterItem( const QString& title ):
        QwtPlotItem( QwtText( title ) )
    
    pixhawk's avatar
    pixhawk committed
    {
        init();
    }
    
    //! Constructor
    
    Bryant's avatar
    Bryant committed
    QwtPlotRasterItem::QwtPlotRasterItem( const QwtText& title ):
        QwtPlotItem( title )
    
    pixhawk's avatar
    pixhawk committed
    {
        init();
    }
    
    //! Destructor
    QwtPlotRasterItem::~QwtPlotRasterItem()
    {
        delete d_data;
    }
    
    void QwtPlotRasterItem::init()
    {
        d_data = new PrivateData();
    
    
    Bryant's avatar
    Bryant committed
        setItemAttribute( QwtPlotItem::AutoScale, true );
        setItemAttribute( QwtPlotItem::Legend, false );
    
        setZ( 8.0 );
    }
    
    /*!
      Specify an attribute how to draw the raster item
    
      \param attribute Paint attribute
      \param on On/Off
      /sa PaintAttribute, testPaintAttribute()
    */
    void QwtPlotRasterItem::setPaintAttribute( PaintAttribute attribute, bool on )
    {
        if ( on )
            d_data->paintAttributes |= attribute;
        else
            d_data->paintAttributes &= ~attribute;
    }
    
    pixhawk's avatar
    pixhawk committed
    
    
    Bryant's avatar
    Bryant committed
    /*!
        \return True, when attribute is enabled
        \sa PaintAttribute, setPaintAttribute()
    */
    bool QwtPlotRasterItem::testPaintAttribute( PaintAttribute attribute ) const
    {
        return ( d_data->paintAttributes & attribute );
    
    pixhawk's avatar
    pixhawk committed
    }
    
    /*!
       \brief Set an alpha value for the raster data
    
       Often a plot has several types of raster data organized in layers.
       ( f.e a geographical map, with weather statistics ).
       Using setAlpha() raster items can be stacked easily.
    
       The alpha value is a value [0, 255] to
    
       control the transparency of the image. 0 represents a fully
    
    pixhawk's avatar
    pixhawk committed
       transparent color, while 255 represents a fully opaque color.
    
    pixhawk's avatar
    pixhawk committed
       \param alpha Alpha value
    
       - alpha >= 0\n
    
         All alpha values of the pixels returned by renderImage() will be set to
         alpha, beside those with an alpha value of 0 (invalid pixels).
    
    pixhawk's avatar
    pixhawk committed
       - alpha < 0
         The alpha values returned by renderImage() are not changed.
    
       The default alpha value is -1.
    
       \sa alpha()
    */
    
    Bryant's avatar
    Bryant committed
    void QwtPlotRasterItem::setAlpha( int alpha )
    
    pixhawk's avatar
    pixhawk committed
    {
        if ( alpha < 0 )
            alpha = -1;
    
        if ( alpha > 255 )
            alpha = 255;
    
    
    Bryant's avatar
    Bryant committed
        if ( alpha != d_data->alpha )
        {
    
    pixhawk's avatar
    pixhawk committed
            d_data->alpha = alpha;
    
            itemChanged();
        }
    }
    
    /*!
      \return Alpha value of the raster item
      \sa setAlpha()
    */
    int QwtPlotRasterItem::alpha() const
    {
        return d_data->alpha;
    }
    
    /*!
      Change the cache policy
    
      The default policy is NoCache
    
      \param policy Cache policy
      \sa CachePolicy, cachePolicy()
    */
    void QwtPlotRasterItem::setCachePolicy(
    
    Bryant's avatar
    Bryant committed
        QwtPlotRasterItem::CachePolicy policy )
    
    pixhawk's avatar
    pixhawk committed
    {
    
    Bryant's avatar
    Bryant committed
        if ( d_data->cache.policy != policy )
        {
    
    pixhawk's avatar
    pixhawk committed
            d_data->cache.policy = policy;
    
            invalidateCache();
            itemChanged();
        }
    }
    
    /*!
      \return Cache policy
      \sa CachePolicy, setCachePolicy()
    */
    QwtPlotRasterItem::CachePolicy QwtPlotRasterItem::cachePolicy() const
    {
        return d_data->cache.policy;
    }
    
    /*!
       Invalidate the paint cache
    
    Bryant's avatar
    Bryant committed
       \sa setCachePolicy()
    
    pixhawk's avatar
    pixhawk committed
    */
    void QwtPlotRasterItem::invalidateCache()
    {
        d_data->cache.image = QImage();
    
    Bryant's avatar
    Bryant committed
        d_data->cache.area = QRect();
    
    pixhawk's avatar
    pixhawk committed
        d_data->cache.size = QSize();
    }
    
    /*!
    
    Bryant's avatar
    Bryant committed
       \brief Pixel hint
    
       The geometry of a pixel is used to calculated the resolution and
       alignment of the rendered image. 
    
       Width and height of the hint need to be the horizontal  
       and vertical distances between 2 neighbored points. 
       The center of the hint has to be the position of any point 
       ( it doesn't matter which one ).
    
       Limiting the resolution of the image might significantly improve
       the performance and heavily reduce the amount of memory when rendering
       a QImage from the raster data. 
    
       The default implementation returns an empty rectangle (QRectF()),
       meaning, that the image will be rendered in target device ( f.e screen )
       resolution.
    
    pixhawk's avatar
    pixhawk committed
    
    
    Bryant's avatar
    Bryant committed
       \param area In most implementations the resolution of the data doesn't
                   depend on the requested area.
    
    pixhawk's avatar
    pixhawk committed
    
    
    Bryant's avatar
    Bryant committed
       \return Bounding rectangle of a pixel
    
       \sa render(), renderImage()
    
    pixhawk's avatar
    pixhawk committed
    */
    
    Bryant's avatar
    Bryant committed
    QRectF QwtPlotRasterItem::pixelHint( const QRectF &area ) const
    
    pixhawk's avatar
    pixhawk committed
    {
    
    Bryant's avatar
    Bryant committed
        Q_UNUSED( area );
        return QRectF();
    
    pixhawk's avatar
    pixhawk committed
    }
    
    /*!
      \brief Draw the raster data
      \param painter Painter
      \param xMap X-Scale Map
      \param yMap Y-Scale Map
    
    Bryant's avatar
    Bryant committed
      \param canvasRect Contents rectangle of the plot canvas
    
    pixhawk's avatar
    pixhawk committed
    */
    
    Bryant's avatar
    Bryant committed
    void QwtPlotRasterItem::draw( QPainter *painter,
        const QwtScaleMap &xMap, const QwtScaleMap &yMap,
        const QRectF &canvasRect ) const
    
    pixhawk's avatar
    pixhawk committed
    {
        if ( canvasRect.isEmpty() || d_data->alpha == 0 )
            return;
    
    
    Bryant's avatar
    Bryant committed
        const bool doCache = qwtUseCache( d_data->cache.policy, painter );
    
    pixhawk's avatar
    pixhawk committed
    
    
    Bryant's avatar
    Bryant committed
        const QwtInterval xInterval = interval( Qt::XAxis );
        const QwtInterval yInterval = interval( Qt::YAxis );
    
        /*
            Scaling an image always results in a loss of
            precision/quality. So we always render the image in
            paint device resolution.
        */
    
        QwtScaleMap xxMap, yyMap;
        qwtTransformMaps( painter->transform(), xMap, yMap, xxMap, yyMap );
    
        QRectF paintRect = painter->transform().mapRect( canvasRect );
        QRectF area = QwtScaleMap::invTransform( xxMap, yyMap, paintRect );
    
    pixhawk's avatar
    pixhawk committed
    
    
    Bryant's avatar
    Bryant committed
        const QRectF br = boundingRect();
        if ( br.isValid() && !br.contains( area ) )
        {
            area &= br;
            if ( !area.isValid() )
                return;
    
            paintRect = QwtScaleMap::transform( xxMap, yyMap, area );
        }
    
        QRectF imageRect;
    
    pixhawk's avatar
    pixhawk committed
        QImage image;
    
    
    Bryant's avatar
    Bryant committed
        QRectF pixelRect = pixelHint(area);
        if ( !pixelRect.isEmpty() )
        {
            // pixel in target device resolution 
            const double dx = qAbs( xxMap.invTransform( 1 ) - xxMap.invTransform( 0 ) );
            const double dy = qAbs( yyMap.invTransform( 1 ) - yyMap.invTransform( 0 ) );
    
            if ( dx > pixelRect.width() && dy > pixelRect.height() )
            {
                /*
                  When the resolution of the data pixels is higher than
                  the resolution of the target device we render in
                  target device resolution.
                 */
                pixelRect = QRectF();
            }
    
    pixhawk's avatar
    pixhawk committed
        }
    
    
    Bryant's avatar
    Bryant committed
        if ( pixelRect.isEmpty() )
        {
            if ( QwtPainter::roundingAlignment( painter ) )
            {
                // we want to have maps, where the boundaries of
                // the aligned paint rectangle exactly match the area
    
    pixhawk's avatar
    pixhawk committed
    
    
    Bryant's avatar
    Bryant committed
                paintRect = qwtAlignRect(paintRect);
                qwtAdjustMaps(xxMap, yyMap, area, paintRect);
    
    pixhawk's avatar
    pixhawk committed
            }
    
    
    Bryant's avatar
    Bryant committed
            // When we have no information about position and size of
            // data pixels we render in resolution of the paint device.
    
            image = compose(xxMap, yyMap, 
                area, paintRect, paintRect.size().toSize(), doCache);
            if ( image.isNull() )
                return;
    
            // Remove pixels at the boundaries, when explicitly
            // excluded in the intervals
    
            imageRect = qwtStripRect(paintRect, area, 
                xxMap, yyMap, xInterval, yInterval);
    
            if ( imageRect != paintRect )
            {
                const QRect r( 
                    qRound( imageRect.x() - paintRect.x()),
                    qRound( imageRect.y() - paintRect.y() ),
                    qRound( imageRect.width() ),
                    qRound( imageRect.height() ) );
                    
                image = image.copy(r);
            }   
        }
        else
        {
            if ( QwtPainter::roundingAlignment( painter ) )
                paintRect = qwtAlignRect(paintRect);
    
            // align the area to the data pixels
            QRectF imageArea = qwtExpandToPixels(area, pixelRect);
    
            if ( imageArea.right() == xInterval.maxValue() &&
                !( xInterval.borderFlags() & QwtInterval::ExcludeMaximum ) )
            {
                imageArea.adjust(0, 0, pixelRect.width(), 0);
            }
            if ( imageArea.bottom() == yInterval.maxValue() &&
                !( yInterval.borderFlags() & QwtInterval::ExcludeMaximum ) )
            {
                imageArea.adjust(0, 0, 0, pixelRect.height() );
            }
    
            QSize imageSize;
            imageSize.setWidth( qRound( imageArea.width() / pixelRect.width() ) );
            imageSize.setHeight( qRound( imageArea.height() / pixelRect.height() ) );
            image = compose(xxMap, yyMap, 
                imageArea, paintRect, imageSize, doCache );
            if ( image.isNull() )
                return;
    
            imageRect = qwtStripRect(paintRect, area, 
                xxMap, yyMap, xInterval, yInterval);
    
            if ( ( image.width() > 1 || image.height() > 1 ) &&
                testPaintAttribute( PaintInDeviceResolution ) )
            {
                // Because of rounding errors the pixels 
                // need to be expanded manually to rectangles of 
                // different sizes
    
                image = qwtExpandImage(image, xxMap, yyMap, 
                    imageArea, area, paintRect, xInterval, yInterval );
            }
        }
    
        painter->save();
        painter->setWorldTransform( QTransform() );
        
        QwtPainter::drawImage( painter, imageRect, image );
    
        painter->restore();
    }
    
    /*!
       \return Bounding interval for an axis
    
       This method is intended to be reimplemented by derived classes.
       The default implementation returns an invalid interval.
       
       \param axis X, Y, or Z axis
    */
    QwtInterval QwtPlotRasterItem::interval(Qt::Axis axis) const
    {
        Q_UNUSED( axis );
        return QwtInterval();
    }
    
    /*!
       \return Bounding rectangle of the data
       \sa QwtPlotRasterItem::interval()
    */
    QRectF QwtPlotRasterItem::boundingRect() const
    {
        const QwtInterval intervalX = interval( Qt::XAxis );
        const QwtInterval intervalY = interval( Qt::YAxis );
    
        if ( !intervalX.isValid() && !intervalY.isValid() )
            return QRectF(); // no bounding rect
    
        QRectF r;
    
        if ( intervalX.isValid() )
        {
            r.setLeft( intervalX.minValue() );
            r.setRight( intervalX.maxValue() );
        }
        else
        {
            r.setLeft(-0.5 * FLT_MAX);
            r.setWidth(FLT_MAX);
        }
    
        if ( intervalY.isValid() )
        {
            r.setTop( intervalY.minValue() );
            r.setBottom( intervalY.maxValue() );
        }
        else
        {
            r.setTop(-0.5 * FLT_MAX);
            r.setHeight(FLT_MAX);
        }
    
        return r.normalized();
    }
    
    pixhawk's avatar
    pixhawk committed
    
    
    Bryant's avatar
    Bryant committed
    QImage QwtPlotRasterItem::compose( 
        const QwtScaleMap &xMap, const QwtScaleMap &yMap,
        const QRectF &imageArea, const QRectF &paintRect, 
        const QSize &imageSize, bool doCache) const
    {
        QImage image;
        if ( imageArea.isEmpty() || paintRect.isEmpty() || imageSize.isEmpty() )
            return image;
    
        if ( doCache )
        {
            if ( !d_data->cache.image.isNull()
                && d_data->cache.area == imageArea
                && d_data->cache.size == paintRect.size() )
            {
    
    pixhawk's avatar
    pixhawk committed
                image = d_data->cache.image;
            }
        }
    
    
    Bryant's avatar
    Bryant committed
        if ( image.isNull() )
        {
            double dx = 0.0;
            if ( paintRect.toRect().width() > imageSize.width() )
                dx = imageArea.width() / imageSize.width();
    
            const QwtScaleMap xxMap = 
                imageMap(Qt::Horizontal, xMap, imageArea, imageSize, dx);
            
            double dy = 0.0;
            if ( paintRect.toRect().height() > imageSize.height() )
                dy = imageArea.height() / imageSize.height();
    
            const QwtScaleMap yyMap = 
                imageMap(Qt::Vertical, yMap, imageArea, imageSize, dy);
    
            image = renderImage( xxMap, yyMap, imageArea, imageSize );
    
            if ( doCache )
            {
                d_data->cache.area = imageArea;
                d_data->cache.size = paintRect.size();
                d_data->cache.image = image;
            }
        }
    
        if ( d_data->alpha >= 0 && d_data->alpha < 255 )
        {
            QImage alphaImage( image.size(), QImage::Format_ARGB32 );
    
    #if QT_VERSION >= 0x040400 && !defined(QT_NO_QFUTURE)
            uint numThreads = renderThreadCount();
    
            if ( numThreads <= 0 )
                numThreads = QThread::idealThreadCount();
    
            if ( numThreads <= 0 )
                numThreads = 1;
    
            const int numRows = image.height() / numThreads;
    
            QList< QFuture<void> > futures;
            for ( uint i = 0; i < numThreads; i++ )
            {
                QRect tile( 0, i * numRows, image.width(), numRows );
                if ( i == numThreads - 1 )
                {
                    tile.setHeight( image.height() - i * numRows );
                    qwtToRgba( &image, &alphaImage, tile, d_data->alpha );
                }
                else
                {
                    futures += QtConcurrent::run(
                        &qwtToRgba, &image, &alphaImage, tile, d_data->alpha );
                }
            }
            for ( int i = 0; i < futures.size(); i++ )
                futures[i].waitForFinished();
    #else
            const QRect tile( 0, 0, image.width(), image.height() );
            qwtToRgba( &image, &alphaImage, tile, d_data->alpha );
    #endif
            image = alphaImage;
        }
    
        return image;
    }
    
    /*!
       \brief Calculate a scale map for painting to an image
    
       \param orientation Orientation, Qt::Horizontal means a X axis
       \param map Scale map for rendering the plot item
       \param area Area to be painted on the image
       \param imageSize Image size
       \param pixelSize Width/Height of a data pixel
    
       \return Calculated scale map
    */
    QwtScaleMap QwtPlotRasterItem::imageMap(
        Qt::Orientation orientation,
        const QwtScaleMap &map, const QRectF &area,
        const QSize &imageSize, double pixelSize) const
    {
        double p1, p2, s1, s2;
    
        if ( orientation == Qt::Horizontal )
        {
            p1 = 0.0;
            p2 = imageSize.width();
            s1 = area.left();
            s2 = area.right();
        }
        else
        {
            p1 = 0.0;
            p2 = imageSize.height();
            s1 = area.top();
            s2 = area.bottom();
        }
    
        if ( pixelSize > 0.0 )
        {
            double off = 0.5 * pixelSize;
            if ( map.isInverting() )
                off = -off;
    
            s1 += off;
            s2 += off;
        }
        else
        {
            p2--;
        }
    
        if ( map.isInverting() && ( s1 < s2 ) )
            qSwap( s1, s2 );
    
        QwtScaleMap newMap = map;
        newMap.setPaintInterval( p1, p2 );
        newMap.setScaleInterval( s1, s2 );
    
        return newMap;
    
    pixhawk's avatar
    pixhawk committed
    }