finally got the hang of bw/colour image handling
This commit is contained in:
		| @@ -7,9 +7,10 @@ Copyright by aceinnolab | ||||
| """ | ||||
| import logging | ||||
| import os | ||||
| from typing import Literal | ||||
|  | ||||
| import PIL | ||||
| import numpy | ||||
| import PIL | ||||
| import requests | ||||
| from PIL import Image | ||||
|  | ||||
| @@ -17,8 +18,7 @@ logger = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class Inkyimage: | ||||
|     """Custom Imgae class written for commonly used image operations. | ||||
|     """ | ||||
|     """Custom Imgae class written for commonly used image operations.""" | ||||
|  | ||||
|     def __init__(self, image=None): | ||||
|         """Initialize InkyImage module""" | ||||
| @@ -27,9 +27,9 @@ class Inkyimage: | ||||
|         self.image = image | ||||
|  | ||||
|         # give an OK message | ||||
|         logger.info(f'{__name__} loaded') | ||||
|         logger.info(f"{__name__} loaded") | ||||
|  | ||||
|     def load(self, path:str) -> None: | ||||
|     def load(self, path: str) -> None: | ||||
|         """loads an image from a URL or filepath. | ||||
|  | ||||
|         Args: | ||||
| @@ -45,54 +45,54 @@ class Inkyimage: | ||||
|         """ | ||||
|         # Try to open the image if it exists and is an image file | ||||
|         try: | ||||
|             if path.startswith('http'): | ||||
|                 logger.info('loading image from URL') | ||||
|             if path.startswith("http"): | ||||
|                 logger.info("loading image from URL") | ||||
|                 image = Image.open(requests.get(path, stream=True).raw) | ||||
|             else: | ||||
|                 logger.info('loading image from local path') | ||||
|                 logger.info("loading image from local path") | ||||
|                 image = Image.open(path) | ||||
|         except FileNotFoundError: | ||||
|             logger.error('No image file found', exc_info=True) | ||||
|             raise Exception(f'Your file could not be found. Please check the filepath: {path}') | ||||
|             logger.error("No image file found", exc_info=True) | ||||
|             raise Exception(f"Your file could not be found. Please check the filepath: {path}") | ||||
|  | ||||
|         except OSError: | ||||
|             logger.error('Invalid Image file provided', exc_info=True) | ||||
|             raise Exception('Please check if the path points to an image file.') | ||||
|             logger.error("Invalid Image file provided", exc_info=True) | ||||
|             raise Exception("Please check if the path points to an image file.") | ||||
|  | ||||
|         logger.info(f'width: {image.width}, height: {image.height}') | ||||
|         logger.info(f"width: {image.width}, height: {image.height}") | ||||
|  | ||||
|         image.convert(mode='RGBA')  # convert to a more suitable format | ||||
|         image.convert(mode="RGBA")  # convert to a more suitable format | ||||
|         self.image = image | ||||
|         logger.info('loaded Image') | ||||
|         logger.info("loaded Image") | ||||
|  | ||||
|     def clear(self): | ||||
|         """Removes currently saved image if present.""" | ||||
|         if self.image: | ||||
|             self.image = None | ||||
|             logger.info('cleared previous image') | ||||
|             logger.info("cleared previous image") | ||||
|  | ||||
|     def _preview(self): | ||||
|         """Preview the image on gpicview (only works on Rapsbian with Desktop)""" | ||||
|         if self._image_loaded(): | ||||
|             path = '/home/pi/Desktop/' | ||||
|             self.image.save(path + 'temp.png') | ||||
|             os.system("gpicview " + path + 'temp.png') | ||||
|             os.system('rm ' + path + 'temp.png') | ||||
|             path = "/home/pi/Desktop/" | ||||
|             self.image.save(path + "temp.png") | ||||
|             os.system("gpicview " + path + "temp.png") | ||||
|             os.system("rm " + path + "temp.png") | ||||
|  | ||||
|     @staticmethod | ||||
|     def preview(image): | ||||
|         """Previews an image on gpicview (only works on Rapsbian with Desktop).""" | ||||
|         path = '~/temp' | ||||
|         image.save(path + '/temp.png') | ||||
|         os.system("gpicview " + path + '/temp.png') | ||||
|         os.system('rm ' + path + '/temp.png') | ||||
|         path = "~/temp" | ||||
|         image.save(path + "/temp.png") | ||||
|         os.system("gpicview " + path + "/temp.png") | ||||
|         os.system("rm " + path + "/temp.png") | ||||
|  | ||||
|     def _image_loaded(self): | ||||
|         """returns True if image was loaded""" | ||||
|         if self.image: | ||||
|             return True | ||||
|         else: | ||||
|             logger.error('image not loaded') | ||||
|             logger.error("image not loaded") | ||||
|             return False | ||||
|  | ||||
|     def flip(self, angle): | ||||
| @@ -105,12 +105,12 @@ class Inkyimage: | ||||
|  | ||||
|             image = self.image | ||||
|             if not angle % 90 == 0: | ||||
|                 logger.error('Angle must be a multiple of 90') | ||||
|                 logger.error("Angle must be a multiple of 90") | ||||
|                 return | ||||
|  | ||||
|             image = image.rotate(angle, expand=True) | ||||
|             self.image = image | ||||
|             logger.info(f'flipped image by {angle} degrees') | ||||
|             logger.info(f"flipped image by {angle} degrees") | ||||
|  | ||||
|     def autoflip(self, layout: str) -> None: | ||||
|         """flips the image automatically to the given layout. | ||||
| @@ -129,17 +129,17 @@ class Inkyimage: | ||||
|         if self._image_loaded(): | ||||
|  | ||||
|             image = self.image | ||||
|             if layout == 'horizontal': | ||||
|             if layout == "horizontal": | ||||
|                 if image.height > image.width: | ||||
|                     logger.info('image width greater than image height, flipping') | ||||
|                     logger.info("image width greater than image height, flipping") | ||||
|                     image = image.rotate(90, expand=True) | ||||
|  | ||||
|             elif layout == 'vertical': | ||||
|             elif layout == "vertical": | ||||
|                 if image.width > image.height: | ||||
|                     logger.info('image width greater than image height, flipping') | ||||
|                     logger.info("image width greater than image height, flipping") | ||||
|                     image = image.rotate(90, expand=True) | ||||
|             else: | ||||
|                 logger.error('layout not supported') | ||||
|                 logger.error("layout not supported") | ||||
|                 return | ||||
|             self.image = image | ||||
|  | ||||
| @@ -153,26 +153,26 @@ class Inkyimage: | ||||
|             image = self.image | ||||
|  | ||||
|             if len(image.getbands()) == 4: | ||||
|                 logger.info('removing alpha channel') | ||||
|                 bg = Image.new('RGBA', (image.width, image.height), 'white') | ||||
|                 logger.info("removing alpha channel") | ||||
|                 bg = Image.new("RGBA", (image.width, image.height), "white") | ||||
|                 im = Image.alpha_composite(bg, image) | ||||
|  | ||||
|                 self.image.paste(im, (0, 0)) | ||||
|                 logger.info('removed transparency') | ||||
|                 logger.info("removed transparency") | ||||
|  | ||||
|     def resize(self, width=None, height=None): | ||||
|         """Resize an image to desired width or height""" | ||||
|         if self._image_loaded(): | ||||
|  | ||||
|             if not width and not height: | ||||
|                 logger.error('no height of width specified') | ||||
|                 logger.error("no height of width specified") | ||||
|                 return | ||||
|  | ||||
|             image = self.image | ||||
|  | ||||
|             if width: | ||||
|                 initial_width = image.width | ||||
|                 wpercent = (width / float(image.width)) | ||||
|                 wpercent = width / float(image.width) | ||||
|                 hsize = int((float(image.height) * float(wpercent))) | ||||
|                 image = image.resize((width, hsize), Image.LANCZOS) | ||||
|                 logger.info(f"resized image from {initial_width} to {image.width}") | ||||
| @@ -180,7 +180,7 @@ class Inkyimage: | ||||
|  | ||||
|             if height: | ||||
|                 initial_height = image.height | ||||
|                 hpercent = (height / float(image.height)) | ||||
|                 hpercent = height / float(image.height) | ||||
|                 wsize = int(float(image.width) * float(hpercent)) | ||||
|                 image = image.resize((wsize, height), Image.LANCZOS) | ||||
|                 logger.info(f"resized image from {initial_height} to {image.height}") | ||||
| @@ -203,131 +203,129 @@ class Inkyimage: | ||||
|  | ||||
|         def clear_white(img): | ||||
|             """Replace all white pixels from image with transparent pixels""" | ||||
|             x = numpy.asarray(img.convert('RGBA')).copy() | ||||
|             x = numpy.asarray(img.convert("RGBA")).copy() | ||||
|             x[:, :, 3] = (255 * (x[:, :, :3] != 255).any(axis=2)).astype(numpy.uint8) | ||||
|             return Image.fromarray(x) | ||||
|  | ||||
|         image2 = clear_white(image2) | ||||
|         image1.paste(image2, (0, 0), image2) | ||||
|         logger.info('merged given images into one') | ||||
|         logger.info("merged given images into one") | ||||
|  | ||||
|         return image1 | ||||
|  | ||||
|     def to_palette(self, palette, dither=True) -> (PIL.Image, PIL.Image): | ||||
|         """Maps an image to a given colour palette. | ||||
|  | ||||
|         Maps each pixel from the image to a colour from the palette. | ||||
| def image_to_palette( | ||||
|     image: Image, palette: Literal = ["bwr", "bwy", "bw", "16gray"], dither: bool = True | ||||
| ) -> (PIL.Image, PIL.Image): | ||||
|     """Maps an image to a given colour palette. | ||||
|  | ||||
|         Args: | ||||
|           - palette: A supported token. (see below) | ||||
|           - dither:->bool. Use dithering? Set to `False` for solid colour fills. | ||||
|     Maps each pixel from the image to a colour from the palette. | ||||
|  | ||||
|         Returns: | ||||
|           - two images: one for the coloured band and one for the black band. | ||||
|     Args: | ||||
|         - palette: A supported token. (see below) | ||||
|         - dither:->bool. Use dithering? Set to `False` for solid colour fills. | ||||
|  | ||||
|         Raises: | ||||
|           - ValueError if palette token is not supported | ||||
|     Returns: | ||||
|         - two images: one for the coloured band and one for the black band. | ||||
|  | ||||
|         Supported palette tokens: | ||||
|     Raises: | ||||
|         - ValueError if palette token is not supported | ||||
|  | ||||
|         >>> 'bwr' # black-white-red | ||||
|         >>> 'bwy' # black-white-yellow | ||||
|         >>> 'bw'  # black-white | ||||
|         >>> '16gray' # 16 shades of gray | ||||
|         """ | ||||
|         # Check if an image is loaded | ||||
|         if self._image_loaded(): | ||||
|             image = self.image.convert('RGB') | ||||
|         else: | ||||
|             raise FileNotFoundError | ||||
|     Supported palette tokens: | ||||
|  | ||||
|         if palette == 'bwr': | ||||
|             # black-white-red palette | ||||
|             pal = [255, 255, 255, 0, 0, 0, 255, 0, 0] | ||||
|     >>> 'bwr' # black-white-red | ||||
|     >>> 'bwy' # black-white-yellow | ||||
|     >>> 'bw'  # black-white | ||||
|     >>> '16gray' # 16 shades of gray | ||||
|     """ | ||||
|  | ||||
|         elif palette == 'bwy': | ||||
|             # black-white-yellow palette | ||||
|             pal = [255, 255, 255, 0, 0, 0, 255, 255, 0] | ||||
|     if palette == "bwr": | ||||
|         # black-white-red palette | ||||
|         pal = [255, 255, 255, 0, 0, 0, 255, 0, 0] | ||||
|  | ||||
|         elif palette == 'bw': | ||||
|             pal = None | ||||
|         elif palette == '16gray': | ||||
|             pal = [x for x in range(0, 256, 16)] * 3 | ||||
|             pal.sort() | ||||
|     elif palette == "bwy": | ||||
|         # black-white-yellow palette | ||||
|         pal = [255, 255, 255, 0, 0, 0, 255, 255, 0] | ||||
|  | ||||
|         else: | ||||
|             logger.error('The given palette is unsupported.') | ||||
|             raise ValueError('The given palette is not supported.') | ||||
|     elif palette == "bw": | ||||
|         pal = None | ||||
|     elif palette == "16gray": | ||||
|         pal = [x for x in range(0, 256, 16)] * 3 | ||||
|         pal.sort() | ||||
|  | ||||
|         if pal: | ||||
|             # The palette needs to have 256 colors, for this, the black-colour | ||||
|             # is added until the | ||||
|             colours = len(pal) // 3 | ||||
|             # print(f'The palette has {colours} colours') | ||||
|     else: | ||||
|         logger.error("The given palette is unsupported.") | ||||
|         raise ValueError("The given palette is not supported.") | ||||
|  | ||||
|             if 256 % colours != 0: | ||||
|                 # print('Filling palette with black') | ||||
|                 pal += (256 % colours) * [0, 0, 0] | ||||
|     if pal: | ||||
|         # The palette needs to have 256 colors, for this, the black-colour | ||||
|         # is added until the | ||||
|         colours = len(pal) // 3 | ||||
|         # print(f'The palette has {colours} colours') | ||||
|  | ||||
|             # print(pal) | ||||
|             colours = len(pal) // 3 | ||||
|             # print(f'The palette now has {colours} colours') | ||||
|         if 256 % colours != 0: | ||||
|             # print('Filling palette with black') | ||||
|             pal += (256 % colours) * [0, 0, 0] | ||||
|  | ||||
|             # Create a dummy image to be used as a palette | ||||
|             palette_im = Image.new('P', (1, 1)) | ||||
|         # print(pal) | ||||
|         colours = len(pal) // 3 | ||||
|         # print(f'The palette now has {colours} colours') | ||||
|  | ||||
|             # Attach the created palette. The palette should have 256 colours | ||||
|             # equivalent to 768 integers | ||||
|             palette_im.putpalette(pal * (256 // colours)) | ||||
|         # Create a dummy image to be used as a palette | ||||
|         palette_im = Image.new("P", (1, 1)) | ||||
|  | ||||
|             # Quantize the image to given palette | ||||
|             quantized_im = image.quantize(palette=palette_im, dither=dither) | ||||
|             quantized_im = quantized_im.convert('RGB') | ||||
|         # Attach the created palette. The palette should have 256 colours | ||||
|         # equivalent to 768 integers | ||||
|         palette_im.putpalette(pal * (256 // colours)) | ||||
|  | ||||
|             # get rgb of the non-black-white colour from the palette | ||||
|             rgb = [pal[x:x + 3] for x in range(0, len(pal), 3)] | ||||
|             rgb = [col for col in rgb if col != [0, 0, 0] and col != [255, 255, 255]][0] | ||||
|             r_col, g_col, b_col = rgb | ||||
|             # print(f'r:{r_col} g:{g_col} b:{b_col}') | ||||
|         # Quantize the image to given palette | ||||
|         quantized_im = image.quantize(palette=palette_im, dither=dither) | ||||
|         quantized_im = quantized_im.convert("RGB") | ||||
|  | ||||
|             # Create an image buffer for black pixels | ||||
|             buffer1 = numpy.array(quantized_im) | ||||
|         # get rgb of the non-black-white colour from the palette | ||||
|         rgb = [pal[x : x + 3] for x in range(0, len(pal), 3)] | ||||
|         rgb = [col for col in rgb if col != [0, 0, 0] and col != [255, 255, 255]][0] | ||||
|         r_col, g_col, b_col = rgb | ||||
|         # print(f'r:{r_col} g:{g_col} b:{b_col}') | ||||
|  | ||||
|             # Get RGB values of each pixel | ||||
|             r, g, b = buffer1[:, :, 0], buffer1[:, :, 1], buffer1[:, :, 2] | ||||
|         # Create an image buffer for black pixels | ||||
|         buffer1 = numpy.array(quantized_im) | ||||
|  | ||||
|             # convert coloured pixels to white | ||||
|             buffer1[numpy.logical_and(r == r_col, g == g_col)] = [255, 255, 255] | ||||
|         # Get RGB values of each pixel | ||||
|         r, g, b = buffer1[:, :, 0], buffer1[:, :, 1], buffer1[:, :, 2] | ||||
|  | ||||
|             # reconstruct image for black-band | ||||
|             im_black = Image.fromarray(buffer1) | ||||
|         # convert coloured pixels to white | ||||
|         buffer1[numpy.logical_and(r == r_col, g == g_col)] = [255, 255, 255] | ||||
|  | ||||
|             # Create a buffer for coloured pixels | ||||
|             buffer2 = numpy.array(quantized_im) | ||||
|         # reconstruct image for black-band | ||||
|         im_black = Image.fromarray(buffer1) | ||||
|  | ||||
|             # Get RGB values of each pixel | ||||
|             r, g, b = buffer2[:, :, 0], buffer2[:, :, 1], buffer2[:, :, 2] | ||||
|         # Create a buffer for coloured pixels | ||||
|         buffer2 = numpy.array(quantized_im) | ||||
|  | ||||
|             # convert black pixels to white | ||||
|             buffer2[numpy.logical_and(r == 0, g == 0)] = [255, 255, 255] | ||||
|         # Get RGB values of each pixel | ||||
|         r, g, b = buffer2[:, :, 0], buffer2[:, :, 1], buffer2[:, :, 2] | ||||
|  | ||||
|             # convert non-white pixels to black | ||||
|             buffer2[numpy.logical_and(g == g_col, b == 0)] = [0, 0, 0] | ||||
|         # convert black pixels to white | ||||
|         buffer2[numpy.logical_and(r == 0, g == 0)] = [255, 255, 255] | ||||
|  | ||||
|             # reconstruct image for colour-band | ||||
|             im_colour = Image.fromarray(buffer2) | ||||
|         # convert non-white pixels to black | ||||
|         buffer2[numpy.logical_and(g == g_col, b == 0)] = [0, 0, 0] | ||||
|  | ||||
|             # self.preview(im_black) | ||||
|             # self.preview(im_colour) | ||||
|         # reconstruct image for colour-band | ||||
|         im_colour = Image.fromarray(buffer2) | ||||
|  | ||||
|         else: | ||||
|             im_black = image.convert('1', dither=dither) | ||||
|             im_colour = Image.new(mode='1', size=im_black.size, color='white') | ||||
|         # self.preview(im_black) | ||||
|         # self.preview(im_colour) | ||||
|  | ||||
|         logger.info('mapped image to specified palette') | ||||
|     else: | ||||
|         im_black = image.convert("1", dither=dither) | ||||
|         im_colour = Image.new(mode="1", size=im_black.size, color="white") | ||||
|  | ||||
|         return im_black, im_colour | ||||
|     logger.info("mapped image to specified palette") | ||||
|  | ||||
|     return im_black, im_colour | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     print(f'running {__name__} in standalone/debug mode') | ||||
| if __name__ == "__main__": | ||||
|     print(f"running {__name__} in standalone/debug mode") | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 mrbwburns
					mrbwburns