From 6efc1ffc71b2e02ac2c21c8172c9339ce28a4136 Mon Sep 17 00:00:00 2001 From: Ace Date: Fri, 29 May 2020 04:00:39 +0200 Subject: [PATCH] Added rendering capabilites Removed dummy settings.json file --- inkycal/main.py | 251 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 197 insertions(+), 54 deletions(-) diff --git a/inkycal/main.py b/inkycal/main.py index 116e2ab..923f3d5 100644 --- a/inkycal/main.py +++ b/inkycal/main.py @@ -1,7 +1,7 @@ -from config import Settings, Layout +from inkycal import Settings, Layout from inkycal.custom import * -import os.path.exists +from os.path import exists import traceback import logging import arrow @@ -20,27 +20,50 @@ except ImportError: print('pip3 install numpy') logger = logging.getLogger('inkycal') -logger.setLevel(level=logging.DEBUG) +logger.setLevel(level=logging.ERROR) -class inkycal: +class Inkycal: """Main class""" - def __init__(self, settings_path, render=False): + def __init__(self, settings_path, render=True): """initialise class settings_path = str -> location/folder of settings file render = bool -> show something on the ePaper? """ + self._release = '2.0.0beta' + # Check if render is boolean if not isinstance(render, bool): raise Exception('render must be True or False, not "{}"'.format(render)) self.render = render - # load+validate settings file. Import and setup specified modules + # Init settings class 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() for module in self.active_modules: try: - loader = 'from modules import {0}'.format(module) + loader = 'from inkycal.modules import {0}'.format(module) module_data = self.Settings.get_config(module) size, conf = module_data['size'], module_data['config'] setup = 'self.{} = {}(size, conf)'.format(module, module) @@ -95,7 +118,12 @@ class inkycal: return remaining_time 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: generate_im = 'self.{0}.generate_image()'.format(module) @@ -107,17 +135,20 @@ class inkycal: print('Error!') print(traceback.format_exc()) - def run(self, render = True): + def run(self): """Runs the main inykcal program nonstop (cannot be stopped anymore!) - Set render to True to show something on the display""" - - # TODO: rendering - # TODO: printing traceback on display (or at least a smaller message?) - # Upside down - # Calibration - # Stitch images together ,merge black&colour if required + Will show something on the display if render was set to True""" - # Count the number of times without any crashs + # TODO: printing traceback on display (or at least a smaller message?) + # Calibration + + # 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 while True: @@ -133,57 +164,169 @@ class inkycal: counter = 0 print('OK') - if render == True: - print('rendering....') -## if upside_down == True: -## image = image.rotate(180, expand=True) -## if three_colour_support == True: -## image_col = image_col.rotate(180, expand=True) + # Assemble image from each module + self._assemble() + + # Check if image should be rendered + if self.render == 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('{} display_updates'.format(counter)) + print('{} display updates'.format(counter)) + print('That was {}'.format(runtime.humanize())) + counter += 1 - sleep_time = self.countdown(10) ##### + sleep_time = self.countdown() time.sleep(sleep_time) + def _merge_bands(): + """Merges black and coloured bands for black-white ePapers + returns the merged image + """ - def _merge() - """Stitches images from each module a single one (for each colour) - Merges black and colour band for black-white epaper - """ + im_path = images - image = Image.new('RGB', - im_location = images - # Check if both files exist - # Center sub images + im1_path, im2_path = images+'canvas.png', images+'canvas_colour.png' + + # If there is an image for black and colour, merge them + if exists(im1_path) and exists(im2_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) + + # If there is no image for the coloured-band, return the bw-image + elif exists(im1_path) and not exists(im2_path): + im1 = Image.open(im1_name).convert('RGBA') + + return im1 - for module in self.active_modules: - - im1_name, im2_name = module+'.png', module+'_colour.png' + def _assemble(self): + """Assmebles all sub-images to a single image""" - # 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') + # Create an empty canvas with the size of the display + width, height = self.Settings.Layout.display_size + height, width = width, height - # White to transparent pixels - 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) + im_black = Image.new('RGB', (width, height), color = 'white') + im_colour = Image.new('RGB', (width ,height), color = 'white') - # Paste black pixels of im2 on im1 - im2 = clear_white(im2) - im1.paste(im2, (0,0), im2) - im1.save(module+'_comb.png', 'PNG') + # Set cursor for y-axis + im1_cursor = 0 + im2_cursor = 0 - # Check if display can support colour - elif self.Settings.supports_colour == True: + 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..')