diff --git a/images/background.png b/images/background.png new file mode 100644 index 00000000000..86a255a1b3b Binary files /dev/null and b/images/background.png differ diff --git a/images/openbb_horizontal_logo.png b/images/openbb_horizontal_logo.png new file mode 100644 index 00000000000..9d88b6cd3c1 Binary files /dev/null and b/images/openbb_horizontal_logo.png differ diff --git a/openbb_terminal/helper_funcs.py b/openbb_terminal/helper_funcs.py index 40b00241ba7..9487cd01b0e 100644 --- a/openbb_terminal/helper_funcs.py +++ b/openbb_terminal/helper_funcs.py @@ -2,6 +2,7 @@ __docformat__ = "numpy" # pylint: disable=too-many-lines import argparse +import io import logging from pathlib import Path from typing import List, Union, Optional, Dict @@ -35,6 +36,8 @@ from screeninfo import get_monitors import yfinance as yf import numpy as np +from PIL import Image, ImageDraw + from openbb_terminal.rich_config import console from openbb_terminal import feature_flags as obbff from openbb_terminal import config_plot as cfgPlot @@ -1671,6 +1674,109 @@ def search_wikipedia(expression: str) -> None: ) +def screenshot() -> None: + """ + Screenshot the terminal window or the plot window + + Parameters + ---------- + terminal_window_target: bool + Target the terminal window + """ + try: + if plt.get_fignums(): + img_buf = io.BytesIO() + plt.savefig(img_buf, format="png") + shot = Image.open(img_buf) + screenshot_to_canvas(shot, plot_exists=True) + console.print("") + + else: + console.print("No plots found.\n") + + except Exception as e: + console.print(f"Cannot reach window - {e}\n") + + +def screenshot_to_canvas(shot, plot_exists: bool = False): + """ + Frame image to OpenBB canvas. + + Parameters + ---------- + shot + Image to frame with OpenBB Canvas + plot_exists: bool + Variable to say whether the image is a plot or screenshot of terminal + """ + + WHITE_LINE_WIDTH = 3 + OUTSIDE_CANVAS_WIDTH = shot.width + 4 * WHITE_LINE_WIDTH + 5 + OUTSIDE_CANVAS_HEIGHT = shot.height + 4 * WHITE_LINE_WIDTH + 5 + UPPER_SPACE = 40 + BACKGROUND_WIDTH_SLACK = 150 + BACKGROUND_HEIGHT_SLACK = 150 + + background = Image.open(Path("images/background.png")) + logo = Image.open(Path("images/openbb_horizontal_logo.png")) + + try: + if plot_exists: + HEADER_HEIGHT = 0 + RADIUS = 8 + + background = background.resize( + ( + shot.width + BACKGROUND_WIDTH_SLACK, + shot.height + BACKGROUND_HEIGHT_SLACK, + ) + ) + + x = int((background.width - OUTSIDE_CANVAS_WIDTH) / 2) + y = UPPER_SPACE + + white_shape = [ + (x, y), + (x + OUTSIDE_CANVAS_WIDTH, y + OUTSIDE_CANVAS_HEIGHT), + ] + img = ImageDraw.Draw(background) + img.rounded_rectangle( + white_shape, + fill="black", + outline="white", + width=WHITE_LINE_WIDTH, + radius=RADIUS, + ) + background.paste(shot, (x + WHITE_LINE_WIDTH + 5, y + WHITE_LINE_WIDTH + 5)) + + # Logo + background.paste( + logo, + ( + int((background.width - logo.width) / 2), + UPPER_SPACE + + OUTSIDE_CANVAS_HEIGHT + + HEADER_HEIGHT + + int( + ( + background.height + - UPPER_SPACE + - OUTSIDE_CANVAS_HEIGHT + - HEADER_HEIGHT + - logo.height + ) + / 2 + ), + ), + logo, + ) + + background.show(title="screenshot") + + except Exception: + console.print("Shot failed.") + + @lru_cache def load_json(path: str) -> Dict[str, str]: """Loads a dictionary from a json file path diff --git a/openbb_terminal/parent_classes.py b/openbb_terminal/parent_classes.py index 57621e465cf..fc59254d3ed 100644 --- a/openbb_terminal/parent_classes.py +++ b/openbb_terminal/parent_classes.py @@ -42,6 +42,7 @@ from openbb_terminal.helper_funcs import ( load_json, parse_and_split_input, search_wikipedia, + screenshot, ) from openbb_terminal.config_terminal import theme from openbb_terminal.rich_config import console, get_ordered_list_sources @@ -92,6 +93,7 @@ class BaseController(metaclass=ABCMeta): "wiki", "record", "stop", + "screenshot", ] CHOICES_COMMANDS: List[str] = [] @@ -623,6 +625,22 @@ class BaseController(metaclass=ABCMeta): RECORD_SESSION = False SESSION_RECORDED = list() + @log_start_end(log=logger) + def call_screenshot(self, other_args: List[str]) -> None: + """Process screenshot command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="screenshot", + description="Screenshot terminal window or plot figure open into an OpenBB frame. " + "Default target is plot if there is one open, otherwise it's terminal window. " + " In case the user wants the terminal window, it can be forced with '-t` or '--terminal' flag passed.", + ) + ns_parser = parse_simple_args(parser, other_args) + + if ns_parser: + screenshot() + def parse_known_args_and_warn( self, parser: argparse.ArgumentParser,