Skip to content

Commit

Permalink
Merge pull request #23 from dynobo/dev
Browse files Browse the repository at this point in the history
Add cli args and saving capability
  • Loading branch information
dynobo committed Aug 30, 2019
2 parents 3e8cb8e + d20cd45 commit f549f88
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 87 deletions.
161 changes: 83 additions & 78 deletions normcap/crop.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ def _select_region_with_gui(self, selection):
root = tkinter.Tk()
for idx, shot in enumerate(selection.shots):
if idx == 0:
_FullscreenWindow(root, root, shot)
_CropWindow(root, root, shot, selection.cli_args)
else:
top = tkinter.Toplevel()
_FullscreenWindow(root, top, shot)
_CropWindow(root, top, shot, selection.cli_args)
root.mainloop()

# Store result in selection class
Expand All @@ -56,42 +56,47 @@ def _crop_image(self, selection):
return selection


class _FullscreenWindow:
def __init__(self, root, current_window, shot):
class _CropWindow:
def __init__(self, root, current_window, shot, cli_args):
self.logger = logging.getLogger(__name__)
self.tk = current_window
self.root = root
self.cli_args = cli_args

if root == current_window:
self.init_root_vars()
self.root = self._init_root_vars(self.root)

self.tk.configure(bg="black") # To hide top border on i3
self.shot = shot

self.show_windows()
self.draw_border()
self.set_bindings()
self.set_window_geometry()
self.set_fullscreen()
self._show_window()
self._draw_border()
self._set_bindings()
self._set_window_geometry()
self._set_fullscreen()

def init_root_vars(self):
self.root.active_canvas = None
def _init_root_vars(self, root):
root.active_canvas = None

self.root.rect = None
self.root.start_x = 0
self.root.start_y = 0
self.root.x = 0
self.root.y = 0
root.rect = None
root.start_x = 0
root.start_y = 0
root.x = 0
root.y = 0

self.root.mode_indicator = None
self.root.modes = ("raw", "parse", "trigger")
root.mode_indicator = None
root.modes = ("raw", "parse", "trigger")
# "☷" https://en.wikipedia.org/wiki/Miscellaneous_Symbols
self.root.modes_chars = ("☰", "⚙", "★")
self.root.current_mode = self.root.modes[0]
root.modes_chars = ("☰", "⚙", "★")
root.current_mode = self.cli_args.mode

self.root.area_thres = 400
root.color = self.cli_args.color
root.img_path = self.cli_args.path

def set_fullscreen(self):
root.area_thres = 400
return root

def _set_fullscreen(self):
# Set Fullscreen
# Behave different per OS, because:
# - with tk.attributes I couldn't move the windows to the correct screen on MS Windows
Expand All @@ -101,46 +106,15 @@ def set_fullscreen(self):
else:
self.tk.attributes("-fullscreen", True)

def next_mode(self):
idx = self.root.modes.index(self.root.current_mode)
idx += 1
if idx >= len(self.root.modes):
idx = 0
self.root.current_mode = self.root.modes[idx]

def get_mode_char(self):
idx = self.root.modes.index(self.root.current_mode)
return self.root.modes_chars[idx]

def set_window_geometry(self):
self.tk.geometry(
f"{self.shot['position']['width']}x{self.shot['position']['height']}"
+ f"+{self.shot['position']['left']}+{self.shot['position']['top']}"
)

def set_bindings(self):
self.root.bind_all("<Escape>", self.on_escape_press)
self.root.bind_all("<space>", self.on_space_press)
self.canvas.bind("<B1-Motion>", self.on_move_press)
self.canvas.bind("<ButtonPress-1>", self.on_button_press)
self.canvas.bind("<ButtonRelease-1>", self.on_button_release)

def on_space_press(self, event):
self.next_mode()
if self.root.mode_indicator:
self.root.active_canvas.itemconfig(
self.root.mode_indicator, text=self.get_mode_char()
)

def show_windows(self):
def _show_window(self):
# Produces frame, useful for debug
self.frame = tkinter.Frame(self.tk)
self.frame.pack()

# Create canvas
self.canvas = tkinter.Canvas(
self.tk,
bg="red",
bg=self.root.color,
width=self.shot["position"]["width"],
height=self.shot["position"]["height"],
highlightthickness=0,
Expand All @@ -155,20 +129,37 @@ def show_windows(self):
self.screen_gc = tkimage # Prevent img being garbage collected
self.canvas.create_image(0, 0, anchor="nw", image=tkimage)

def draw_border(self):
def _draw_border(self):
self.canvas.create_rectangle(
0,
0,
self.shot["position"]["width"] - 1,
self.shot["position"]["height"] - 2,
width=3,
outline="red",
outline=self.root.color,
)

def on_escape_press(self, event):
self.logger.info("ESC pressed: Aborting screen capture")
self.end_fullscreen()
def _set_window_geometry(self):
self.tk.geometry(
f"{self.shot['position']['width']}x{self.shot['position']['height']}"
+ f"+{self.shot['position']['left']}+{self.shot['position']['top']}"
)

def _set_bindings(self):
self.root.bind_all("<Escape>", self._on_escape_press)
self.root.bind_all("<space>", self._on_space_press)
self.canvas.bind("<B1-Motion>", self._on_move_press)
self.canvas.bind("<ButtonPress-1>", self._on_button_press)
self.canvas.bind("<ButtonRelease-1>", self._on_button_release)

def _end_fullscreen(self, result=None):
if self.is_valid_selected_area(result):
self.root.result = result
else:
self.root.result = None
self.root.destroy()

# TODO: Outsource to intermediate checker
def is_valid_selected_area(self, position):
# Calculate selected area
if position is not None:
Expand All @@ -189,41 +180,56 @@ def is_valid_selected_area(self, position):

return large_enough

def end_fullscreen(self, result=None):
if self.is_valid_selected_area(result):
self.root.result = result
else:
self.root.result = None
self.root.destroy()
def _next_mode(self):
idx = self.root.modes.index(self.root.current_mode)
idx += 1
if idx >= len(self.root.modes):
idx = 0
self.root.current_mode = self.root.modes[idx]

def _get_mode_char(self):
idx = self.root.modes.index(self.root.current_mode)
return self.root.modes_chars[idx]

def get_top_right(self):
def _get_top_right(self):
top = min([self.root.start_y, self.root.y])
right = max([self.root.start_x, self.root.x])
return top, right

def on_button_press(self, event):
def _on_escape_press(self, event):
self.logger.info("ESC pressed: Aborting screen capture")
self._end_fullscreen()

def _on_space_press(self, event):
self._next_mode()
if self.root.mode_indicator:
self.root.active_canvas.itemconfig(
self.root.mode_indicator, text=self._get_mode_char()
)

def _on_button_press(self, event):
# save mouse start position
self.root.start_x = self.canvas.canvasx(event.x)
self.root.start_y = self.canvas.canvasy(event.y)

# create rectangle
# initially create rectangle
if not self.root.rect:
# Draw outline
self.root.rect = self.canvas.create_rectangle(
self.root.x, self.root.y, 1, 1, outline="red"
self.root.x, self.root.y, 1, 1, outline=self.root.color
)
# Draw indicator
self.root.mode_indicator = self.canvas.create_text(
self.root.start_x,
self.root.start_y,
anchor="se",
text=self.get_mode_char(),
fill="red",
text=self._get_mode_char(),
fill=self.root.color,
font=("Sans", 18),
)
self.root.active_canvas = self.canvas

def on_move_press(self, event):
def _on_move_press(self, event):
self.root.x = self.canvas.canvasx(event.x)
self.root.y = self.canvas.canvasy(event.y)

Expand All @@ -235,13 +241,12 @@ def on_move_press(self, event):
self.root.x,
self.root.y,
)
# self.canvas.itemconfig(self.root.mode_indicator, text=self.get_mode_char())

# Move indicator
top, right = self.get_top_right()
top, right = self._get_top_right()
self.canvas.coords(self.root.mode_indicator, right, top)

def on_button_release(self, event):
def _on_button_release(self, event):
self.root.x = self.canvas.canvasx(event.x)
self.root.y = self.canvas.canvasy(event.y)

Expand All @@ -254,4 +259,4 @@ def on_button_release(self, event):
"mode": self.root.current_mode,
}

self.end_fullscreen(result=crop_args)
self._end_fullscreen(result=crop_args)
1 change: 1 addition & 0 deletions normcap/data_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class Selection:
line_boxes: list = field(default_factory=list)
shots: list = field(default_factory=list)
mode: str = ""
cli_args: dict = field(default_factory=dict)

@property
def text(self) -> str:
Expand Down
82 changes: 73 additions & 9 deletions normcap/normcap.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
"""
# Default
import logging
import argparse
import pathlib
import datetime

# Extra
import pyperclip

# Own
from capture import Capture
Expand All @@ -10,31 +16,89 @@
from ocr import Ocr
from utils import log_dataclass

# Extra
import pyperclip

def parse_cli_args():
# Parse cli args
arg_parser = argparse.ArgumentParser(
prog="normcap",
description="Intelligent screencapture tool to capture information instead of images",
)
arg_parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="print debug information to console",
default=False,
)
arg_parser.add_argument(
"-m",
"--mode",
type=str,
default="raw",
help="set default mode to [raw, parse, trigger]",
)
arg_parser.add_argument(
"-c",
"--color",
type=str,
default="red",
help="set color for border and selection tool",
)
arg_parser.add_argument(
"-p",
"--path",
type=str,
default="",
help="set a path to store cropped and original images",
)
return arg_parser.parse_args()

def main():
# Setup logging

def init_logging(log_level):
logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
datefmt="%H:%M:%S",
level=logging.DEBUG,
level=log_level,
)
logger = logging.getLogger(__name__)
logger.info("Starting normcap...")
return logger


selection = Selection()
def _store_images(path, images):
storage_path = pathlib.Path(path)
now = datetime.datetime.now()

for idx, image in enumerate(images):
name = f"{now:%Y-%m-%d_%H:%M}_{idx}.png"
image.save(storage_path / name)


def main():
args = parse_cli_args()

# Setup logging
if args.verbose:
logger = init_logging(logging.DEBUG)
else:
logger = init_logging(logging.WARN)

logger.info("Creating data object...")
selection = Selection(cli_args=args)

logger.info("Taking screenshot(s)...")
selection = Capture().capture_screen(selection)

logger.info("Launching gui for selection...")
selection = Crop().select_and_crop(selection)

if selection.cli_args.path:
logger.info("Saving images to {selection.cli_args.path}...")
images = [selection.image] + [s["image"] for s in selection.shots]
_store_images(selection.cli_args.path, images)

log_dataclass(selection)
return
# cap.select_region_with_gui()
# cap.crop_shot()
# selection = cap.selection

ocr = Ocr()
selection.line_boxes = ocr.recognize(selection.image)
Expand Down

0 comments on commit f549f88

Please sign in to comment.