/* -*- 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_plot_rasteritem.h" #include "qwt_legend.h" #include "qwt_legend_item.h" #include "qwt_scale_map.h" #include "qwt_painter.h" #include #include #include #include #include class QwtPlotRasterItem::PrivateData { public: PrivateData(): alpha( -1 ), paintAttributes( QwtPlotRasterItem::PaintInDeviceResolution ) { cache.policy = QwtPlotRasterItem::NoCache; } int alpha; QwtPlotRasterItem::PaintAttributes paintAttributes; struct ImageCache { QwtPlotRasterItem::CachePolicy policy; QRectF area; QSizeF size; QImage image; } cache; }; static QRectF qwtAlignRect(const QRectF &rect) { QRectF r; r.setLeft( qRound( rect.left() ) ); r.setRight( qRound( rect.right() ) ); r.setTop( qRound( rect.top() ) ); r.setBottom( qRound( rect.bottom() ) ); return r; } 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); } } 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); } } 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); } } 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); } } 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 = (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 = ( 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 expandToPixels(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(); 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 transformMaps( 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 adjustMaps( 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 useCache( 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 QImage toRgba( const QImage& image, int alpha ) { if ( alpha < 0 || alpha >= 255 ) return image; QImage alphaImage( image.size(), QImage::Format_ARGB32 ); 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 w = image.size().width(); const int h = image.size().height(); if ( image.depth() == 8 ) { for ( int y = 0; y < h; y++ ) { QRgb* alphaLine = ( QRgb* )alphaImage.scanLine( y ); const unsigned char *line = image.scanLine( y ); for ( int x = 0; x < w; x++ ) *alphaLine++ = ( image.color( *line++ ) & mask2 ) | mask1; } } else if ( image.depth() == 32 ) { for ( int y = 0; y < h; y++ ) { QRgb* alphaLine = ( QRgb* )alphaImage.scanLine( y ); const QRgb* line = ( const QRgb* ) image.scanLine( y ); for ( int x = 0; x < w; x++ ) { const QRgb rgb = *line++; if ( rgb & mask3 ) // alpha != 0 *alphaLine++ = ( rgb & mask2 ) | mask1; else *alphaLine++ = rgb; } } } return alphaImage; } //! Constructor QwtPlotRasterItem::QwtPlotRasterItem( const QString& title ): QwtPlotItem( QwtText( title ) ) { init(); } //! Constructor QwtPlotRasterItem::QwtPlotRasterItem( const QwtText& title ): QwtPlotItem( title ) { init(); } //! Destructor QwtPlotRasterItem::~QwtPlotRasterItem() { delete d_data; } void QwtPlotRasterItem::init() { d_data = new PrivateData(); 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; } /*! \brief Return the current paint attributes \sa PaintAttribute, setPaintAttribute() */ bool QwtPlotRasterItem::testPaintAttribute( PaintAttribute attribute ) const { return ( d_data->paintAttributes & attribute ); } /*! \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 transparent color, while 255 represents a fully opaque color. \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). - alpha < 0 The alpha values returned by renderImage() are not changed. The default alpha value is -1. \sa alpha() */ void QwtPlotRasterItem::setAlpha( int alpha ) { if ( alpha < 0 ) alpha = -1; if ( alpha > 255 ) alpha = 255; if ( alpha != d_data->alpha ) { 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( QwtPlotRasterItem::CachePolicy policy ) { if ( d_data->cache.policy != policy ) { 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 \sa setCachePolicy() */ void QwtPlotRasterItem::invalidateCache() { d_data->cache.image = QImage(); d_data->cache.area = QRect(); d_data->cache.size = QSize(); } /*! \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 neighboured 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. \param area In most implementations the resolution of the data doesn't depend on the requested area. \return Bounding rectangle of a pixel \sa render(), renderImage() */ QRectF QwtPlotRasterItem::pixelHint( const QRectF &area ) const { Q_UNUSED( area ); return QRectF(); } /*! \brief Draw the raster data \param painter Painter \param xMap X-Scale Map \param yMap Y-Scale Map \param canvasRect Contents rect of the plot canvas */ void QwtPlotRasterItem::draw( QPainter *painter, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect ) const { if ( canvasRect.isEmpty() || d_data->alpha == 0 ) return; const bool doCache = useCache( d_data->cache.policy, painter ); const QwtInterval xInterval = interval( Qt::XAxis ); const QwtInterval yInterval = interval( Qt::YAxis ); /* Scaling a rastered image always results in a loss of precision/quality. So we always render the image in paint device resolution. */ QwtScaleMap xxMap, yyMap; transformMaps( painter->transform(), xMap, yMap, xxMap, yyMap ); QRectF paintRect = painter->transform().mapRect( canvasRect ); QRectF area = QwtScaleMap::invTransform( xxMap, yyMap, paintRect ); const QRectF br = boundingRect(); if ( br.isValid() && !br.contains( area ) ) { area &= br; if ( !area.isValid() ) return; paintRect = QwtScaleMap::transform( xxMap, yyMap, area ); } QRectF imageRect; QImage image; QRectF pixelRect = pixelHint(area); if ( !pixelRect.isEmpty() ) { const QRectF r = QwtScaleMap::invTransform( xxMap, yyMap, QRectF(0, 0, 1, 1) ).normalized(); if ( r.width() > pixelRect.width() && r.height() > 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(); } } if ( pixelRect.isEmpty() ) { if ( QwtPainter::roundingAlignment( painter ) ) { // we want to have maps, where the boundaries of // the aligned paint rectangle exactly match the area paintRect = qwtAlignRect(paintRect); adjustMaps(xxMap, yyMap, area, paintRect); } // 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 = expandToPixels(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 rect 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(); } 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() ) { image = d_data->cache.image; } } 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 ) image = toRgba( image, d_data->alpha ); 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 */ 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; }