1# Copyright 2004-2021 Tom Rothamel <pytom@bishoujo.us>
2#
3# Permission is hereby granted, free of charge, to any person
4# obtaining a copy of this software and associated documentation files
5# (the "Software"), to deal in the Software without restriction,
6# including without limitation the rights to use, copy, modify, merge,
7# publish, distribute, sublicense, and/or sell copies of the Software,
8# and to permit persons to whom the Software is furnished to do so,
9# subject to the following conditions:
10#
11# The above copyright notice and this permission notice shall be
12# included in all copies or substantial portions of the Software.
13#
14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22# This contains ColorMatrix and the various *Matrix classes.
23
24init -1500 python:
25    import math as _math
26
27    class _BaseMatrix(object):
28        """
29        :undocumented:
30
31        Documented in text. The base class for various *Matrix classes
32        that are intended to return a Matrix, both ColorMatrix and
33        TransformMatrix.
34        """
35
36        def __init__(self, value=1.0):
37            self.value = value
38
39        def __call__(self, other, done):
40
41            if type(other) is not type(self):
42                return self.get(self.value)
43
44            value = other.value + (self.value - other.value) * done
45            return self.get(value)
46
47        def __mul__(self, other):
48            return _MultiplyMatrix(self, other)
49
50        def __eq__(self, other):
51            if type(self) != type(other):
52                return False
53
54            return self.__dict__ == other.__dict__
55
56        def __ne__(self, other):
57            return not (self == other)
58
59    class ColorMatrix(_BaseMatrix):
60        """
61        :undocumented:
62
63        Documented in text. The base class for various *Matrix classes
64        that are intended to return a Matrix that transforms colors.
65        """
66
67        pass
68
69    class _MultiplyMatrix(ColorMatrix):
70        """
71        :undocumented:
72
73        This created when two ColorMatrixes are multiplied together.
74        """
75
76        def __init__(self, left, right):
77            self.left = left
78            self.right = right
79            self.value = 1.0
80
81        def __call__(self, other, done):
82            if type(other) is not type(self):
83                return self.left(None, 1.0) * self.right(None, 1.0)
84
85            return self.left(other.left, done) * self.right(other.right, done)
86
87
88    class IdentityMatrix(ColorMatrix):
89        """
90        :doc: colormatrix
91        :args: ()
92
93        A ColorMatrix that can be used with :tpref:`matrixcolor` that does not
94        change the color or alpha of what is supplied to it.
95
96        `value`
97            Is ignored.
98        """
99
100        def get(self, value):
101            return Matrix([ 1.0, 0.0, 0.0, 0.0,
102                            0.0, 1.0, 0.0, 0.0,
103                            0.0, 0.0, 1.0, 0.0,
104                            0.0, 0.0, 0.0, 1.0, ])
105
106
107    class SaturationMatrix(ColorMatrix):
108        """
109        :doc: colormatrix
110
111        A ColorMatrix that can be used with :tpref:`matrixcolor` that alters
112        the saturation of an image, while leaving the alpha channel
113        alone.
114
115        `value`
116            The amount of saturation in the resulting image. 1.0 is
117            the unaltered image, while 0.0 is grayscale.
118
119        `desat`
120            This is a 3-element tuple that controls how much of the
121            red, green, and blue channels will be placed into all
122            three channels of a fully desaturated image. The default
123            is based on the constants used for the luminance channel
124            of an NTSC television signal. Since the human eye is
125            mostly sensitive to green, more of the green channel is
126            kept then the other two channels.
127        """
128
129        def __init__(self, value, desat=(0.2126, 0.7152, 0.0722)):
130            self.value = value
131            self.desat = desat
132
133        def get(self, value):
134            r, g, b = self.desat
135
136            def I(a, b):
137                return a + (b - a) * value
138
139            return Matrix([ I(r, 1), I(g, 0), I(b, 0), 0,
140                            I(r, 0), I(g, 1), I(b, 0), 0,
141                            I(r, 0), I(g, 0), I(b, 1), 0,
142                            0, 0, 0, 1 ])
143
144    class TintMatrix(ColorMatrix):
145        """
146        :doc: colormatrix
147
148        A ColorMatrix can be used with :tpref:`matrixcolor` to tint
149        an image, while leaving the alpha channel alone.
150
151        `color`
152            The color that the matrix will tint things to. This is passed
153            to :func:`Color`, and so may be anything that Color supports
154            as its first argument.
155
156        """
157
158        def __init__(self, color):
159            self.color = Color(color)
160
161        def __call__(self, other, done):
162
163            if type(other) is not type(self):
164
165                # When not using an old color, we can take
166                # r, g, b, and a from self.color.
167                r, g, b, a = self.color.rgba
168
169            else:
170
171                # Otherwise, we have to extract from self.color
172                # and other.color, and interpolate the results.
173                oldr, oldg, oldb, olda = other.color.rgba
174                r, g, b, a = self.color.rgba
175
176                r = oldr + (r - oldr) * done
177                g = oldg + (g - oldg) * done
178                b = oldb + (b - oldb) * done
179                a = olda + (a - olda) * done
180
181            # To properly handle premultiplied alpha, the color channels
182            # have to be multiplied by the alpha channel.
183            r *= a
184            g *= a
185            b *= a
186
187            # Return a Matrix.
188            return Matrix([ r, 0, 0, 0,
189                            0, g, 0, 0,
190                            0, 0, b, 0,
191                            0, 0, 0, a ])
192
193    class BrightnessMatrix(ColorMatrix):
194        """
195        :doc: colormatrix
196
197        A ColorMatrix that can be used with :tpref:`matrixcolor` to change
198        the brightness of an image, while leaving the Alpha channel
199        alone.
200
201        `value`
202            The amount of change in image brightness. This should be
203            a number between -1 and 1, with -1 the darkest possible
204            image and 1 the brightest.
205        """
206
207        def get(self, value):
208
209            return Matrix([ 1, 0, 0, value,
210                            0, 1, 0, value,
211                            0, 0, 1, value,
212                            0, 0, 0, 1 ])
213
214    class OpacityMatrix(ColorMatrix):
215        """
216        :doc: colormatrix
217
218        A ColorMatrix that can be used with :tpref:`matrixcolor` to change
219        the opacity of an image, while leaving color channels alone.
220
221        `value`
222            The amount the alpha channel should be multiplied by,
223            a number between 0.0 and 1.0.
224        """
225
226        def get(self, value):
227
228            return Matrix([ value, 0, 0, 0,
229                            0, value, 0, 0,
230                            0, 0, value, 0,
231                            0, 0, 0, value, ])
232
233
234
235    class ContrastMatrix(ColorMatrix):
236        """
237        :doc: colormatrix
238
239        A ColorMatrix that can be used with :tpref:`matrixcolor` to change
240        the brightness of an image, while leaving the Alpha channel
241        alone.
242
243        `value`
244            The contrast value. Values between 0.0 and 1.0 decrease
245            the contrast, while values above 1.0 increase the contrast.
246        """
247
248        def get(self, value):
249            d = value
250            v = value / -2.0 + .5
251
252            return Matrix([ d, 0, 0, v,
253                            0, d, 0, v,
254                            0, 0, d, v,
255                            0, 0, 0, 1, ])
256
257
258    class ColorizeMatrix(ColorMatrix):
259        """
260        :doc: colormatrix
261
262        A ColorMatrix that can be used with :tpref:`matrixcolor` to colorize
263        black and white displayables. It uses the color of each pixel
264        in the black and white to interpolate between the black color
265        and the white color.
266
267        The alpha channel is not touched.
268
269        This is inteded for use with a black and white image (or one that
270        has been desaturated with :func:`SaturationMatrix`), and will yield
271        strange results when used with images that are not black and white.
272
273        `black_color`, `white_color`
274            The colors used in the interpolation.
275        """
276
277        def __init__(self, black_color, white_color):
278            self.black = Color(black_color)
279            self.white = Color(white_color)
280
281
282        def __call__(self, other, done):
283
284            if type(other) is not type(self):
285                other = self
286
287            # Break the colors up into variables.
288            obr, obg, obb = other.black.rgb
289            owr, owg, owb = other.white.rgb
290            nbr, nbg, nbb = self.black.rgb
291            nwr, nwg, nwb = self.white.rgb
292
293            # Interpolate to get black and white colors.
294            br = obr + (nbr - obr) * done
295            bg = obg + (nbg - obg) * done
296            bb = obb + (nbb - obb) * done
297            wr = owr + (nwr - owr) * done
298            wg = owg + (nwg - owg) * done
299            wb = owb + (nwb - owb) * done
300
301            # Return the matrix.
302            return Matrix([ (wr - br), 0, 0, br,
303                            0, (wg - bg), 0, bg,
304                            0, 0, (wb - bb), bb,
305                            0, 0, 0, 1, ])
306
307
308    class HueMatrix(ColorMatrix):
309        """
310        :doc: colormatrix
311
312        A ColorMatrix that can be used with :tpref:`matrixcolor` to rotate the hue by
313        `value` degrees. While `value` can be any number, positive or negative,
314        360 degrees makes a complete rotation. The alpha channel is left alone.
315        """
316
317        # from http://www.gskinner.com/blog/archives/2005/09/flash_8_source.html
318
319        def get(self, value):
320
321            h = _math.pi * value / 180.0
322
323            cosVal = _math.cos(h)
324            sinVal = _math.sin(h)
325
326            lumR = 0.213
327            lumG = 0.715
328            lumB = 0.072
329
330            return Matrix([
331                lumR + cosVal * (1 - lumR) + sinVal * (-lumR), lumG + cosVal * (-lumG) + sinVal * (-lumG), lumB + cosVal * (-lumB) + sinVal * (1 - lumB), 0.0,
332                lumR + cosVal * (-lumR) + sinVal * (0.143), lumG + cosVal * (1 - lumG) + sinVal * (0.140), lumB + cosVal * (-lumB) + sinVal * (-0.283), 0.0,
333                lumR + cosVal * (-lumR) + sinVal * (-(1 - lumR)), lumG + cosVal * (-lumG) + sinVal * (lumG), lumB + cosVal * (1 - lumB) + sinVal * (lumB), 0.0,
334                0, 0, 0, 1.0 ])
335
336
337    class InvertMatrix(ColorMatrix):
338        """
339        :doc: colormatrix
340
341        A ColorMatrix that can be used with :tpref:`matrixcolor` to invert
342        each of the color channels. The alpha channel is left alone.
343
344        `value`
345            The amount to inverty by. 0.0 is not inverted, 1.0 is fully
346            inverted. Used to animate inversion.
347        """
348
349        def get(self, value):
350            d = 1.0 - 2 * value
351            v = value
352
353            return Matrix([ d, 0, 0, v,
354                            0, d, 0, v,
355                            0, 0, d, v,
356                            0, 0, 0, 1, ])
357
358    def SepiaMatrix(tint="#ffeec2", desat=(0.2126, 0.7152, 0.0722)):
359        """
360        :doc: colormatrix
361
362        A function that returns a ColorMatrix that can be used with :tpref:`matrixcolor`
363        to sepia-tone a displayable. This is the equivalent of::
364
365            TintMatrix(tint) * SaturationMatrix(0.0, desat)
366        """
367
368        return TintMatrix(tint) * SaturationMatrix(0.0, desat)
369