Source code for pycharmers.utils.pil_utils

# coding: utf-8
import os
import math
import urllib
import string
import textwrap
from PIL import Image, ImageDraw, ImageFont
from typing import Optional, Union, Tuple

from .print_utils import pretty_3quote
from .generic_utils import assign_trbl, handleKeyError, flatten_dual
from ._colorings import toBLUE, toGREEN

[docs]def pilread(img=None, path=None): """Opens and identifies the given image file. Args: img (PIL.Image) : PIL Image object path (str) : Path or URL to image file. Returns img (PIL.Image) : PIL Image object Examples: >>> from pycharmers.utils import pilread >>> img = pilread(img=None, path="https://iwasakishuto.github.io/Python-Charmers/_static/favicon.png") >>> img == pilread(img=img, path=None) True """ if path is not None: if isinstance(path, str) and (not os.path.exists(path)): with urllib.request.urlopen(path) as web_file: img = Image.open(web_file) else: img = Image.open(path) return img
[docs]def roughen_img(img=None, path=None, rrate=5): """Roughen the Image. Args: img (PIL.Image) : image file. path (str) : Path or URL to image file. rrate (float) : Reduction rate. Returns img (PIL.Image) : Roughened PIL Image object Examples: >>> from pycharmers.utils import roughen_img, pilread >>> img = pilread(path="https://iwasakishuto.github.io/Python-Charmers/_static/favicon.png") >>> roughened_img = roughen_img(img=img, rrate=5) >>> img.size == roughened_img.size True """ img = pilread(img=img, path=path) img_size_origin = img._size img_size_small = [int(s/rrate) for s in img_size_origin] return img.resize(size=img_size_small).resize(size=img_size_origin)
[docs]def draw_text_in_pil(text:str, img:Optional[Image.Image]=None, ttfontname:Optional[str]=None, img_size:Tuple[int,int]=(250, 250), text_width:Optional[int]=None, fontsize:int=16, fontwidth:Optional[int]=None, fontheight:Optional[int]=None, margin:int=10, line_height:Optional[int]=None, drop_whitespace:bool=False, bgRGB:Union[str,Tuple]=(255,255,255), textRGB:Union[str,Tuple]=(0,0,0), mode:str="RGB", ret_position:str="line", **kwargs): """Draw text in ``PIL.Image`` object. Args: text (str) : Text to be drawn to ``img``. img (PIL.Image) : The image to draw in. If this argment is ``None``, img will be created using ``img_size`` and ``bgRGB`` arguments. ttfontname (str) : A filename or file-like object containing a TrueType font. If the file is not found in this filename, the loader may also search in other directories, such as the ``fonts/`` directory on Windows or ``/Library/Fonts/`` , ``/System/Library/Fonts/`` and ``~/Library/Fonts/`` on macOS. img_size (tuple) : The image size. text_width (int) : The length of characters in one line. fontsize (int) : The requested size, in points. fontwidth (int) : The font width. (If not given, automatically calculated.) fontheight (int) : The font height. (If not given, automatically calculated.) margin (int) : The margin size. line_height (int) : The line height. If not specify, use ``font.getsize(string.ascii_letters)`` drop_whitespace (bool) : If ``True``, whitespace at the beginning and ending of every line. Defaults to ``False``. bgRGB (tuple) : The color of background image. (RGB) textRGB (tuple) : The color of text. (RGB) mode (str) : Optional mode to use for color values. ret_position (str) : Type of the position of next text to be returned. Please choose from ``["line", "word"]``. Defaults to ``"line"``. \*\*kwargs (dict) : Specify ``margin_top`` , ``margin_right`` , ``margin_bottom`` , ``margin_left`` . Returns: tuple (PIL.Image, pos): img, Position of next text ( ``x`` , ``y`` ). Example: >>> from pycharmers.utils import draw_text_in_pil >>> img, y = draw_text_in_pil("Hello World!!") >>> img.save("sample.png") """ if img is None: img = Image.new(mode=mode, size=img_size, color=bgRGB) if ttfontname is None: raise TypeError(*pretty_3quote(f""" Please define the {toGREEN('ttfontname')}. If you dont't know where the font file is, check the * {toBLUE('fonts/')} directory on Windows * {toBLUE('/Library/Fonts/')}, {toBLUE('/System/Library/Fonts/')}, or {toBLUE('~/Library/Fonts/')} on macOS or, use {toGREEN('pycharmers.utils.generic_urils.get_random_ttfontname')} to get the path to a random font file. """)) handleKeyError(lst=["line", "word"], ret_position=ret_position) iw,ih = img.size kwargs["margin"] = margin mt,mr,mb,ml = assign_trbl(data=kwargs, name="margin") ml = kwargs.get("x", ml); mt = kwargs.get("y", mt) font = ImageFont.truetype(font=ttfontname, size=fontsize) fw,fh = font.getsize(string.ascii_letters) fw = fontwidth or fw//len(string.ascii_letters) fh = fontheight or line_height or fh max_text_width = (iw-(mr+ml))//fw text_width = text_width or max_text_width wrapped_lines = flatten_dual([textwrap.wrap( text=t, width=text_width, drop_whitespace=drop_whitespace ) for t in text.split("\n")]) max_text_height = (ih-(mt+mb))//fh if len(textRGB)>3 and mode=="RGBA": text_canvas = Image.new(mode=mode, size=img.size, color=(255,255,255,0)) draw = ImageDraw.Draw(im=text_canvas, mode=mode) else: draw = ImageDraw.Draw(im=img, mode=mode) if len(wrapped_lines)>0: for i,line in enumerate(wrapped_lines): y = i*fh+mt draw.multiline_text((ml, y), line, fill=textRGB, font=font) else: y = mt; line=[] if ret_position == "line": pos = (ml,y+fh) elif ret_position == "word": pos = (fw*len(line)+ml,y) if len(textRGB)>3 and mode=="RGBA": img = Image.alpha_composite(im1=img, im2=text_canvas).convert(mode) return img, pos
[docs]def draw_cross(img, size, width=5, fill_color=(255,0,0,255), outline=None, color_mode="RGBA", margin=0, **kwargs): """Draw Cross Mark. Args: img (PIL.Image) : Pillow Image. size (int/tuple) : Cross mark size. (width,Height) width (int) : The width of the cross mark. fill_color (tuple) : The color in the line. outline (tuple) : The color of the edge of the line. color_mode (str) : Color Mode (ex. ``"RGBA"`` , ``"P"`` ) margin (int/list) : Specify the position. \*\*kwargs (dict) : Specify the individual margin ( ``margin_top`` , ``margin_right`` ) Examples: >>> from PIL import Image >>> from pycharmers.opencv import SAMPLE_LENA_IMG >>> from pycharmers.utils import draw_cross >>> img = Image.open(SAMPLE_LENA_IMG) >>> draw_cross(img=img, size=200, width=10) >>> draw_cross(img=img, size=(100,200), width=10, outline=(0,255,0)) """ ori_mode = img.mode img = img.convert(color_mode) draw = ImageDraw.Draw(img) W,H = img.size kwargs["margin"] = margin mt,mr,mb,ml = assign_trbl(data=kwargs, name="margin") if hasattr(size, "__len__"): sx,sy = size[:2] else: sx = sy = size sx /= 2; sy /= 2 angle = math.atan(sy/sx) cx = (W-ml-mr)//2+ml cy = (H-mt-mb)//2+mt dx = (width/2) * math.sin(angle) dy = (width/2) * math.cos(angle) for xy in [ ((cx-sx-dx,cy-sy+dy),(cx+sx-dx,cy+sy+dy),(cx+sx+dx,cy+sy-dy),(cx-sx+dx,cy-sy-dy)), ((cx+sx-dx,cy-sy-dy),(cx+sx+dx,cy-sy+dy),(cx-sx+dx,cy+sy+dy),(cx-sx-dx,cy+sy-dy)) ]: draw.polygon( xy=xy, fill=fill_color, outline=outline, ) return img.convert(ori_mode)
[docs]def draw_frame(img, width=10, border_color=(255,255,255), is_radius=True): """Draw Frame with Image. Args: img (PIL.Image) : Pillow Image. width (int) : The width of the border. border_color (tuple) : The color in the border. is_radius (bool) : Whether to round the corners. Examples: >>> from PIL import Image >>> from pycharmers.opencv import SAMPLE_LENA_IMG >>> from pycharmers.utils import draw_frame >>> img = Image.open(SAMPLE_LENA_IMG) >>> draw_frame(img=img, width=10) """ w,h = img.size hw = width//2 kwargs = {"fill": border_color, "width": width} if is_radius: l,r,t,b = (width, w-width, width, h-width) else: l,r,t,b = (0,w,0,h) draw = ImageDraw.Draw(img) for xy in [(l,hw-1,r,hw-1),(w-hw,t,w-hw,b),(r,h-hw,l,h-hw),(hw,b,hw,t)]: draw.line(xy=xy, **kwargs) if is_radius: draw.arc((0, 0, width*2, width*2), start=180, end=270, **kwargs) draw.arc((w-width*2, 0, w, width*2), start=270, end=360, **kwargs) draw.arc((w-width*2, h-width*2, w, h), start=0, end=90, **kwargs) draw.arc((0, h-width*2, width*2, h), start=90, end=180, **kwargs) return img