00001
00002
00003
00004
00005
00006
00007
00008
00009
00010 #include "qwt_point_mapper.h"
00011 #include "qwt_scale_map.h"
00012 #include "qwt_pixel_matrix.h"
00013 #include <qpolygon.h>
00014 #include <qimage.h>
00015 #include <qpen.h>
00016 #include <qpainter.h>
00017
00018 #include <qthread.h>
00019 #include <qfuture.h>
00020 #include <qtconcurrentrun.h>
00021
00022 #if !defined(QT_NO_QFUTURE)
00023 #define QWT_USE_THREADS 0
00024 #endif
00025
00026 static QRectF qwtInvalidRect( 0.0, 0.0, -1.0, -1.0 );
00027
00028 static inline int qwtRoundValue( double value )
00029 {
00030 return qRound( value );
00031 }
00032
00033 static inline double qwtRoundValueF( double value )
00034 {
00035 #if 1
00036
00037 return ( value >= 0.0 ) ? ::floor( value + 0.5 ) : ::ceil( value - 0.5 );
00038 #else
00039 return nearbyint( value );
00040 #endif
00041 }
00042
00043 static Qt::Orientation qwtProbeOrientation(
00044 const QwtSeriesData<QPointF> *series, int from, int to )
00045 {
00046 if ( to - from < 20 )
00047 {
00048
00049 return Qt::Horizontal;
00050 }
00051
00052 const double x0 = series->sample( from ).x();
00053 const double xn = series->sample( to ).x();
00054
00055 if ( x0 == xn )
00056 return Qt::Vertical;
00057
00058 const int step = ( to - from ) / 10;
00059 const bool isIncreasing = xn > x0;
00060
00061 double x1 = x0;
00062 for ( int i = from + step; i < to; i += step )
00063 {
00064 const double x2 = series->sample( i ).x();
00065 if ( x2 != x1 )
00066 {
00067 if ( ( x2 > x1 ) != isIncreasing )
00068 return Qt::Vertical;
00069 }
00070
00071 x1 = x2;
00072 }
00073
00074 return Qt::Horizontal;
00075 }
00076
00077 template <class Polygon, class Point>
00078 class QwtPolygonQuadrupelX
00079 {
00080 public:
00081 inline void start( int x, int y )
00082 {
00083 x0 = x;
00084 y1 = yMin = yMax = y2 = y;
00085 }
00086
00087 inline bool append( int x, int y )
00088 {
00089 if ( x0 != x )
00090 return false;
00091
00092 if ( y < yMin )
00093 yMin = y;
00094 else if ( y > yMax )
00095 yMax = y;
00096
00097 y2 = y;
00098
00099 return true;
00100 }
00101
00102 inline void flush( Polygon &polyline )
00103 {
00104 appendTo( y1, polyline );
00105
00106 if ( y2 > y1 )
00107 qSwap( yMin, yMax );
00108
00109 if ( yMax != y1 )
00110 appendTo( yMax, polyline );
00111
00112 if ( yMin != yMax )
00113 appendTo( yMin, polyline );
00114
00115 if ( y2 != yMin )
00116 appendTo( y2, polyline );
00117 }
00118
00119 private:
00120 inline void appendTo( int y, Polygon &polyline )
00121 {
00122 polyline += Point( x0, y );
00123 }
00124
00125 private:
00126 int x0, y1, yMin, yMax, y2;
00127 };
00128
00129 template <class Polygon, class Point>
00130 class QwtPolygonQuadrupelY
00131 {
00132 public:
00133 inline void start( int x, int y )
00134 {
00135 y0 = y;
00136 x1 = xMin = xMax = x2 = x;
00137 }
00138
00139 inline bool append( int x, int y )
00140 {
00141 if ( y0 != y )
00142 return false;
00143
00144 if ( x < xMin )
00145 xMin = x;
00146 else if ( x > xMax )
00147 xMax = x;
00148
00149 x2 = x;
00150
00151 return true;
00152 }
00153
00154 inline void flush( Polygon &polyline )
00155 {
00156 appendTo( x1, polyline );
00157
00158 if ( x2 > x1 )
00159 qSwap( xMin, xMax );
00160
00161 if ( xMax != x1 )
00162 appendTo( xMax, polyline );
00163
00164 if ( xMin != xMax )
00165 appendTo( xMin, polyline );
00166
00167 if ( x2 != xMin )
00168 appendTo( x2, polyline );
00169 }
00170
00171 private:
00172 inline void appendTo( int x, Polygon &polyline )
00173 {
00174 polyline += Point( x, y0 );
00175 }
00176
00177 int y0, x1, xMin, xMax, x2;
00178 };
00179
00180 template <class Polygon, class Point, class PolygonQuadrupel>
00181 static Polygon qwtMapPointsQuad( const QwtScaleMap &xMap, const QwtScaleMap &yMap,
00182 const QwtSeriesData<QPointF> *series, int from, int to )
00183 {
00184 const QPointF sample0 = series->sample( from );
00185
00186 PolygonQuadrupel q;
00187 q.start( qwtRoundValue( xMap.transform( sample0.x() ) ),
00188 qwtRoundValue( yMap.transform( sample0.y() ) ) );
00189
00190 Polygon polyline;
00191 for ( int i = from; i <= to; i++ )
00192 {
00193 const QPointF sample = series->sample( i );
00194
00195 const int x = qwtRoundValue( xMap.transform( sample.x() ) );
00196 const int y = qwtRoundValue( yMap.transform( sample.y() ) );
00197
00198 if ( !q.append( x, y ) )
00199 {
00200 q.flush( polyline );
00201 q.start( x, y );
00202 }
00203 }
00204 q.flush( polyline );
00205
00206 return polyline;
00207 }
00208
00209 template <class Polygon, class Point, class PolygonQuadrupel>
00210 static Polygon qwtMapPointsQuad( const Polygon &polyline )
00211 {
00212 const int numPoints = polyline.size();
00213
00214 if ( numPoints < 3 )
00215 return polyline;
00216
00217 const Point *points = polyline.constData();
00218
00219 Polygon polylineXY;
00220
00221 PolygonQuadrupel q;
00222 q.start( points[0].x(), points[0].y() );
00223
00224 for ( int i = 0; i < numPoints; i++ )
00225 {
00226 const int x = points[i].x();
00227 const int y = points[i].y();
00228
00229 if ( !q.append( x, y ) )
00230 {
00231 q.flush( polylineXY );
00232 q.start( x, y );
00233 }
00234 }
00235 q.flush( polylineXY );
00236
00237 return polylineXY;
00238 }
00239
00240
00241 template <class Polygon, class Point>
00242 static Polygon qwtMapPointsQuad( const QwtScaleMap &xMap, const QwtScaleMap &yMap,
00243 const QwtSeriesData<QPointF> *series, int from, int to )
00244 {
00245 Polygon polyline;
00246 if ( from > to )
00247 return polyline;
00248
00249
00250
00251
00252
00253 const Qt::Orientation orientation = qwtProbeOrientation( series, from, to );
00254
00255 if ( orientation == Qt::Horizontal )
00256 {
00257 polyline = qwtMapPointsQuad< Polygon, Point,
00258 QwtPolygonQuadrupelY<Polygon, Point> >( xMap, yMap, series, from, to );
00259
00260 polyline = qwtMapPointsQuad< Polygon, Point,
00261 QwtPolygonQuadrupelX<Polygon, Point> >( polyline );
00262 }
00263 else
00264 {
00265 polyline = qwtMapPointsQuad< Polygon, Point,
00266 QwtPolygonQuadrupelX<Polygon, Point> >( xMap, yMap, series, from, to );
00267
00268 polyline = qwtMapPointsQuad< Polygon, Point,
00269 QwtPolygonQuadrupelY<Polygon, Point> >( polyline );
00270 }
00271
00272 return polyline;
00273 }
00274
00275
00276
00277 class QwtDotsCommand
00278 {
00279 public:
00280 const QwtSeriesData<QPointF> *series;
00281 int from;
00282 int to;
00283 QRgb rgb;
00284 };
00285
00286 static void qwtRenderDots(
00287 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
00288 const QwtDotsCommand command, const QPoint &pos, QImage *image )
00289 {
00290 const QRgb rgb = command.rgb;
00291 QRgb *bits = reinterpret_cast<QRgb *>( image->bits() );
00292
00293 const int w = image->width();
00294 const int h = image->height();
00295
00296 const int x0 = pos.x();
00297 const int y0 = pos.y();
00298
00299 for ( int i = command.from; i <= command.to; i++ )
00300 {
00301 const QPointF sample = command.series->sample( i );
00302
00303 const int x = static_cast<int>( xMap.transform( sample.x() ) + 0.5 ) - x0;
00304 const int y = static_cast<int>( yMap.transform( sample.y() ) + 0.5 ) - y0;
00305
00306 if ( x >= 0 && x < w && y >= 0 && y < h )
00307 bits[ y * w + x ] = rgb;
00308 }
00309 }
00310
00311
00312 struct QwtRoundI
00313 {
00314 inline int operator()( double value ) const
00315 {
00316 return qwtRoundValue( value );
00317 }
00318 };
00319
00320 struct QwtRoundF
00321 {
00322 inline double operator()( double value ) const
00323 {
00324 return qwtRoundValueF( value );
00325 }
00326 };
00327
00328 struct QwtNoRoundF
00329 {
00330 inline double operator()( double value ) const
00331 {
00332 return value;
00333 }
00334 };
00335
00336
00337
00338
00339 template<class Polygon, class Point, class Round>
00340 static inline Polygon qwtToPoints(
00341 const QRectF &boundingRect,
00342 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
00343 const QwtSeriesData<QPointF> *series,
00344 int from, int to, Round round )
00345 {
00346 Polygon polyline( to - from + 1 );
00347 Point *points = polyline.data();
00348
00349 int numPoints = 0;
00350
00351 if ( boundingRect.isValid() )
00352 {
00353
00354
00355
00356
00357 for ( int i = from; i <= to; i++ )
00358 {
00359 const QPointF sample = series->sample( i );
00360
00361 const double x = xMap.transform( sample.x() );
00362 const double y = yMap.transform( sample.y() );
00363
00364 if ( boundingRect.contains( x, y ) )
00365 {
00366 points[ numPoints ].rx() = round( x );
00367 points[ numPoints ].ry() = round( y );
00368
00369 numPoints++;
00370 }
00371 }
00372
00373 polyline.resize( numPoints );
00374 }
00375 else
00376 {
00377
00378
00379
00380 for ( int i = from; i <= to; i++ )
00381 {
00382 const QPointF sample = series->sample( i );
00383
00384 const double x = xMap.transform( sample.x() );
00385 const double y = yMap.transform( sample.y() );
00386
00387 points[ numPoints ].rx() = round( x );
00388 points[ numPoints ].ry() = round( y );
00389
00390 numPoints++;
00391 }
00392 }
00393
00394 return polyline;
00395 }
00396
00397 static inline QPolygon qwtToPointsI(
00398 const QRectF &boundingRect,
00399 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
00400 const QwtSeriesData<QPointF> *series,
00401 int from, int to )
00402 {
00403 return qwtToPoints<QPolygon, QPoint>(
00404 boundingRect, xMap, yMap, series, from, to, QwtRoundI() );
00405 }
00406
00407 template<class Round>
00408 static inline QPolygonF qwtToPointsF(
00409 const QRectF &boundingRect,
00410 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
00411 const QwtSeriesData<QPointF> *series,
00412 int from, int to, Round round )
00413 {
00414 return qwtToPoints<QPolygonF, QPointF>(
00415 boundingRect, xMap, yMap, series, from, to, round );
00416 }
00417
00418
00419
00420
00421 template<class Polygon, class Point, class Round>
00422 static inline Polygon qwtToPolylineFiltered(
00423 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
00424 const QwtSeriesData<QPointF> *series,
00425 int from, int to, Round round )
00426 {
00427
00428
00429
00430
00431
00432 Polygon polyline( to - from + 1 );
00433 Point *points = polyline.data();
00434
00435 const QPointF sample0 = series->sample( from );
00436
00437 points[0].rx() = round( xMap.transform( sample0.x() ) );
00438 points[0].ry() = round( yMap.transform( sample0.y() ) );
00439
00440 int pos = 0;
00441 for ( int i = from + 1; i <= to; i++ )
00442 {
00443 const QPointF sample = series->sample( i );
00444
00445 const Point p( round( xMap.transform( sample.x() ) ),
00446 round( yMap.transform( sample.y() ) ) );
00447
00448 if ( points[pos] != p )
00449 points[++pos] = p;
00450 }
00451
00452 polyline.resize( pos + 1 );
00453 return polyline;
00454 }
00455
00456 static inline QPolygon qwtToPolylineFilteredI(
00457 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
00458 const QwtSeriesData<QPointF> *series,
00459 int from, int to )
00460 {
00461 return qwtToPolylineFiltered<QPolygon, QPoint>(
00462 xMap, yMap, series, from, to, QwtRoundI() );
00463 }
00464
00465 template<class Round>
00466 static inline QPolygonF qwtToPolylineFilteredF(
00467 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
00468 const QwtSeriesData<QPointF> *series,
00469 int from, int to, Round round )
00470 {
00471 return qwtToPolylineFiltered<QPolygonF, QPointF>(
00472 xMap, yMap, series, from, to, round );
00473 }
00474
00475 template<class Polygon, class Point>
00476 static inline Polygon qwtToPointsFiltered(
00477 const QRectF &boundingRect,
00478 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
00479 const QwtSeriesData<QPointF> *series, int from, int to )
00480 {
00481
00482
00483
00484 Polygon polygon( to - from + 1 );
00485 Point *points = polygon.data();
00486
00487 QwtPixelMatrix pixelMatrix( boundingRect.toAlignedRect() );
00488
00489 int numPoints = 0;
00490 for ( int i = from; i <= to; i++ )
00491 {
00492 const QPointF sample = series->sample( i );
00493
00494 const int x = qwtRoundValue( xMap.transform( sample.x() ) );
00495 const int y = qwtRoundValue( yMap.transform( sample.y() ) );
00496
00497 if ( pixelMatrix.testAndSetPixel( x, y, true ) == false )
00498 {
00499 points[ numPoints ].rx() = x;
00500 points[ numPoints ].ry() = y;
00501
00502 numPoints++;
00503 }
00504 }
00505
00506 polygon.resize( numPoints );
00507 return polygon;
00508 }
00509
00510 static inline QPolygon qwtToPointsFilteredI(
00511 const QRectF &boundingRect,
00512 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
00513 const QwtSeriesData<QPointF> *series, int from, int to )
00514 {
00515 return qwtToPointsFiltered<QPolygon, QPoint>(
00516 boundingRect, xMap, yMap, series, from, to );
00517 }
00518
00519 static inline QPolygonF qwtToPointsFilteredF(
00520 const QRectF &boundingRect,
00521 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
00522 const QwtSeriesData<QPointF> *series, int from, int to )
00523 {
00524 return qwtToPointsFiltered<QPolygonF, QPointF>(
00525 boundingRect, xMap, yMap, series, from, to );
00526 }
00527
00528 class QwtPointMapper::PrivateData
00529 {
00530 public:
00531 PrivateData():
00532 boundingRect( qwtInvalidRect )
00533 {
00534 }
00535
00536 QRectF boundingRect;
00537 QwtPointMapper::TransformationFlags flags;
00538 };
00539
00541 QwtPointMapper::QwtPointMapper()
00542 {
00543 d_data = new PrivateData();
00544 }
00545
00547 QwtPointMapper::~QwtPointMapper()
00548 {
00549 delete d_data;
00550 }
00551
00558 void QwtPointMapper::setFlags( TransformationFlags flags )
00559 {
00560 d_data->flags = flags;
00561 }
00562
00567 QwtPointMapper::TransformationFlags QwtPointMapper::flags() const
00568 {
00569 return d_data->flags;
00570 }
00571
00580 void QwtPointMapper::setFlag( TransformationFlag flag, bool on )
00581 {
00582 if ( on )
00583 d_data->flags |= flag;
00584 else
00585 d_data->flags &= ~flag;
00586 }
00587
00593 bool QwtPointMapper::testFlag( TransformationFlag flag ) const
00594 {
00595 return d_data->flags & flag;
00596 }
00597
00606 void QwtPointMapper::setBoundingRect( const QRectF &rect )
00607 {
00608 d_data->boundingRect = rect;
00609 }
00610
00615 QRectF QwtPointMapper::boundingRect() const
00616 {
00617 return d_data->boundingRect;
00618 }
00619
00641 QPolygonF QwtPointMapper::toPolygonF(
00642 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
00643 const QwtSeriesData<QPointF> *series, int from, int to ) const
00644 {
00645 QPolygonF polyline;
00646
00647 if ( d_data->flags & RoundPoints )
00648 {
00649 if ( d_data->flags & WeedOutIntermediatePoints )
00650 {
00651 polyline = qwtMapPointsQuad<QPolygonF, QPointF>(
00652 xMap, yMap, series, from, to );
00653 }
00654 else if ( d_data->flags & WeedOutPoints )
00655 {
00656 polyline = qwtToPolylineFilteredF(
00657 xMap, yMap, series, from, to, QwtRoundF() );
00658 }
00659 else
00660 {
00661 polyline = qwtToPointsF( qwtInvalidRect,
00662 xMap, yMap, series, from, to, QwtRoundF() );
00663 }
00664 }
00665 else
00666 {
00667 if ( d_data->flags & WeedOutPoints )
00668 {
00669 polyline = qwtToPolylineFilteredF(
00670 xMap, yMap, series, from, to, QwtNoRoundF() );
00671 }
00672 else
00673 {
00674 polyline = qwtToPointsF( qwtInvalidRect,
00675 xMap, yMap, series, from, to, QwtNoRoundF() );
00676 }
00677 }
00678
00679 return polyline;
00680 }
00681
00696 QPolygon QwtPointMapper::toPolygon(
00697 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
00698 const QwtSeriesData<QPointF> *series, int from, int to ) const
00699 {
00700 QPolygon polyline;
00701
00702 if ( d_data->flags & WeedOutIntermediatePoints )
00703 {
00704
00705 polyline = qwtMapPointsQuad<QPolygon, QPoint>(
00706 xMap, yMap, series, from, to );
00707 }
00708 else if ( d_data->flags & WeedOutPoints )
00709 {
00710 polyline = qwtToPolylineFilteredI(
00711 xMap, yMap, series, from, to );
00712 }
00713 else
00714 {
00715 polyline = qwtToPointsI(
00716 qwtInvalidRect, xMap, yMap, series, from, to );
00717 }
00718
00719 return polyline;
00720 }
00721
00753 QPolygonF QwtPointMapper::toPointsF(
00754 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
00755 const QwtSeriesData<QPointF> *series, int from, int to ) const
00756 {
00757 QPolygonF points;
00758
00759 if ( d_data->flags & WeedOutPoints )
00760 {
00761 if ( d_data->flags & RoundPoints )
00762 {
00763 if ( d_data->boundingRect.isValid() )
00764 {
00765 points = qwtToPointsFilteredF( d_data->boundingRect,
00766 xMap, yMap, series, from, to );
00767 }
00768 else
00769 {
00770
00771
00772
00773
00774 points = qwtToPolylineFilteredF(
00775 xMap, yMap, series, from, to, QwtRoundF() );
00776 }
00777 }
00778 else
00779 {
00780
00781
00782
00783 points = qwtToPolylineFilteredF(
00784 xMap, yMap, series, from, to, QwtNoRoundF() );
00785 }
00786 }
00787 else
00788 {
00789 if ( d_data->flags & RoundPoints )
00790 {
00791 points = qwtToPointsF( d_data->boundingRect,
00792 xMap, yMap, series, from, to, QwtRoundF() );
00793 }
00794 else
00795 {
00796 points = qwtToPointsF( d_data->boundingRect,
00797 xMap, yMap, series, from, to, QwtNoRoundF() );
00798 }
00799 }
00800
00801 return points;
00802 }
00803
00827 QPolygon QwtPointMapper::toPoints(
00828 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
00829 const QwtSeriesData<QPointF> *series, int from, int to ) const
00830 {
00831 QPolygon points;
00832
00833 if ( d_data->flags & WeedOutPoints )
00834 {
00835 if ( d_data->boundingRect.isValid() )
00836 {
00837 points = qwtToPointsFilteredI( d_data->boundingRect,
00838 xMap, yMap, series, from, to );
00839 }
00840 else
00841 {
00842
00843
00844
00845 points = qwtToPolylineFilteredI(
00846 xMap, yMap, series, from, to );
00847 }
00848 }
00849 else
00850 {
00851 points = qwtToPointsI(
00852 d_data->boundingRect, xMap, yMap, series, from, to );
00853 }
00854
00855 return points;
00856 }
00857
00858
00877 QImage QwtPointMapper::toImage(
00878 const QwtScaleMap &xMap, const QwtScaleMap &yMap,
00879 const QwtSeriesData<QPointF> *series, int from, int to,
00880 const QPen &pen, bool antialiased, uint numThreads ) const
00881 {
00882 Q_UNUSED( antialiased )
00883
00884 #if QWT_USE_THREADS
00885 if ( numThreads == 0 )
00886 numThreads = QThread::idealThreadCount();
00887
00888 if ( numThreads <= 0 )
00889 numThreads = 1;
00890 #else
00891 Q_UNUSED( numThreads )
00892 #endif
00893
00894
00895
00896
00897 const QRect rect = d_data->boundingRect.toAlignedRect();
00898
00899 QImage image( rect.size(), QImage::Format_ARGB32 );
00900 image.fill( Qt::transparent );
00901
00902 if ( pen.width() <= 1 && pen.color().alpha() == 255 )
00903 {
00904 QwtDotsCommand command;
00905 command.series = series;
00906 command.rgb = pen.color().rgba();
00907
00908 #if QWT_USE_THREADS
00909 const int numPoints = ( to - from + 1 ) / numThreads;
00910
00911 QList< QFuture<void> > futures;
00912 for ( uint i = 0; i < numThreads; i++ )
00913 {
00914 const QPoint pos = rect.topLeft();
00915
00916 const int index0 = from + i * numPoints;
00917 if ( i == numThreads - 1 )
00918 {
00919 command.from = index0;
00920 command.to = to;
00921
00922 qwtRenderDots( xMap, yMap, command, pos, &image );
00923 }
00924 else
00925 {
00926 command.from = index0;
00927 command.to = index0 + numPoints - 1;
00928
00929 futures += QtConcurrent::run( &qwtRenderDots,
00930 xMap, yMap, command, pos, &image );
00931 }
00932 }
00933 for ( int i = 0; i < futures.size(); i++ )
00934 futures[i].waitForFinished();
00935 #else
00936 command.from = from;
00937 command.to = to;
00938
00939 qwtRenderDots( xMap, yMap, command, rect.topLeft(), &image );
00940 #endif
00941 }
00942 else
00943 {
00944
00945
00946
00947 QPainter painter( &image );
00948 painter.setPen( pen );
00949 painter.setRenderHint( QPainter::Antialiasing, antialiased );
00950
00951 const int chunkSize = 1000;
00952 for ( int i = from; i <= to; i += chunkSize )
00953 {
00954 const int indexTo = qMin( i + chunkSize - 1, to );
00955 const QPolygon points = toPoints(
00956 xMap, yMap, series, i, indexTo );
00957
00958 painter.drawPoints( points );
00959 }
00960 }
00961
00962 return image;
00963 }