diff --git a/README.md b/README.md index c35b8ec6..c33f7a62 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Tool for live presentations using either [Manim (community edition)](https://www > **_NOTE:_** This project extends the work of [`manim-presentation`](https://github.com/galatolofederico/manim-presentation), with a lot more features! -- [Install](#install) +- [Installation](#installation) * [Dependencies](#dependencies) * [Pip install](#pip-install) * [Install From Repository](#install-from-repository) @@ -18,6 +18,8 @@ Tool for live presentations using either [Manim (community edition)](https://www * [Key Bindings](#key-bindings) * [Other Examples](#other-examples) - [Features and Comparison with Original manim-presentation](#features-and-comparison-with-original-manim-presentation) +- [F.A.Q](#faq) + * [How to increase quality on Windows](#how-to-increase-quality-on-windows) - [Contributing](#contributing) ## Installation @@ -168,6 +170,16 @@ Below is a non-exhaustive list of features: | Documented code | :heavy_check_mark: | :heavy_multiplication_x: | | Tested on Unix, macOS, and Windows | :heavy_check_mark: | :heavy_multiplication_x: | +## F.A.Q + +### How to increase quality on Windows + +On Windows platform, one may encounter a lower image resolution than expected. Usually, this is observed because Windows rescales every application to fit the screen. +As found by [@arashash](https://github.com/arashash), in [#20](https://github.com/jeertmans/manim-slides/issues/20), the problem can be addressed by changing the scaling factor to 100%: + +![Windows Fix Scaling](static/windows_quality_fix.png) + +in *Settings*->*Display*. ## Contributing diff --git a/manim_slides/config.py b/manim_slides/config.py index 1d0989ba..9d57f07b 100644 --- a/manim_slides/config.py +++ b/manim_slides/config.py @@ -2,7 +2,7 @@ from enum import Enum from typing import List, Optional, Set -from pydantic import BaseModel, FilePath, root_validator, validator +from pydantic import BaseModel, root_validator, validator from .defaults import LEFT_ARROW_KEY_CODE, RIGHT_ARROW_KEY_CODE diff --git a/manim_slides/present.py b/manim_slides/present.py index 5d500cee..d1f2ca3c 100644 --- a/manim_slides/present.py +++ b/manim_slides/present.py @@ -7,9 +7,6 @@ from enum import IntEnum, auto, unique from typing import List, Tuple -if platform.system() == "Windows": - import ctypes - import click import cv2 import numpy as np @@ -19,8 +16,19 @@ from .config import Config, PresentationConfig, SlideConfig, SlideType from .defaults import CONFIG_PATH, FOLDER_PATH, FONT_ARGS +INTERPOLATION_FLAGS = { + "nearest": cv2.INTER_NEAREST, + "linear": cv2.INTER_LINEAR, + "cubic": cv2.INTER_CUBIC, + "area": cv2.INTER_AREA, + "lanczos4": cv2.INTER_LANCZOS4, + "linear-exact": cv2.INTER_LINEAR_EXACT, + "nearest-exact": cv2.INTER_NEAREST_EXACT, +} + WINDOW_NAME = "Manim Slides" WINDOW_INFO_NAME = f"{WINDOW_NAME}: Info" +WINDOWS = platform.system() == "Windows" @unique @@ -252,14 +260,19 @@ def __init__( start_paused=False, fullscreen=False, skip_all=False, - resolution=(1280, 720), + resolution=(1980, 1080), + interpolation_flag=cv2.INTER_LINEAR, ): self.presentations = presentations self.start_paused = start_paused self.config = config self.skip_all = skip_all self.fullscreen = fullscreen - self.is_windows = platform.system() == "Windows" + self.resolution = resolution + self.interpolation_flag = interpolation_flag + self.window_flags = ( + cv2.WINDOW_GUI_NORMAL | cv2.WINDOW_FREERATIO | cv2.WINDOW_NORMAL + ) self.state = State.PLAYING self.lastframe = None @@ -274,39 +287,16 @@ def __init__( cv2.WINDOW_GUI_NORMAL | cv2.WINDOW_FREERATIO | cv2.WINDOW_AUTOSIZE, ) - if self.is_windows: - user32 = ctypes.windll.user32 - self.screen_width, self.screen_height = user32.GetSystemMetrics( - 0 - ), user32.GetSystemMetrics(1) - if self.fullscreen: - cv2.namedWindow(WINDOW_NAME, cv2.WND_PROP_FULLSCREEN) + cv2.namedWindow( + WINDOW_NAME, cv2.WINDOW_GUI_NORMAL | cv2.WND_PROP_FULLSCREEN + ) cv2.setWindowProperty( WINDOW_NAME, cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN ) else: - cv2.namedWindow( - WINDOW_NAME, - cv2.WINDOW_GUI_NORMAL | cv2.WINDOW_FREERATIO | cv2.WINDOW_NORMAL, - ) - cv2.resizeWindow(WINDOW_NAME, *resolution) - - def resize_frame_to_screen(self, frame: np.ndarray) -> np.ndarray: - """ - Resizes a given frame to match screen dimensions. - - Only works on Windows. - """ - assert self.is_windows, "Only Windows platforms need this method" - frame_height, frame_width = frame.shape[:2] - - scale_height = self.screen_height / frame_height - scale_width = self.screen_width / frame_width - - scale = min(scale_height, scale_width) - - return cv2.resize(frame, (int(scale * frame_height), int(scale * frame_width))) + cv2.namedWindow(WINDOW_NAME, self.window_flags) + cv2.resizeWindow(WINDOW_NAME, *self.resolution) @property def current_presentation(self) -> Presentation: @@ -343,8 +333,17 @@ def show_video(self): frame = self.lastframe - if self.is_windows and self.fullscreen: - frame = self.resize_frame_to_screen(frame) + # If Window was manually closed (impossible in fullscreen), + # we reopen it + if cv2.getWindowProperty(WINDOW_NAME, cv2.WND_PROP_VISIBLE) < 1: + cv2.namedWindow(WINDOW_NAME, self.window_flags) + cv2.resizeWindow(WINDOW_NAME, *self.resolution) + + if WINDOWS: # Only resize on Windows + _, _, w, h = cv2.getWindowImageRect(WINDOW_NAME) + + if (h, w) != frame.shape[:2]: # Only if shape is different + frame = cv2.resize(frame, (w, h), self.interpolation_flag) cv2.imshow(WINDOW_NAME, frame) @@ -477,15 +476,31 @@ def _list_scenes(folder) -> List[str]: help="Skip all slides, useful the test if slides are working.", ) @click.option( + "-r", "--resolution", type=(int, int), - default=(1280, 720), + default=(1920, 1080), help="Window resolution used if fullscreen is not set. You may manually resize the window afterward.", show_default=True, ) +@click.option( + "-i", + "--interpolation-flag", + type=click.Choice(INTERPOLATION_FLAGS.keys(), case_sensitive=False), + default="linear", + help="Set the interpolation flag to be used when resizing image. See OpenCV cv::InterpolationFlags.", + show_default=True, +) @click.help_option("-h", "--help") def present( - scenes, config_path, folder, start_paused, fullscreen, skip_all, resolution + scenes, + config_path, + folder, + start_paused, + fullscreen, + skip_all, + resolution, + interpolation_flag, ): """Present the different scenes.""" @@ -552,5 +567,6 @@ def value_proc(value: str): fullscreen=fullscreen, skip_all=skip_all, resolution=resolution, + interpolation_flag=INTERPOLATION_FLAGS[interpolation_flag], ) display.run() diff --git a/static/windows_quality_fix.png b/static/windows_quality_fix.png new file mode 100644 index 00000000..3ae84034 Binary files /dev/null and b/static/windows_quality_fix.png differ