Skip to content
qwt_plot_rasteritem.cpp 25 KiB
Newer Older
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
}