source: ntrip/trunk/BNC/qwtpolar/qwt_polar_spectrogram.cpp @ 8127

Last change on this file since 8127 was 8127, checked in by stoecker, 2 years ago

update qwt and qwtpolar, many QT5 fixes (unfinished)

File size: 13.2 KB
Line 
1/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
2 * QwtPolar Widget Library
3 * Copyright (C) 2008   Uwe Rathmann
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the Qwt License, Version 1.0
7 *****************************************************************************/
8
9#include "qwt_polar_spectrogram.h"
10#include "qwt_polar.h"
11#include "qwt_polar_plot.h"
12#include <qwt_color_map.h>
13#include <qwt_scale_map.h>
14#include <qwt_raster_data.h>
15#include <qwt_math.h>
16#include <qpainter.h>
17#if QT_VERSION >= 0x040400
18#include <qthread.h>
19#include <qfuture.h>
20#include <qtconcurrentrun.h>
21#endif
22
23#if QWT_VERSION < 0x060100
24
25static inline double qwtFastAtan( double x )
26{
27    if ( x < -1.0 )
28        return -M_PI_2 - x / ( x * x + 0.28 );
29
30    if ( x > 1.0 )
31        return M_PI_2 - x / ( x * x + 0.28 );
32
33    return x / ( 1.0 + x * x * 0.28 );
34}
35
36static inline double qwtFastAtan2( double y, double x )
37{
38    if ( x > 0 )
39        return qwtFastAtan( y / x );
40
41    if ( x < 0 )
42    {
43        const double d = qwtFastAtan( y / x );
44        return ( y >= 0 ) ? d + M_PI : d - M_PI;
45    }
46
47    if ( y < 0.0 )
48        return -M_PI_2;
49
50    if ( y > 0.0 )
51        return M_PI_2;
52
53    return 0.0;
54}
55
56#endif // QWT_VERSION < 0x060100
57
58#if QT_VERSION < 0x040601
59#define qAtan2(y, x) ::atan2(y, x)
60#endif
61
62static bool qwtNeedsClipping( const QRectF &plotRect, const QRectF &rect )
63{
64    QPointF points[4];
65    points[0] = rect.topLeft();
66    points[1] = rect.topRight();
67    points[2] = rect.bottomLeft();
68    points[3] = rect.bottomRight();
69
70    const double radius = plotRect.width() / 2.0;
71    const QPointF pole = plotRect.center();
72
73    for ( int i = 0; i < 4; i++ )
74    {
75        const double dx = points[i].x() - pole.x();
76        const double dy = points[i].y() - pole.y();
77
78        if ( qSqrt( dx * dx + dy * dy ) > radius )
79            return true;
80    }
81
82    return false;
83}
84
85class QwtPolarSpectrogram::TileInfo
86{
87public:
88    QPoint imagePos;
89    QRect rect;
90    QImage *image;
91};
92
93class QwtPolarSpectrogram::PrivateData
94{
95public:
96    PrivateData():
97        data( NULL )
98    {
99        colorMap = new QwtLinearColorMap();
100    }
101
102    ~PrivateData()
103    {
104        delete data;
105        delete colorMap;
106    }
107
108    QwtRasterData *data;
109    QwtColorMap *colorMap;
110
111    QwtPolarSpectrogram::PaintAttributes paintAttributes;
112};
113
114//!  Constructor
115QwtPolarSpectrogram::QwtPolarSpectrogram():
116    QwtPolarItem( QwtText( "Spectrogram" ) )
117{
118    d_data = new PrivateData;
119
120    setItemAttribute( QwtPolarItem::AutoScale );
121    setItemAttribute( QwtPolarItem::Legend, false );
122
123    setZ( 20.0 );
124}
125
126//! Destructor
127QwtPolarSpectrogram::~QwtPolarSpectrogram()
128{
129    delete d_data;
130}
131
132//! \return QwtPolarItem::Rtti_PolarSpectrogram
133int QwtPolarSpectrogram::rtti() const
134{
135    return QwtPolarItem::Rtti_PolarSpectrogram;
136}
137
138/*!
139  Set the data to be displayed
140
141  \param data Spectrogram Data
142  \sa data()
143
144  \warning QwtRasterData::initRaster() is called each time before the
145           image is rendered, but without any useful parameters.
146           Also QwtRasterData::rasterHint() is not used.
147*/
148void QwtPolarSpectrogram::setData( QwtRasterData *data )
149{
150    if ( data != d_data->data )
151    {
152        delete d_data->data;
153        d_data->data = data;
154
155        itemChanged();
156    }
157}
158
159/*!
160  \return Spectrogram data
161  \sa setData()
162*/
163const QwtRasterData *QwtPolarSpectrogram::data() const
164{
165    return d_data->data;
166}
167
168/*!
169  Change the color map
170
171  Often it is useful to display the mapping between intensities and
172  colors as an additional plot axis, showing a color bar.
173
174  \param colorMap Color Map
175
176  \sa colorMap(), QwtScaleWidget::setColorBarEnabled(),
177      QwtScaleWidget::setColorMap()
178*/
179void QwtPolarSpectrogram::setColorMap( QwtColorMap *colorMap )
180{
181    if ( d_data->colorMap != colorMap )
182    {
183        delete d_data->colorMap;
184        d_data->colorMap = colorMap;
185    }
186
187    itemChanged();
188}
189
190/*!
191   \return Color Map used for mapping the intensity values to colors
192   \sa setColorMap()
193*/
194const QwtColorMap *QwtPolarSpectrogram::colorMap() const
195{
196    return d_data->colorMap;
197}
198
199/*!
200  Specify an attribute how to draw the curve
201
202  \param attribute Paint attribute
203  \param on On/Off
204  \sa testPaintAttribute()
205*/
206void QwtPolarSpectrogram::setPaintAttribute( PaintAttribute attribute, bool on )
207{
208    if ( on )
209        d_data->paintAttributes |= attribute;
210    else
211        d_data->paintAttributes &= ~attribute;
212}
213
214/*!
215    \param attribute Paint attribute
216    \return True, when attribute has been set
217    \sa setPaintAttribute()
218*/
219bool QwtPolarSpectrogram::testPaintAttribute( PaintAttribute attribute ) const
220{
221    return ( d_data->paintAttributes & attribute );
222}
223
224/*!
225  Draw the spectrogram
226
227  \param painter Painter
228  \param azimuthMap Maps azimuth values to values related to 0.0, M_2PI
229  \param radialMap Maps radius values into painter coordinates.
230  \param pole Position of the pole in painter coordinates
231  \param radius Radius of the complete plot area in painter coordinates
232  \param canvasRect Contents rect of the canvas in painter coordinates
233*/
234void QwtPolarSpectrogram::draw( QPainter *painter,
235    const QwtScaleMap &azimuthMap, const QwtScaleMap &radialMap,
236    const QPointF &pole, double,
237    const QRectF &canvasRect ) const
238{
239    const QRectF plotRect = plot()->plotRect( canvasRect.toRect() );
240
241    QRegion clipRegion( canvasRect.toRect() );
242    if ( qwtNeedsClipping( plotRect, canvasRect ) )
243    {
244        // For large plotRects the ellipse becomes a huge polygon.
245        // So we better clip only, when we really need to.
246
247        clipRegion &= QRegion( plotRect.toRect(), QRegion::Ellipse );
248    }
249
250    QRect imageRect = canvasRect.toRect();
251
252    if ( painter->hasClipping() )
253        imageRect &= painter->clipRegion().boundingRect();
254
255    const QwtInterval radialInterval =
256        boundingInterval( QwtPolar::ScaleRadius );
257    if ( radialInterval.isValid() )
258    {
259        const double radius = radialMap.transform( radialInterval.maxValue() ) -
260                              radialMap.transform( radialInterval.minValue() );
261
262        QRectF r( 0, 0, 2 * radius, 2 * radius );
263        r.moveCenter( pole );
264
265        clipRegion &= QRegion( r.toRect(), QRegion::Ellipse );;
266
267        imageRect &= r.toRect();
268    }
269
270    const QImage image = renderImage( azimuthMap, radialMap, pole, imageRect );
271
272    painter->save();
273    painter->setClipRegion( clipRegion );
274
275    painter->drawImage( imageRect, image );
276
277    painter->restore();
278}
279
280/*!
281   \brief Render an image from the data and color map.
282
283   The area is translated into a rect of the paint device.
284   For each pixel of this rect the intensity is mapped
285   into a color.
286
287  \param azimuthMap Maps azimuth values to values related to 0.0, M_2PI
288  \param radialMap Maps radius values into painter coordinates.
289  \param pole Position of the pole in painter coordinates
290  \param rect Target rectangle of the image in painter coordinates
291
292   \return A QImage::Format_Indexed8 or QImage::Format_ARGB32 depending
293           on the color map.
294
295   \sa QwtRasterData::intensity(), QwtColorMap::rgb(),
296       QwtColorMap::colorIndex()
297*/
298QImage QwtPolarSpectrogram::renderImage(
299    const QwtScaleMap &azimuthMap, const QwtScaleMap &radialMap,
300    const QPointF &pole, const QRect &rect ) const
301{
302    if ( d_data->data == NULL || d_data->colorMap == NULL )
303        return QImage();
304
305    QImage image( rect.size(), d_data->colorMap->format() == QwtColorMap::RGB
306                  ? QImage::Format_ARGB32 : QImage::Format_Indexed8 );
307
308    const QwtInterval intensityRange = d_data->data->interval( Qt::ZAxis );
309    if ( !intensityRange.isValid() )
310        return image;
311
312    if ( d_data->colorMap->format() == QwtColorMap::Indexed )
313        image.setColorTable( d_data->colorMap->colorTable( intensityRange ) );
314
315    /*
316     For the moment we only announce the composition of the image by
317     calling initRaster(), but we don't pass any useful parameters.
318     ( How to map rect into something, that is useful to initialize a matrix
319       of values in polar coordinates ? )
320     */
321    d_data->data->initRaster( QRectF(), QSize() );
322
323
324#if QT_VERSION >= 0x040400 && !defined(QT_NO_QFUTURE)
325    uint numThreads = renderThreadCount();
326
327    if ( numThreads <= 0 )
328        numThreads = QThread::idealThreadCount();
329
330    if ( numThreads <= 0 )
331        numThreads = 1;
332
333    const int numRows = rect.height() / numThreads;
334
335
336    QVector<TileInfo> tileInfos;
337    for ( uint i = 0; i < numThreads; i++ )
338    {
339        QRect tile( rect.x(), rect.y() + i * numRows, rect.width(), numRows );
340        if ( i == numThreads - 1 )
341            tile.setHeight( rect.height() - i * numRows );
342
343        TileInfo tileInfo;
344        tileInfo.imagePos = rect.topLeft();
345        tileInfo.rect = tile;
346        tileInfo.image = &image;
347
348        tileInfos += tileInfo;
349    }
350
351    QVector< QFuture<void> > futures;
352    for ( int i = 0; i < tileInfos.size(); i++ )
353    {
354        if ( i == tileInfos.size() - 1 )
355        {
356            renderTile( azimuthMap, radialMap, pole, &tileInfos[i] );
357        }
358        else
359        {
360            futures += QtConcurrent::run( this, &QwtPolarSpectrogram::renderTile,
361                azimuthMap, radialMap, pole, &tileInfos[i] );
362        }
363    }
364    for ( int i = 0; i < futures.size(); i++ )
365        futures[i].waitForFinished();
366
367#else // QT_VERSION < 0x040400
368    renderTile( azimuthMap, radialMap, pole, rect.topLeft(), rect, &image );
369#endif
370
371    d_data->data->discardRaster();
372
373    return image;
374}
375
376void QwtPolarSpectrogram::renderTile(
377    const QwtScaleMap &azimuthMap, const QwtScaleMap &radialMap,
378    const QPointF &pole, TileInfo *tileInfo ) const
379{
380    renderTile( azimuthMap, radialMap, pole,
381        tileInfo->imagePos, tileInfo->rect, tileInfo->image );
382}
383
384/*!
385  \brief Render a sub-rectangle of an image
386
387  renderTile() is called by renderImage() to render different parts
388  of the image by concurrent threads.
389
390  \param azimuthMap Maps azimuth values to values related to 0.0, M_2PI
391  \param radialMap Maps radius values into painter coordinates.
392  \param pole Position of the pole in painter coordinates
393  \param imagePos Top/left position of the image in painter coordinates
394  \param tile Sub-rectangle of the tile in painter coordinates
395  \param image Image to be rendered
396
397   \sa setRenderThreadCount()
398   \note renderTile needs to be reentrant
399*/
400void QwtPolarSpectrogram::renderTile(
401    const QwtScaleMap &azimuthMap, const QwtScaleMap &radialMap,
402    const QPointF &pole, const QPoint &imagePos,
403    const QRect &tile, QImage *image ) const
404{
405    const QwtInterval intensityRange = d_data->data->interval( Qt::ZAxis );
406    if ( !intensityRange.isValid() )
407        return;
408
409    const bool doFastAtan = testPaintAttribute( ApproximatedAtan );
410
411    const int y0 = imagePos.y();
412    const int y1 = tile.top();
413    const int y2 = tile.bottom();
414
415    const int x0 = imagePos.x();
416    const int x1 = tile.left();
417    const int x2 = tile.right();
418
419    if ( d_data->colorMap->format() == QwtColorMap::RGB )
420    {
421        for ( int y = y1; y <= y2; y++ )
422        {
423            const double dy = pole.y() - y;
424            const double dy2 = qwtSqr( dy );
425
426            QRgb *line = reinterpret_cast<QRgb *>( image->scanLine( y - y0 ) );
427            line += x1 - x0;
428
429            for ( int x = x1; x <= x2; x++ )
430            {
431                const double dx = x - pole.x();
432
433                double a =  doFastAtan ? qwtFastAtan2( dy, dx ) : qAtan2( dy, dx );
434                if ( a < 0.0 )
435                    a += 2 * M_PI;
436                if ( a < azimuthMap.p1() )
437                    a += 2 * M_PI;
438
439                const double r = qSqrt( qwtSqr( dx ) + dy2 );
440
441                const double azimuth = azimuthMap.invTransform( a );
442                const double radius = radialMap.invTransform( r );
443
444                const double value = d_data->data->value( azimuth, radius );
445                *line++ = d_data->colorMap->rgb( intensityRange, value );
446            }
447        }
448    }
449    else if ( d_data->colorMap->format() == QwtColorMap::Indexed )
450    {
451        for ( int y = y1; y <= y2; y++ )
452        {
453            const double dy = pole.y() - y;
454            const double dy2 = qwtSqr( dy );
455
456            unsigned char *line = image->scanLine( y - y0 );
457            line += x1 - x0;
458            for ( int x = x1; x <= x2; x++ )
459            {
460                const double dx = x - pole.x();
461
462                double a =  doFastAtan ? qwtFastAtan2( dy, dx ) : qAtan2( dy, dx );
463                if ( a < 0.0 )
464                    a += 2 * M_PI;
465                if ( a < azimuthMap.p1() )
466                    a += 2 * M_PI;
467
468                const double r = qSqrt( qwtSqr( dx ) + dy2 );
469
470                const double azimuth = azimuthMap.invTransform( a );
471                const double radius = radialMap.invTransform( r );
472
473                const double value = d_data->data->value( azimuth, radius );
474                *line++ = d_data->colorMap->colorIndex( intensityRange, value );
475            }
476        }
477    }
478}
479
480/*!
481   Interval, that is necessary to display the item
482   This interval can be useful for operations like clipping or autoscaling
483
484   \param scaleId Scale index
485   \return bounding interval ( == position )
486
487   \sa position()
488*/
489QwtInterval QwtPolarSpectrogram::boundingInterval( int scaleId ) const
490{
491    if ( scaleId == QwtPolar::ScaleRadius )
492        return d_data->data->interval( Qt::YAxis );
493
494    return QwtPolarItem::boundingInterval( scaleId );
495}
Note: See TracBrowser for help on using the repository browser.