1
- from colorsys import rgb_to_hsv
1
+ from typing import Tuple , Callable , Any
2
2
3
3
from PIL import Image , ImageStat
4
4
from scipy .spatial .distance import cosine
5
5
6
6
def generate_color_variations (color_dict , max_abs_difference = 16 ):
7
+ '''Creates color combinations in given max_abs_difference.'''
7
8
new_dict = {}
8
9
9
10
for rgb_tuple , value in color_dict .items ():
@@ -30,110 +31,105 @@ def generate_color_variations(color_dict, max_abs_difference=16):
30
31
if total_diff <= max_abs_difference :
31
32
new_rgb_tuple = (new_r , new_g , new_b )
32
33
new_dict [new_rgb_tuple ] = value
33
-
34
34
return new_dict
35
35
36
36
class Method :
37
37
def __init__ (self , blocks , compression_level : int = 16 ) -> None :
38
38
self .compression_level = compression_level
39
- self .caching = dict ()
39
+ self .cache = dict ()
40
40
self .blocks = blocks
41
41
42
+ def add_to_caching (self , median_rgb : Tuple [int , int , int ], closest_block : str ):
43
+ self .cache [median_rgb ] = closest_block
44
+ new_dict = dict ()
45
+ new_dict [median_rgb ] = closest_block
46
+ all_permutations = generate_color_variations (new_dict , self .compression_level )
47
+ self .cache .update (all_permutations )
48
+
49
+ def check_caching (func : Callable [..., Any ]):
50
+ '''Checks if chunk was already cached, and if so returns cached closest block.'''
51
+ def wrapper (self , chunk , * args , ** kwargs ):
52
+ img_median = tuple (ImageStat .Stat (chunk ).median )
53
+ if img_median in self .cache :
54
+ return self .cache [img_median ]
55
+ else :
56
+ return func (self , chunk , * args , ** kwargs )
57
+ return wrapper
58
+
59
+ @check_caching
42
60
def find_closest_block_rgb_abs_diff (self , chunk : Image ) -> str :
43
61
'''Calculates the median value of an input image.
44
62
Then compares this median to the medians for each block,
45
63
and returns the block with the closest match based on the sum of absolute differences between its RGB values and the median of the input image.
46
64
If there are multiple blocks with equal minimum difference, it will return the first one encountered.
47
65
'''
48
- og_median = tuple (ImageStat .Stat (chunk ).median )
49
- og_median_rgb = tuple ([og_median [0 ], og_median [1 ], og_median [2 ]])
50
- if og_median_rgb in self .caching :
51
- return self .caching [og_median_rgb ]
52
- else :
53
- rgb_closests_diff = []
54
- for channel in range (3 ):
55
- min_diff = float ('inf' )
56
- for block in self .blocks :
57
- diff = abs (og_median_rgb [channel ] - self .blocks [block ]["median" ][channel ])
58
- if diff < min_diff :
59
- min_diff = diff
60
- min_diff_block = block
61
- rgb_closests_diff .append (min_diff_block )
62
-
63
- lowest_difference = float ("inf" )
64
- lowest_block = None
65
- for block in rgb_closests_diff :
66
- difference = sum (abs (a - b ) for a , b in zip (self .blocks [block ]["median" ], og_median_rgb ))
67
- if difference < lowest_difference :
68
- lowest_difference = difference
69
- lowest_block = block
70
-
71
- self .caching [og_median_rgb ] = lowest_block
72
- new_dict = dict ()
73
- new_dict [og_median_rgb ] = lowest_block
74
- all_permutations = generate_color_variations (new_dict , self .compression_level )
75
- self .caching .update (all_permutations )
76
- return lowest_block
66
+ img_median = tuple (ImageStat .Stat (chunk ).median )
77
67
68
+ rgb_closests_diff = []
69
+ for channel in range (3 ):
70
+ min_diff = float ('inf' )
71
+ for block in self .blocks :
72
+ diff = abs (img_median [channel ] - self .blocks [block ]["median" ][channel ])
73
+ if diff < min_diff :
74
+ min_diff = diff
75
+ min_diff_block = block
76
+ rgb_closests_diff .append (min_diff_block )
77
+
78
+ lowest_difference = float ("inf" )
79
+ closest_block = None
80
+ for block in rgb_closests_diff :
81
+ difference = sum (abs (a - b ) for a , b in zip (self .blocks [block ]["median" ], img_median ))
82
+ if difference < lowest_difference :
83
+ lowest_difference = difference
84
+ closest_block = block
85
+
86
+ self .add_to_caching (img_median , closest_block )
87
+ return closest_block
88
+
89
+ @check_caching
78
90
def find_closest_block_cosine_similarity (self , chunk : Image ) -> str :
79
91
'''Calculates the median value of an input image.
80
92
Then compares this median to the medians for each block,
81
93
and returns the block with the closest match based on the cosine similarity between its RGB values and the median of the input image.
82
94
If there are multiple blocks with equal maximum similarity, it will return the first one encountered.
83
95
'''
84
- og_median = tuple (ImageStat .Stat (chunk ).median )
85
- og_median_rgb = tuple ([og_median [0 ], og_median [1 ], og_median [2 ]])
96
+ img_median = tuple (ImageStat .Stat (chunk ).median )
97
+
98
+ closest_block = None
99
+ max_similarity = - 1
86
100
87
- if og_median_rgb in self .caching :
88
- return self .caching [og_median_rgb ]
89
- else :
90
- closest_block = None
91
- max_similarity = - 1
92
-
93
- for block in self .blocks :
94
- block_rgb = self .blocks [block ]["median" ]
95
- similarity = 1 - cosine (og_median_rgb , block_rgb )
96
-
97
- if similarity > max_similarity :
98
- max_similarity = similarity
99
- closest_block = block
101
+ for block in self .blocks :
102
+ block_rgb = self .blocks [block ]["median" ]
103
+ similarity = 1 - cosine (img_median , block_rgb )
100
104
101
- self . caching [ og_median_rgb ] = closest_block
102
- new_dict = dict ()
103
- new_dict [ og_median_rgb ] = closest_block
104
- all_permutations = generate_color_variations ( new_dict , self . compression_level )
105
- self .caching . update ( all_permutations )
106
- return closest_block
105
+ if similarity > max_similarity :
106
+ max_similarity = similarity
107
+ closest_block = block
108
+
109
+ self .add_to_caching ( img_median , closest_block )
110
+ return closest_block
107
111
112
+ @check_caching
108
113
def find_closest_block_minkowski_distance (self , chunk : Image , p : int = 2 ) -> str :
109
114
'''Calculates the median value of an input image.
110
115
Then compares this median to the medians for each block,
111
116
and returns the block with the closest match based on the Minkowski distance between its RGB values and the median of the input image.
112
117
If there are multiple blocks with equal minimum distance, it will return the first one encountered.
113
118
'''
114
- og_median = tuple (ImageStat .Stat (chunk ).median )
115
- og_median_rgb = tuple ([og_median [0 ], og_median [1 ], og_median [2 ]])
119
+ img_median = tuple (ImageStat .Stat (chunk ).median )
120
+ closest_block = None
121
+ min_distance = float ('inf' )
116
122
117
- if og_median_rgb in self .caching :
118
- return self .caching [og_median_rgb ]
119
- else :
120
- closest_block = None
121
- min_distance = float ('inf' )
122
-
123
- for block in self .blocks :
124
- block_rgb = self .blocks [block ]["median" ]
125
- distance = sum (abs (a - b ) ** p for a , b in zip (og_median_rgb , block_rgb )) ** (1 / p )
123
+ for block in self .blocks :
124
+ block_rgb = self .blocks [block ]["median" ]
125
+ distance = sum (abs (a - b ) ** p for a , b in zip (img_median , block_rgb )) ** (1 / p )
126
126
127
- if distance < min_distance :
128
- min_distance = distance
129
- closest_block = block
127
+ if distance < min_distance :
128
+ min_distance = distance
129
+ closest_block = block
130
130
131
- self .caching [og_median_rgb ] = closest_block
132
- new_dict = dict ()
133
- new_dict [og_median_rgb ] = closest_block
134
- all_permutations = generate_color_variations (new_dict , self .compression_level )
135
- self .caching .update (all_permutations )
136
- return closest_block
131
+ self .add_to_caching (img_median , closest_block )
132
+ return closest_block
137
133
138
134
def find_closest_block_manhattan_distance (self , chunk : Image ) -> str :
139
135
return self .find_closest_block_minkowski_distance (chunk , 1 )
@@ -147,65 +143,50 @@ def find_closest_block_chebyshev_distance(self, chunk: Image) -> str:
147
143
def find_closest_block_taxicab_distance (self , chunk : Image ) -> str :
148
144
return self .find_closest_block_minkowski_distance (chunk , 4 )
149
145
146
+ @check_caching
150
147
def find_closest_block_hamming_distance (self , chunk : Image ) -> str :
151
148
'''Calculates the median value of an input image.
152
149
Then compares this median to the medians for each block,
153
150
and returns the block with the closest match based on the Hamming distance between its RGB values and the median of the input image.
154
151
If there are multiple blocks with equal minimum distance, it will return the first one encountered.
155
152
'''
156
- og_median = tuple (ImageStat .Stat (chunk ).median )
157
- og_median_rgb = tuple ([og_median [0 ], og_median [1 ], og_median [2 ]])
153
+ img_median = tuple (ImageStat .Stat (chunk ).median )
158
154
159
- if og_median_rgb in self .caching :
160
- return self .caching [og_median_rgb ]
161
- else :
162
- closest_block = None
163
- min_distance = float ('inf' )
155
+ closest_block = None
156
+ min_distance = float ('inf' )
164
157
165
- for block in self .blocks :
166
- block_rgb = self .blocks [block ]["median" ]
167
- distance = sum (a != b for a , b in zip (og_median_rgb , block_rgb ))
158
+ for block in self .blocks :
159
+ block_rgb = self .blocks [block ]["median" ]
160
+ distance = sum (a != b for a , b in zip (img_median , block_rgb ))
168
161
169
- if distance < min_distance :
170
- min_distance = distance
171
- closest_block = block
162
+ if distance < min_distance :
163
+ min_distance = distance
164
+ closest_block = block
172
165
173
- self .caching [og_median_rgb ] = closest_block
174
- new_dict = dict ()
175
- new_dict [og_median_rgb ] = closest_block
176
- all_permutations = generate_color_variations (new_dict , self .compression_level )
177
- self .caching .update (all_permutations )
178
- return closest_block
166
+ self .add_to_caching (img_median , closest_block )
167
+ return closest_block
179
168
169
+ @check_caching
180
170
def find_closest_block_canberra_distance (self , chunk : Image ) -> str :
181
171
'''Calculates the median value of an input image.
182
172
Then compares this median to the medians for each block,
183
173
and returns the block with the closest match based on the Canberra distance between its RGB values and the median of the input image.
184
174
If there are multiple blocks with equal minimum distance, it will return the first one encountered.
185
175
'''
186
- og_median = tuple (ImageStat .Stat (chunk ).median )
187
- og_median_rgb = tuple ([og_median [0 ], og_median [1 ], og_median [2 ]])
188
-
189
- if og_median_rgb in self .caching :
190
- return self .caching [og_median_rgb ]
191
- else :
192
- closest_block = None
193
- min_distance = float ('inf' )
194
-
195
- for block in self .blocks :
196
- block_rgb = self .blocks [block ]["median" ]
197
- distance = sum (
198
- abs (a - b ) / (abs (a ) + abs (b )) if abs (a ) + abs (b ) != 0 else float ('inf' )
199
- for a , b in zip (og_median_rgb , block_rgb )
200
- )
201
-
202
- if distance < min_distance :
203
- min_distance = distance
204
- closest_block = block
205
-
206
- self .caching [og_median_rgb ] = closest_block
207
- new_dict = dict ()
208
- new_dict [og_median_rgb ] = closest_block
209
- all_permutations = generate_color_variations (new_dict , self .compression_level )
210
- self .caching .update (all_permutations )
211
- return closest_block
176
+ img_median = tuple (ImageStat .Stat (chunk ).median )
177
+ closest_block = None
178
+ min_distance = float ('inf' )
179
+
180
+ for block in self .blocks :
181
+ block_rgb = self .blocks [block ]["median" ]
182
+ distance = sum (
183
+ abs (a - b ) / (abs (a ) + abs (b )) if abs (a ) + abs (b ) != 0 else float ('inf' )
184
+ for a , b in zip (img_median , block_rgb )
185
+ )
186
+
187
+ if distance < min_distance :
188
+ min_distance = distance
189
+ closest_block = block
190
+
191
+ self .add_to_caching (img_median , closest_block )
192
+ return closest_block
0 commit comments