| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  | """
 | 
					
						
							|  |  |  | Copyright (c) 2019 Lorenz Diener | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Permission is hereby granted, free of charge, to any person obtaining a copy | 
					
						
							|  |  |  | of this software and associated documentation files (the "Software"), to deal | 
					
						
							|  |  |  | in the Software without restriction, including without limitation the rights | 
					
						
							|  |  |  | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | 
					
						
							|  |  |  | copies of the Software, and to permit persons to whom the Software is | 
					
						
							|  |  |  | furnished to do so, subject to the following conditions: | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-05 17:22:37 +00:00
										 |  |  | * The above copyright notice and this permission notice shall be included | 
					
						
							|  |  |  | in all copies or substantial portions of the Software. | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | * You and any organization you work for may not promote white supremacy, hate | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  | speech and homo- or transphobia - this license is void if you do. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | 
					
						
							|  |  |  | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | 
					
						
							|  |  |  | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | 
					
						
							|  |  |  | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | 
					
						
							|  |  |  | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | 
					
						
							|  |  |  | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | 
					
						
							|  |  |  | SOFTWARE. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | https://github.com/halcy/blurhash-python | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | Pure python blurhash decoder with no additional dependencies, for | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  | both de- and encoding. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Very close port of the original Swift implementation by Dag Ågren. | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import math | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  | # Alphabet for base 83 | 
					
						
							| 
									
										
										
										
											2020-04-05 17:22:37 +00:00
										 |  |  | alphabet = \ | 
					
						
							|  |  |  |     "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + \ | 
					
						
							|  |  |  |     "abcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~" | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  | alphabet_values = dict(zip(alphabet, range(len(alphabet)))) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | def base83_decode(base83_str): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Decodes a base83 string, as used in blurhash, to an integer. | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |     value = 0 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     for base83_char in base83_str: | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |         value = value * 83 + alphabet_values[base83_char] | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     return value | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  | def base83_encode(value, length): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Decodes an integer to a base83 string, as used in blurhash. | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     Length is how long the resulting string should be. Will complain | 
					
						
							|  |  |  |     if the specified length is too short. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     if int(value) // (83 ** (length)) != 0: | 
					
						
							| 
									
										
										
										
											2020-04-05 17:22:37 +00:00
										 |  |  |         raise ValueError("Specified length is too short to " + | 
					
						
							|  |  |  |                          "encode given value.") | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |     result = "" | 
					
						
							| 
									
										
										
										
											2020-04-05 17:22:37 +00:00
										 |  |  |     for i in range(1, length + 1): | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |         digit = int(value) // (83 ** (length - i)) % 83 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |         result += alphabet[int(digit)] | 
					
						
							|  |  |  |     return result | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  | def srgb_to_linear(value): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     srgb 0-255 integer to linear 0.0-1.0 floating point conversion. | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |     value = float(value) / 255.0 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     if value <= 0.04045: | 
					
						
							|  |  |  |         return value / 12.92 | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |     return math.pow((value + 0.055) / 1.055, 2.4) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | def sign_pow(value, exp): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Sign-preserving exponentiation. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     return math.copysign(math.pow(abs(value), exp), value) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  | def linear_to_srgb(value): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     linear 0.0-1.0 floating point to srgb 0-255 integer conversion. | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |     value = max(0.0, min(1.0, value)) | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     if value <= 0.0031308: | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |         return int(value * 12.92 * 255 + 0.5) | 
					
						
							|  |  |  |     return int((1.055 * math.pow(value, 1 / 2.4) - 0.055) * 255 + 0.5) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | def blurhash_components(blurhash): | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     Decodes and returns the number of x and y components in the given blurhash. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     if len(blurhash) < 6: | 
					
						
							|  |  |  |         raise ValueError("BlurHash must be at least 6 characters long.") | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     # Decode metadata | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |     size_info = base83_decode(blurhash[0]) | 
					
						
							|  |  |  |     size_y = int(size_info / 9) + 1 | 
					
						
							|  |  |  |     size_x = (size_info % 9) + 1 | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     return size_x, size_y | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | def blurhash_decode(blurhash, width, height, punch=1.0, linear=False): | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     """
 | 
					
						
							|  |  |  |     Decodes the given blurhash to an image of the specified size. | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     Returns the resulting image a list of lists of 3-value sRGB 8 bit integer | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  |     lists. Set linear to True if you would prefer to get linear floating point | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     RGB back. | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     The punch parameter can be used to de- or increase the contrast of the | 
					
						
							|  |  |  |     resulting image. | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     As per the original implementation it is suggested to only decode | 
					
						
							|  |  |  |     to a relatively small size and then scale the result up, as it | 
					
						
							|  |  |  |     basically looks the same anyways. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     if len(blurhash) < 6: | 
					
						
							|  |  |  |         raise ValueError("BlurHash must be at least 6 characters long.") | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     # Decode metadata | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |     size_info = base83_decode(blurhash[0]) | 
					
						
							|  |  |  |     size_y = int(size_info / 9) + 1 | 
					
						
							|  |  |  |     size_x = (size_info % 9) + 1 | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |     quant_max_value = base83_decode(blurhash[1]) | 
					
						
							| 
									
										
										
										
											2020-04-05 17:22:37 +00:00
										 |  |  |     real_max_value = (float(quant_max_value + 1) / 166.0) * punch | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     # Make sure we at least have the right number of characters | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |     if len(blurhash) != 4 + 2 * size_x * size_y: | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |         raise ValueError("Invalid BlurHash length.") | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     # Decode DC component | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |     dc_value = base83_decode(blurhash[2:6]) | 
					
						
							|  |  |  |     colours = [( | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |         srgb_to_linear(dc_value >> 16), | 
					
						
							|  |  |  |         srgb_to_linear((dc_value >> 8) & 255), | 
					
						
							|  |  |  |         srgb_to_linear(dc_value & 255) | 
					
						
							|  |  |  |     )] | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     # Decode AC components | 
					
						
							|  |  |  |     for component in range(1, size_x * size_y): | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |         ac_value = base83_decode(blurhash[4+component*2:4+(component+1)*2]) | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |         colours.append(( | 
					
						
							| 
									
										
										
										
											2020-04-05 17:22:37 +00:00
										 |  |  |             sign_pow((float(int(ac_value / (19 * 19))) - 9.0) | 
					
						
							|  |  |  |                      / 9.0, 2.0) * real_max_value, | 
					
						
							|  |  |  |             sign_pow((float(int(ac_value / 19) % 19) - 9.0) | 
					
						
							|  |  |  |                      / 9.0, 2.0) * real_max_value, | 
					
						
							|  |  |  |             sign_pow((float(ac_value % 19) - 9.0) | 
					
						
							|  |  |  |                      / 9.0, 2.0) * real_max_value | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |         )) | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # Return image RGB values, as a list of lists of lists, | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     # consumable by something like numpy or PIL. | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |     pixels = [] | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     for y in range(height): | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |         pixel_row = [] | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |         for x in range(width): | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |             pixel = [0.0, 0.0, 0.0] | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |             for j in range(size_y): | 
					
						
							|  |  |  |                 for i in range(size_x): | 
					
						
							| 
									
										
										
										
											2020-04-05 17:22:37 +00:00
										 |  |  |                     basis = \ | 
					
						
							|  |  |  |                         math.cos(math.pi * float(x) * float(i) / | 
					
						
							|  |  |  |                                  float(width)) * \ | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |                         math.cos(math.pi * float(y) * float(j) / float(height)) | 
					
						
							|  |  |  |                     colour = colours[i + j * size_x] | 
					
						
							|  |  |  |                     pixel[0] += colour[0] * basis | 
					
						
							|  |  |  |                     pixel[1] += colour[1] * basis | 
					
						
							|  |  |  |                     pixel[2] += colour[2] * basis | 
					
						
							| 
									
										
										
										
											2020-04-05 17:22:37 +00:00
										 |  |  |             if linear is False: | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |                 pixel_row.append([ | 
					
						
							|  |  |  |                     linear_to_srgb(pixel[0]), | 
					
						
							|  |  |  |                     linear_to_srgb(pixel[1]), | 
					
						
							|  |  |  |                     linear_to_srgb(pixel[2]), | 
					
						
							|  |  |  |                 ]) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 pixel_row.append(pixel) | 
					
						
							|  |  |  |         pixels.append(pixel_row) | 
					
						
							|  |  |  |     return pixels | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | def blurhash_encode(image, components_x=4, components_y=4, linear=False): | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2020-04-05 17:22:37 +00:00
										 |  |  |     Calculates the blurhash for an image using the given x and y | 
					
						
							|  |  |  |      component counts. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     Image should be a 3-dimensional array, with the first dimension | 
					
						
							|  |  |  |     being y, the second being x, and the third being the three rgb | 
					
						
							|  |  |  |     components that are assumed to be 0-255 srgb integers | 
					
						
							|  |  |  |     (incidentally, this is the format you will get from a PIL RGB image). | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     You can also pass in already linear data - to do this, set linear | 
					
						
							|  |  |  |     to True. This is useful if you want to encode a version of your | 
					
						
							|  |  |  |     image resized to a smaller size (which you should ideally do in | 
					
						
							|  |  |  |     linear colour). | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |     if components_x < 1 or components_x > 9 or \ | 
					
						
							|  |  |  |        components_y < 1 or components_y > 9: | 
					
						
							|  |  |  |         raise ValueError("x and y component counts must be " + | 
					
						
							|  |  |  |                          "between 1 and 9 inclusive.") | 
					
						
							|  |  |  |     height = float(len(image)) | 
					
						
							|  |  |  |     width = float(len(image[0])) | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     # Convert to linear if neeeded | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |     image_linear = [] | 
					
						
							| 
									
										
										
										
											2020-04-05 17:22:37 +00:00
										 |  |  |     if linear is False: | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |         for y in range(int(height)): | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |             image_linear_line = [] | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |             for x in range(int(width)): | 
					
						
							|  |  |  |                 image_linear_line.append([ | 
					
						
							|  |  |  |                     srgb_to_linear(image[y][x][0]), | 
					
						
							|  |  |  |                     srgb_to_linear(image[y][x][1]), | 
					
						
							|  |  |  |                     srgb_to_linear(image[y][x][2]) | 
					
						
							|  |  |  |                 ]) | 
					
						
							|  |  |  |             image_linear.append(image_linear_line) | 
					
						
							|  |  |  |     else: | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |         image_linear = image | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     # Calculate components | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |     components = [] | 
					
						
							|  |  |  |     max_ac_component = 0.0 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     for j in range(components_y): | 
					
						
							|  |  |  |         for i in range(components_x): | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |             norm_factor = 1.0 if (i == 0 and j == 0) else 2.0 | 
					
						
							|  |  |  |             component = [0.0, 0.0, 0.0] | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |             for y in range(int(height)): | 
					
						
							|  |  |  |                 for x in range(int(width)): | 
					
						
							| 
									
										
										
										
											2020-04-05 17:22:37 +00:00
										 |  |  |                     basis = \ | 
					
						
							|  |  |  |                         norm_factor * \ | 
					
						
							|  |  |  |                         math.cos(math.pi * float(i) * float(x) / width) * \ | 
					
						
							| 
									
										
										
										
											2020-03-22 20:36:19 +00:00
										 |  |  |                         math.cos(math.pi * float(j) * float(y) / height) | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |                     component[0] += basis * image_linear[y][x][0] | 
					
						
							|  |  |  |                     component[1] += basis * image_linear[y][x][1] | 
					
						
							|  |  |  |                     component[2] += basis * image_linear[y][x][2] | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |             component[0] /= (width * height) | 
					
						
							|  |  |  |             component[1] /= (width * height) | 
					
						
							|  |  |  |             component[2] /= (width * height) | 
					
						
							|  |  |  |             components.append(component) | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-05 17:22:37 +00:00
										 |  |  |             if not (i == 0 and j == 0): | 
					
						
							|  |  |  |                 max_ac_component = \ | 
					
						
							|  |  |  |                     max(max_ac_component, abs(component[0]), | 
					
						
							|  |  |  |                         abs(component[1]), abs(component[2])) | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     # Encode components | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |     dc_value = (linear_to_srgb(components[0][0]) << 16) + \ | 
					
						
							|  |  |  |         (linear_to_srgb(components[0][1]) << 8) + \ | 
					
						
							| 
									
										
										
										
											2020-03-22 20:36:19 +00:00
										 |  |  |         linear_to_srgb(components[0][2]) | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |     quant_max_ac_component = int(max(0, min(82, | 
					
						
							|  |  |  |                                             math.floor(max_ac_component * | 
					
						
							|  |  |  |                                                        166 - 0.5)))) | 
					
						
							|  |  |  |     ac_component_norm_factor = float(quant_max_ac_component + 1) / 166.0 | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |     ac_values = [] | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     for r, g, b in components[1:]: | 
					
						
							| 
									
										
										
										
											2020-04-05 17:22:37 +00:00
										 |  |  |         r2 = r / ac_component_norm_factor | 
					
						
							|  |  |  |         g2 = g / ac_component_norm_factor | 
					
						
							|  |  |  |         b2 = b / ac_component_norm_factor | 
					
						
							|  |  |  |         r3 = math.floor(sign_pow(r2, 0.5) * 9.0 + 9.5) | 
					
						
							|  |  |  |         g3 = math.floor(sign_pow(g2, 0.5) * 9.0 + 9.5) | 
					
						
							|  |  |  |         b3 = math.floor(sign_pow(b2, 0.5) * 9.0 + 9.5) | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |         ac_values.append( | 
					
						
							| 
									
										
										
										
											2020-04-05 17:22:37 +00:00
										 |  |  |             int(max(0.0, min(18.0, r3))) * 19 * 19 + | 
					
						
							|  |  |  |             int(max(0.0, min(18.0, g3))) * 19 + | 
					
						
							|  |  |  |             int(max(0.0, min(18.0, b3))) | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     # Build final blurhash | 
					
						
							| 
									
										
										
										
											2020-04-01 22:22:51 +00:00
										 |  |  |     blurhash = "" | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     blurhash += base83_encode((components_x - 1) + (components_y - 1) * 9, 1) | 
					
						
							|  |  |  |     blurhash += base83_encode(quant_max_ac_component, 1) | 
					
						
							|  |  |  |     blurhash += base83_encode(dc_value, 4) | 
					
						
							|  |  |  |     for ac_value in ac_values: | 
					
						
							|  |  |  |         blurhash += base83_encode(ac_value, 2) | 
					
						
							| 
									
										
										
										
											2020-03-22 21:16:02 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-12 19:08:46 +00:00
										 |  |  |     return blurhash |