Added rendering capabilites
Removed dummy settings.json file
This commit is contained in:
		
							
								
								
									
										229
									
								
								inkycal/main.py
									
									
									
									
									
								
							
							
						
						
									
										229
									
								
								inkycal/main.py
									
									
									
									
									
								
							| @@ -1,7 +1,7 @@ | |||||||
| from config import Settings, Layout | from inkycal import Settings, Layout | ||||||
| from inkycal.custom import * | from inkycal.custom import * | ||||||
|  |  | ||||||
| import os.path.exists | from os.path import exists | ||||||
| import traceback | import traceback | ||||||
| import logging | import logging | ||||||
| import arrow | import arrow | ||||||
| @@ -20,27 +20,50 @@ except ImportError: | |||||||
|   print('pip3 install numpy') |   print('pip3 install numpy') | ||||||
|  |  | ||||||
| logger = logging.getLogger('inkycal') | logger = logging.getLogger('inkycal') | ||||||
| logger.setLevel(level=logging.DEBUG) | logger.setLevel(level=logging.ERROR) | ||||||
|  |  | ||||||
| class inkycal: | class Inkycal: | ||||||
|   """Main class""" |   """Main class""" | ||||||
|  |  | ||||||
|   def __init__(self, settings_path, render=False): |   def __init__(self, settings_path, render=True): | ||||||
|     """initialise class |     """initialise class | ||||||
|     settings_path = str -> location/folder of settings file |     settings_path = str -> location/folder of settings file | ||||||
|     render = bool -> show something on the ePaper? |     render = bool -> show something on the ePaper? | ||||||
|     """ |     """ | ||||||
|  |     self._release = '2.0.0beta' | ||||||
|  |  | ||||||
|     # Check if render is boolean |     # Check if render is boolean | ||||||
|     if not isinstance(render, bool): |     if not isinstance(render, bool): | ||||||
|       raise Exception('render must be True or False, not "{}"'.format(render)) |       raise Exception('render must be True or False, not "{}"'.format(render)) | ||||||
|     self.render = render |     self.render = render | ||||||
|  |  | ||||||
|     # load+validate settings file. Import and setup specified modules |     # Init settings class | ||||||
|     self.Settings = Settings(settings_path) |     self.Settings = Settings(settings_path) | ||||||
|  |  | ||||||
|  |     # Check if display support colour | ||||||
|  |     self.supports_colour = self.Settings.Layout.supports_colour | ||||||
|  |  | ||||||
|  |     # Option to flip image upside down | ||||||
|  |     self.upside_down = False | ||||||
|  |  | ||||||
|  |     # Option to use epaper image optimisation | ||||||
|  |     self.optimize = True | ||||||
|  |  | ||||||
|  |     # Load drivers if image should be rendered | ||||||
|  |     if self.render == True: | ||||||
|  |  | ||||||
|  |       # Get model and check if colour can be rendered | ||||||
|  |       model= self.Settings.model | ||||||
|  |  | ||||||
|  |       # Init Display class | ||||||
|  |       from inkycal.display import Display | ||||||
|  |       self.Display = Display(model) | ||||||
|  |  | ||||||
|  |     # load+validate settings file. Import and setup specified modules | ||||||
|     self.active_modules = self.Settings.active_modules() |     self.active_modules = self.Settings.active_modules() | ||||||
|     for module in self.active_modules: |     for module in self.active_modules: | ||||||
|       try: |       try: | ||||||
|         loader = 'from modules import {0}'.format(module) |         loader = 'from inkycal.modules import {0}'.format(module) | ||||||
|         module_data = self.Settings.get_config(module) |         module_data = self.Settings.get_config(module) | ||||||
|         size, conf = module_data['size'], module_data['config'] |         size, conf = module_data['size'], module_data['config'] | ||||||
|         setup = 'self.{} = {}(size, conf)'.format(module, module) |         setup = 'self.{} = {}(size, conf)'.format(module, module) | ||||||
| @@ -95,7 +118,12 @@ class inkycal: | |||||||
|     return remaining_time |     return remaining_time | ||||||
|  |  | ||||||
|   def test(self): |   def test(self): | ||||||
|     """Test if inkycal can be run correctly""" |     """Inkycal test run""" | ||||||
|  |     print('You are running inkycal v{}'.format(self._release)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     print('Running inkyal test-run for {} ePaper'.format( | ||||||
|  |       self.Settings.model)) | ||||||
|  |  | ||||||
|     for module in self.active_modules: |     for module in self.active_modules: | ||||||
|       generate_im = 'self.{0}.generate_image()'.format(module) |       generate_im = 'self.{0}.generate_image()'.format(module) | ||||||
| @@ -107,17 +135,20 @@ class inkycal: | |||||||
|         print('Error!') |         print('Error!') | ||||||
|         print(traceback.format_exc()) |         print(traceback.format_exc()) | ||||||
|  |  | ||||||
|   def run(self, render = True): |   def run(self): | ||||||
|     """Runs the main inykcal program nonstop (cannot be stopped anymore!) |     """Runs the main inykcal program nonstop (cannot be stopped anymore!) | ||||||
|     Set render to True to show something on the display""" |     Will show something on the display if render was set to True""" | ||||||
|  |  | ||||||
|     # TODO: rendering |  | ||||||
|     # TODO: printing traceback on display (or at least a smaller message?) |     # TODO: printing traceback on display (or at least a smaller message?) | ||||||
|     # Upside down |  | ||||||
|     # Calibration |     # Calibration | ||||||
|     # Stitch images together ,merge black&colour if required |  | ||||||
|  |  | ||||||
|     # Count the number of times without any crashs |     # Get the time of initial run | ||||||
|  |     runtime = arrow.now() | ||||||
|  |  | ||||||
|  |     # Function to flip images upside down | ||||||
|  |     upside_down = lambda image: image.rotate(180, expand=True) | ||||||
|  |  | ||||||
|  |     # Count the number of times without any errors | ||||||
|     counter = 1 |     counter = 1 | ||||||
|  |  | ||||||
|     while True: |     while True: | ||||||
| @@ -133,43 +164,60 @@ class inkycal: | |||||||
|           counter = 0 |           counter = 0 | ||||||
|       print('OK') |       print('OK') | ||||||
|  |  | ||||||
|       if render == True: |       # Assemble image from each module | ||||||
|         print('rendering....') |       self._assemble() | ||||||
| ##      if upside_down == True: |  | ||||||
| ##      image = image.rotate(180, expand=True) |       # Check if image should be rendered | ||||||
| ##      if three_colour_support == True: |       if self.render == True: | ||||||
| ##        image_col = image_col.rotate(180, expand=True) |         Display = self.Display | ||||||
|  |  | ||||||
|  |         if self.supports_colour == True: | ||||||
|  |           im_black = Image.open(images+'canvas.png') | ||||||
|  |           im_colour = Image.open(images+'canvas_colour.png') | ||||||
|  |  | ||||||
|  |           # Flip the image by 180° if required | ||||||
|  |           if self.upside_down == True: | ||||||
|  |             upside_down(im_black) | ||||||
|  |             upside_down(im_colour) | ||||||
|  |  | ||||||
|  |           # render the image on the display | ||||||
|  |           Display.render(im_black, im_colour) | ||||||
|  |  | ||||||
|  |         # Part for black-white ePapers | ||||||
|  |         elif self.supports_colour == False: | ||||||
|  |  | ||||||
|  |           im_black = self._merge_bands() | ||||||
|  |  | ||||||
|  |           # Flip the image by 180° if required | ||||||
|  |           if self.upside_down == True: | ||||||
|  |             upside_down(im_black) | ||||||
|  |  | ||||||
|  |           Display.render(im_black) | ||||||
|  |  | ||||||
|       print('\ninkycal has been running without any errors for', end = ' ') |       print('\ninkycal has been running without any errors for', end = ' ') | ||||||
|       print('{} display_updates'.format(counter)) |       print('{} display updates'.format(counter)) | ||||||
|  |       print('That was {}'.format(runtime.humanize())) | ||||||
|  |  | ||||||
|       counter += 1 |       counter += 1 | ||||||
|  |  | ||||||
|       sleep_time = self.countdown(10) ##### |       sleep_time = self.countdown() | ||||||
|       time.sleep(sleep_time) |       time.sleep(sleep_time) | ||||||
|  |  | ||||||
|  |   def _merge_bands(): | ||||||
|   def _merge() |     """Merges black and coloured bands for black-white ePapers | ||||||
|       """Stitches images from each module a single one (for each colour) |     returns the merged image | ||||||
|       Merges black and colour band for black-white epaper |  | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|       image = Image.new('RGB',  |     im_path = images | ||||||
|       im_location = images |  | ||||||
|       # Check if both files exist |  | ||||||
|       # Center sub images |  | ||||||
|  |  | ||||||
|  |     im1_path, im2_path = images+'canvas.png', images+'canvas_colour.png' | ||||||
|  |  | ||||||
|       for module in self.active_modules: |     # If there is an image for black and colour, merge them | ||||||
|  |     if exists(im1_path) and exists(im2_path): | ||||||
|  |  | ||||||
|         im1_name, im2_name = module+'.png', module+'_colour.png' |       im1 = Image.open(im1_name).convert('RGBA') | ||||||
|  |       im2 = Image.open(im2_name).convert('RGBA') | ||||||
|  |  | ||||||
|         # Check if display can only show black-white |  | ||||||
|         if self.Settings.supports_colour == False: |  | ||||||
|           if exists(im1_name) and exists(im2_name): |  | ||||||
|             im1 = Image.open(images+im1_name).convert('RGBA') |  | ||||||
|             im2 = Image.open(images+im2_name).convert('RGBA') |  | ||||||
|  |  | ||||||
|             # White to transparent pixels |  | ||||||
|       def clear_white(img): |       def clear_white(img): | ||||||
|         """Replace all white pixels from image with transparent pixels |         """Replace all white pixels from image with transparent pixels | ||||||
|         """ |         """ | ||||||
| @@ -177,13 +225,108 @@ class inkycal: | |||||||
|         x[:, :, 3] = (255 * (x[:, :, :3] != 255).any(axis=2)).astype(numpy.uint8) |         x[:, :, 3] = (255 * (x[:, :, :3] != 255).any(axis=2)).astype(numpy.uint8) | ||||||
|         return Image.fromarray(x) |         return Image.fromarray(x) | ||||||
|  |  | ||||||
|             # Paste black pixels of im2 on im1 |  | ||||||
|       im2 = clear_white(im2) |       im2 = clear_white(im2) | ||||||
|       im1.paste(im2, (0,0), im2) |       im1.paste(im2, (0,0), im2) | ||||||
|             im1.save(module+'_comb.png', 'PNG') |  | ||||||
|  |  | ||||||
|         # Check if display can support colour |     # If there is no image for the coloured-band, return the bw-image | ||||||
|         elif self.Settings.supports_colour == True: |     elif exists(im1_path) and not exists(im2_path): | ||||||
|  |       im1 = Image.open(im1_name).convert('RGBA') | ||||||
|  |  | ||||||
|  |     return im1 | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   def _assemble(self): | ||||||
|  |     """Assmebles all sub-images to a single image""" | ||||||
|  |  | ||||||
|  |     # Create an empty canvas with the size of the display | ||||||
|  |     width, height = self.Settings.Layout.display_size | ||||||
|  |     height, width = width, height | ||||||
|  |  | ||||||
|  |     im_black = Image.new('RGB', (width, height), color = 'white') | ||||||
|  |     im_colour = Image.new('RGB', (width ,height), color = 'white') | ||||||
|  |  | ||||||
|  |     # Set cursor for y-axis | ||||||
|  |     im1_cursor = 0 | ||||||
|  |     im2_cursor = 0 | ||||||
|  |  | ||||||
|  |     for module in self.active_modules: | ||||||
|  |  | ||||||
|  |       im1_path = images+module+'.png' | ||||||
|  |       im2_path = images+module+'_colour.png' | ||||||
|  |  | ||||||
|  |       # Check if there is an image for the black band | ||||||
|  |       if exists(im1_path): | ||||||
|  |  | ||||||
|  |         # Get actual size of image | ||||||
|  |         im1 = Image.open(im1_path).convert('RGBA') | ||||||
|  |         im1_size = im1.size | ||||||
|  |  | ||||||
|  |         # Get the size of the section | ||||||
|  |         section_size = self.Settings.get_config(module)['size'] | ||||||
|  |  | ||||||
|  |         # Calculate coordinates to center the image | ||||||
|  |         x = int( (section_size[0]-im1_size[0]) /2) | ||||||
|  |  | ||||||
|  |         # If this is the first module, use the y-offset | ||||||
|  |         if im1_cursor == 0: | ||||||
|  |           y = int( (section_size[1]-im1_size[1]) /2) | ||||||
|  |         else: | ||||||
|  |           y = im1_cursor | ||||||
|  |  | ||||||
|  |         # center the image in the section space | ||||||
|  |         im_black.paste(im1, (x,y), im1) | ||||||
|  |  | ||||||
|  |         # Shift the y-axis cursor at the beginning of next section | ||||||
|  |         im1_cursor += section_size[1] - y | ||||||
|  |  | ||||||
|  |       # Check if there is an image for the coloured band | ||||||
|  |       if exists(im2_path): | ||||||
|  |  | ||||||
|  |         # Get actual size of image | ||||||
|  |         im2 = Image.open(im2_path).convert('RGBA') | ||||||
|  |         im2_size = im2.size | ||||||
|  |  | ||||||
|  |         # Get the size of the section | ||||||
|  |         section_size = self.Settings.get_config(module)['size'] | ||||||
|  |  | ||||||
|  |         # Calculate coordinates to center the image | ||||||
|  |         x = int( (section_size[0]-im2_size[0]) /2) | ||||||
|  |  | ||||||
|  |         # If this is the first module, use the y-offset | ||||||
|  |         if im2_cursor == 0: | ||||||
|  |           y = int( (section_size[1]-im2_size[1]) /2) | ||||||
|  |         else: | ||||||
|  |           y = im2_cursor | ||||||
|  |  | ||||||
|  |         # center the image in the section space | ||||||
|  |         im_colour.paste(im2, (x,y), im2) | ||||||
|  |  | ||||||
|  |         # Shift the y-axis cursor at the beginning of next section | ||||||
|  |         im2_cursor += section_size[1] - y | ||||||
|  |  | ||||||
|  |     if self.optimize == True: | ||||||
|  |       self._optimize_im(im_black).save(images+'canvas.png', 'PNG') | ||||||
|  |       self._optimize_im(im_colour).save(images+'canvas_colour.png', 'PNG') | ||||||
|  |     else: | ||||||
|  |       im_black.save(images+'canvas.png', 'PNG') | ||||||
|  |       im_colour.save(images+'canvas_colour.png', 'PNG') | ||||||
|  |  | ||||||
|  |   def _optimize_im(self, image, threshold=220): | ||||||
|  |     """Optimize the image for rendering on ePaper displays""" | ||||||
|  |  | ||||||
|  |     buffer = numpy.array(image.convert('RGB')) | ||||||
|  |     red, green = buffer[:, :, 0], buffer[:, :, 1] | ||||||
|  |     buffer[numpy.logical_and(red <= threshold, green <= threshold)] = [0,0,0] #grey->black | ||||||
|  |     image = Image.fromarray(buffer) | ||||||
|  |     return image | ||||||
|  |  | ||||||
|  |   def calibrate(self): | ||||||
|  |     """Calibrate the ePaper display to prevent burn-ins (ghosting) | ||||||
|  |     Currently has to be run manually""" | ||||||
|  |     self.Display.calibrate() | ||||||
|  |      | ||||||
|  |  | ||||||
|  |   def _check_for_updates(self): | ||||||
|  |     """Check if a new update is available for inkycal""" | ||||||
|  |     raise NotImplementedError('Tha developer were too lazy to implement this..') | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user