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_color_map.h"
11 #include "qwt_math.h"
12 #include "qwt_interval.h"
13 #include <qnumeric.h>
14 
15 class QwtLinearColorMap::ColorStops
16 {
17 public:
ColorStops()18     ColorStops():
19         d_doAlpha( false )
20     {
21         d_stops.reserve( 256 );
22     }
23 
24     void insert( double pos, const QColor &color );
25     QRgb rgb( QwtLinearColorMap::Mode, double pos ) const;
26 
27     QVector<double> stops() const;
28 
29 private:
30 
31     class ColorStop
32     {
33     public:
ColorStop()34         ColorStop():
35             pos( 0.0 ),
36             rgb( 0 )
37         {
38         };
39 
ColorStop(double p,const QColor & c)40         ColorStop( double p, const QColor &c ):
41             pos( p ),
42             rgb( c.rgba() )
43         {
44             r = qRed( rgb );
45             g = qGreen( rgb );
46             b = qBlue( rgb );
47             a = qAlpha( rgb );
48 
49             /*
50                 when mapping a value to rgb we will have to calcualate:
51                    - const int v = int( ( s1.v0 + ratio * s1.vStep ) + 0.5 );
52 
53                 Thus adding 0.5 ( for rounding ) can be done in advance
54              */
55             r0 = r + 0.5;
56             g0 = g + 0.5;
57             b0 = b + 0.5;
58             a0 = a + 0.5;
59 
60             rStep = gStep = bStep = aStep = 0.0;
61             posStep = 0.0;
62         }
63 
updateSteps(const ColorStop & nextStop)64         void updateSteps( const ColorStop &nextStop )
65         {
66             rStep = nextStop.r - r;
67             gStep = nextStop.g - g;
68             bStep = nextStop.b - b;
69             aStep = nextStop.a - a;
70             posStep = nextStop.pos - pos;
71         }
72 
73         double pos;
74         QRgb rgb;
75         int r, g, b, a;
76 
77         // precalculated values
78         double rStep, gStep, bStep, aStep;
79         double r0, g0, b0, a0;
80         double posStep;
81     };
82 
83     inline int findUpper( double pos ) const;
84     QVector<ColorStop> d_stops;
85     bool d_doAlpha;
86 };
87 
insert(double pos,const QColor & color)88 void QwtLinearColorMap::ColorStops::insert( double pos, const QColor &color )
89 {
90     // Lookups need to be very fast, insertions are not so important.
91     // Anyway, a balanced tree is what we need here. TODO ...
92 
93     if ( pos < 0.0 || pos > 1.0 )
94         return;
95 
96     int index;
97     if ( d_stops.size() == 0 )
98     {
99         index = 0;
100         d_stops.resize( 1 );
101     }
102     else
103     {
104         index = findUpper( pos );
105         if ( index == d_stops.size() ||
106                 qAbs( d_stops[index].pos - pos ) >= 0.001 )
107         {
108             d_stops.resize( d_stops.size() + 1 );
109             for ( int i = d_stops.size() - 1; i > index; i-- )
110                 d_stops[i] = d_stops[i-1];
111         }
112     }
113 
114     d_stops[index] = ColorStop( pos, color );
115     if ( color.alpha() != 255 )
116         d_doAlpha = true;
117 
118     if ( index > 0 )
119         d_stops[index-1].updateSteps( d_stops[index] );
120 
121     if ( index < d_stops.size() - 1 )
122         d_stops[index].updateSteps( d_stops[index+1] );
123 }
124 
stops() const125 inline QVector<double> QwtLinearColorMap::ColorStops::stops() const
126 {
127     QVector<double> positions( d_stops.size() );
128     for ( int i = 0; i < d_stops.size(); i++ )
129         positions[i] = d_stops[i].pos;
130     return positions;
131 }
132 
findUpper(double pos) const133 inline int QwtLinearColorMap::ColorStops::findUpper( double pos ) const
134 {
135     int index = 0;
136     int n = d_stops.size();
137 
138     const ColorStop *stops = d_stops.data();
139 
140     while ( n > 0 )
141     {
142         const int half = n >> 1;
143         const int middle = index + half;
144 
145         if ( stops[middle].pos <= pos )
146         {
147             index = middle + 1;
148             n -= half + 1;
149         }
150         else
151             n = half;
152     }
153 
154     return index;
155 }
156 
rgb(QwtLinearColorMap::Mode mode,double pos) const157 inline QRgb QwtLinearColorMap::ColorStops::rgb(
158     QwtLinearColorMap::Mode mode, double pos ) const
159 {
160     if ( pos <= 0.0 )
161         return d_stops[0].rgb;
162     if ( pos >= 1.0 )
163         return d_stops[ d_stops.size() - 1 ].rgb;
164 
165     const int index = findUpper( pos );
166     if ( mode == FixedColors )
167     {
168         return d_stops[index-1].rgb;
169     }
170     else
171     {
172         const ColorStop &s1 = d_stops[index-1];
173 
174         const double ratio = ( pos - s1.pos ) / ( s1.posStep );
175 
176         const int r = int( s1.r0 + ratio * s1.rStep );
177         const int g = int( s1.g0 + ratio * s1.gStep );
178         const int b = int( s1.b0 + ratio * s1.bStep );
179 
180         if ( d_doAlpha )
181         {
182             if ( s1.aStep )
183             {
184                 const int a = int( s1.a0 + ratio * s1.aStep );
185                 return qRgba( r, g, b, a );
186             }
187             else
188             {
189                 return qRgba( r, g, b, s1.a );
190             }
191         }
192         else
193         {
194             return qRgb( r, g, b );
195         }
196     }
197 }
198 
199 //! Constructor
QwtColorMap(Format format)200 QwtColorMap::QwtColorMap( Format format ):
201     d_format( format )
202 {
203 }
204 
205 //! Destructor
~QwtColorMap()206 QwtColorMap::~QwtColorMap()
207 {
208 }
209 
210 /*!
211    Build and return a color map of 256 colors
212 
213    The color table is needed for rendering indexed images in combination
214    with using colorIndex().
215 
216    \param interval Range for the values
217    \return A color table, that can be used for a QImage
218 */
colorTable(const QwtInterval & interval) const219 QVector<QRgb> QwtColorMap::colorTable( const QwtInterval &interval ) const
220 {
221     QVector<QRgb> table( 256 );
222 
223     if ( interval.isValid() )
224     {
225         const double step = interval.width() / ( table.size() - 1 );
226         for ( int i = 0; i < table.size(); i++ )
227             table[i] = rgb( interval, interval.minValue() + step * i );
228     }
229 
230     return table;
231 }
232 
233 class QwtLinearColorMap::PrivateData
234 {
235 public:
236     ColorStops colorStops;
237     QwtLinearColorMap::Mode mode;
238 };
239 
240 /*!
241    Build a color map with two stops at 0.0 and 1.0. The color
242    at 0.0 is Qt::blue, at 1.0 it is Qt::yellow.
243 
244    \param format Preferred format of the color map
245 */
QwtLinearColorMap(QwtColorMap::Format format)246 QwtLinearColorMap::QwtLinearColorMap( QwtColorMap::Format format ):
247     QwtColorMap( format )
248 {
249     d_data = new PrivateData;
250     d_data->mode = ScaledColors;
251 
252     setColorInterval( Qt::blue, Qt::yellow );
253 }
254 
255 /*!
256    Build a color map with two stops at 0.0 and 1.0.
257 
258    \param color1 Color used for the minimum value of the value interval
259    \param color2 Color used for the maximum value of the value interval
260    \param format Preferred format for the color map
261 */
QwtLinearColorMap(const QColor & color1,const QColor & color2,QwtColorMap::Format format)262 QwtLinearColorMap::QwtLinearColorMap( const QColor &color1,
263         const QColor &color2, QwtColorMap::Format format ):
264     QwtColorMap( format )
265 {
266     d_data = new PrivateData;
267     d_data->mode = ScaledColors;
268     setColorInterval( color1, color2 );
269 }
270 
271 //! Destructor
~QwtLinearColorMap()272 QwtLinearColorMap::~QwtLinearColorMap()
273 {
274     delete d_data;
275 }
276 
277 /*!
278    \brief Set the mode of the color map
279 
280    FixedColors means the color is calculated from the next lower
281    color stop. ScaledColors means the color is calculated
282    by interpolating the colors of the adjacent stops.
283 
284    \sa mode()
285 */
setMode(Mode mode)286 void QwtLinearColorMap::setMode( Mode mode )
287 {
288     d_data->mode = mode;
289 }
290 
291 /*!
292    \return Mode of the color map
293    \sa setMode()
294 */
mode() const295 QwtLinearColorMap::Mode QwtLinearColorMap::mode() const
296 {
297     return d_data->mode;
298 }
299 
300 /*!
301    Set the color range
302 
303    Add stops at 0.0 and 1.0.
304 
305    \param color1 Color used for the minimum value of the value interval
306    \param color2 Color used for the maximum value of the value interval
307 
308    \sa color1(), color2()
309 */
setColorInterval(const QColor & color1,const QColor & color2)310 void QwtLinearColorMap::setColorInterval(
311     const QColor &color1, const QColor &color2 )
312 {
313     d_data->colorStops = ColorStops();
314     d_data->colorStops.insert( 0.0, color1 );
315     d_data->colorStops.insert( 1.0, color2 );
316 }
317 
318 /*!
319    Add a color stop
320 
321    The value has to be in the range [0.0, 1.0].
322    F.e. a stop at position 17.0 for a range [10.0,20.0] must be
323    passed as: (17.0 - 10.0) / (20.0 - 10.0)
324 
325    \param value Value between [0.0, 1.0]
326    \param color Color stop
327 */
addColorStop(double value,const QColor & color)328 void QwtLinearColorMap::addColorStop( double value, const QColor& color )
329 {
330     if ( value >= 0.0 && value <= 1.0 )
331         d_data->colorStops.insert( value, color );
332 }
333 
334 /*!
335    \return Positions of color stops in increasing order
336 */
colorStops() const337 QVector<double> QwtLinearColorMap::colorStops() const
338 {
339     return d_data->colorStops.stops();
340 }
341 
342 /*!
343   \return the first color of the color range
344   \sa setColorInterval()
345 */
color1() const346 QColor QwtLinearColorMap::color1() const
347 {
348     return QColor::fromRgba( d_data->colorStops.rgb( d_data->mode, 0.0 ) );
349 }
350 
351 /*!
352   \return the second color of the color range
353   \sa setColorInterval()
354 */
color2() const355 QColor QwtLinearColorMap::color2() const
356 {
357     return QColor::fromRgba( d_data->colorStops.rgb( d_data->mode, 1.0 ) );
358 }
359 
360 /*!
361   Map a value of a given interval into a RGB value
362 
363   \param interval Range for all values
364   \param value Value to map into a RGB value
365 
366   \return RGB value for value
367 */
rgb(const QwtInterval & interval,double value) const368 QRgb QwtLinearColorMap::rgb(
369     const QwtInterval &interval, double value ) const
370 {
371     if ( qIsNaN(value) )
372         return 0u;
373 
374     const double width = interval.width();
375     if ( width <= 0.0 )
376         return 0u;
377 
378     const double ratio = ( value - interval.minValue() ) / width;
379     return d_data->colorStops.rgb( d_data->mode, ratio );
380 }
381 
382 /*!
383   \brief Map a value of a given interval into a color index
384 
385   \param interval Range for all values
386   \param value Value to map into a color index
387 
388   \return Index, between 0 and 255
389 */
colorIndex(const QwtInterval & interval,double value) const390 unsigned char QwtLinearColorMap::colorIndex(
391     const QwtInterval &interval, double value ) const
392 {
393     const double width = interval.width();
394 
395     if ( qIsNaN(value) || width <= 0.0 || value <= interval.minValue() )
396         return 0;
397 
398     if ( value >= interval.maxValue() )
399         return 255;
400 
401     const double ratio = ( value - interval.minValue() ) / width;
402 
403     unsigned char index;
404     if ( d_data->mode == FixedColors )
405         index = static_cast<unsigned char>( ratio * 255 ); // always floor
406     else
407         index = static_cast<unsigned char>( ratio * 255 + 0.5 );
408 
409     return index;
410 }
411 
412 class QwtAlphaColorMap::PrivateData
413 {
414 public:
415     QColor color;
416     QRgb rgb;
417     QRgb rgbMax;
418 };
419 
420 
421 /*!
422    Constructor
423    \param color Color of the map
424 */
QwtAlphaColorMap(const QColor & color)425 QwtAlphaColorMap::QwtAlphaColorMap( const QColor &color ):
426     QwtColorMap( QwtColorMap::RGB )
427 {
428     d_data = new PrivateData;
429     setColor( color );
430 }
431 
432 //! Destructor
~QwtAlphaColorMap()433 QwtAlphaColorMap::~QwtAlphaColorMap()
434 {
435     delete d_data;
436 }
437 
438 /*!
439    Set the color
440 
441    \param color Color
442    \sa color()
443 */
setColor(const QColor & color)444 void QwtAlphaColorMap::setColor( const QColor &color )
445 {
446     d_data->color = color;
447     d_data->rgb = color.rgb() & qRgba( 255, 255, 255, 0 );
448     d_data->rgbMax = d_data->rgb | ( 255 << 24 );
449 }
450 
451 /*!
452   \return the color
453   \sa setColor()
454 */
color() const455 QColor QwtAlphaColorMap::color() const
456 {
457     return d_data->color;
458 }
459 
460 /*!
461   \brief Map a value of a given interval into a alpha value
462 
463   alpha := (value - interval.minValue()) / interval.width();
464 
465   \param interval Range for all values
466   \param value Value to map into a RGB value
467   \return RGB value, with an alpha value
468 */
rgb(const QwtInterval & interval,double value) const469 QRgb QwtAlphaColorMap::rgb( const QwtInterval &interval, double value ) const
470 {
471     if ( qIsNaN(value) )
472         return 0u;
473 
474     const double width = interval.width();
475     if ( width <= 0.0 )
476         return 0u;
477 
478     if ( value <= interval.minValue() )
479         return d_data->rgb;
480 
481     if ( value >= interval.maxValue() )
482         return d_data->rgbMax;
483 
484     const double ratio = ( value - interval.minValue() ) / width;
485     return d_data->rgb | ( qRound( 255 * ratio ) << 24 );
486 }
487 
488 /*!
489   Dummy function, needed to be implemented as it is pure virtual
490   in QwtColorMap. Color indices make no sense in combination with
491   an alpha channel.
492 
493   \return Always 0
494 */
colorIndex(const QwtInterval &,double) const495 unsigned char QwtAlphaColorMap::colorIndex(
496     const QwtInterval &, double ) const
497 {
498     return 0;
499 }
500