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 
qwtFastAtan(double x)25 static 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 
qwtFastAtan2(double y,double x)36 static 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 
qwtNeedsClipping(const QRectF & plotRect,const QRectF & rect)62 static 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 
85 class QwtPolarSpectrogram::TileInfo
86 {
87 public:
88     QPoint imagePos;
89     QRect rect;
90     QImage *image;
91 };
92 
93 class QwtPolarSpectrogram::PrivateData
94 {
95 public:
PrivateData()96     PrivateData():
97         data( NULL )
98     {
99         colorMap = new QwtLinearColorMap();
100     }
101 
~PrivateData()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
QwtPolarSpectrogram()115 QwtPolarSpectrogram::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
~QwtPolarSpectrogram()127 QwtPolarSpectrogram::~QwtPolarSpectrogram()
128 {
129     delete d_data;
130 }
131 
132 //! \return QwtPolarItem::Rtti_PolarSpectrogram
rtti() const133 int 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 */
setData(QwtRasterData * data)148 void 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 */
data() const163 const 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 */
setColorMap(QwtColorMap * colorMap)179 void 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 */
colorMap() const194 const 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 */
setPaintAttribute(PaintAttribute attribute,bool on)206 void 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 */
testPaintAttribute(PaintAttribute attribute) const219 bool 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 */
draw(QPainter * painter,const QwtScaleMap & azimuthMap,const QwtScaleMap & radialMap,const QPointF & pole,double,const QRectF & canvasRect) const234 void 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 */
renderImage(const QwtScaleMap & azimuthMap,const QwtScaleMap & radialMap,const QPointF & pole,const QRect & rect) const298 QImage 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 
renderTile(const QwtScaleMap & azimuthMap,const QwtScaleMap & radialMap,const QPointF & pole,TileInfo * tileInfo) const376 void 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 */
renderTile(const QwtScaleMap & azimuthMap,const QwtScaleMap & radialMap,const QPointF & pole,const QPoint & imagePos,const QRect & tile,QImage * image) const400 void 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 */
boundingInterval(int scaleId) const489 QwtInterval 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 }
496