Source code for wed.chaptors.marquee

# coding: utf-8
"""This submodule contains a set of functions for editing the following image:

.. image:: _images/chaptors/marquee.png
   :class: popup-img

"""
from typing import Any, Dict, List, Optional, Tuple, Union

import numpy as np
import numpy.typing as npt
import scipy as sp
import scipy.ndimage
from PIL import Image

from ..utils._colorings import toBLUE, toGREEN
from ..utils._fonts import FONT_LEAGUEGOTHIC_PATH
from ..utils.generic_utils import Cycler, handleKeyError
from ..utils.image_utils import arr2pil, draw_text_in_pil, pil2arr
from .base_editor import BaseWedOPEditor


[docs]class MarqueeEditor(BaseWedOPEditor): # UPPER_LINE_TOP: int = 127 # LOWER_LINE_TOP: int = 259 SIMPLE_UPPER_LINE_TOP: int = 137 SIMPLE_LOWER_LINE_TOP: int = 269 BIG_UPPER_LINE_TOP: int = 100 BIG_LOWER_LINE_TOP: int = 280 SLANTED_UPPER_LINE_TOP: int = 187 SLANTED_LOWER_LINE_TOP: int = 457 def __init__( self, upper_text: str = "WEDNESDAY", lower_text: str = "DOWNTOWN", ttfontname: str = FONT_LEAGUEGOTHIC_PATH, simple_cycler: Cycler = Cycler( sizes=[140], diffs=[30], periods=[15], shifts=[0, 5, 5, 10, 5], ), big_cycler: Cycler = Cycler( sizes=[180], diffs=[50], periods=[15], shifts=[5, 10, 10, 5, 0], ), slanted_cycler: Cycler = Cycler( sizes=[250], diffs=[80], periods=[15], shifts=[5, 0, 10, 5, 0], ), atol: int = 100, ): super().__init__( positions=(46, 140), texts=dict(upper=upper_text, lower=lower_text), ) self.ttfontname = ttfontname self.set_fontcyrcler_attributes( prefix="simple", cycler=simple_cycler, ttfontname=ttfontname, anchor="mm", ) self.set_fontcyrcler_attributes( prefix="big", cycler=big_cycler, ttfontname=ttfontname, anchor="mm", ) self.set_fontcyrcler_attributes( prefix="slanted", cycler=slanted_cycler, ttfontname=ttfontname, anchor="mm", ) self.atol = atol self.big_start_pos = 87 self.big_end_pos = 110 self.slanted_start_pos = 111 self.slanted_end_pos = 118 self.mouse_start_pos = 127 self.mouse_end_pos = 141 def _get_args(self, prefix: str) -> Tuple[List[int], Cycler, int]: """Get appropriate arguments for :meth:`draw_text <wed.chaptors.marquee.MarqueeEditor.draw_text>` Args: prefix (str) : Prefix for each attribute. Returns: Tuple[List[int], Cycler, int]: Appropriate arguments. """ ys = [ getattr(MarqueeEditor, f"{prefix}_UPPER_LINE_TOP".upper()), getattr(MarqueeEditor, f"{prefix}_LOWER_LINE_TOP".upper()), ] cycler = getattr(self, f"{prefix}_cycler".lower()) fontwidth = getattr(self, f"{prefix}_fontwidth".lower()) return (ys, cycler, fontwidth)
[docs] def edit( self, frame: npt.NDArray[np.uint8], pos: int, span: int = 20 ) -> npt.NDArray[np.uint8]: """Edit the image if it is an assigned chapter (``pos``) Args: frame (npt.NDArray[np.uint8]) : Current frame (BGR image) in the video. pos (int) : Current position in the video. Returns: npt.NDArray[np.uint8]: Edited frame. """ if self.start_pos <= pos <= self.end_pos: stroke_widths: List[int] = [5, 5] textRGBs: List[Union[str, Tuple]] = ["white", "white"] stroke_fills: List[Union[str, Tuple]] = ["black", "#881c3d"] method: str = "l2r" speed: int = 3 kwargs: Dict[str, Any] = {} prefix: str = "default" if self.big_start_pos <= pos <= self.big_end_pos: prefix = "big" stroke_fills = ["#3c8fef", "#3c8fef"] elif self.slanted_start_pos <= pos <= self.slanted_end_pos: prefix = "slanted" method = "bl2tr" kwargs["angle"] = -33 else: # if (pos <= 86) or (119 <= pos <= 141) prefix = "simple" ys, cycler, fontwidth = self._get_args(prefix) span += fontwidth xys: List[Tuple[int, int]] = [(int(-pos * speed), y) for y in ys] for i, text in enumerate([self.upper_texts, self.lower_texts]): frame = self.draw_text( frame=frame, text=text, pos=pos, xy=xys[i], cycler=cycler, textRGB=textRGBs[i], span=span, stroke_width=stroke_widths[i], stroke_fill=stroke_fills[i], method=method, **kwargs, ) if self.mouse_start_pos <= pos <= self.mouse_end_pos: frame_ = self.get_frame(pos=pos) mask = ( np.sum( np.abs( frame_ - self.get_frame( pos=pos + int((pos - 127) * 0.95) - 68 ).astype(int) ), axis=2, ) < self.atol ) frame = np.where(mask[:, :, None], frame, frame_) return frame
[docs] def draw_text( self, frame: npt.NDArray[np.uint8], text: str, pos: int, xy: Tuple[int, int], cycler: Cycler, textRGB: Union[str, Tuple] = "white", span: int = 20, stroke_width: int = 5, stroke_fill: Optional[Union[str, Tuple]] = "black", method: str = "l2r", **kwargs, ) -> npt.NDArray[np.uint8]: """Draw ``text`` in ``frame`` using :func:`wed.utils.image_utils.draw_text_in_pil`. Args: frame (npt.NDArray[np.uint8]) : Input frame (BGR ndarray image). text (str) : Text to be drawn to ``frame``. pos (int) : The position of the frame to get. xy (Tuple, optional) : Where to write the ``text``. This value means the coordinates of (``x``, ``y``). Defaults to ``(0, 0)``. cycler (Cycler) : Cycler that adjusts the size of each character in ``text``. textRGB (Union[str, Tuple], optional) : The color of text. Defaults to ``"white"``. span (int, optional) : Span between characters. Defaults to ``20``. stroke_width (int, optional) : The width of the text stroke. Defaults to ``5``. stroke_fill (Optional[Union[str, Tuple]], optional) : Color to use for the text stroke. If not given, will default to the ``textRGB`` parameter. Defaults to ``black``. method (str, optional) : How to draw ``text``. Please choose from ``["l2r", "bl2tr"]``. Defaults to ``"l2r"``. Returns: npt.NDArray[np.uint8]: ``frame`` with ``text`` drawn. >>> from wed.chaptors import MarqueeEditor >>> from wed.utils import cv2plot >>> pos = 80 >>> editor = MarqueeEditor() >>> frame = editor.get_frame(pos) >>> fig, ax = plt.subplots() >>> ys, cycler, fontwidth = editor._get_args("simple") >>> for text,y in zip([editor.upper_texts, editor.lower_texts],ys): ... frame = editor.draw_text(frame=frame, text=text, pos=pos, xy=(40, y), cycler=cycler, span=fontwidth) >>> ax = cv2plot(frame, ax=ax) >>> fig.show() """ handleKeyError(lst=["l2r", "bl2tr"], method=method) if method == "l2r": func = self.draw_text_l2r elif method == "bl2tr": func = self.draw_text_bl2tr return func( frame=frame, text=text, pos=pos, xy=xy, cycler=cycler, textRGB=textRGB, span=span, stroke_width=stroke_width, stroke_fill=stroke_fill, **kwargs, )
[docs] def draw_text_l2r( self, frame: npt.NDArray[np.uint8], text: str, pos: int, xy: Tuple[int, int], cycler: Cycler, textRGB: Union[str, Tuple] = "white", span: int = 20, stroke_width: int = 5, stroke_fill: Optional[Union[str, Tuple]] = "black", **kwargs, ) -> npt.NDArray[np.uint8]: img = arr2pil(frame) x, y = xy for i, t in enumerate(text): fontsize = int(cycler.get(pos, i)) img, _ = draw_text_in_pil( text=t, img=img, xy=(x, y), textRGB=textRGB, fontsize=fontsize, ttfontname=self.ttfontname, anchor="mm", stroke_width=stroke_width, stroke_fill=stroke_fill, ) if x > (BaseWedOPEditor.FRAME_WIDTH + fontsize): break x += span frame = pil2arr(img) return frame
[docs] def draw_text_bl2tr( self, frame: npt.NDArray[np.uint8], text: str, pos: int, xy: Tuple[int, int], cycler: Cycler, textRGB: Union[str, Tuple] = "white", span: int = 20, stroke_width: int = 5, stroke_fill: Optional[Union[str, Tuple]] = "black", angle: int = -33, **kwargs, ) -> npt.NDArray[np.uint8]: frame = sp.ndimage.rotate(frame, angle=angle) frame = self.draw_text_l2r( frame=frame, text=text, pos=pos, xy=xy, cycler=cycler, textRGB=textRGB, span=span, stroke_width=stroke_width, stroke_fill=stroke_fill, ) frame = sp.ndimage.rotate(frame, angle=-angle) h, w = frame.shape[:2] t, l = ( (h - BaseWedOPEditor.FRAME_HEIGHT) // 2, (w - BaseWedOPEditor.FRAME_WIDTH) // 2, ) frame = frame[ t : t + BaseWedOPEditor.FRAME_HEIGHT, l : l + BaseWedOPEditor.FRAME_WIDTH ] return frame