caption.py

This module contains helper stuff to handle putting a caption on top of the final image.

do_caption(path_in, path_out, dict_cfg)

Make caption version of image

:param path_in: Path to original image :type path_in: Path :param path_out: Path to new image with caption :type path_out: Path :param dict_apod: The APOD dict :type dict_apod: dict :param dict_cap: Dictionary with caption properties :type dict_cap: dict

This method will create a new image from the original image, containing a caption box.

Source code in src/caption.py
def do_caption(path_in: Path, path_out: Path, dict_cfg: dict):
    """

    Make caption version of image

    :param path_in: Path to original image
    :type path_in: Path
    :param path_out: Path to new image with caption
    :type path_out: Path
    :param dict_apod: The APOD dict
    :type dict_apod: dict
    :param dict_cap: Dictionary with caption properties
    :type dict_cap: dict

    This method will create a new image from the original image, containing a
    caption box.
    """

    # sanity check
    if not path_in.exists():
        return

    # get downloaded image and size
    wp = Image.open(path_in)
    wp_w, wp_h = wp.size

    # --------------------------------------------------------------------------
    # get top dict object

    dict_cap = dict_cfg[K.S_KEY_CAPTION]

    # --------------------------------------------------------------------------
    # get font object

    # get sub dict
    dict_font = dict_cap[K.S_KEY_FONT]

    # get font name and size
    font_name = dict_font[K.S_KEY_FONT_NAME]
    font_size = dict_font[K.S_KEY_FONT_SIZE]

    # try to get specified font
    try:
        font = ImageFont.truetype(font_name, font_size)

    # NB: also applies when font name = "" (still working on it)
    except OSError:

        # can't get font, use default
        font = ImageFont.load_default(font_size)

    # combine font color and transparency
    font_color = []
    font_color.extend(dict_font[K.S_KEY_FONT_COLOR])
    font_color.append(dict_font[K.S_KEY_FONT_TRANS])
    font_color = [int(item) for item in font_color]
    font_color = tuple(font_color)

    # --------------------------------------------------------------------------
    # get text size

    # get all lines in text and wrap/separate
    lines = _get_lines(dict_cfg)

    # put the lines list back together for printing
    text = "".join(lines)

    # create a dummy image of no size to get a draw
    img_text = Image.new("RGB", (0, 0))
    draw_text = ImageDraw.Draw(img_text)

    # draw text to dummy image to get size
    bbox = draw_text.textbbox((0, 0), text, font)
    text_w = bbox[2]  # (x, y, w, h) = width == 2
    text_h = bbox[3]  # (x, y, w, h) = height == 3

    # --------------------------------------------------------------------------
    # get box size

    # get sub dict
    dict_box = dict_cap[K.S_KEY_BOX]

    # first, internal padding
    box_pad = dict_box[K.S_KEY_BOX_PAD]

    # add padding to longest line
    box_w = text_w + (box_pad * 2)
    box_h = text_h + (box_pad * 2)

    # --------------------------------------------------------------------------
    # make box

    # get radius
    box_rad = dict_box[K.S_KEY_BOX_RAD]

    # combine box color and transparency
    box_color = []
    box_color.extend(dict_box[K.S_KEY_BOX_COLOR])
    box_color.append(dict_box[K.S_KEY_BOX_TRANS])
    box_color = [int(item) for item in box_color]
    box_color = tuple(box_color)

    # draw box
    img_box = Image.new("RGBA", (int(box_w), int(box_h)), color=(0, 0, 0, 0))
    draw = ImageDraw.Draw(img_box)
    draw.rounded_rectangle(
        [(0, 0), (int(box_w), int(box_h))],
        radius=int(box_rad),
        fill=box_color,
    )

    # draw text in box
    draw.multiline_text((box_pad, box_pad), text, fill=font_color, font=font)

    # --------------------------------------------------------------------------

    # get sub dict
    dict_pad = dict_cap[K.S_KEY_PAD]

    # get initial pos (to prevent unbound)
    pos_x = 0
    pos_y = 0

    # get different pads
    pad_ext_l = dict_pad[K.S_KEY_PAD_L]
    pad_ext_t = dict_pad[K.S_KEY_PAD_T]
    pad_ext_r = dict_pad[K.S_KEY_PAD_R]
    pad_ext_b = dict_pad[K.S_KEY_PAD_B]

    # get combined center (actually x,y offset of box)

    mid_x = (wp_w - box_w) // 2
    mid_y = (wp_h - box_h) // 2

    # get position of caption from pos
    match dict_cap[K.S_KEY_CAPTION_POS]:
        case 0:  # top left
            pos_x = pad_ext_l
            pos_y = pad_ext_t
        case 1:  # top center
            pos_x = max(pad_ext_l, mid_x)
            pos_y = pad_ext_t
        case 2:  # top right
            pos_x = wp_w - pad_ext_r - box_w
            pos_y = pad_ext_t

        case 3:  # center left
            pos_x = pad_ext_l
            pos_y = max(pad_ext_t, mid_y)
        case 4:  # center center
            pos_x = max(pad_ext_l, mid_x)
            pos_y = max(pad_ext_t, mid_y)
        case 5:  # center right
            pos_x = wp_w - pad_ext_r - box_w
            pos_y = max(pad_ext_t, mid_y)

        case 6:  # bottom left
            pos_x = pad_ext_l
            pos_y = wp_h - pad_ext_b - box_h
        case 7:  # bottom center
            pos_x = max(pad_ext_l, mid_x)
            pos_y = wp_h - pad_ext_b - box_h
        case 8:  # bottom right
            pos_x = wp_w - pad_ext_r - box_w
            pos_y = wp_h - pad_ext_b - box_h

    # --------------------------------------------------------------------------
    # put caption on wallpaper at position
    pos_x = int(pos_x)
    pos_y = int(pos_y)
    wp.paste(img_box, (pos_x, pos_y), mask=img_box)

    # save captioned wallpaper
    wp.save(path_out)