1414from typing import TYPE_CHECKING as _TYPE_CHECKING
1515import math
1616import random
17+ import numpy as np
1718
1819if _TYPE_CHECKING :
1920 from pycolorit .color import RGBColor
@@ -27,78 +28,109 @@ def generate(target: RGBColor) -> tuple[list[float], float, str]:
2728class Color :
2829 def __init__ (self , r , g , b ):
2930 self .set (r , g , b )
31+ return
3032
3133 def __str__ (self ):
3234 return f"rgb({ round (self .r )} , { round (self .g )} , { round (self .b )} )"
3335
3436 def set (self , r , g , b ):
35- self .r = self . clamp ( r )
36- self .g = self .clamp ( g )
37- self . b = self . clamp ( b )
37+ self .rgb = np . clip ( np . array ([ r , g , b ], dtype = np . float64 ), 0 , 255 )
38+ self .r , self . g , self . b = self .rgb
39+ return
3840
3941 def hue_rotate (self , angle = 0 ):
40- angle = angle / 180 * math .pi
41- sin = math .sin (angle )
42- cos = math .cos (angle )
43-
44- self .multiply (
42+ angle = np .radians (angle )
43+ sin , cos = np .sin (angle ), np .cos (angle )
44+ matrix = np .array (
4545 [
46- 0.213 + cos * 0.787 - sin * 0.213 ,
47- 0.715 - cos * 0.715 - sin * 0.715 ,
48- 0.072 - cos * 0.072 + sin * 0.928 ,
49- 0.213 - cos * 0.213 + sin * 0.143 ,
50- 0.715 + cos * 0.285 + sin * 0.140 ,
51- 0.072 - cos * 0.072 - sin * 0.283 ,
52- 0.213 - cos * 0.213 - sin * 0.787 ,
53- 0.715 - cos * 0.715 + sin * 0.715 ,
54- 0.072 + cos * 0.928 + sin * 0.072 ,
46+ [
47+ 0.213 + cos * 0.787 - sin * 0.213 ,
48+ 0.715 - cos * 0.715 - sin * 0.715 ,
49+ 0.072 - cos * 0.072 + sin * 0.928 ,
50+ ],
51+ [
52+ 0.213 - cos * 0.213 + sin * 0.143 ,
53+ 0.715 + cos * 0.285 + sin * 0.140 ,
54+ 0.072 - cos * 0.072 - sin * 0.283 ,
55+ ],
56+ [
57+ 0.213 - cos * 0.213 - sin * 0.787 ,
58+ 0.715 - cos * 0.715 + sin * 0.715 ,
59+ 0.072 + cos * 0.928 + sin * 0.072 ,
60+ ],
5561 ]
5662 )
63+ self .multiply (matrix )
64+ return
5765
5866 def grayscale (self , x = 1 ):
59- self . multiply (
67+ matrix = np . array (
6068 [
61- 0.2126 + 0.7874 * (1 - x ),
62- 0.7152 - 0.7152 * (1 - x ),
63- 0.0722 - 0.0722 * (1 - x ),
64- 0.2126 - 0.2126 * (1 - x ),
65- 0.7152 + 0.2848 * (1 - x ),
66- 0.0722 - 0.0722 * (1 - x ),
67- 0.2126 - 0.2126 * (1 - x ),
68- 0.7152 - 0.7152 * (1 - x ),
69- 0.0722 + 0.9278 * (1 - x ),
70- ]
69+ [
70+ 0.2126 + 0.7874 * (1 - x ),
71+ 0.7152 - 0.7152 * (1 - x ),
72+ 0.0722 - 0.0722 * (1 - x ),
73+ ],
74+ [
75+ 0.2126 - 0.2126 * (1 - x ),
76+ 0.7152 + 0.2848 * (1 - x ),
77+ 0.0722 - 0.0722 * (1 - x ),
78+ ],
79+ [
80+ 0.2126 - 0.2126 * (1 - x ),
81+ 0.7152 - 0.7152 * (1 - x ),
82+ 0.0722 + 0.9278 * (1 - x ),
83+ ],
84+ ],
7185 )
86+ self .multiply (matrix )
87+ return
7288
7389 def sepia (self , x = 1 ):
74- self . multiply (
90+ matrix = np . array (
7591 [
76- 0.393 + 0.607 * (1 - x ),
77- 0.769 - 0.769 * (1 - x ),
78- 0.189 - 0.189 * (1 - x ),
79- 0.349 - 0.349 * (1 - x ),
80- 0.686 + 0.314 * (1 - x ),
81- 0.168 - 0.168 * (1 - x ),
82- 0.272 - 0.272 * (1 - x ),
83- 0.534 - 0.534 * (1 - x ),
84- 0.131 + 0.869 * (1 - x ),
85- ]
92+ [
93+ 0.393 + 0.607 * (1 - x ),
94+ 0.769 - 0.769 * (1 - x ),
95+ 0.189 - 0.189 * (1 - x ),
96+ ],
97+ [
98+ 0.349 - 0.349 * (1 - x ),
99+ 0.686 + 0.314 * (1 - x ),
100+ 0.168 - 0.168 * (1 - x ),
101+ ],
102+ [
103+ 0.272 - 0.272 * (1 - x ),
104+ 0.534 - 0.534 * (1 - x ),
105+ 0.131 + 0.869 * (1 - x ),
106+ ],
107+ ],
86108 )
109+ self .multiply (matrix )
110+ return
87111
88112 def saturate (self , x = 1 ):
89- self . multiply (
113+ matrix = np . array (
90114 [
91- 0.213 + 0.787 * x ,
92- 0.715 - 0.715 * x ,
93- 0.072 - 0.072 * x ,
94- 0.213 - 0.213 * x ,
95- 0.715 + 0.285 * x ,
96- 0.072 - 0.072 * x ,
97- 0.213 - 0.213 * x ,
98- 0.715 - 0.715 * x ,
99- 0.072 + 0.928 * x ,
100- ]
115+ [
116+ 0.213 + 0.787 * x ,
117+ 0.715 - 0.715 * x ,
118+ 0.072 - 0.072 * x ,
119+ ],
120+ [
121+ 0.213 - 0.213 * x ,
122+ 0.715 + 0.285 * x ,
123+ 0.072 - 0.072 * x ,
124+ ],
125+ [
126+ 0.213 - 0.213 * x ,
127+ 0.715 - 0.715 * x ,
128+ 0.072 + 0.928 * x ,
129+ ],
130+ ],
101131 )
132+ self .multiply (matrix )
133+ return
102134
103135 def brightness (self , x = 1 ):
104136 """
@@ -109,12 +141,9 @@ def brightness(self, x=1):
109141 x : int >= 0
110142 Linear multiplier, with 0 creating a completely black color, 1 (i.e. 100%) having no effect,
111143 and values over 1 brightening the color.
112-
113- Returns
114- -------
115-
116144 """
117- self .multiply ([x , 0 , 0 , 0 , x , 0 , 0 , 0 , x ])
145+ self .multiply (np .diag ([x , x , x ]))
146+ return
118147
119148 def contrast (self , x = 1 ):
120149 """
@@ -124,45 +153,38 @@ def contrast(self, x=1):
124153 ----------
125154 x : int >= 0
126155 A value of 0 makes the color grey, 1 (i.e. 100%) has no effect, and values over 1 create a contrast.
127-
128- Returns
129- -------
130-
131156 """
132- intercept = tuple (127.5 * (1 - x ) for i in range (3 ))
133- self .multiply ([x , 0 , 0 , 0 , x , 0 , 0 , 0 , x ], intercept )
134-
135- def clamp (self , value ):
136- return min (255 , max (0 , value ))
157+ intercept = np .array ([127.5 * (1 - x )] * 3 )
158+ self .multiply (np .diag ([x , x , x ]), intercept )
159+ return
137160
138- def multiply (self , matrix , vector = (0 , 0 , 0 )):
139- r = self .r * matrix [0 ] + self .g * matrix [1 ] + self .b * matrix [2 ] + vector [0 ]
140- g = self .r * matrix [3 ] + self .g * matrix [4 ] + self .b * matrix [5 ] + vector [1 ]
141- b = self .r * matrix [6 ] + self .g * matrix [7 ] + self .b * matrix [8 ] + vector [2 ]
142- self .set (r , g , b )
161+ def multiply (self , matrix , vector = None ):
162+ result = np .dot (matrix , self .rgb )
163+ if vector is not None :
164+ result += vector
165+ self .set (* result )
166+ return
143167
144168 def invert (self , x = 1 ):
145- r = (x + self .r / 255 * (1 - 2 * x )) * 255
146- g = (x + self .g / 255 * (1 - 2 * x )) * 255
147- b = (x + self .b / 255 * (1 - 2 * x )) * 255
148- self .set (r , g , b )
169+ self .rgb = (x + self .rgb / 255 * (1 - 2 * x )) * 255
170+ self .set (* self .rgb )
149171 return
150172
151173 def rgb_to_hsl (self ):
152- r , g , b = self .r / 255 , self . g / 255 , self . b / 255
153- max_value = max (r , g , b )
154- min_value = min ( r , g , b )
155- h , s , l = 0 , 0 , ( max_value + min_value ) / 2
156- if max_value != min_value :
157- d = max_value - min_value
158- s = d / ( 2 - max_value - min_value ) if l > 0.5 else d / ( max_value + min_value )
159- if max_value == r :
160- h = ( g - b ) / d + ( 6 if g < b else 0 )
161- elif max_value == g :
162- h = ( b - r ) / d + 2
163- else :
164- h = ( r - g ) / d + 4
165- h /= 6
174+ rgb = self .rgb / 255
175+ max_val , min_val = np . max (rgb ), np . min ( rgb )
176+ l = ( max_val + min_val ) / 2
177+ if max_val == min_val :
178+ h = s = 0
179+ else :
180+ d = max_val - min_val
181+ s = d / ( 2 - max_val - min_val ) if l > 0.5 else d / ( max_val + min_val )
182+ idx = np . argmax ( rgb )
183+ h = [
184+ ( rgb [ 1 ] - rgb [ 2 ] ) / d + ( 6 if rgb [ 1 ] < rgb [ 2 ] else 0 ),
185+ ( rgb [ 2 ] - rgb [ 0 ]) / d + 2 ,
186+ ( rgb [ 0 ] - rgb [ 1 ] ) / d + 4
187+ ][ idx ] / 6
166188 return h * 100 , s * 100 , l * 100
167189
168190
0 commit comments