| 
									
										
										
										
											2020-05-21 01:00:37 +02:00
										 |  |  | #!/usr/bin/python3 | 
					
						
							|  |  |  | # -*- coding: utf-8 -*- | 
					
						
							| 
									
										
										
										
											2020-11-10 22:48:04 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-21 01:00:37 +02:00
										 |  |  | """
 | 
					
						
							| 
									
										
										
										
											2020-11-10 22:48:04 +01:00
										 |  |  | Image module for Inkycal Project | 
					
						
							| 
									
										
										
										
											2020-05-21 01:00:37 +02:00
										 |  |  | Copyright by aceisace | 
					
						
							|  |  |  | """
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-10 22:48:04 +01:00
										 |  |  | from inkycal.modules.template import inkycal_module | 
					
						
							|  |  |  | from inkycal.custom import * | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-21 01:00:37 +02:00
										 |  |  | from PIL import ImageOps | 
					
						
							|  |  |  | import requests | 
					
						
							|  |  |  | import numpy | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-10 22:48:04 +01:00
										 |  |  | filename = os.path.basename(__file__).split('.py')[0] | 
					
						
							|  |  |  | logger = logging.getLogger(filename) | 
					
						
							|  |  |  | logger.setLevel(level=logging.ERROR) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class Inkyimage(inkycal_module): | 
					
						
							|  |  |  |   """Image class
 | 
					
						
							|  |  |  |   display an image from a given path or URL | 
					
						
							|  |  |  |   """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   name = "Inykcal Image - show an image from a URL or local path" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   requires = { | 
					
						
							|  |  |  |   'path': { | 
					
						
							|  |  |  |     "label":"Please enter the path of the image file (local or URL)", | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   optional = { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   'rotation':{ | 
					
						
							|  |  |  |     "label":"Specify the angle to rotate the image. Default is 0", | 
					
						
							|  |  |  |     "options": [0, 90, 180, 270, 360, "auto"], | 
					
						
							|  |  |  |     "default":0, | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   'layout':{ | 
					
						
							|  |  |  |     "label":"How should the image be displayed on the display? Default is auto", | 
					
						
							|  |  |  |     "options": ['fill', 'center', 'fit', 'auto'], | 
					
						
							|  |  |  |     "default": "auto" | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def __init__(self, config): | 
					
						
							|  |  |  |     """Initialize inkycal_rss module""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     super().__init__(config) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     config = config['config'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # required parameters | 
					
						
							|  |  |  |     for param in self.requires: | 
					
						
							|  |  |  |       if not param in config: | 
					
						
							|  |  |  |         raise Exception('config is missing {}'.format(param)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # optional parameters | 
					
						
							|  |  |  |     self.image_path = self.config['path'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     self.rotation = self.config['rotation'] | 
					
						
							|  |  |  |     self.layout = self.config['layout'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # give an OK message | 
					
						
							|  |  |  |     print('{0} loaded'.format(self.name)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def _validate(self): | 
					
						
							|  |  |  |     """Validate module-specific parameters""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Validate image_path | 
					
						
							|  |  |  |     if not isinstance(self.image_path, str): | 
					
						
							|  |  |  |       print( | 
					
						
							|  |  |  |         'image_path has to be a string: "URL1" or "/home/pi/Desktop/im.png"') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Validate layout | 
					
						
							|  |  |  |     if not isinstance(self.layout, str): | 
					
						
							|  |  |  |       print('layout has to be a string') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def generate_image(self): | 
					
						
							|  |  |  |     """Generate image for this module""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Define new image size with respect to padding | 
					
						
							|  |  |  |     im_width = self.width | 
					
						
							|  |  |  |     im_height = self.height | 
					
						
							|  |  |  |     im_size = im_width, im_height | 
					
						
							|  |  |  |     logger.info('image size: {} x {} px'.format(im_width, im_height)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Try to open the image if it exists and is an image file | 
					
						
							|  |  |  |     try: | 
					
						
							|  |  |  |       if self.image_path.startswith('http'): | 
					
						
							|  |  |  |         logger.debug('identified url') | 
					
						
							|  |  |  |         self.image = Image.open(requests.get(self.image_path, stream=True).raw) | 
					
						
							|  |  |  |       else: | 
					
						
							|  |  |  |         logger.info('identified local path') | 
					
						
							|  |  |  |         self.image = Image.open(self.image_path) | 
					
						
							|  |  |  |     except FileNotFoundError: | 
					
						
							|  |  |  |       raise ('Your file could not be found. Please check the filepath') | 
					
						
							|  |  |  |     except OSError: | 
					
						
							|  |  |  |       raise ('Please check if the path points to an image file.') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     logger.debug(('image-width:', self.image.width)) | 
					
						
							|  |  |  |     logger.debug(('image-height:', self.image.height)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Create an image for black pixels and one for coloured pixels | 
					
						
							|  |  |  |     im_black = Image.new('RGB', size = im_size, color = 'white') | 
					
						
							|  |  |  |     im_colour = Image.new('RGB', size = im_size, color = 'white') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # do the required operations | 
					
						
							|  |  |  |     self._remove_alpha() | 
					
						
							|  |  |  |     self._to_layout() | 
					
						
							|  |  |  |     black, colour = self._map_colours() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # paste the images on the canvas | 
					
						
							|  |  |  |     im_black.paste(black, (self.x, self.y)) | 
					
						
							|  |  |  |     im_colour.paste(colour, (self.x, self.y)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Save images of black and colour channel in image-folder | 
					
						
							|  |  |  |     im_black.save(images+self.name+'.png', 'PNG') | 
					
						
							|  |  |  |     im_colour.save(images+self.name+'_colour.png', 'PNG') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def _rotate(self, angle=None): | 
					
						
							|  |  |  |     """Rotate the image to a given angle
 | 
					
						
							|  |  |  |     angle must be one of :[0, 90, 180, 270, 360, 'auto'] | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     im = self.image | 
					
						
							|  |  |  |     if angle == None: | 
					
						
							|  |  |  |       angle = self.rotation | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Check if angle is supported | 
					
						
							|  |  |  |     if angle not in self._allowed_rotation: | 
					
						
							|  |  |  |       print('invalid angle provided, setting to fallback: 0 deg') | 
					
						
							|  |  |  |       angle = 0 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Autoflip the image if angle == 'auto' | 
					
						
							|  |  |  |     if angle == 'auto': | 
					
						
							|  |  |  |       if (im.width > self.height) and (im.width < self.height): | 
					
						
							|  |  |  |         print('display vertical, image horizontal -> flipping image') | 
					
						
							|  |  |  |         image = im.rotate(90, expand=True) | 
					
						
							|  |  |  |       if (im.width < self.height) and (im.width > self.height): | 
					
						
							|  |  |  |         print('display horizontal, image vertical -> flipping image') | 
					
						
							|  |  |  |         image = im.rotate(90, expand=True) | 
					
						
							|  |  |  |     # if not auto, flip to specified angle | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |       image = im.rotate(angle, expand = True) | 
					
						
							|  |  |  |     self.image = image | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def _fit_width(self, width=None): | 
					
						
							|  |  |  |     """Resize an image to desired width""" | 
					
						
							|  |  |  |     im = self.image | 
					
						
							|  |  |  |     if width == None: width = self.width | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     logger.debug(('resizing width from', im.width, 'to')) | 
					
						
							|  |  |  |     wpercent = (width/float(im.width)) | 
					
						
							|  |  |  |     hsize = int((float(im.height)*float(wpercent))) | 
					
						
							|  |  |  |     image = im.resize((width, hsize), Image.ANTIALIAS) | 
					
						
							|  |  |  |     logger.debug(image.width) | 
					
						
							|  |  |  |     self.image = image | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def _fit_height(self, height=None): | 
					
						
							|  |  |  |     """Resize an image to desired height""" | 
					
						
							|  |  |  |     im = self.image | 
					
						
							|  |  |  |     if height == None: height = self.height | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     logger.debug(('resizing height from', im.height, 'to')) | 
					
						
							|  |  |  |     hpercent = (height / float(im.height)) | 
					
						
							|  |  |  |     wsize = int(float(im.width) * float(hpercent)) | 
					
						
							|  |  |  |     image = im.resize((wsize, height), Image.ANTIALIAS) | 
					
						
							|  |  |  |     logger.debug(image.height) | 
					
						
							|  |  |  |     self.image = image | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def _to_layout(self, mode=None): | 
					
						
							|  |  |  |     """Adjust the image to suit the layout
 | 
					
						
							|  |  |  |     mode can be center, fit or fill"""
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     im = self.image | 
					
						
							|  |  |  |     if mode == None: mode = self.layout | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if mode not in self._allowed_layout: | 
					
						
							|  |  |  |       print('{} is not supported. Should be one of {}'.format( | 
					
						
							|  |  |  |         mode, self._allowed_layout)) | 
					
						
							|  |  |  |       print('setting layout to fallback: centre') | 
					
						
							|  |  |  |       mode = 'center' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # If mode is center, just center the image | 
					
						
							|  |  |  |     if mode == 'center': | 
					
						
							|  |  |  |       pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # if mode is fit, adjust height of the image while keeping ascept-ratio | 
					
						
							|  |  |  |     if mode == 'fit': | 
					
						
							|  |  |  |       self._fit_height() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # if mode is fill, enlargen or shrink the image to fit width | 
					
						
							|  |  |  |     if mode == 'fill': | 
					
						
							|  |  |  |       self._fit_width() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # in auto mode, flip image automatically and fit both height and width | 
					
						
							|  |  |  |     if mode == 'auto': | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # Check if width is bigger than height and rotate by 90 deg if true | 
					
						
							|  |  |  |       if im.width > im.height: | 
					
						
							|  |  |  |         self._rotate(90) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # fit both height and width | 
					
						
							|  |  |  |       self._fit_height() | 
					
						
							|  |  |  |       self._fit_width() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if self.image.width > self.width: | 
					
						
							|  |  |  |       x = int( (self.image.width - self.width) / 2) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |       x = int( (self.width - self.image.width) / 2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if self.image.height > self.height: | 
					
						
							|  |  |  |       y = int( (self.image.height - self.height) / 2) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |       y = int( (self.height - self.image.height) / 2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     self.x, self.y = x, y | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def _remove_alpha(self): | 
					
						
							|  |  |  |     im = self.image | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if len(im.getbands()) == 4: | 
					
						
							|  |  |  |       logger.debug('removing transparency') | 
					
						
							|  |  |  |       bg = Image.new('RGBA', (im.width, im.height), 'white') | 
					
						
							|  |  |  |       im = Image.alpha_composite(bg, im) | 
					
						
							|  |  |  |     self.image.paste(im, (0,0)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   def _map_colours(self, colours = None): | 
					
						
							|  |  |  |     """Map image colours to display-supported colours """ | 
					
						
							|  |  |  |     im = self.image.convert('RGB') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if colours == 'bw': | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # For black-white images, use monochrome dithering | 
					
						
							|  |  |  |       im_black = im.convert('1', dither=True) | 
					
						
							|  |  |  |       im_colour = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     elif colours == 'bwr': | 
					
						
							|  |  |  |       # For black-white-red images, create corresponding palette | 
					
						
							|  |  |  |       pal = [255,255,255, 0,0,0, 255,0,0, 255,255,255] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     elif colours == 'bwy': | 
					
						
							|  |  |  |       # For black-white-yellow images, create corresponding palette""" | 
					
						
							|  |  |  |       pal = [255,255,255, 0,0,0, 255,255,0, 255,255,255] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Map each pixel of the opened image to the Palette | 
					
						
							|  |  |  |     if colours == 'bwr' or colours == 'bwy': | 
					
						
							|  |  |  |       palette_im = Image.new('P', (3,1)) | 
					
						
							|  |  |  |       palette_im.putpalette(pal * 64) | 
					
						
							|  |  |  |       quantized_im = im.quantize(palette=palette_im) | 
					
						
							|  |  |  |       quantized_im.convert('RGB') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # Create buffer for coloured pixels | 
					
						
							|  |  |  |       buffer1 = numpy.array(quantized_im.convert('RGB')) | 
					
						
							|  |  |  |       r1,g1,b1 = buffer1[:, :, 0], buffer1[:, :, 1], buffer1[:, :, 2] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # Create buffer for black pixels | 
					
						
							|  |  |  |       buffer2 = numpy.array(quantized_im.convert('RGB')) | 
					
						
							|  |  |  |       r2,g2,b2 = buffer2[:, :, 0], buffer2[:, :, 1], buffer2[:, :, 2] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if colours == 'bwr': | 
					
						
							|  |  |  |         # Create image for only red pixels | 
					
						
							|  |  |  |         buffer2[numpy.logical_and(r2 ==  0, b2 == 0)] = [255,255,255] # black->white | 
					
						
							|  |  |  |         buffer2[numpy.logical_and(r2 ==  255, b2 == 0)] = [0,0,0] #red->black | 
					
						
							|  |  |  |         im_colour = Image.fromarray(buffer2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Create image for only black pixels | 
					
						
							|  |  |  |         buffer1[numpy.logical_and(r1 ==  255, b1 == 0)] = [255,255,255] | 
					
						
							|  |  |  |         im_black = Image.fromarray(buffer1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if colours == 'bwy': | 
					
						
							|  |  |  |         # Create image for only yellow pixels | 
					
						
							|  |  |  |         buffer2[numpy.logical_and(r2 ==  0, b2 == 0)] = [255,255,255] # black->white | 
					
						
							|  |  |  |         buffer2[numpy.logical_and(g2 == 255, b2 == 0)] = [0,0,0] #yellow -> black | 
					
						
							|  |  |  |         im_colour = Image.fromarray(buffer2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Create image for only black pixels | 
					
						
							|  |  |  |         buffer1[numpy.logical_and(g1 == 255, b1 == 0)] = [255,255,255] | 
					
						
							|  |  |  |         im_black = Image.fromarray(buffer1) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return im_black, im_colour | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @staticmethod | 
					
						
							|  |  |  |   def save(image, path): | 
					
						
							|  |  |  |     im = self.image | 
					
						
							|  |  |  |     im.save(path, 'PNG') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @staticmethod | 
					
						
							|  |  |  |   def _show(image): | 
					
						
							|  |  |  |     """Preview the image on gpicview (only works on Rapsbian with Desktop)""" | 
					
						
							|  |  |  |     path = '/home/pi/Desktop/' | 
					
						
							|  |  |  |     image.save(path+'temp.png') | 
					
						
							|  |  |  |     os.system("gpicview "+path+'temp.png') | 
					
						
							|  |  |  |     os.system('rm '+path+'temp.png') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | if __name__ == '__main__': | 
					
						
							|  |  |  |   print('running {0} in standalone/debug mode'.format(filename)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   #a = Inkyimage((480,800), {'path': "https://raw.githubusercontent.com/aceisace/Inky-Calendar/dev_ver2_0/Gallery/logo.png"}) | 
					
						
							|  |  |  |   #a = Inkyimage((480,800), {'path': "https://raw.githubusercontent.com/aceisace/Inky-Calendar/dev_ver2_0/Gallery/logo.png"}) | 
					
						
							|  |  |  |   a = Inkyimage((480, 800), {'path': "/home/pi/Desktop/im/IMG_0475.JPG"}) | 
					
						
							|  |  |  |   a.generate_image() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 |