source: ntrip/trunk/BNC/qwt/qwt_point_mapper.cpp@ 10573

Last change on this file since 10573 was 9383, checked in by stoecker, 4 years ago

update to qwt verion 6.1.1 to fix build with newer Qt5

File size: 18.6 KB
RevLine 
[8127]1/* -*- mode: C++ ; c-file-style: "stroustrup" -*- *****************************
2 * Qwt Widget Library
3 * Copyright (C) 1997 Josef Wilgen
4 * Copyright (C) 2002 Uwe Rathmann
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the Qwt License, Version 1.0
8 *****************************************************************************/
9
10#include "qwt_point_mapper.h"
11#include "qwt_scale_map.h"
12#include "qwt_pixel_matrix.h"
13#include <qpolygon.h>
14#include <qimage.h>
15#include <qpen.h>
16#include <qpainter.h>
17
18#if QT_VERSION >= 0x040400
19
20#include <qthread.h>
21#include <qfuture.h>
22#include <qtconcurrentrun.h>
23
24#if !defined(QT_NO_QFUTURE)
[9383]25#define QWT_USE_THREADS 1
[8127]26#endif
27
28#endif
29
30static QRectF qwtInvalidRect( 0.0, 0.0, -1.0, -1.0 );
31
32// Helper class to work around the 5 parameters
33// limitation of QtConcurrent::run()
34class QwtDotsCommand
35{
36public:
37 const QwtSeriesData<QPointF> *series;
38 int from;
39 int to;
40 QRgb rgb;
41};
42
43static void qwtRenderDots(
44 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
[9383]45 const QwtDotsCommand command, const QPoint &pos, QImage *image )
[8127]46{
47 const QRgb rgb = command.rgb;
48 QRgb *bits = reinterpret_cast<QRgb *>( image->bits() );
49
50 const int w = image->width();
51 const int h = image->height();
52
53 const int x0 = pos.x();
54 const int y0 = pos.y();
55
56 for ( int i = command.from; i <= command.to; i++ )
57 {
58 const QPointF sample = command.series->sample( i );
59
60 const int x = static_cast<int>( xMap.transform( sample.x() ) + 0.5 ) - x0;
61 const int y = static_cast<int>( yMap.transform( sample.y() ) + 0.5 ) - y0;
62
63 if ( x >= 0 && x < w && y >= 0 && y < h )
64 bits[ y * w + x ] = rgb;
65 }
66}
67
68static inline int qwtRoundValue( double value )
69{
70 return qRound( value );
71}
72
73// some functors, so that the compile can inline
74struct QwtRoundI
75{
76 inline int operator()( double value )
77 {
78 return qwtRoundValue( value );
79 }
80};
81
82struct QwtRoundF
83{
84 inline double operator()( double value )
85 {
86#if 1
87 // MS Windows and at least IRIX does not have C99's nearbyint() function
88 return ( value >= 0.0 ) ? ::floor( value + 0.5 ) : ::ceil( value - 0.5 );
89#else
90 // slightly faster than the code above
91 return nearbyint( value );
92#endif
93 }
94};
95
96struct QwtNoRoundF
[9383]97{
[8127]98 inline double operator()( double value )
99 {
100 return value;
101 }
102};
103
104// mapping points without any filtering - beside checking
105// the bounding rectangle
106
107template<class Polygon, class Point, class Round>
[9383]108static inline Polygon qwtToPoints(
[8127]109 const QRectF &boundingRect,
110 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
[9383]111 const QwtSeriesData<QPointF> *series,
[8127]112 int from, int to, Round round )
113{
114 Polygon polyline( to - from + 1 );
115 Point *points = polyline.data();
116
117 int numPoints = 0;
118
119 if ( boundingRect.isValid() )
120 {
121 // iterating over all values
122 // filtering out all points outside of
123 // the bounding rectangle
124
125 for ( int i = from; i <= to; i++ )
126 {
127 const QPointF sample = series->sample( i );
128
129 const double x = xMap.transform( sample.x() );
130 const double y = yMap.transform( sample.y() );
131
132 if ( boundingRect.contains( x, y ) )
133 {
134 points[ numPoints ].rx() = round( x );
135 points[ numPoints ].ry() = round( y );
136
137 numPoints++;
138 }
139 }
140
141 polyline.resize( numPoints );
142 }
143 else
144 {
145 // simply iterating over all values
146 // without any filtering
147
148 for ( int i = from; i <= to; i++ )
149 {
150 const QPointF sample = series->sample( i );
151
152 const double x = xMap.transform( sample.x() );
153 const double y = yMap.transform( sample.y() );
154
155 points[ numPoints ].rx() = round( x );
156 points[ numPoints ].ry() = round( y );
157
158 numPoints++;
159 }
160 }
161
162 return polyline;
163}
164
165static inline QPolygon qwtToPointsI(
166 const QRectF &boundingRect,
167 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
168 const QwtSeriesData<QPointF> *series,
169 int from, int to )
170{
[9383]171 return qwtToPoints<QPolygon, QPoint>(
[8127]172 boundingRect, xMap, yMap, series, from, to, QwtRoundI() );
173}
174
175template<class Round>
176static inline QPolygonF qwtToPointsF(
177 const QRectF &boundingRect,
178 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
179 const QwtSeriesData<QPointF> *series,
180 int from, int to, Round round )
181{
[9383]182 return qwtToPoints<QPolygonF, QPointF>(
[8127]183 boundingRect, xMap, yMap, series, from, to, round );
184}
185
186// Mapping points with filtering out consecutive
187// points mapped to the same position
188
189template<class Polygon, class Point, class Round>
[9383]190static inline Polygon qwtToPolylineFiltered(
[8127]191 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
[9383]192 const QwtSeriesData<QPointF> *series,
[8127]193 int from, int to, Round round )
194{
195 // in curves with many points consecutive points
196 // are often mapped to the same position. As this might
197 // result in empty lines ( or symbols hidden by others )
198 // we try to filter them out
199
200 Polygon polyline( to - from + 1 );
201 Point *points = polyline.data();
202
203 const QPointF sample0 = series->sample( from );
204
205 points[0].rx() = round( xMap.transform( sample0.x() ) );
206 points[0].ry() = round( yMap.transform( sample0.y() ) );
207
208 int pos = 0;
209 for ( int i = from + 1; i <= to; i++ )
210 {
211 const QPointF sample = series->sample( i );
212
213 const Point p( round( xMap.transform( sample.x() ) ),
214 round( yMap.transform( sample.y() ) ) );
215
216 if ( points[pos] != p )
217 points[++pos] = p;
218 }
219
220 polyline.resize( pos + 1 );
221 return polyline;
222}
223
224static inline QPolygon qwtToPolylineFilteredI(
225 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
226 const QwtSeriesData<QPointF> *series,
227 int from, int to )
228{
229 return qwtToPolylineFiltered<QPolygon, QPoint>(
230 xMap, yMap, series, from, to, QwtRoundI() );
231}
232
233template<class Round>
234static inline QPolygonF qwtToPolylineFilteredF(
235 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
236 const QwtSeriesData<QPointF> *series,
237 int from, int to, Round round )
238{
239 return qwtToPolylineFiltered<QPolygonF, QPointF>(
240 xMap, yMap, series, from, to, round );
[9383]241}
[8127]242
243template<class Polygon, class Point>
244static inline Polygon qwtToPointsFiltered(
245 const QRectF &boundingRect,
246 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
247 const QwtSeriesData<QPointF> *series, int from, int to )
248{
249 // F.e. in scatter plots ( no connecting lines ) we
250 // can sort out all duplicates ( not only consecutive points )
251
252 Polygon polygon( to - from + 1 );
253 Point *points = polygon.data();
254
255 QwtPixelMatrix pixelMatrix( boundingRect.toAlignedRect() );
256
257 int numPoints = 0;
258 for ( int i = from; i <= to; i++ )
259 {
260 const QPointF sample = series->sample( i );
261
262 const int x = qwtRoundValue( xMap.transform( sample.x() ) );
263 const int y = qwtRoundValue( yMap.transform( sample.y() ) );
264
265 if ( pixelMatrix.testAndSetPixel( x, y, true ) == false )
266 {
267 points[ numPoints ].rx() = x;
268 points[ numPoints ].ry() = y;
269
270 numPoints++;
271 }
272 }
273
274 polygon.resize( numPoints );
275 return polygon;
276}
277
278static inline QPolygon qwtToPointsFilteredI(
279 const QRectF &boundingRect,
280 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
281 const QwtSeriesData<QPointF> *series, int from, int to )
282{
283 return qwtToPointsFiltered<QPolygon, QPoint>(
284 boundingRect, xMap, yMap, series, from, to );
[9383]285}
[8127]286
287static inline QPolygonF qwtToPointsFilteredF(
288 const QRectF &boundingRect,
289 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
290 const QwtSeriesData<QPointF> *series, int from, int to )
291{
292 return qwtToPointsFiltered<QPolygonF, QPointF>(
293 boundingRect, xMap, yMap, series, from, to );
294}
295
296class QwtPointMapper::PrivateData
297{
298public:
299 PrivateData():
300 boundingRect( qwtInvalidRect )
301 {
302 }
303
304 QRectF boundingRect;
305 QwtPointMapper::TransformationFlags flags;
306};
307
308//! Constructor
309QwtPointMapper::QwtPointMapper()
310{
311 d_data = new PrivateData();
312}
313
314//! Destructor
315QwtPointMapper::~QwtPointMapper()
316{
317 delete d_data;
318}
319
320/*!
321 Set the flags affecting the transformation process
322
323 \param flags Flags
324 \sa flags(), setFlag()
325 */
326void QwtPointMapper::setFlags( TransformationFlags flags )
327{
328 d_data->flags = flags;
329}
330
331/*!
332 \return Flags affecting the transformation process
333 \sa setFlags(), setFlag()
334 */
335QwtPointMapper::TransformationFlags QwtPointMapper::flags() const
336{
337 return d_data->flags;
338}
339
340/*!
341 Modify a flag affecting the transformation process
342
343 \param flag Flag type
344 \param on Value
345
346 \sa flag(), setFlags()
347 */
348void QwtPointMapper::setFlag( TransformationFlag flag, bool on )
349{
350 if ( on )
351 d_data->flags |= flag;
352 else
353 d_data->flags &= ~flag;
354}
355
356/*!
357 \return True, when the flag is set
358 \param flag Flag type
359 \sa setFlag(), setFlags()
360 */
361bool QwtPointMapper::testFlag( TransformationFlag flag ) const
362{
363 return d_data->flags & flag;
364}
365
366/*!
367 Set a bounding rectangle for the point mapping algorithm
368
369 A valid bounding rectangle can be used for optimizations
370
371 \param rect Bounding rectangle
372 \sa boundingRect()
373 */
374void QwtPointMapper::setBoundingRect( const QRectF &rect )
375{
376 d_data->boundingRect = rect;
377}
378
379/*!
380 \return Bounding rectangle
381 \sa setBoundingRect()
382 */
383QRectF QwtPointMapper::boundingRect() const
384{
385 return d_data->boundingRect;
386}
387
388/*!
389 \brief Translate a series of points into a QPolygonF
390
391 When the WeedOutPoints flag is enabled consecutive points,
[9383]392 that are mapped to the same position will be one point.
[8127]393
394 When RoundPoints is set all points are rounded to integers
395 but returned as PolygonF - what only makes sense
396 when the further processing of the values need a QPolygonF.
397
398 \param xMap x map
399 \param yMap y map
400 \param series Series of points to be mapped
401 \param from Index of the first point to be painted
402 \param to Index of the last point to be painted
403
404 \return Translated polygon
405*/
406QPolygonF QwtPointMapper::toPolygonF(
407 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
408 const QwtSeriesData<QPointF> *series, int from, int to ) const
409{
410 QPolygonF polyline;
411
412 if ( d_data->flags & WeedOutPoints )
413 {
414 if ( d_data->flags & RoundPoints )
415 {
[9383]416 polyline = qwtToPolylineFilteredF(
[8127]417 xMap, yMap, series, from, to, QwtRoundF() );
418 }
419 else
420 {
[9383]421 polyline = qwtToPolylineFilteredF(
[8127]422 xMap, yMap, series, from, to, QwtNoRoundF() );
423 }
424 }
425 else
426 {
427 if ( d_data->flags & RoundPoints )
428 {
[9383]429 polyline = qwtToPointsF( qwtInvalidRect,
[8127]430 xMap, yMap, series, from, to, QwtRoundF() );
431 }
432 else
433 {
[9383]434 polyline = qwtToPointsF( qwtInvalidRect,
[8127]435 xMap, yMap, series, from, to, QwtNoRoundF() );
436 }
437 }
438
439 return polyline;
440}
441
442/*!
443 \brief Translate a series of points into a QPolygon
444
445 When the WeedOutPoints flag is enabled consecutive points,
[9383]446 that are mapped to the same position will be one point.
[8127]447
448 \param xMap x map
449 \param yMap y map
450 \param series Series of points to be mapped
451 \param from Index of the first point to be painted
452 \param to Index of the last point to be painted
453
454 \return Translated polygon
455*/
456QPolygon QwtPointMapper::toPolygon(
457 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
458 const QwtSeriesData<QPointF> *series, int from, int to ) const
459{
460 QPolygon polyline;
461
462 if ( d_data->flags & WeedOutPoints )
463 {
[9383]464 polyline = qwtToPolylineFilteredI(
[8127]465 xMap, yMap, series, from, to );
466 }
467 else
468 {
[9383]469 polyline = qwtToPointsI(
[8127]470 qwtInvalidRect, xMap, yMap, series, from, to );
471 }
472
473 return polyline;
474}
475
476/*!
477 \brief Translate a series into a QPolygonF
478
479 - WeedOutPoints & RoundPoints & boundingRect().isValid()
[9383]480 All points that are mapped to the same position
[8127]481 will be one point. Points outside of the bounding
482 rectangle are ignored.
[9383]483
[8127]484 - WeedOutPoints & RoundPoints & !boundingRect().isValid()
[9383]485 All consecutive points that are mapped to the same position
[8127]486 will one point
487
[9383]488 - WeedOutPoints & !RoundPoints
489 All consecutive points that are mapped to the same position
[8127]490 will one point
491
492 - !WeedOutPoints & boundingRect().isValid()
493 Points outside of the bounding rectangle are ignored.
494
495 When RoundPoints is set all points are rounded to integers
496 but returned as PolygonF - what only makes sense
497 when the further processing of the values need a QPolygonF.
498
499 \param xMap x map
500 \param yMap y map
501 \param series Series of points to be mapped
502 \param from Index of the first point to be painted
503 \param to Index of the last point to be painted
504
505 \return Translated polygon
506*/
507QPolygonF QwtPointMapper::toPointsF(
508 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
509 const QwtSeriesData<QPointF> *series, int from, int to ) const
510{
511 QPolygonF points;
512
513 if ( d_data->flags & WeedOutPoints )
514 {
515 if ( d_data->flags & RoundPoints )
516 {
517 if ( d_data->boundingRect.isValid() )
[9383]518 {
[8127]519 points = qwtToPointsFilteredF( d_data->boundingRect,
520 xMap, yMap, series, from, to );
521 }
522 else
[9383]523 {
[8127]524 // without a bounding rectangle all we can
525 // do is to filter out duplicates of
526 // consecutive points
527
[9383]528 points = qwtToPolylineFilteredF(
[8127]529 xMap, yMap, series, from, to, QwtRoundF() );
530 }
531 }
532 else
533 {
534 // when rounding is not allowed we can't use
535 // qwtToPointsFilteredF
536
[9383]537 points = qwtToPolylineFilteredF(
[8127]538 xMap, yMap, series, from, to, QwtNoRoundF() );
539 }
540 }
541 else
542 {
543 if ( d_data->flags & RoundPoints )
544 {
545 points = qwtToPointsF( d_data->boundingRect,
546 xMap, yMap, series, from, to, QwtRoundF() );
547 }
548 else
549 {
550 points = qwtToPointsF( d_data->boundingRect,
551 xMap, yMap, series, from, to, QwtNoRoundF() );
552 }
553 }
554
555 return points;
556}
557
558/*!
559 \brief Translate a series of points into a QPolygon
560
561 - WeedOutPoints & boundingRect().isValid()
[9383]562 All points that are mapped to the same position
[8127]563 will be one point. Points outside of the bounding
564 rectangle are ignored.
[9383]565
[8127]566 - WeedOutPoints & !boundingRect().isValid()
[9383]567 All consecutive points that are mapped to the same position
[8127]568 will one point
569
570 - !WeedOutPoints & boundingRect().isValid()
571 Points outside of the bounding rectangle are ignored.
572
573 \param xMap x map
574 \param yMap y map
575 \param series Series of points to be mapped
576 \param from Index of the first point to be painted
577 \param to Index of the last point to be painted
578
579 \return Translated polygon
580*/
581QPolygon QwtPointMapper::toPoints(
582 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
583 const QwtSeriesData<QPointF> *series, int from, int to ) const
584{
585 QPolygon points;
586
587 if ( d_data->flags & WeedOutPoints )
588 {
589 if ( d_data->boundingRect.isValid() )
590 {
591 points = qwtToPointsFilteredI( d_data->boundingRect,
592 xMap, yMap, series, from, to );
593 }
594 else
595 {
596 // when we don't have the bounding rectangle all
597 // we can do is to filter out consecutive duplicates
598
[9383]599 points = qwtToPolylineFilteredI(
[8127]600 xMap, yMap, series, from, to );
601 }
602 }
603 else
604 {
[9383]605 points = qwtToPointsI(
[8127]606 d_data->boundingRect, xMap, yMap, series, from, to );
607 }
608
609 return points;
610}
611
612
613/*!
614 \brief Translate a series into a QImage
615
616 \param xMap x map
617 \param yMap y map
618 \param series Series of points to be mapped
619 \param from Index of the first point to be painted
620 \param to Index of the last point to be painted
621 \param pen Pen used for drawing a point
622 of the image, where a point is mapped to
623 \param antialiased True, when the dots should be displayed
624 antialiased
625 \param numThreads Number of threads to be used for rendering.
626 If numThreads is set to 0, the system specific
627 ideal thread count is used.
628
629 \return Image displaying the series
630*/
631QImage QwtPointMapper::toImage(
632 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
[9383]633 const QwtSeriesData<QPointF> *series, int from, int to,
[8127]634 const QPen &pen, bool antialiased, uint numThreads ) const
635{
636 Q_UNUSED( antialiased )
637
638#if QWT_USE_THREADS
639 if ( numThreads == 0 )
640 numThreads = QThread::idealThreadCount();
641
642 if ( numThreads <= 0 )
643 numThreads = 1;
644#else
645 Q_UNUSED( numThreads )
646#endif
647
648 // a very special optimization for scatter plots
649 // where every sample is mapped to one pixel only.
650
651 const QRect rect = d_data->boundingRect.toAlignedRect();
652
653 QImage image( rect.size(), QImage::Format_ARGB32 );
654 image.fill( Qt::transparent );
655
656 if ( pen.width() <= 1 && pen.color().alpha() == 255 )
657 {
658 QwtDotsCommand command;
659 command.series = series;
660 command.rgb = pen.color().rgba();
661
662#if QWT_USE_THREADS
663 const int numPoints = ( to - from + 1 ) / numThreads;
664
665 QList< QFuture<void> > futures;
666 for ( uint i = 0; i < numThreads; i++ )
667 {
668 const QPoint pos = rect.topLeft();
669
670 const int index0 = from + i * numPoints;
671 if ( i == numThreads - 1 )
672 {
673 command.from = index0;
674 command.to = to;
675
676 qwtRenderDots( xMap, yMap, command, pos, &image );
677 }
678 else
679 {
680 command.from = index0;
681 command.to = index0 + numPoints - 1;
682
[9383]683 futures += QtConcurrent::run( &qwtRenderDots,
[8127]684 xMap, yMap, command, pos, &image );
685 }
686 }
687 for ( int i = 0; i < futures.size(); i++ )
688 futures[i].waitForFinished();
689#else
690 command.from = from;
691 command.to = to;
692
693 qwtRenderDots( xMap, yMap, command, rect.topLeft(), &image );
694#endif
695 }
696 else
697 {
698 // fallback implementation: to be replaced later by
699 // setting the pixels of the image like above, TODO ...
700
701 QPainter painter( &image );
702 painter.setPen( pen );
703 painter.setRenderHint( QPainter::Antialiasing, antialiased );
704
705 const int chunkSize = 1000;
706 for ( int i = from; i <= to; i += chunkSize )
707 {
708 const int indexTo = qMin( i + chunkSize - 1, to );
709 const QPolygon points = toPoints(
710 xMap, yMap, series, i, indexTo );
711
712 painter.drawPoints( points );
713 }
714 }
715
716 return image;
717}
Note: See TracBrowser for help on using the repository browser.