From 67b4731c6f4b967816adaf3486901f1667f4469a Mon Sep 17 00:00:00 2001 From: Mike Roberts <2947595+m-roberts@users.noreply.github.com> Date: Fri, 12 Feb 2021 16:05:52 +0000 Subject: [PATCH 1/4] Outline of a possible better approach to auto wrap text --- pitop/core/ImageFunctions.py | 42 ++++++++++++- pitop/miniscreen/oled/oled.py | 108 +++++++++++----------------------- 2 files changed, 74 insertions(+), 76 deletions(-) diff --git a/pitop/core/ImageFunctions.py b/pitop/core/ImageFunctions.py index c978c9865..7f9816022 100644 --- a/pitop/core/ImageFunctions.py +++ b/pitop/core/ImageFunctions.py @@ -1,8 +1,9 @@ -from PIL import Image from numpy import ( asarray, ndarray, ) +from PIL import Image, ImageDraw +from re import split from urllib.request import urlopen from pitopcommon.formatting import is_url @@ -63,3 +64,42 @@ def get_pil_image_from_path(file_path_or_url): test_image.verify() return image + + +def get_word_wrapped_text_for_image(text, font, xy, image): + max_width = image.width - xy[0] + + # Push and pop each line to stack + output_text = list() + + remaining = max_width + + # Split up text based on all whitespace + for field in split(r'(\s+)', text): + # Get text size + field_width, field_height = ImageDraw.Draw(image).textsize( + text=str(field), + font=font, + spacing=0, + ) + if field_width > remaining: + # Update remaining width + remaining = max_width - field_width + + # Not enough space to add to current line - start new one + output_text.append(field) + else: + # Update remaining width + remaining = remaining - field_width + + # Is enough space + if not output_text: + # First time - just append + output_text.append(field) + else: + # Pop latest line from list + # Append the field with a space + # Add back to list + output_text.append(output_text.pop() + f" {field}") + + return "\n".join(output_text) diff --git a/pitop/miniscreen/oled/oled.py b/pitop/miniscreen/oled/oled.py index 25b7ff7fa..9d3ac0425 100644 --- a/pitop/miniscreen/oled/oled.py +++ b/pitop/miniscreen/oled/oled.py @@ -291,9 +291,16 @@ def display_image(self, image, xy=None): """ self.__display(self.prepare_image(image)) - def display_text(self, text, xy=None, font_size=None): + # TODO: also add align! + def display_text( + self, + text, + xy=None, + font_size=30, + auto_word_wrap=True + ): """ - Renders a single line of text to the screen at a given position and size. + Renders text to the screen at a given position and size. The display's positional properties (e.g. `top_left`, `top_right`) can be used to assist with specifying the `xy` position parameter. @@ -304,95 +311,27 @@ def display_text(self, text, xy=None, font_size=None): the screen. :param int font_size: The font size in pixels. If not provided or passed as `None`, the default font size will be used + :param auto_word_wrap: Add newlines to text that is too wide for the display """ if xy is None: xy = self.top_left if font_size is None: + # Possible future feature - dynamic font size calculation? font_size = 30 - # Create empty image image = self.__empty_image - # 'Draw' text to empty image, using desired font size - ImageDraw.Draw(image).text( - xy, - str(text), - font=ImageFont.truetype( - self.__font_path(), - size=font_size - ), - fill=1, - spacing=0, - align="left" - ) - - # Display image - self.display_image(image) - - def display_multiline_text(self, text, xy=None, font_size=None): - """ - Renders multi-lined text to the screen at a given position and size. Text that - is too long for the screen will automatically wrap to the next line. - - The display's positional properties (e.g. `top_left`, `top_right`) can be used to assist with - specifying the `xy` position parameter. - - :param string text: The text to render - :param tuple xy: The position on the screen to render the image. If not - provided or passed as `None` the image will be drawn in the top-left of - the screen. - :param int font_size: The font size in pixels. If not provided or passed as - `None`, the default font size will be used - """ - if xy is None: - xy = self.top_left - - if font_size is None: - font_size = 30 - - # Create empty image - image = self.__empty_image - - # Create font font = ImageFont.truetype( self.__font_path(), size=font_size ) - def format_multiline_text(text): - def get_text_size(text): - return ImageDraw.Draw(self.__empty_image).textsize( - text=str(text), - font=font, - spacing=0, - ) - - remaining = self.width - space_width, _ = get_text_size(" ") - # use this list as a stack, push/popping each line - output_text = [] - # split on whitespace... - for word in text.split(None): - word_width, _ = get_text_size(word) - if word_width + space_width > remaining: - output_text.append(word) - remaining = self.width - word_width - else: - if not output_text: - output_text.append(word) - else: - output = output_text.pop() - output += " %s" % word - output_text.append(output) - remaining = remaining - (word_width + space_width) - return "\n".join(output_text) - - # Format text - text = format_multiline_text(text) + if auto_word_wrap: + text = ImageFunctions.get_word_wrapped_text_for_image(text, font, xy, image) # 'Draw' text to empty image, using desired font size - ImageDraw.Draw(image).multiline_text( + ImageDraw.Draw(image).text( xy, str(text), font=font, @@ -535,6 +474,25 @@ def bottom_right(self): ####################### # Deprecation support # ####################### + def display_multiline_text(self, text, xy=None, font_size=None): + """ + Renders multi-lined text to the screen at a given position and size. + + .. warning:: + This method is deprecated and will be deleted on the next major release of the SDK. + + The display's positional properties (e.g. `top_left`, `top_right`) can be used to assist with + specifying the `xy` position parameter. + + :param string text: The text to render + :param tuple xy: The position on the screen to render the image. If not + provided or passed as `None` the image will be drawn in the top-left of + the screen. + :param int font_size: The font size in pixels. If not provided or passed as + `None`, the default font size will be used + """ + self.display_text(text, xy=None, font_size=None, auto_word_wrap=True) + def display(self, force=False): """ Displays what is on the current canvas to the screen as a single frame. From a7cb5e0b89ef209a23a4fd5872e7bc569bd855e5 Mon Sep 17 00:00:00 2001 From: Mike Roberts <2947595+m-roberts@users.noreply.github.com> Date: Mon, 29 Mar 2021 15:33:34 +0100 Subject: [PATCH 2/4] Fix flake8 errors --- pitop/miniscreen/oled/oled.py | 49 +++-------------------------------- 1 file changed, 3 insertions(+), 46 deletions(-) diff --git a/pitop/miniscreen/oled/oled.py b/pitop/miniscreen/oled/oled.py index 59eafa4d7..19a2afb42 100644 --- a/pitop/miniscreen/oled/oled.py +++ b/pitop/miniscreen/oled/oled.py @@ -293,8 +293,7 @@ def display_text( invert=False, auto_word_wrap=True ): - """ - Renders text to the screen at a given position and size. + """Renders text to the screen at a given position and size. The display's positional properties (e.g. `top_left`, `top_right`) can be used to assist with specifying the `xy` position parameter. @@ -317,47 +316,6 @@ def display_text( image = self.__empty_image - # 'Draw' text to empty image, using desired font size - ImageDraw.Draw(image).text( - xy, - str(text), - font=ImageFont.truetype( - self.__font_path(), - size=font_size - ), - fill=1, - spacing=0, - align="left" - ) - - # Display image - self.display_image(image, invert=invert) - - def display_multiline_text(self, text, xy=None, font_size=None): - """Renders multi-lined text to the screen at a given position and size. - Text that is too long for the screen will automatically wrap to the - next line. - - The display's positional properties (e.g. `top_left`, `top_right`) can be used to assist with - specifying the `xy` position parameter. - - :param string text: The text to render - :param tuple xy: The position on the screen to render the image. If not - provided or passed as `None` the image will be drawn in the top-left of - the screen. - :param int font_size: The font size in pixels. If not provided or passed as - `None`, the default font size will be used - """ - if xy is None: - xy = self.top_left - - if font_size is None: - font_size = 30 - - # Create empty image - image = self.__empty_image - - # Create font font = ImageFont.truetype( self.__font_path(), size=font_size @@ -377,7 +335,7 @@ def display_multiline_text(self, text, xy=None, font_size=None): ) # Display image - self.display_image(image) + self.display_image(image, invert=invert) def __display(self, image_to_display, force=False, invert=False): self.stop_animated_image() @@ -509,8 +467,7 @@ def bottom_right(self): # Deprecation support # ####################### def display_multiline_text(self, text, xy=None, font_size=None): - """ - Renders multi-lined text to the screen at a given position and size. + """Renders multi-lined text to the screen at a given position and size. .. warning:: This method is deprecated and will be deleted on the next major release of the SDK. From 731ef888c7618d3d13546d8174060868bcdbee3a Mon Sep 17 00:00:00 2001 From: Mike Roberts <2947595+m-roberts@users.noreply.github.com> Date: Tue, 30 Mar 2021 18:10:19 +0100 Subject: [PATCH 3/4] Quick commit --- pitop/core/image_utils.py | 67 +++++++++++++++++++++++++++++++++++ pitop/miniscreen/oled/oled.py | 36 +++++++++++++++++-- 2 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 pitop/core/image_utils.py diff --git a/pitop/core/image_utils.py b/pitop/core/image_utils.py new file mode 100644 index 000000000..13ffaf740 --- /dev/null +++ b/pitop/core/image_utils.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# coding: utf-8 + +# Copyright 2011 Álvaro Justen [alvarojusten at gmail dot com] +# License: GPL + +from PIL import ImageDraw, ImageFont + + +class ImageTextFunctions(object): + def get_font_size(text, font_filename, max_width=None, max_height=None): + if max_width is None and max_height is None: + raise ValueError('You need to pass max_width or max_height') + font_size = 1 + text_size = ImageTextFunctions.get_text_size(font_filename, font_size, text) + if (max_width is not None and text_size[0] > max_width) or \ + (max_height is not None and text_size[1] > max_height): + raise ValueError("Text can't be filled in only (%dpx, %dpx)" % + text_size) + while True: + if (max_width is not None and text_size[0] >= max_width) or \ + (max_height is not None and text_size[1] >= max_height): + return font_size - 1 + font_size += 1 + text_size = ImageTextFunctions.get_text_size(font_filename, font_size, text) + + def get_text_size(font_filename, font_size, text): + font = ImageFont.truetype(font_filename, font_size) + return font.getsize(text) + + def write_text(image, xy, text, font_filename, font_size='fill', + color=(0, 0, 0), max_width=None, max_height=None): + + if font_size == 'fill' and \ + (max_width is not None or max_height is not None): + font_size = ImageTextFunctions.get_font_size( + text, + font_filename, + max_width, + max_height + ) + + font = ImageFont.truetype( + font_filename, + font_size + ) + + text_size = ImageTextFunctions.get_text_size( + image, + font_filename, + font_size, + text + ) + + x, y = xy + if x == 'center': + x = (image.size[0] - text_size[0]) / 2 + if y == 'center': + y = (image.size[1] - text_size[1]) / 2 + + ImageDraw.Draw(image).text( + (x, y), + text, + font=font, + fill=color + ) + return text_size diff --git a/pitop/miniscreen/oled/oled.py b/pitop/miniscreen/oled/oled.py index 19a2afb42..0b532d1ea 100644 --- a/pitop/miniscreen/oled/oled.py +++ b/pitop/miniscreen/oled/oled.py @@ -1,4 +1,5 @@ from pitop.core import ImageFunctions +from pitop.core.image_utils import ImageText from .core import ( Canvas, FPS_Regulator, @@ -284,14 +285,14 @@ def display_image(self, image, xy=None, invert=False): invert=invert, ) - # TODO: also add align! def display_text( self, text, xy=None, font_size=30, invert=False, - auto_word_wrap=True + auto_word_wrap=True, + align="left" ): """Renders text to the screen at a given position and size. @@ -311,7 +312,6 @@ def display_text( xy = self.top_left if font_size is None: - # Possible future feature - dynamic font size calculation? font_size = 30 image = self.__empty_image @@ -321,6 +321,36 @@ def display_text( size=font_size ) + font = 'unifont.ttf' + img = ImageText((800, 600), background=(255, 255, 255, 200)) # 200 = alpha + + box_width, box_height = img.write_text_box( + xy=xy, + text=text, + font_filename=self.__font_path(), + font_size=font_size, + color=1, + box_width=200, + place=align + ) + + text_width, text_height = img.write_text( + xy=xy, + text=text, + font_filename=self.__font_path(), + font_size=font_size, + color=1, + max_width=None, + max_height=None, + ) + + # You don't need to specify text size: can specify max_width or max_height + # and tell write_text to fill the text in this space, so it'll compute font + # size automatically + # write_text will return (width, height) of the wrote text + img.write_text((100, 350), 'test fill', font_filename=font, + font_size='fill', max_height=150, color=1) + if auto_word_wrap: text = ImageFunctions.get_word_wrapped_text_for_image(text, font, xy, image) From 807571dbf5ca6b7ce3dc38747c681b8ba8ecd914 Mon Sep 17 00:00:00 2001 From: Mike Roberts <2947595+m-roberts@users.noreply.github.com> Date: Tue, 30 Mar 2021 20:41:08 +0100 Subject: [PATCH 4/4] Still a work in progress --- .pre-commit-config.yaml | 34 +++--- pitop/camera/camera.py | 9 +- .../camera/core/cameras/file_system_camera.py | 4 +- .../core/capture_actions/generic_action.py | 4 +- .../core/{ImageFunctions.py => functions.py} | 102 +++++++++++------- pitop/core/image_utils.py | 67 ------------ pitop/miniscreen/oled/oled.py | 99 +++++++++-------- pitop/processing/algorithms/line_detect.py | 8 +- 8 files changed, 140 insertions(+), 187 deletions(-) rename pitop/core/{ImageFunctions.py => functions.py} (68%) delete mode 100644 pitop/core/image_utils.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a289bda37..adcd910bc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,20 +10,11 @@ repos: - id: check-symlinks - id: check-added-large-files -- repo: https://github.com/myint/docformatter - rev: v1.4 - hooks: - - id: docformatter - -- repo: https://github.com/myint/rstcheck - rev: '3f92957478422df87bd730abde66f089cc1ee19b' +- repo: https://gitlab.com/PyCQA/flake8 + rev: 3.8.4 hooks: - - id: rstcheck - args: [ - "--report", "warning", - "--ignore-roles", "class", - "--ignore-directives", "autoclass,automodule", - ] + - id: flake8 + args: ["--max-line-length=150"] - repo: https://github.com/pre-commit/mirrors-autopep8 rev: v1.5.4 @@ -48,8 +39,17 @@ repos: "--ignore", "E226,E24,W50,W690,E402" ] -- repo: https://gitlab.com/PyCQA/flake8 - rev: 3.8.4 +- repo: https://github.com/myint/docformatter + rev: v1.4 hooks: - - id: flake8 - args: ["--max-line-length=150"] + - id: docformatter + +- repo: https://github.com/myint/rstcheck + rev: '3f92957478422df87bd730abde66f089cc1ee19b' + hooks: + - id: rstcheck + args: [ + "--report", "warning", + "--ignore-roles", "class", + "--ignore-directives", "autoclass,automodule", + ] diff --git a/pitop/camera/camera.py b/pitop/camera/camera.py index c69045500..2f11fe4e9 100644 --- a/pitop/camera/camera.py +++ b/pitop/camera/camera.py @@ -4,7 +4,10 @@ FrameHandler, CameraTypes) from .core.capture_actions import CaptureActions -from pitop.core import ImageFunctions +from pitop.core.functions import ( + image_format_check, + image_convert, +) from pitop.core.mixins import ( Stateful, Recreatable, @@ -96,7 +99,7 @@ def format(self): @format.setter def format(self, format_value): - ImageFunctions.image_format_check(format_value) + image_format_check(format_value) self._format = format_value.lower() @classmethod @@ -242,7 +245,7 @@ def __get_processed_current_frame(self): image = self.__frame_handler.frame if self.format.lower() == "opencv": - image = ImageFunctions.convert(image, format="opencv") + image = image_convert(image, format="opencv") return image diff --git a/pitop/camera/core/cameras/file_system_camera.py b/pitop/camera/core/cameras/file_system_camera.py index df4a036d9..bd80cf99b 100644 --- a/pitop/camera/core/cameras/file_system_camera.py +++ b/pitop/camera/core/cameras/file_system_camera.py @@ -1,11 +1,11 @@ -from pitop.core import ImageFunctions +from pitop.core.functions import get_pil_image_from_path import os class FsImage: def __init__(self, path: str): self.path = path - self.data = ImageFunctions.get_pil_image_from_path(self.path) + self.data = get_pil_image_from_path(self.path) if self.data is None: raise AttributeError(f"Couldn't load image {path}") diff --git a/pitop/camera/core/capture_actions/generic_action.py b/pitop/camera/core/capture_actions/generic_action.py index 53ad924a3..df98498d6 100644 --- a/pitop/camera/core/capture_actions/generic_action.py +++ b/pitop/camera/core/capture_actions/generic_action.py @@ -1,6 +1,6 @@ from .capture_action_base import CaptureActionBase -from pitop.core import ImageFunctions +from pitop.core.functions import image_convert from concurrent.futures import ThreadPoolExecutor from inspect import signature @@ -29,7 +29,7 @@ def __del__(self): def process(self, frame): if isinstance(self.__format, str) and self.__format.lower() == 'opencv': - frame = ImageFunctions.convert(frame, format="opencv") + frame = image_convert(frame, format="opencv") if self.__elapsed_frames % self.__frame_interval == 0: if self.callback_has_argument: diff --git a/pitop/core/ImageFunctions.py b/pitop/core/functions.py similarity index 68% rename from pitop/core/ImageFunctions.py rename to pitop/core/functions.py index 7f9816022..82619b3c9 100644 --- a/pitop/core/ImageFunctions.py +++ b/pitop/core/functions.py @@ -2,19 +2,78 @@ asarray, ndarray, ) -from PIL import Image, ImageDraw +from PIL import Image, ImageFont from re import split from urllib.request import urlopen from pitopcommon.formatting import is_url +def get_text_size(font_filename, font_size, text): + return ImageFont.truetype(font_filename, font_size).getsize(text) + + +def get_word_wrapped_text(text, font_filename, font_size, max_width): + # Push and pop each line to stack + output_text = list() + + # TODO: add height support + remaining = max_width + + # Split up text based on all whitespace + for field in split(r'(\s+)', text): + # Get text size + field_width, field_height = get_text_size(font_filename, font_size, str(field)) + if field_width > remaining: + # Update remaining width + remaining = max_width - field_width + + # Not enough space to add to current line - start new one + output_text.append(field) + else: + # Update remaining width + remaining = remaining - field_width + + # Is enough space + if not output_text: + # First time - just append + output_text.append(field) + else: + # Pop latest line from list + # Append the field with a space + # Add back to list + output_text.append(output_text.pop() + f" {field}") + + return "\n".join(output_text) + + +def get_font_size(text, font_filename, word_wrap, max_width=None, max_height=None): + if max_width is None and max_height is None: + raise ValueError('You need to pass max_width or max_height') + + font_size = 1 + text_width, text_height = get_text_size(font_filename, font_size, text) + + if (max_width is not None and text_width > max_width) or \ + (max_height is not None and text_height > max_height): + raise ValueError("Text can't be filled in only (%dpx, %dpx)" % (text_width, text_height)) + + # Increase font size until width or height exceeds box + # TODO: add word wrapping + while True: + if (max_width is not None and text_width >= max_width) or \ + (max_height is not None and text_height >= max_height): + return font_size - 1 + font_size += 1 + text_width, text_height = get_text_size(font_filename, font_size, text) + + def image_format_check(format): assert isinstance(format, str) assert format.lower() in ("pil", "opencv") -def convert(image, format="PIL"): +def image_convert(image, format="PIL"): try: from cv2 import ( @@ -64,42 +123,3 @@ def get_pil_image_from_path(file_path_or_url): test_image.verify() return image - - -def get_word_wrapped_text_for_image(text, font, xy, image): - max_width = image.width - xy[0] - - # Push and pop each line to stack - output_text = list() - - remaining = max_width - - # Split up text based on all whitespace - for field in split(r'(\s+)', text): - # Get text size - field_width, field_height = ImageDraw.Draw(image).textsize( - text=str(field), - font=font, - spacing=0, - ) - if field_width > remaining: - # Update remaining width - remaining = max_width - field_width - - # Not enough space to add to current line - start new one - output_text.append(field) - else: - # Update remaining width - remaining = remaining - field_width - - # Is enough space - if not output_text: - # First time - just append - output_text.append(field) - else: - # Pop latest line from list - # Append the field with a space - # Add back to list - output_text.append(output_text.pop() + f" {field}") - - return "\n".join(output_text) diff --git a/pitop/core/image_utils.py b/pitop/core/image_utils.py deleted file mode 100644 index 13ffaf740..000000000 --- a/pitop/core/image_utils.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 - -# Copyright 2011 Álvaro Justen [alvarojusten at gmail dot com] -# License: GPL - -from PIL import ImageDraw, ImageFont - - -class ImageTextFunctions(object): - def get_font_size(text, font_filename, max_width=None, max_height=None): - if max_width is None and max_height is None: - raise ValueError('You need to pass max_width or max_height') - font_size = 1 - text_size = ImageTextFunctions.get_text_size(font_filename, font_size, text) - if (max_width is not None and text_size[0] > max_width) or \ - (max_height is not None and text_size[1] > max_height): - raise ValueError("Text can't be filled in only (%dpx, %dpx)" % - text_size) - while True: - if (max_width is not None and text_size[0] >= max_width) or \ - (max_height is not None and text_size[1] >= max_height): - return font_size - 1 - font_size += 1 - text_size = ImageTextFunctions.get_text_size(font_filename, font_size, text) - - def get_text_size(font_filename, font_size, text): - font = ImageFont.truetype(font_filename, font_size) - return font.getsize(text) - - def write_text(image, xy, text, font_filename, font_size='fill', - color=(0, 0, 0), max_width=None, max_height=None): - - if font_size == 'fill' and \ - (max_width is not None or max_height is not None): - font_size = ImageTextFunctions.get_font_size( - text, - font_filename, - max_width, - max_height - ) - - font = ImageFont.truetype( - font_filename, - font_size - ) - - text_size = ImageTextFunctions.get_text_size( - image, - font_filename, - font_size, - text - ) - - x, y = xy - if x == 'center': - x = (image.size[0] - text_size[0]) / 2 - if y == 'center': - y = (image.size[1] - text_size[1]) / 2 - - ImageDraw.Draw(image).text( - (x, y), - text, - font=font, - fill=color - ) - return text_size diff --git a/pitop/miniscreen/oled/oled.py b/pitop/miniscreen/oled/oled.py index 0b532d1ea..718ec232b 100644 --- a/pitop/miniscreen/oled/oled.py +++ b/pitop/miniscreen/oled/oled.py @@ -1,5 +1,8 @@ -from pitop.core import ImageFunctions -from pitop.core.image_utils import ImageText +from pitop.core.functions import ( + get_pil_image_from_path, + # get_word_wrapped_text, + get_font_size, +) from .core import ( Canvas, FPS_Regulator, @@ -260,7 +263,7 @@ def display_image_file(self, file_path_or_url, xy=None, invert=False): :param bool invert: Set to True to flip the on/off state of each pixel in the image """ self.display_image( - ImageFunctions.get_pil_image_from_path(file_path_or_url), + get_pil_image_from_path(file_path_or_url), xy=xy, invert=invert, ) @@ -289,10 +292,12 @@ def display_text( self, text, xy=None, - font_size=30, + font_size=None, invert=False, - auto_word_wrap=True, - align="left" + word_wrap=True, + align="justify", + max_width=None, + max_height=None ): """Renders text to the screen at a given position and size. @@ -306,62 +311,53 @@ def display_text( :param int font_size: The font size in pixels. If not provided or passed as `None`, the default font size will be used :param bool invert: Set to True to flip the on/off state of each pixel in the image - :param auto_word_wrap: Add newlines to text that is too wide for the display + :param word_wrap: Add newlines to text that is too wide for the display """ - if xy is None: - xy = self.top_left - - if font_size is None: - font_size = 30 image = self.__empty_image - font = ImageFont.truetype( - self.__font_path(), - size=font_size - ) + x, y = xy - font = 'unifont.ttf' - img = ImageText((800, 600), background=(255, 255, 255, 200)) # 200 = alpha + # Limit to bottom right of the image if not specified + if max_width is None: + max_width = image.width - x - box_width, box_height = img.write_text_box( - xy=xy, - text=text, - font_filename=self.__font_path(), - font_size=font_size, - color=1, - box_width=200, - place=align - ) + if max_height is None: + max_height = image.height - y - text_width, text_height = img.write_text( - xy=xy, - text=text, - font_filename=self.__font_path(), - font_size=font_size, - color=1, - max_width=None, - max_height=None, - ) + # Dynamic font size if not specified + if font_size is None: - # You don't need to specify text size: can specify max_width or max_height - # and tell write_text to fill the text in this space, so it'll compute font - # size automatically - # write_text will return (width, height) of the wrote text - img.write_text((100, 350), 'test fill', font_filename=font, - font_size='fill', max_height=150, color=1) + # if word_wrap: + # text = get_word_wrapped_text( + # text, + # self.__font_path, + # font_size, + # xy, + # image + # ) + + font_size = get_font_size( + text, + self.__font_path, + word_wrap, + max_width, + max_height + ) - if auto_word_wrap: - text = ImageFunctions.get_word_wrapped_text_for_image(text, font, xy, image) + if xy is None: + xy = self.top_left - # 'Draw' text to empty image, using desired font size ImageDraw.Draw(image).text( - xy, - str(text), - font=font, + (x, y), + text, + font=ImageFont.truetype( + self.__font_path, + font_size + ), fill=1, spacing=0, - align="left" + align=align ) # Display image @@ -392,7 +388,7 @@ def play_animated_image_file(self, file_path_or_url, background=False, loop=Fals :param bool loop: Set whether the image animation should start again when it has finished """ - image = ImageFunctions.get_pil_image_from_path(file_path_or_url) + image = get_pil_image_from_path(file_path_or_url) self.play_animated_image(image, background, loop) def play_animated_image(self, image, background=False, loop=False): @@ -512,7 +508,7 @@ def display_multiline_text(self, text, xy=None, font_size=None): :param int font_size: The font size in pixels. If not provided or passed as `None`, the default font size will be used """ - self.display_text(text, xy=None, font_size=None, auto_word_wrap=True) + self.display_text(text, xy=None, font_size=None, word_wrap=True) def display(self, force=False): """Displays what is on the current canvas to the screen as a single @@ -642,6 +638,7 @@ def __cleanup(self): if self.__file_monitor_thread is not None and self.__file_monitor_thread.is_alive(): self.__file_monitor_thread.join(0) + @property def __font_path(self): primary_font_path = "/usr/share/fonts/opentype/FSMePro/FSMePro-Light.otf" fallback_font_path = "/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf" diff --git a/pitop/processing/algorithms/line_detect.py b/pitop/processing/algorithms/line_detect.py index fe0d1c2ad..7f28e6f7e 100644 --- a/pitop/processing/algorithms/line_detect.py +++ b/pitop/processing/algorithms/line_detect.py @@ -7,7 +7,7 @@ import_opencv, scale_frame, ) -from pitop.core import ImageFunctions +from pitop.core.functions import image_convert def calculate_blue_limits(): @@ -32,7 +32,7 @@ def calculate_blue_limits(): def process_frame_for_line(frame, image_format="PIL", scale_factor=0.5): cv2 = import_opencv() - cv_frame = ImageFunctions.convert(frame, format="OpenCV") + cv_frame = image_convert(frame, format="OpenCV") resized_frame = scale_frame(cv_frame, scale=scale_factor) hsv_lower, hsv_upper = calculate_blue_limits() @@ -52,7 +52,7 @@ def process_frame_for_line(frame, image_format="PIL", scale_factor=0.5): robot_view_img = robot_view(resized_frame, image_mask, line_contour, scaled_image_centroid) if image_format.lower() != 'opencv': - robot_view_img = ImageFunctions.convert(robot_view_img, format="PIL") + robot_view_img = image_convert(robot_view_img, format="PIL") class dotdict(dict): """dot.notation access to dictionary attributes.""" @@ -89,7 +89,7 @@ def centroid_reposition(centroid, scale, frame): # scale centroid so it matches original camera frame resolution centroid_x = int(centroid[0]/scale) centroid_y = int(centroid[1]/scale) - # convert so (0, 0) is at the middle bottom of the frame + # image_convert so (0, 0) is at the middle bottom of the frame centroid_x = centroid_x - int(frame.shape[1] / 2) centroid_y = int(frame.shape[0] / 2) - centroid_y