Skip to content

Commit

Permalink
Merge pull request #28 from BryanJacobs/master
Browse files Browse the repository at this point in the history
Allow fixed-size crops and tag carrying
  • Loading branch information
weclaw1 authored May 14, 2024
2 parents 2e7a4e5 + f86188b commit c6ee5f1
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 13 deletions.
63 changes: 50 additions & 13 deletions inbac/controller.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import itertools
import mimetypes
import os
import shutil

from typing import Optional, List, Tuple

Expand Down Expand Up @@ -62,11 +63,19 @@ def load_images(self):
def display_image_on_canvas(self, image: Image):
self.clear_canvas()
self.model.current_image = image
self.model.canvas_image_dimensions = self.calculate_canvas_image_dimensions(
self.model.canvas_image_scaling_ratio = self.calculate_canvas_image_scaling(
self.model.current_image.size[0],
self.model.current_image.size[1],
self.view.image_canvas.winfo_width(),
self.view.image_canvas.winfo_height())
if self.model.canvas_image_scaling_ratio is None:
self.model.canvas_image_dimensions = self.model.current_image.size
self.model.canvas_image_scaling_ratio = 1.0
else:
self.model.canvas_image_dimensions = self.calculate_canvas_image_dimensions(
self.model.current_image.size[0],
self.model.current_image.size[1],
self.model.canvas_image_scaling_ratio)
displayed_image: Image = self.model.current_image.copy()
displayed_image.thumbnail(
self.model.canvas_image_dimensions, Image.ANTIALIAS)
Expand Down Expand Up @@ -102,6 +111,7 @@ def stop_selection(self):
def start_selection(self, press_coord: Tuple[int, int]):
self.model.press_coord = press_coord
self.model.move_coord = press_coord

if self.model.enabled_selection_mode and self.model.selection_box is not None:
selected_box: Tuple[int, int, int, int] = self.view.get_canvas_object_coords(
self.model.selection_box)
Expand Down Expand Up @@ -152,6 +162,11 @@ def save(self) -> bool:
self.model.selection_box)
box: Tuple[int, int, int, int] = self.get_real_box(
selected_box, self.model.current_image.size, self.model.canvas_image_dimensions)
if self.model.selected_fixed_size is not None:
box = [box[0],
box[1],
box[0] + self.model.selected_fixed_size[0],
box[1] + self.model.selected_fixed_size[1]]
new_filename: str = self.find_available_name(
self.model.args.output_dir, self.model.images[self.model.current_file])
saved_image: Image = self.model.current_image.copy().crop(box)
Expand All @@ -168,6 +183,16 @@ def save(self) -> bool:
new_filename),
self.model.args.image_format,
quality=self.model.args.image_quality)

if self.model.args.copy_tag_files:
original_filename, _ = os.path.splitext(self.model.images[self.model.current_file])
tag_file = os.path.join(self.model.args.input_dir, f"{original_filename}.txt")
if os.path.exists(tag_file):
target_tag_base, _ = os.path.splitext(new_filename)
target_tag_file = os.path.join(self.model.args.output_dir, f"{target_tag_base}.txt")
if not os.path.exists(target_tag_file):
shutil.copyfile(tag_file, target_tag_file, follow_symlinks=True)

self.clear_selection_box()
return True

Expand All @@ -177,25 +202,30 @@ def rotate_image(self):
self.model.current_image.close()
self.model.current_image = None
self.display_image_on_canvas(rotated_image)

def rotate_aspect_ratio(self):
if self.model.args.aspect_ratio is not None:
self.model.args.aspect_ratio = (
int(self.model.args.aspect_ratio[1]), int(self.model.args.aspect_ratio[0]))

@staticmethod
def calculate_canvas_image_dimensions(image_width: int,
image_height: int,
canvas_width: int,
canvas_height: int) -> Tuple[int, int]:
def calculate_canvas_image_scaling(image_width: int,
image_height: int,
canvas_width: int,
canvas_height: int) -> Optional[float]:
if image_width > canvas_width or image_height > canvas_height:
width_ratio: float = canvas_width / image_width
height_ratio: float = canvas_height / image_height
ratio: float = min(width_ratio, height_ratio)
new_image_width: int = int(image_width * ratio)
new_image_height: int = int(image_height * ratio)
return (new_image_width, new_image_height)
return (image_width, image_height)
return min(width_ratio, height_ratio)
return None

@staticmethod
def calculate_canvas_image_dimensions(image_width: int,
image_height: int,
scaling_ratio: float) -> Tuple[int, int]:
new_image_width: int = int(image_width * scaling_ratio)
new_image_height: int = int(image_height * scaling_ratio)
return (new_image_width, new_image_height)

@staticmethod
def load_image_list(directory: str) -> List[str]:
Expand Down Expand Up @@ -259,8 +289,8 @@ def get_selection_box_for_aspect_ratio(selection_box: Tuple[int,
selection_box[0] = selection_box[2] - width
return tuple(selection_box)

@staticmethod
def get_selected_box(mouse_press_coord: Tuple[int,
def get_selected_box(self,
mouse_press_coord: Tuple[int,
int],
mouse_move_coord: Tuple[int,
int],
Expand All @@ -269,6 +299,13 @@ def get_selected_box(mouse_press_coord: Tuple[int,
int,
int,
int]:

if self.model.selected_fixed_size is not None:
mouse_press_coord = mouse_move_coord
mouse_move_coord = (mouse_press_coord[0] + self.model.selected_fixed_size[0] * self.model.canvas_image_scaling_ratio,
mouse_press_coord[1] + self.model.selected_fixed_size[1] * self.model.canvas_image_scaling_ratio)


selection_top_left_x: int = min(
mouse_press_coord[0], mouse_move_coord[0])
selection_top_left_y: int = min(
Expand Down
3 changes: 3 additions & 0 deletions inbac/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ def __init__(self, args):
self.move_coord: Tuple[int, int] = (0, 0)
self.displayed_image: Optional[PhotoImage] = None
self.canvas_image: Optional[Any] = None
self.cavnas_image_scaling_ratio: Optional[float] = None
self.canvas_image_dimensions: Tuple[int, int] = (0, 0)
self.current_image: Optional[Image] = None
self.enabled_selection_mode: bool = False
self.box_selected: bool = False
self.current_file: int = 0
self.selected_fixed_size_index: Optional[int] = None
self.selected_fixed_size: Optional[Tuple[int, int]] = None
28 changes: 28 additions & 0 deletions inbac/parse_arguments.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
import argparse


class FixedSizeAction(argparse.Action):
def __init__(self, option_strings, dest, nargs=None, **kwargs):
if nargs is not None:
raise ValueError("nargs not allowed")

super().__init__(option_strings, dest, **kwargs)

def __call__(self, parser, namespace, value, option_string=None):
list_val = getattr(namespace, self.dest, [])

parts = value.split('x')
if len(parts) != 2:
raise ValueError(f"fixed_size format example: 1024x768. Gotten value '{value}' is invalid")
list_val.append((int(parts[0]), int(parts[1])))

setattr(namespace, self.dest, list_val)

def parse_arguments():
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
Expand Down Expand Up @@ -46,6 +63,17 @@ def parse_arguments():
default=[
800,
600])
parser.add_argument(
"--fixed_size",
action=FixedSizeAction,
default=[],
dest='fixed_sizes',
help="a fixed crop size to use (example: 1920x1080): may be specified multiple times")
parser.add_argument(
"--copy_tag_files",
action="store_true",
default=False,
help="if set, copy any .txt file with the same basename to the output directory and name it like the crop")
parser.add_argument("-f", "--image_format",
help="define the croped image format")
parser.add_argument("-q", "--image_quality", type=int,
Expand Down
17 changes: 17 additions & 0 deletions inbac/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def bind_events(self):
self.master.bind('x', self.save)
self.master.bind('c', self.rotate_image)
self.master.bind('r', self.rotate_aspect_ratio)
self.master.bind('f', self.cycle_fixed_selection_sizes)
self.master.bind('<Left>', self.previous_image)
self.master.bind('<Right>', self.next_image)
self.master.bind('<ButtonPress-3>', self.next_image)
Expand Down Expand Up @@ -264,3 +265,19 @@ def rotate_image(self, event: Event = None):

def rotate_aspect_ratio(self, event: Event = None):
self.controller.rotate_aspect_ratio()

def cycle_fixed_selection_sizes(self, event: Event = None):
model = self.controller.model
if len(model.args.fixed_sizes) == 0:
return

if model.selected_fixed_size_index is None:
model.selected_fixed_size_index = 0
else:
model.selected_fixed_size_index += 1

if model.selected_fixed_size_index == len(model.args.fixed_sizes):
model.selected_fixed_size_index = None
model.selected_fixed_size = None
else:
model.selected_fixed_size = model.args.fixed_sizes[model.selected_fixed_size_index]

0 comments on commit c6ee5f1

Please sign in to comment.