# 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