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

Last change on this file since 9550 was 8127, checked in by stoecker, 8 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.