@@ -94,11 +94,50 @@ def pixel_closest_colour(self, pixel, order=2):
94
94
95
95
@classmethod
96
96
def create_by_kmeans (cls , image ):
97
- pass
97
+ raise NotImplementedError ()
98
98
99
99
@classmethod
100
- def create_by_median_cut (cls , image ):
101
- pass
100
+ def create_by_median_cut (cls , image , n = 16 , dim = None ):
101
+ img = np .array (image )
102
+ # Create pixel buckets to simplify sorting and splitting.
103
+ if img .ndim == 3 :
104
+ pixels = img .reshape ((img .shape [0 ] * img .shape [1 ], img .shape [2 ]))
105
+ elif img .ndim == 2 :
106
+ pixels = img .reshape ((img .shape [0 ] * img .shape [1 ], 1 ))
107
+
108
+ def median_cut (p , dim = None ):
109
+ """Median cut method.
110
+
111
+ Reference:
112
+ https://en.wikipedia.org/wiki/Median_cut
113
+
114
+ :param p: The pixel array to split in two.
115
+ :return: Two numpy arrays, split by median cut method.
116
+ """
117
+ if dim is not None :
118
+ sort_dim = dim
119
+ else :
120
+ mins = p .min (axis = 0 )
121
+ maxs = p .max (axis = 0 )
122
+ sort_dim = np .argmax (maxs - mins )
123
+
124
+ argument = np .argsort (p [:, sort_dim ])
125
+ p = p [argument , :]
126
+ m = np .median (p [:, sort_dim ])
127
+ split_mask = p [:, sort_dim ] >= m
128
+ return [p [- split_mask , :].copy (), p [split_mask , :].copy ()]
129
+
130
+ # Do actual splitting loop.
131
+ bins = [pixels , ]
132
+ while len (bins ) < n :
133
+ new_bins = []
134
+ for bin in bins :
135
+ new_bins += median_cut (bin , dim )
136
+ bins = new_bins
137
+
138
+ # Average over pixels in each bin to create
139
+ colours = np .array ([np .array (bin .mean (axis = 0 ).round (), 'uint8' ) for bin in bins ], 'uint8' )
140
+ return cls (colours )
102
141
103
142
def create_PIL_png_from_closest_colour (self , cc ):
104
143
"""Create a ``P`` PIL image with this palette.
@@ -108,15 +147,15 @@ def create_PIL_png_from_closest_colour(self, cc):
108
147
Reference: http://stackoverflow.com/a/29438149
109
148
110
149
:param :class:`numpy.ndarray` cc: A ``[M x N]`` array with integer
111
- values representing palette colour indices to build image from.
150
+ values representing palette colour indices to build image from.
112
151
:return: A :class:`PIL.Image.Image` image of mode ``P``.
113
152
114
153
"""
115
154
pa_image = Image .new ("P" , cc .shape [::- 1 ])
116
155
pa_image .putpalette (self .colours .flatten ().tolist ())
117
156
im = Image .fromarray (np .array (cc , 'uint8' )).im .convert ("P" , 0 , pa_image .im )
118
157
return pa_image ._makeself (im )
119
-
158
+
120
159
def create_PIL_png_from_rgb_array (self , img_array ):
121
160
"""Create a ``P`` PIL image from a RGB image with this palette.
122
161
0 commit comments