File: color_map.py

package info (click to toggle)
python-qwt 0.12.7-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,376 kB
  • sloc: python: 11,953; makefile: 19; sh: 10
file content (386 lines) | stat: -rw-r--r-- 11,812 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
# -*- coding: utf-8 -*-
#
# Licensed under the terms of the Qwt License
# Copyright (c) 2002 Uwe Rathmann, for the original C++ code
# Copyright (c) 2015 Pierre Raybaut, for the Python translation/optimization
# (see LICENSE file for more details)

"""
Color maps
----------

QwtColorMap
~~~~~~~~~~~

.. autoclass:: QwtColorMap
   :members:

QwtLinearColorMap
~~~~~~~~~~~~~~~~~

.. autoclass:: QwtLinearColorMap
   :members:

QwtAlphaColorMap
~~~~~~~~~~~~~~~~

.. autoclass:: QwtAlphaColorMap
   :members:
"""

from qtpy.QtCore import QObject, Qt, qIsNaN
from qtpy.QtGui import QColor, qAlpha, qBlue, qGreen, qRed, qRgb, qRgba


class ColorStop(object):
    def __init__(self, pos=0.0, color=None):
        self.pos = pos
        if color is None:
            self.rgb = 0
        else:
            self.rgb = color.rgba()
        self.r = qRed(self.rgb)
        self.g = qGreen(self.rgb)
        self.b = qBlue(self.rgb)
        self.a = qAlpha(self.rgb)

        #  when mapping a value to rgb we will have to calcualate:
        #     - const int v = int( ( s1.v0 + ratio * s1.vStep ) + 0.5 );
        #  Thus adding 0.5 ( for rounding ) can be done in advance
        self.r0 = self.r + 0.5
        self.g0 = self.g + 0.5
        self.b0 = self.b + 0.5
        self.a0 = self.a + 0.5

        self.rStep = self.gStep = self.bStep = self.aStep = 0.0
        self.posStep = 0.0

    def updateSteps(self, nextStop):
        self.rStep = nextStop.r - self.r
        self.gStep = nextStop.g - self.g
        self.bStep = nextStop.b - self.b
        self.aStep = nextStop.a - self.a
        self.posStep = nextStop.pos - self.pos


class ColorStops(object):
    def __init__(self):
        self.__doAlpha = False
        self.__stops = []

    def insert(self, pos, color):
        if pos < 0.0 or pos > 1.0:
            return
        if len(self.__stops) == 0:
            index = 0
            self.__stops = [None]
        else:
            index = self.findUpper(pos)
            if (
                index == len(self.__stops)
                or abs(self.__stops[index].pos - pos) >= 0.001
            ):
                self.__stops.append(None)
                for i in range(len(self.__stops) - 1, index, -1):
                    self.__stops[i] = self.__stops[i - 1]
        self.__stops[index] = ColorStop(pos, color)
        self.__doAlpha = color.alpha() != 255
        if index > 0:
            self.__stops[index - 1].updateSteps(self.__stops[index])
        if index < len(self.__stops) - 1:
            self.__stops[index].updateSteps(self.__stops[index + 1])

    def stops(self):
        return self.__stops

    def findUpper(self, pos):
        index = 0
        n = len(self.__stops)

        while n > 0:
            half = n >> 1
            middle = index + half
            if self.__stops[middle].pos <= pos:
                index = middle + 1
                n -= half + 1
            else:
                n = half
        return index

    def rgb(self, mode, pos):
        if pos <= 0.0:
            return self.__stops[0].rgb
        if pos >= 1.0:
            return self.__stops[-1].rgb

        index = self.findUpper(pos)
        if mode == QwtLinearColorMap.FixedColors:
            return self.__stops[index - 1].rgb
        else:
            s1 = self.__stops[index - 1]
            ratio = (pos - s1.pos) / s1.posStep
            r = int(s1.r0 + ratio * s1.rStep)
            g = int(s1.g0 + ratio * s1.gStep)
            b = int(s1.b0 + ratio * s1.bStep)
            if self.__doAlpha:
                if s1.aStep:
                    a = int(s1.a0 + ratio * s1.aStep)
                    return qRgba(r, g, b, a)
                else:
                    return qRgba(r, g, b, s1.a)
            else:
                return qRgb(r, g, b)


class QwtColorMap(object):
    """
    QwtColorMap is used to map values into colors.

    For displaying 3D data on a 2D plane the 3rd dimension is often
    displayed using colors, like f.e in a spectrogram.

    Each color map is optimized to return colors for only one of the
    following image formats:

        * `QImage.Format_Indexed8`
        * `QImage.Format_ARGB32`

    .. py:class:: QwtColorMap(format_)

        :param int format_: Preferred format of the color map (:py:data:`QwtColorMap.RGB` or :py:data:`QwtColorMap.Indexed`)

    .. seealso ::

        :py:data:`qwt.QwtScaleWidget`
    """

    # enum Format
    RGB, Indexed = list(range(2))

    def __init__(self, format_=None):
        if format_ is None:
            format_ = self.RGB
        self.__format = format_

    def color(self, interval, value):
        """
        Map a value into a color

        :param qwt.interval.QwtInterval interval: valid interval for value
        :param float value: value
        :return: the color corresponding to value

        .. warning ::

            This method is slow for Indexed color maps. If it is necessary to
            map many values, its better to get the color table once and find
            the color using `colorIndex()`.
        """
        if self.__format == self.RGB:
            return QColor.fromRgba(self.rgb(interval, value))
        else:
            index = self.colorIndex(interval, value)
            return self.colorTable(interval)[index]

    def format(self):
        return self.__format

    def colorTable(self, interval):
        """
        Build and return a color map of 256 colors

        :param qwt.interval.QwtInterval interval: range for the values
        :return: a color table, that can be used for a `QImage`

        The color table is needed for rendering indexed images in combination
        with using `colorIndex()`.
        """
        table = [0] * 256
        if interval.isValid():
            step = interval.width() / (len(table) - 1)
            for i in range(len(table)):
                table[i] = self.rgb(interval, interval.minValue() + step * i)
        return table

    def rgb(self, interval, value):
        # To be reimplemented
        return QColor().rgb()

    def colorIndex(self, interval, value):
        # To be reimplemented
        return 0


class QwtLinearColorMap_PrivateData(QObject):
    def __init__(self):
        QObject.__init__(self)

        self.colorStops = ColorStops()
        self.mode = None


class QwtLinearColorMap(QwtColorMap):
    """
    Build a linear color map with two stops.

    .. py:class:: QwtLinearColorMap(format_)

        Build a color map with two stops at 0.0 and 1.0.
        The color at 0.0 is `Qt.blue`, at 1.0 it is `Qt.yellow`.

        :param int format_: Preferred format of the color map (:py:data:`QwtColorMap.RGB` or :py:data:`QwtColorMap.Indexed`)

    .. py:class:: QwtLinearColorMap(color1, color2, [format_=QwtColorMap.RGB]):
        :noindex:

        Build a color map with two stops at 0.0 and 1.0.

        :param QColor color1: color at 0.
        :param QColor color2: color at 1.
        :param int format_: Preferred format of the color map (:py:data:`QwtColorMap.RGB` or :py:data:`QwtColorMap.Indexed`)
    """

    # enum Mode
    FixedColors, ScaledColors = list(range(2))

    def __init__(self, *args):
        color1, color2 = QColor(Qt.blue), QColor(Qt.yellow)
        format_ = QwtColorMap.RGB
        if len(args) == 1:
            (format_,) = args
        elif len(args) == 2:
            color1, color2 = args
        elif len(args) == 3:
            color1, color2, format_ = args
        elif len(args) != 0:
            raise TypeError(
                "%s() takes 0, 1, 2 or 3 argument(s) (%s given)"
                % (self.__class__.__name__, len(args))
            )
        super(QwtLinearColorMap, self).__init__(format_)
        self.__data = QwtLinearColorMap_PrivateData()
        self.__data.mode = self.ScaledColors
        self.setColorInterval(color1, color2)

    def setMode(self, mode):
        """
        Set the mode of the color map

        :param int mode: :py:data:`QwtLinearColorMap.FixedColors` or :py:data:`QwtLinearColorMap.ScaledColors`

        `FixedColors` means the color is calculated from the next lower color
        stop. `ScaledColors` means the color is calculated by interpolating
        the colors of the adjacent stops.
        """
        self.__data.mode = mode

    def mode(self):
        """
        :return: the mode of the color map

        .. seealso ::

            :py:meth:`QwtLinearColorMap.setMode`
        """
        return self.__data.mode

    def setColorInterval(self, color1, color2):
        self.__data.colorStops = ColorStops()
        self.__data.colorStops.insert(0.0, QColor(color1))
        self.__data.colorStops.insert(1.0, QColor(color2))

    def addColorStop(self, value, color):
        if value >= 0.0 and value <= 1.0:
            self.__data.colorStops.insert(value, QColor(color))

    def colorStops(self):
        return self.__data.colorStops.stops()

    def color1(self):
        return QColor(self.__data.colorStops.rgb(self.__data.mode, 0.0))

    def color2(self):
        return QColor(self.__data.colorStops.rgb(self.__data.mode, 1.0))

    def rgb(self, interval, value):
        if qIsNaN(value):
            return 0
        width = interval.width()
        if width <= 0.0:
            return 0
        ratio = (value - interval.minValue()) / width
        return self.__data.colorStops.rgb(self.__data.mode, ratio)

    def colorIndex(self, interval, value):
        width = interval.width()
        if qIsNaN(value) or width <= 0.0 or value <= interval.minValue():
            return 0
        if value >= interval.maxValue():
            return 255
        ratio = (value - interval.minValue()) / width
        if self.__data.mode == self.FixedColors:
            return int(ratio * 255)
        else:
            return int(ratio * 255 + 0.5)


class QwtAlphaColorMap_PrivateData(QObject):
    def __init__(self):
        QObject.__init__(self)

        self.color = QColor()
        self.rgb = QColor().rgb()
        self.rgbMax = QColor().rgb()


class QwtAlphaColorMap(QwtColorMap):
    """
    QwtAlphaColorMap varies the alpha value of a color

    .. py:class:: QwtAlphaColorMap(color)

        Build a color map varying the alpha value of a color.

        :param QColor color: color of the map
    """

    def __init__(self, color):
        super(QwtAlphaColorMap, self).__init__(QwtColorMap.RGB)
        self.__data = QwtAlphaColorMap_PrivateData()
        self.setColor(color)

    def setColor(self, color):
        """
        Set the color of the map

        :param QColor color: color of the map
        """
        self.__data.color = color
        self.__data.rgb = color.rgb() & qRgba(255, 255, 255, 0)
        self.__data.rgbMax = self.__data.rgb | (255 << 24)

    def color(self):
        """
        :return: the color of the map

        .. seealso ::

            :py:meth:`QwtAlphaColorMap.setColor`
        """
        return self.__data.color

    def rgb(self, interval, value):
        if qIsNaN(value):
            return 0
        width = interval.width()
        if width <= 0.0:
            return 0
        if value <= interval.minValue():
            return self.__data.rgb
        if value >= interval.maxValue():
            return self.__data.rgbMax
        ratio = (value - interval.minValue()) / width
        return self.__data.rgb | (int(round(255 * ratio)) << 24)

    def colorIndex(self, interval, value):
        return 0