Allow usage without display and SPI when setting render->False
Generated images will be available in the images folder Generate colour full-screen image when combining sub-images
This commit is contained in:
		| @@ -1,32 +0,0 @@ | |||||||
| from PIL import Image |  | ||||||
| import numpy |  | ||||||
|  |  | ||||||
| image1_path = "/home/pi/Desktop/cal.png" |  | ||||||
| image2_path = "/home/pi/Desktop/cal2.png" |  | ||||||
| output_file = "/home/pi/Desktop/merged.png" |  | ||||||
|  |  | ||||||
| def merge(image1, image2, out_filename): |  | ||||||
|   """Merge black pixels from image2 into image 1 |  | ||||||
|   module_name = name of the module generating the image |  | ||||||
|   out_filename = what name to give to the finished file |  | ||||||
|   """ |  | ||||||
|    |  | ||||||
|   im1_name, im2_name = image1_path, image2_path |  | ||||||
|   im1 = Image.open(im1_name).convert('RGBA') |  | ||||||
|   im2 = Image.open(im2_name).convert('RGBA') |  | ||||||
|  |  | ||||||
|   def clear_white(img): |  | ||||||
|     """Replace all white pixels from image with transparent pixels |  | ||||||
|     """ |  | ||||||
|     x = numpy.asarray(img.convert('RGBA')).copy() |  | ||||||
|     x[:, :, 3] = (255 * (x[:, :, :3] != 255).any(axis=2)).astype(numpy.uint8) |  | ||||||
|     return Image.fromarray(x) |  | ||||||
|  |  | ||||||
|   im2 = clear_white(im2) |  | ||||||
|   im1.paste(im2, (0,0), im2) |  | ||||||
|   im1.save(out_filename+'.png', 'PNG') |  | ||||||
|  |  | ||||||
| merge(image1_path, image2_path, output_file) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| print('Done') |  | ||||||
							
								
								
									
										104
									
								
								inkycal/main.py
									
									
									
									
									
								
							
							
						
						
									
										104
									
								
								inkycal/main.py
									
									
									
									
									
								
							| @@ -127,7 +127,7 @@ class Inkycal: | |||||||
|         self.optimize = True |         self.optimize = True | ||||||
|  |  | ||||||
|         # Load drivers if image should be rendered |         # Load drivers if image should be rendered | ||||||
|         if self.render == True: |         if self.render: | ||||||
|             # Init Display class with model in settings file |             # Init Display class with model in settings file | ||||||
|             # from inkycal.display import Display |             # from inkycal.display import Display | ||||||
|             self.Display = Display(settings["model"]) |             self.Display = Display(settings["model"]) | ||||||
| @@ -177,7 +177,7 @@ class Inkycal: | |||||||
|         """Returns the remaining time in seconds until next display update""" |         """Returns the remaining time in seconds until next display update""" | ||||||
|  |  | ||||||
|         # Check if empty, if empty, use value from settings file |         # Check if empty, if empty, use value from settings file | ||||||
|         if interval_mins == None: |         if interval_mins is None: | ||||||
|             interval_mins = self.settings["update_interval"] |             interval_mins = self.settings["update_interval"] | ||||||
|  |  | ||||||
|         # Find out at which minutes the update should happen |         # Find out at which minutes the update should happen | ||||||
| @@ -225,7 +225,7 @@ class Inkycal: | |||||||
|                 black.save(f"{self.image_folder}/module{number}_black.png", "PNG") |                 black.save(f"{self.image_folder}/module{number}_black.png", "PNG") | ||||||
|                 colour.save(f"{self.image_folder}/module{number}_colour.png", "PNG") |                 colour.save(f"{self.image_folder}/module{number}_colour.png", "PNG") | ||||||
|                 print('OK!') |                 print('OK!') | ||||||
|             except Exception as Error: |             except: | ||||||
|                 errors.append(number) |                 errors.append(number) | ||||||
|                 self.info += f"module {number}: Error!  " |                 self.info += f"module {number}: Error!  " | ||||||
|                 print('Error!') |                 print('Error!') | ||||||
| @@ -240,7 +240,7 @@ class Inkycal: | |||||||
|     def run(self): |     def run(self): | ||||||
|         """Runs main program in nonstop mode. |         """Runs main program in nonstop mode. | ||||||
|  |  | ||||||
|         Uses a infinity loop to run Inkycal nonstop. Inkycal generates the image |         Uses an infinity loop to run Inkycal nonstop. Inkycal generates the image | ||||||
|         from all modules, assembles them in one image, refreshed the E-Paper and |         from all modules, assembles them in one image, refreshed the E-Paper and | ||||||
|         then sleeps until the next sheduled update. |         then sleeps until the next sheduled update. | ||||||
|         """ |         """ | ||||||
| @@ -270,7 +270,7 @@ class Inkycal: | |||||||
|  |  | ||||||
|             for number in range(1, self._module_number): |             for number in range(1, self._module_number): | ||||||
|  |  | ||||||
|                 name = eval(f"self.module_{number}.name") |                 # name = eval(f"self.module_{number}.name") | ||||||
|                 module = eval(f'self.module_{number}') |                 module = eval(f'self.module_{number}') | ||||||
|  |  | ||||||
|                 try: |                 try: | ||||||
| @@ -278,7 +278,7 @@ class Inkycal: | |||||||
|                     black.save(f"{self.image_folder}/module{number}_black.png", "PNG") |                     black.save(f"{self.image_folder}/module{number}_black.png", "PNG") | ||||||
|                     colour.save(f"{self.image_folder}/module{number}_colour.png", "PNG") |                     colour.save(f"{self.image_folder}/module{number}_colour.png", "PNG") | ||||||
|                     self.info += f"module {number}: OK  " |                     self.info += f"module {number}: OK  " | ||||||
|                 except Exception as Error: |                 except: | ||||||
|                     errors.append(number) |                     errors.append(number) | ||||||
|                     print('error!') |                     print('error!') | ||||||
|                     print(traceback.format_exc()) |                     print(traceback.format_exc()) | ||||||
| @@ -297,12 +297,12 @@ class Inkycal: | |||||||
|             self._assemble() |             self._assemble() | ||||||
|  |  | ||||||
|             # Check if image should be rendered |             # Check if image should be rendered | ||||||
|             if self.render == True: |             if self.render: | ||||||
|                 Display = self.Display |                 display = self.Display | ||||||
|  |  | ||||||
|                 self._calibration_check() |                 self._calibration_check() | ||||||
|  |  | ||||||
|                 if self.supports_colour == True: |                 if self.supports_colour: | ||||||
|                     im_black = Image.open(f"{self.image_folder}/canvas.png") |                     im_black = Image.open(f"{self.image_folder}/canvas.png") | ||||||
|                     im_colour = Image.open(f"{self.image_folder}/canvas_colour.png") |                     im_colour = Image.open(f"{self.image_folder}/canvas_colour.png") | ||||||
|  |  | ||||||
| @@ -312,10 +312,10 @@ class Inkycal: | |||||||
|                         im_colour = upside_down(im_colour) |                         im_colour = upside_down(im_colour) | ||||||
|  |  | ||||||
|                     # render the image on the display |                     # render the image on the display | ||||||
|                     Display.render(im_black, im_colour) |                     display.render(im_black, im_colour) | ||||||
|  |  | ||||||
|                 # Part for black-white ePapers |                 # Part for black-white ePapers | ||||||
|                 elif self.supports_colour == False: |                 elif not self.supports_colour: | ||||||
|  |  | ||||||
|                     im_black = self._merge_bands() |                     im_black = self._merge_bands() | ||||||
|  |  | ||||||
| @@ -323,7 +323,7 @@ class Inkycal: | |||||||
|                     if self.settings['orientation'] == 180: |                     if self.settings['orientation'] == 180: | ||||||
|                         im_black = upside_down(im_black) |                         im_black = upside_down(im_black) | ||||||
|  |  | ||||||
|                     Display.render(im_black) |                     display.render(im_black) | ||||||
|  |  | ||||||
|             print(f'\nNo errors since {counter} display updates \n' |             print(f'\nNo errors since {counter} display updates \n' | ||||||
|                   f'program started {runtime.humanize()}') |                   f'program started {runtime.humanize()}') | ||||||
| @@ -331,13 +331,12 @@ class Inkycal: | |||||||
|             sleep_time = self.countdown() |             sleep_time = self.countdown() | ||||||
|             time.sleep(sleep_time) |             time.sleep(sleep_time) | ||||||
|  |  | ||||||
|     def _merge_bands(self): |     @staticmethod | ||||||
|  |     def _merge_bands(): | ||||||
|         """Merges black and coloured bands for black-white ePapers |         """Merges black and coloured bands for black-white ePapers | ||||||
|         returns the merged image |         returns the merged image | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         im_path = images |  | ||||||
|  |  | ||||||
|         im1_path, im2_path = images + 'canvas.png', images + 'canvas_colour.png' |         im1_path, im2_path = images + 'canvas.png', images + 'canvas_colour.png' | ||||||
|  |  | ||||||
|         # If there is an image for black and colour, merge them |         # If there is an image for black and colour, merge them | ||||||
| @@ -350,7 +349,10 @@ class Inkycal: | |||||||
|  |  | ||||||
|         # If there is no image for the coloured-band, return the bw-image |         # If there is no image for the coloured-band, return the bw-image | ||||||
|         elif os.path.exists(im1_path) and not os.path.exists(im2_path): |         elif os.path.exists(im1_path) and not os.path.exists(im2_path): | ||||||
|             im1 = Image.open(im1_name).convert('RGBA') |             im1 = Image.open(im1_path).convert('RGBA') | ||||||
|  |  | ||||||
|  |         else: | ||||||
|  |             raise FileNotFoundError("Inkycal cannot find images to merge") | ||||||
|  |  | ||||||
|         return im1 |         return im1 | ||||||
|  |  | ||||||
| @@ -384,8 +386,7 @@ class Inkycal: | |||||||
|                 im1_size = im1.size |                 im1_size = im1.size | ||||||
|  |  | ||||||
|                 # Get the size of the section |                 # Get the size of the section | ||||||
|                 section_size = [i for i in self.settings['modules'] if \ |                 section_size = [i for i in self.settings['modules'] if i['position'] == number][0]['config']['size'] | ||||||
|                                 i['position'] == number][0]['config']['size'] |  | ||||||
|  |  | ||||||
|                 # Calculate coordinates to center the image |                 # Calculate coordinates to center the image | ||||||
|                 x = int((section_size[0] - im1_size[0]) / 2) |                 x = int((section_size[0] - im1_size[0]) / 2) | ||||||
| @@ -410,8 +411,7 @@ class Inkycal: | |||||||
|                 im2_size = im2.size |                 im2_size = im2.size | ||||||
|  |  | ||||||
|                 # Get the size of the section |                 # Get the size of the section | ||||||
|                 section_size = [i for i in self.settings['modules'] if \ |                 section_size = [i for i in self.settings['modules'] if i['position'] == number][0]['config']['size'] | ||||||
|                                 i['position'] == number][0]['config']['size'] |  | ||||||
|  |  | ||||||
|                 # Calculate coordinates to center the image |                 # Calculate coordinates to center the image | ||||||
|                 x = int((section_size[0] - im2_size[0]) / 2) |                 x = int((section_size[0] - im2_size[0]) / 2) | ||||||
| @@ -431,7 +431,7 @@ class Inkycal: | |||||||
|         # Add info-section if specified -- |         # Add info-section if specified -- | ||||||
|  |  | ||||||
|         # Calculate the max. fontsize for info-section |         # Calculate the max. fontsize for info-section | ||||||
|         if self.settings['info_section'] == True: |         if self.settings['info_section']: | ||||||
|             info_height = self.settings["info_section_height"] |             info_height = self.settings["info_section_height"] | ||||||
|             info_width = width |             info_width = width | ||||||
|             font = self.font = ImageFont.truetype( |             font = self.font = ImageFont.truetype( | ||||||
| @@ -442,14 +442,44 @@ class Inkycal: | |||||||
|                   self.info, font=font) |                   self.info, font=font) | ||||||
|  |  | ||||||
|         # optimize the image by mapping colours to pure black and white |         # optimize the image by mapping colours to pure black and white | ||||||
|         if self.optimize == True: |         if self.optimize: | ||||||
|             im_black = self._optimize_im(im_black) |             im_black = self._optimize_im(im_black) | ||||||
|             im_colour = self._optimize_im(im_colour) |             im_colour = self._optimize_im(im_colour) | ||||||
|  |  | ||||||
|         im_black.save(self.image_folder + '/canvas.png', 'PNG') |         im_black.save(self.image_folder + '/canvas.png', 'PNG') | ||||||
|         im_colour.save(self.image_folder + '/canvas_colour.png', 'PNG') |         im_colour.save(self.image_folder + '/canvas_colour.png', 'PNG') | ||||||
|  |  | ||||||
|     def _optimize_im(self, image, threshold=220): |         # Additionally combine the two images with color | ||||||
|  |         def clear_white(img): | ||||||
|  |             """Replace all white pixels from image with transparent pixels | ||||||
|  |             """ | ||||||
|  |             x = numpy.asarray(img.convert('RGBA')).copy() | ||||||
|  |             x[:, :, 3] = (255 * (x[:, :, :3] != 255).any(axis=2)).astype(numpy.uint8) | ||||||
|  |             return Image.fromarray(x) | ||||||
|  |  | ||||||
|  |         # Additionally combine the two images with color | ||||||
|  |         def black_to_colour(img): | ||||||
|  |             """Replace all black pixels from image with red pixels | ||||||
|  |             """ | ||||||
|  |             buffer = numpy.array(img.convert('RGB')) | ||||||
|  |             red, green = buffer[:, :, 0], buffer[:, :, 1] | ||||||
|  |  | ||||||
|  |             threshold = 220 | ||||||
|  |  | ||||||
|  |             # non-white -> red | ||||||
|  |             buffer[numpy.logical_and(red <= threshold, green <= threshold)] = [255, 0, 0] | ||||||
|  |  | ||||||
|  |             return Image.fromarray(buffer) | ||||||
|  |  | ||||||
|  |         # Save full-screen images as well | ||||||
|  |         im_black = clear_white(im_black) | ||||||
|  |         im_colour = black_to_colour(im_colour) | ||||||
|  |  | ||||||
|  |         im_colour.paste(im_black, (0, 0), im_black) | ||||||
|  |         im_colour.save(images + 'full-screen.png', 'PNG') | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def _optimize_im(image, threshold=220): | ||||||
|         """Optimize the image for rendering on ePaper displays""" |         """Optimize the image for rendering on ePaper displays""" | ||||||
|  |  | ||||||
|         buffer = numpy.array(image.convert('RGB')) |         buffer = numpy.array(image.convert('RGB')) | ||||||
| @@ -506,20 +536,16 @@ class Inkycal: | |||||||
|  |  | ||||||
|             then register it with this function:: |             then register it with this function:: | ||||||
|  |  | ||||||
|               >>> import Inkycal |               >>> from inkycal import Inkycal | ||||||
|               >>> Inkycal.add_module('/full/path/to/the/module/in/inkycal/modules.py') |               >>> Inkycal.add_module('/full/path/to/the/module/in/inkycal/modules.py') | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         module_folder = top_level + '/inkycal/modules' |         module_folder = top_level + '/inkycal/modules' | ||||||
|  |  | ||||||
|         # Check if module is inside the modules folder |         if module_folder in filepath: | ||||||
|         if not module_folder in filepath: |  | ||||||
|             raise Exception(f"Your module should be in {module_folder} " |  | ||||||
|                             f"but is currently in {filepath}") |  | ||||||
|  |  | ||||||
|             filename = filepath.split('.py')[0].split('/')[-1] |             filename = filepath.split('.py')[0].split('/')[-1] | ||||||
|  |  | ||||||
|         # Extract name of class from given module and validate if it's a inkycal |             # Extract name of class from given module and validate if it's an inkycal | ||||||
|             # module |             # module | ||||||
|             with open(filepath, mode='r') as module: |             with open(filepath, mode='r') as module: | ||||||
|                 module_content = module.read().splitlines() |                 module_content = module.read().splitlines() | ||||||
| @@ -579,10 +605,15 @@ class Inkycal: | |||||||
|  |  | ||||||
|             print(f"Your module '{filename}' with class '{classname}' has been added " |             print(f"Your module '{filename}' with class '{classname}' has been added " | ||||||
|                   "successfully! Hooray!") |                   "successfully! Hooray!") | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         # Check if module is inside the modules folder | ||||||
|  |         raise Exception(f"Your module should be in {module_folder} " | ||||||
|  |                         f"but is currently in {filepath}") | ||||||
|  |  | ||||||
|     @classmethod |     @classmethod | ||||||
|     def remove_module(cls, filename, remove_file=True): |     def remove_module(cls, filename, remove_file=True): | ||||||
|         """unregisters a inkycal module. |         """unregisters an inkycal module. | ||||||
|  |  | ||||||
|         Looks for given filename.py in /modules folder, removes entries of that |         Looks for given filename.py in /modules folder, removes entries of that | ||||||
|         module in init files inside /inkycal and /inkycal/modules |         module in init files inside /inkycal and /inkycal/modules | ||||||
| @@ -600,7 +631,7 @@ class Inkycal: | |||||||
|  |  | ||||||
|             Use this function to unregister the module from inkycal:: |             Use this function to unregister the module from inkycal:: | ||||||
|  |  | ||||||
|               >>> import Inkycal |               >>> from inkycal import Inkycal | ||||||
|               >>> Inkycal.remove_module('mymodule.py') |               >>> Inkycal.remove_module('mymodule.py') | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
| @@ -621,7 +652,6 @@ class Inkycal: | |||||||
|                           'Not removing it.') |                           'Not removing it.') | ||||||
|                     return |                     return | ||||||
|  |  | ||||||
|  |  | ||||||
|         except FileNotFoundError: |         except FileNotFoundError: | ||||||
|             print(f"No module named {filename} found in {module_folder}") |             print(f"No module named {filename} found in {module_folder}") | ||||||
|             return |             return | ||||||
| @@ -649,14 +679,14 @@ class Inkycal: | |||||||
|         # Remove lines that contain classname |         # Remove lines that contain classname | ||||||
|         with open(f"{top_level}/inkycal/__init__.py", mode='w') as file: |         with open(f"{top_level}/inkycal/__init__.py", mode='w') as file: | ||||||
|             for line in inkycal_init: |             for line in inkycal_init: | ||||||
|                 if not filename in line: |                 if filename in line: | ||||||
|                     file.write(line + '\n') |  | ||||||
|                 else: |  | ||||||
|                     print('found, removing') |                     print('found, removing') | ||||||
|  |                 else: | ||||||
|  |                     file.write(line + '\n') | ||||||
|  |  | ||||||
|         # remove the file of the third party module if it exists and remove_file |         # remove the file of the third party module if it exists and remove_file | ||||||
|         # was set to True (default) |         # was set to True (default) | ||||||
|         if os.path.exists(f"{module_folder}/{filename}.py") and remove_file == True: |         if os.path.exists(f"{module_folder}/{filename}.py") and remove_file is True: | ||||||
|             print('deleting module file') |             print('deleting module file') | ||||||
|             os.remove(f"{module_folder}/{filename}.py") |             os.remove(f"{module_folder}/{filename}.py") | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user