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

Last change on this file since 4440 was 4272, checked in by mervart, 12 years ago
File size: 14.3 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 renderThreadCount( 1 )
99 {
100 colorMap = new QwtLinearColorMap();
101 }
102
103 ~PrivateData()
104 {
105 delete data;
106 delete colorMap;
107 }
108
109 QwtRasterData *data;
110 QwtColorMap *colorMap;
111
112 uint renderThreadCount;
113
114 QwtPolarSpectrogram::PaintAttributes paintAttributes;
115};
116
117//! Constructor
118QwtPolarSpectrogram::QwtPolarSpectrogram():
119 QwtPolarItem( QwtText( "Spectrogram" ) )
120{
121 d_data = new PrivateData;
122
123 setItemAttribute( QwtPolarItem::AutoScale );
124 setItemAttribute( QwtPolarItem::Legend, false );
125
126 setZ( 20.0 );
127}
128
129//! Destructor
130QwtPolarSpectrogram::~QwtPolarSpectrogram()
131{
132 delete d_data;
133}
134
135//! \return QwtPolarItem::Rtti_PolarSpectrogram
136int QwtPolarSpectrogram::rtti() const
137{
138 return QwtPolarItem::Rtti_PolarSpectrogram;
139}
140
141/*!
142 Set the data to be displayed
143
144 \param data Spectrogram Data
145 \sa data()
146
147 \warning QwtRasterData::initRaster() is called each time before the
148 image is rendered, but without any useful parameters.
149 Also QwtRasterData::rasterHint() is not used.
150*/
151void QwtPolarSpectrogram::setData( QwtRasterData *data )
152{
153 if ( data != d_data->data )
154 {
155 delete d_data->data;
156 d_data->data = data;
157
158 itemChanged();
159 }
160}
161
162/*!
163 \return Spectrogram data
164 \sa setData()
165*/
166const QwtRasterData *QwtPolarSpectrogram::data() const
167{
168 return d_data->data;
169}
170
171/*!
172 Change the color map
173
174 Often it is useful to display the mapping between intensities and
175 colors as an additional plot axis, showing a color bar.
176
177 \param colorMap Color Map
178
179 \sa colorMap(), QwtScaleWidget::setColorBarEnabled(),
180 QwtScaleWidget::setColorMap()
181*/
182void QwtPolarSpectrogram::setColorMap( QwtColorMap *colorMap )
183{
184 if ( d_data->colorMap != colorMap )
185 {
186 delete d_data->colorMap;
187 d_data->colorMap = colorMap;
188 }
189
190 itemChanged();
191}
192
193/*!
194 \return Color Map used for mapping the intensity values to colors
195 \sa setColorMap()
196*/
197const QwtColorMap *QwtPolarSpectrogram::colorMap() const
198{
199 return d_data->colorMap;
200}
201
202/*!
203 Specify an attribute how to draw the curve
204
205 \param attribute Paint attribute
206 \param on On/Off
207 \sa testPaintAttribute()
208*/
209void QwtPolarSpectrogram::setPaintAttribute( PaintAttribute attribute, bool on )
210{
211 if ( on )
212 d_data->paintAttributes |= attribute;
213 else
214 d_data->paintAttributes &= ~attribute;
215}
216
217/*!
218 \param attribute Paint attribute
219 \return True, when attribute has been set
220 \sa setPaintAttribute()
221*/
222bool QwtPolarSpectrogram::testPaintAttribute( PaintAttribute attribute ) const
223{
224 return ( d_data->paintAttributes & attribute );
225}
226
227/*!
228 Rendering an image from the raster data can often be done
229 parallel on a multicore system.
230
231 \param numThreads Number of threads to be used for rendering.
232 If numThreads is set to 0, the system specific
233 ideal thread count is used.
234
235 The default thread count is 1 ( = no additional threads )
236
237 \warning Rendering in multiple threads is only supported for Qt >= 4.4
238 \sa renderThreadCount(), renderImage(), renderTile()
239*/
240void QwtPolarSpectrogram::setRenderThreadCount( uint numThreads )
241{
242 d_data->renderThreadCount = numThreads;
243}
244
245/*!
246 \return Number of threads to be used for rendering.
247 If numThreads is set to 0, the system specific
248 ideal thread count is used.
249
250 \warning Rendering in multiple threads is only supported for Qt >= 4.4
251 \sa setRenderThreadCount(), renderImage(), renderTile()
252*/
253uint QwtPolarSpectrogram::renderThreadCount() const
254{
255 return d_data->renderThreadCount;
256}
257
258/*!
259 Draw the spectrogram
260
261 \param painter Painter
262 \param azimuthMap Maps azimuth values to values related to 0.0, M_2PI
263 \param radialMap Maps radius values into painter coordinates.
264 \param pole Position of the pole in painter coordinates
265 \param radius Radius of the complete plot area in painter coordinates
266 \param canvasRect Contents rect of the canvas in painter coordinates
267*/
268void QwtPolarSpectrogram::draw( QPainter *painter,
269 const QwtScaleMap &azimuthMap, const QwtScaleMap &radialMap,
270 const QPointF &pole, double,
271 const QRectF &canvasRect ) const
272{
273 const QRectF plotRect = plot()->plotRect( canvasRect.toRect() );
274
275 QRegion clipRegion( canvasRect.toRect() );
276 if ( qwtNeedsClipping( plotRect, canvasRect ) )
277 {
278 // For large plotRects the ellipse becomes a huge polygon.
279 // So we better clip only, when we really need to.
280
281 clipRegion &= QRegion( plotRect.toRect(), QRegion::Ellipse );
282 }
283
284 QRect imageRect = canvasRect.toRect();
285
286 if ( painter->hasClipping() )
287 imageRect &= painter->clipRegion().boundingRect();
288
289 const QwtInterval radialInterval =
290 boundingInterval( QwtPolar::ScaleRadius );
291 if ( radialInterval.isValid() )
292 {
293 const double radius = radialMap.transform( radialInterval.maxValue() ) -
294 radialMap.transform( radialInterval.minValue() );
295
296 QRectF r( 0, 0, 2 * radius, 2 * radius );
297 r.moveCenter( pole );
298
299 clipRegion &= QRegion( r.toRect(), QRegion::Ellipse );;
300
301 imageRect &= r.toRect();
302 }
303
304 const QImage image = renderImage( azimuthMap, radialMap, pole, imageRect );
305
306 painter->save();
307 painter->setClipRegion( clipRegion );
308
309 painter->drawImage( imageRect, image );
310
311 painter->restore();
312}
313
314/*!
315 \brief Render an image from the data and color map.
316
317 The area is translated into a rect of the paint device.
318 For each pixel of this rect the intensity is mapped
319 into a color.
320
321 \param azimuthMap Maps azimuth values to values related to 0.0, M_2PI
322 \param radialMap Maps radius values into painter coordinates.
323 \param pole Position of the pole in painter coordinates
324 \param rect Target rectangle of the image in painter coordinates
325
326 \return A QImage::Format_Indexed8 or QImage::Format_ARGB32 depending
327 on the color map.
328
329 \sa QwtRasterData::intensity(), QwtColorMap::rgb(),
330 QwtColorMap::colorIndex()
331*/
332QImage QwtPolarSpectrogram::renderImage(
333 const QwtScaleMap &azimuthMap, const QwtScaleMap &radialMap,
334 const QPointF &pole, const QRect &rect ) const
335{
336 if ( d_data->data == NULL || d_data->colorMap == NULL )
337 return QImage();
338
339 QImage image( rect.size(), d_data->colorMap->format() == QwtColorMap::RGB
340 ? QImage::Format_ARGB32 : QImage::Format_Indexed8 );
341
342 const QwtInterval intensityRange = d_data->data->interval( Qt::ZAxis );
343 if ( !intensityRange.isValid() )
344 return image;
345
346 if ( d_data->colorMap->format() == QwtColorMap::Indexed )
347 image.setColorTable( d_data->colorMap->colorTable( intensityRange ) );
348
349 /*
350 For the moment we only announce the composition of the image by
351 calling initRaster(), but we don't pass any useful parameters.
352 ( How to map rect into something, that is useful to initialize a matrix
353 of values in polar coordinates ? )
354 */
355 d_data->data->initRaster( QRectF(), QSize() );
356
357
358#if QT_VERSION >= 0x040400 && !defined(QT_NO_QFUTURE)
359 uint numThreads = d_data->renderThreadCount;
360
361 if ( numThreads <= 0 )
362 numThreads = QThread::idealThreadCount();
363
364 if ( numThreads <= 0 )
365 numThreads = 1;
366
367 const int numRows = rect.height() / numThreads;
368
369
370 QVector<TileInfo> tileInfos;
371 for ( uint i = 0; i < numThreads; i++ )
372 {
373 QRect tile( rect.x(), rect.y() + i * numRows, rect.width(), numRows );
374 if ( i == numThreads - 1 )
375 tile.setHeight( rect.height() - i * numRows );
376
377 TileInfo tileInfo;
378 tileInfo.imagePos = rect.topLeft();
379 tileInfo.rect = tile;
380 tileInfo.image = &image;
381
382 tileInfos += tileInfo;
383 }
384
385 QVector< QFuture<void> > futures;
386 for ( int i = 0; i < tileInfos.size(); i++ )
387 {
388 if ( i == tileInfos.size() - 1 )
389 {
390 renderTile( azimuthMap, radialMap, pole, &tileInfos[i] );
391 }
392 else
393 {
394 futures += QtConcurrent::run( this, &QwtPolarSpectrogram::renderTile,
395 azimuthMap, radialMap, pole, &tileInfos[i] );
396 }
397 }
398 for ( int i = 0; i < futures.size(); i++ )
399 futures[i].waitForFinished();
400
401#else // QT_VERSION < 0x040400
402 renderTile( azimuthMap, radialMap, pole, rect.topLeft(), rect, &image );
403#endif
404
405 d_data->data->discardRaster();
406
407 return image;
408}
409
410void QwtPolarSpectrogram::renderTile(
411 const QwtScaleMap &azimuthMap, const QwtScaleMap &radialMap,
412 const QPointF &pole, TileInfo *tileInfo ) const
413{
414 renderTile( azimuthMap, radialMap, pole,
415 tileInfo->imagePos, tileInfo->rect, tileInfo->image );
416}
417
418/*!
419 \brief Render a sub-rectangle of an image
420
421 renderTile() is called by renderImage() to render different parts
422 of the image by concurrent threads.
423
424 \param azimuthMap Maps azimuth values to values related to 0.0, M_2PI
425 \param radialMap Maps radius values into painter coordinates.
426 \param pole Position of the pole in painter coordinates
427 \param imagePos Top/left position of the image in painter coordinates
428 \param tile Sub-rectangle of the tile in painter coordinates
429 \param image Image to be rendered
430
431 \sa setRenderThreadCount()
432 \note renderTile needs to be reentrant
433*/
434void QwtPolarSpectrogram::renderTile(
435 const QwtScaleMap &azimuthMap, const QwtScaleMap &radialMap,
436 const QPointF &pole, const QPoint &imagePos,
437 const QRect &tile, QImage *image ) const
438{
439 const QwtInterval intensityRange = d_data->data->interval( Qt::ZAxis );
440 if ( !intensityRange.isValid() )
441 return;
442
443 const bool doFastAtan = testPaintAttribute( ApproximatedAtan );
444
445 const int y0 = imagePos.y();
446 const int y1 = tile.top();
447 const int y2 = tile.bottom();
448
449 const int x0 = imagePos.x();
450 const int x1 = tile.left();
451 const int x2 = tile.right();
452
453 if ( d_data->colorMap->format() == QwtColorMap::RGB )
454 {
455 for ( int y = y1; y <= y2; y++ )
456 {
457 const double dy = pole.y() - y;
458 const double dy2 = qwtSqr( dy );
459
460 QRgb *line = reinterpret_cast<QRgb *>( image->scanLine( y - y0 ) );
461 line += x1 - x0;
462
463 for ( int x = x1; x <= x2; x++ )
464 {
465 const double dx = x - pole.x();
466
467 double a = doFastAtan ? qwtFastAtan2( dy, dx ) : qAtan2( dy, dx );
468 if ( a < 0.0 )
469 a += 2 * M_PI;
470 if ( a < azimuthMap.p1() )
471 a += 2 * M_PI;
472
473 const double r = qSqrt( qwtSqr( dx ) + dy2 );
474
475 const double azimuth = azimuthMap.invTransform( a );
476 const double radius = radialMap.invTransform( r );
477
478 const double value = d_data->data->value( azimuth, radius );
479 *line++ = d_data->colorMap->rgb( intensityRange, value );
480 }
481 }
482 }
483 else if ( d_data->colorMap->format() == QwtColorMap::Indexed )
484 {
485 for ( int y = y1; y <= y2; y++ )
486 {
487 const double dy = pole.y() - y;
488 const double dy2 = qwtSqr( dy );
489
490 unsigned char *line = image->scanLine( y - y0 );
491 line += x1 - x0;
492 for ( int x = x1; x <= x2; x++ )
493 {
494 const double dx = x - pole.x();
495
496 double a = doFastAtan ? qwtFastAtan2( dy, dx ) : qAtan2( dy, dx );
497 if ( a < 0.0 )
498 a += 2 * M_PI;
499 if ( a < azimuthMap.p1() )
500 a += 2 * M_PI;
501
502 const double r = qSqrt( qwtSqr( dx ) + dy2 );
503
504 const double azimuth = azimuthMap.invTransform( a );
505 const double radius = radialMap.invTransform( r );
506
507 const double value = d_data->data->value( azimuth, radius );
508 *line++ = d_data->colorMap->colorIndex( intensityRange, value );
509 }
510 }
511 }
512}
513
514/*!
515 Interval, that is necessary to display the item
516 This interval can be useful for operations like clipping or autoscaling
517
518 \param scaleId Scale index
519 \return bounding interval ( == position )
520
521 \sa position()
522*/
523QwtInterval QwtPolarSpectrogram::boundingInterval( int scaleId ) const
524{
525 if ( scaleId == QwtPolar::ScaleRadius )
526 return d_data->data->interval( Qt::YAxis );
527
528 return QwtPolarItem::boundingInterval( scaleId );
529}
Note: See TracBrowser for help on using the repository browser.