From 4ce6d718c92c441c909e6eb8a04a24651197102c Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 14 Jan 2022 22:02:35 +0530 Subject: [PATCH] icat kitten: Add options to mirror images and remove their transparency before displaying them Fixes #4513 --- docs/changelog.rst | 3 +++ kittens/icat/main.py | 35 ++++++++++++++++++++++++++++++++--- kittens/tui/images.py | 21 +++++++++++++++++---- kitty/typing.pyi | 2 +- 4 files changed, 53 insertions(+), 8 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 897f4e528..4fac16d26 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -102,6 +102,9 @@ Detailed list of changes - Draw the dots for braille characters more evenly spaced at all font sizes (:iss:`4499`) +- icat kitten: Add options to mirror images and remove their transparency + before displaying them (:iss:`4513`) + - macOS: Respect the users system-wide global keyboard shortcut preferences (:iss:`4501`) diff --git a/kittens/icat/main.py b/kittens/icat/main.py index 62ef30ca5..1c87a4d3d 100755 --- a/kittens/icat/main.py +++ b/kittens/icat/main.py @@ -19,6 +19,7 @@ from kitty.cli import parse_args from kitty.cli_stub import IcatCLIOptions from kitty.constants import appname from kitty.guess_mime_type import guess_type +from kitty.rgb import to_color from kitty.types import run_once from kitty.typing import GRT_f, GRT_t from kitty.utils import ( @@ -55,6 +56,19 @@ are smaller than the specified area to be scaled up to use as much of the specified area as possible. +--background +default=none +Specify a background color, this will cause transparent images to be composited on +top of the specified color. + + +--mirror +default=none +type=choices +choices=none,horizontal,vertical,both +Mirror the image about a horizontal or vertical axis or both. + + --clear type=bool-set Remove all images currently displayed on the screen. @@ -307,6 +321,9 @@ class ParsedOpts: place: Optional['Place'] = None z_index: int = 0 + remove_alpha: str = '' + flip: bool = False + flop: bool = False def process(path: str, args: IcatCLIOptions, parsed_opts: ParsedOpts, is_tempfile: bool) -> bool: @@ -316,9 +333,10 @@ def process(path: str, args: IcatCLIOptions, parsed_opts: ParsedOpts, is_tempfil available_height = parsed_opts.place.height * (ss.height // ss.rows) if parsed_opts.place else 10 * m.height needs_scaling = m.width > available_width or m.height > available_height needs_scaling = needs_scaling or args.scale_up + needs_conversion = needs_scaling or bool(parsed_opts.remove_alpha) or parsed_opts.flip or parsed_opts.flop file_removed = False use_number = 0 - if m.fmt == 'png' and not needs_scaling: + if m.fmt == 'png' and not needs_conversion: outfile = path transmit_mode: 'GRT_t' = 't' if is_tempfile else 'f' fmt: 'GRT_f' = 100 @@ -328,13 +346,17 @@ def process(path: str, args: IcatCLIOptions, parsed_opts: ParsedOpts, is_tempfil fmt = 24 if m.mode == 'rgb' else 32 transmit_mode = 't' if len(m) == 1 or args.loop == 0: - outfile, width, height = render_as_single_image(path, m, available_width, available_height, args.scale_up) + outfile, width, height = render_as_single_image( + path, m, available_width, available_height, args.scale_up, + remove_alpha=parsed_opts.remove_alpha, flip=parsed_opts.flip, flop=parsed_opts.flop) else: import struct use_number = max(1, struct.unpack('@I', os.urandom(4))[0]) with NamedTemporaryFile() as f: prefix = f.name - frame_data = render_image(path, prefix, m, available_width, available_height, args.scale_up) + frame_data = render_image( + path, prefix, m, available_width, available_height, args.scale_up, + remove_alpha=parsed_opts.remove_alpha, flip=parsed_opts.flip, flop=parsed_opts.flop) outfile, width, height = frame_data.frames[0].path, frame_data.width, frame_data.height show( outfile, width, height, parsed_opts.z_index, fmt, transmit_mode, @@ -526,6 +548,13 @@ def main(args: List[str] = sys.argv) -> None: parsed_opts.z_index = parse_z_index(cli_opts.z_index) except Exception: raise SystemExit(f'Not a valid z-index specification: {cli_opts.z_index}') + try: + if cli_opts.background != 'none': + parsed_opts.remove_alpha = to_color(cli_opts.background, validate=True).as_sharp + except ValueError: + raise SystemExit(f'Not a valid color specification: {cli_opts.background}') + parsed_opts.flip = cli_opts.mirror in ('both', 'vertical') + parsed_opts.flop = cli_opts.mirror in ('both', 'horizontal') if cli_opts.detect_support: if not detect_support(wait_for=cli_opts.detection_timeout, silent=True): diff --git a/kittens/tui/images.py b/kittens/tui/images.py index 28dca3f0f..c3c714c28 100644 --- a/kittens/tui/images.py +++ b/kittens/tui/images.py @@ -189,7 +189,9 @@ def render_image( m: ImageData, available_width: int, available_height: int, scale_up: bool, - only_first_frame: bool = False + only_first_frame: bool = False, + remove_alpha: str = '', + flip: bool = False, flop: bool = False, ) -> RenderedImage: import tempfile has_multiple_frames = len(m) > 1 @@ -202,7 +204,15 @@ def render_image( if exe is None: raise OSError('Failed to find the ImageMagick convert executable, make sure it is present in PATH') cmd = [exe] - cmd += ['-background', 'none', '--', path] + if remove_alpha: + cmd += ['-background', remove_alpha, '-alpha', 'remove'] + else: + cmd += ['-background', 'none'] + if flip: + cmd.append('-flip') + if flop: + cmd.append('-flop') + cmd += ['--', path] if only_first_frame and has_multiple_frames: cmd[-1] += '[0]' cmd.append('-auto-orient') @@ -287,12 +297,15 @@ def render_as_single_image( path: str, m: ImageData, available_width: int, available_height: int, scale_up: bool, - tdir: Optional[str] = None + tdir: Optional[str] = None, + remove_alpha: str = '', flip: bool = False, flop: bool = False, ) -> Tuple[str, int, int]: import tempfile fd, output = tempfile.mkstemp(prefix='icat-', suffix=f'.{m.mode}', dir=tdir) os.close(fd) - result = render_image(path, output, m, available_width, available_height, scale_up, only_first_frame=True) + result = render_image( + path, output, m, available_width, available_height, scale_up, + only_first_frame=True, remove_alpha=remove_alpha, flip=flip, flop=flop) os.rename(result.frames[0].path, output) return output, result.width, result.height diff --git a/kitty/typing.pyi b/kitty/typing.pyi index 53b5d595e..1ac67811b 100644 --- a/kitty/typing.pyi +++ b/kitty/typing.pyi @@ -55,7 +55,7 @@ __all__ = ( 'GraphicsCommandType', 'HandlerType', 'AbstractEventLoop', 'AddressFamily', 'Socket', 'CompletedProcess', 'PopenType', 'Protocol', 'TypedDict', 'MarkType', 'ImageManagerType', 'Debug', 'LoopType', 'MouseEvent', 'TermManagerType', 'BossType', 'ChildType', 'BadLineType', 'MouseButton', - 'KeyActionType', 'KeyMap', 'KittyCommonOpts', 'SequenceMap', 'CoreTextFont', 'WindowSystemMouseEvent', + 'KeyActionType', 'KeyMap', 'KittyCommonOpts', 'AliasMap', 'SequenceMap', 'CoreTextFont', 'WindowSystemMouseEvent', 'FontConfigPattern', 'ScreenType', 'StartupCtx', 'KeyEventType', 'LayoutType', 'PowerlineStyle', 'RemoteCommandType', 'SessionType', 'SessionTab', 'SpecialWindowInstance', 'TabType', 'ScreenSize', 'WindowType' )