From 89e20fd61e782d3b06a14e63278704ff5650a2ed Mon Sep 17 00:00:00 2001 From: henriquenakashima Date: Mon, 10 Jul 2023 15:07:22 -0400 Subject: [PATCH] Move SheetLayout, make InputCardImage to resize --- base_proxy.py | 3 ++ card_images.py | 6 +++ crop_card.py | 140 +++++++++++------------------------------------- sheet_layout.py | 82 ++++++++++++++++++++++++++++ 4 files changed, 123 insertions(+), 108 deletions(-) diff --git a/base_proxy.py b/base_proxy.py index f0893ad..188b25b 100644 --- a/base_proxy.py +++ b/base_proxy.py @@ -1,5 +1,8 @@ from pathlib import Path +CARD_W_INCHES = 2.48031 # 63mm +CARD_H_INCHES = 3.46457 # 88mm + LEAVE_BLEED_INCHES = 0.04 BASE_DIR = Path(__file__).parent diff --git a/card_images.py b/card_images.py index 5dd5924..5148250 100644 --- a/card_images.py +++ b/card_images.py @@ -3,6 +3,7 @@ from pathlib import Path from PIL import Image from base_proxy import LEAVE_BLEED_INCHES +from sheet_layout import SheetLayout W_INCHES_EXPECTED = 2.72 @@ -33,3 +34,8 @@ class InputCardImage: cropped_part = rgb_im.crop(crop_box) return cropped_part + + def get_cropped_and_resized(self, sheet_layout: SheetLayout) -> Image: + im = self.crop() + im = im.resize((sheet_layout.cropped_image_dest_w, sheet_layout.cropped_image_dest_h), resample=Image.Resampling.LANCZOS) + return im diff --git a/crop_card.py b/crop_card.py index 2d6cba4..f0a81e5 100644 --- a/crop_card.py +++ b/crop_card.py @@ -1,12 +1,14 @@ import math import os from pathlib import Path -from typing import List, Optional, Tuple +from typing import List from PIL import Image, ImageDraw, ImageCms -from base_proxy import BASE_DIR, LEAVE_BLEED_INCHES +from base_proxy import BASE_DIR from card_images import InputCardImage +import sheet_layout +from sheet_layout import SheetLayout import util @@ -31,126 +33,49 @@ def gather_input_cards() -> List[Path]: return gather_images(directory=BASE_DIR / 'input_cards') -CARD_W_INCHES = 2.48031 # 63mm -CARD_H_INCHES = 3.46457 # 88mm - - -class SheetLayout: - def __init__(self, dpi: int, w_inches: float, h_inches: float): - self.dpi = dpi - - self.card_w_inches = CARD_W_INCHES - self.card_h_inches = CARD_H_INCHES - self.card_w = self.in_to_px(self.card_w_inches) - self.card_h = self.in_to_px(self.card_h_inches) - - self.w_inches = w_inches - self.h_inches = h_inches - self.w = self.in_to_px(self.w_inches) - self.h = self.in_to_px(self.h_inches) - self.leave_bleed = self.in_to_px(LEAVE_BLEED_INCHES) - self.cropped_image_dest_w = self.card_w + self.leave_bleed * 2 - self.cropped_image_dest_h = self.card_h + self.leave_bleed * 2 - - self.spacing_inches = 0.05 - self.spacing = self.in_to_px(self.spacing_inches) - - self.min_padding_inches = 0.12 - self.min_padding = self.in_to_px(self.min_padding_inches) - - self.cards_per_row = ((self.w - self.min_padding * 2 + self.spacing) - // (self.cropped_image_dest_w + self.spacing)) - self.cards_per_column = ((self.h - self.min_padding * 2 + self.spacing) - // (self.cropped_image_dest_h + self.spacing)) - self.cards_per_sheet = self.cards_per_row * self.cards_per_column - - self.total_w_content = self.cropped_image_dest_w * self.cards_per_row + self.spacing * (self.cards_per_row - 1) - self.total_h_content = self.cropped_image_dest_h * self.cards_per_column + self.spacing * (self.cards_per_column - 1) - self.blank_space_w = (self.w - self.total_w_content) // 2 - self.blank_space_h = (self.h - self.total_h_content) // 2 - - print(self.__dict__) - - def in_to_px(self, inches: float) -> float: - return round(inches * self.dpi) - - def top_of_row(self, y_index: int, with_bleed: bool) -> int: - assert 0 <= y_index < self.cards_per_column - top = self.blank_space_h + (self.cropped_image_dest_h + self.spacing) * y_index - if not with_bleed: - top += self.leave_bleed - return top - - def bottom_of_row(self, y_index: int, with_bleed: bool) -> int: - if with_bleed: - return self.top_of_row(y_index, True) + self.cropped_image_dest_h - else: - return self.top_of_row(y_index, False) + self.card_h - - def left_of_column(self, x_index: int, with_bleed: bool) -> int: - assert 0 <= x_index < self.cards_per_row - left = self.blank_space_w + (self.cropped_image_dest_w + self.spacing) * x_index - if not with_bleed: - left += self.leave_bleed - return left - - def right_of_column(self, x_index: int, with_bleed: bool) -> int: - if with_bleed: - return self.left_of_column(x_index, True) + self.cropped_image_dest_w - else: - return self.left_of_column(x_index, False) + self.card_w - - def topleft_of_card_with_bleed(self, y_index: int, x_index: int) -> Tuple[int, int]: - x = self.left_of_column(x_index, with_bleed=True) - y = self.top_of_row(y_index, with_bleed=True) - return (x, y) - - -def create_sheet(input_cards: List[InputCardImage], sheet_filepath: Path, sheet_layout: SheetLayout): - sheet = Image.new('RGB', (sheet_layout.w, sheet_layout.h), 'white') +def create_sheet(input_cards: List[InputCardImage], sheet_filepath: Path, layout: SheetLayout): + sheet = Image.new('RGB', (layout.w, layout.h), 'white') # icc_profile = ImageCms.getOpenProfile(str(BASE_DIR / 'icc' / 'AdobeRGB1998.icc')) # Draw external guidelines draw = ImageDraw.Draw(sheet) color = 'black' - guide_thickness = sheet_layout.in_to_px(CUT_GUIDE_WIDTH_INCHES) + guide_thickness = layout.in_to_px(CUT_GUIDE_WIDTH_INCHES) - for x_index in range(sheet_layout.cards_per_column): - left_x = sheet_layout.left_of_column(x_index, with_bleed=False) - right_x = sheet_layout.right_of_column(x_index, with_bleed=False) - draw.line([(left_x, 0), (left_x, sheet_layout.h)], fill=color, width=guide_thickness) - draw.line([(right_x, 0), (right_x, sheet_layout.h)], fill=color, width=guide_thickness) + for x_index in range(layout.cards_per_column): + left_x = layout.left_of_column(x_index, with_bleed=False) + right_x = layout.right_of_column(x_index, with_bleed=False) + draw.line([(left_x, 0), (left_x, layout.h)], fill=color, width=guide_thickness) + draw.line([(right_x, 0), (right_x, layout.h)], fill=color, width=guide_thickness) - for y_index in range(sheet_layout.cards_per_row): - top_y = sheet_layout.top_of_row(y_index, with_bleed=False) - bottom_y = sheet_layout.bottom_of_row(y_index, with_bleed=False) - draw.line([(0, top_y), (sheet_layout.w, top_y)], fill=color, width=guide_thickness) - draw.line([(0, bottom_y), (sheet_layout.w, bottom_y)], fill=color, width=guide_thickness) + for y_index in range(layout.cards_per_row): + top_y = layout.top_of_row(y_index, with_bleed=False) + bottom_y = layout.bottom_of_row(y_index, with_bleed=False) + draw.line([(0, top_y), (layout.w, top_y)], fill=color, width=guide_thickness) + draw.line([(0, bottom_y), (layout.w, bottom_y)], fill=color, width=guide_thickness) # Draw cards input_cards_iter = input_cards.__iter__() - for y_index in range(sheet_layout.cards_per_column): - for x_index in range(sheet_layout.cards_per_row): + for y_index in range(layout.cards_per_column): + for x_index in range(layout.cards_per_row): try: input_card: InputCardImage = input_cards_iter.__next__() except StopIteration: break - card_im = input_card.crop() - card_im = card_im.resize((sheet_layout.cropped_image_dest_w, sheet_layout.cropped_image_dest_h), - resample = Image.Resampling.LANCZOS) - sheet.paste(card_im, sheet_layout.topleft_of_card_with_bleed(y_index, x_index)) + card_im = input_card.get_cropped_and_resized(layout) + sheet.paste(card_im, layout.topleft_of_card_with_bleed(y_index, x_index)) # Draw internal guidelines color = 'magenta' - guide_len = sheet_layout.in_to_px(LENGTH_CUT_GUIDE_INCHES) + guide_len = layout.in_to_px(LENGTH_CUT_GUIDE_INCHES) # -1 because the pixels are in [0, W - 1] and [0, H - 1] - left_x = sheet_layout.left_of_column(x_index, with_bleed=False) - right_x = sheet_layout.right_of_column(x_index, with_bleed=False) - 1 - top_y = sheet_layout.top_of_row(y_index, with_bleed=False) - bottom_y = sheet_layout.bottom_of_row(y_index, with_bleed=False) - 1 + left_x = layout.left_of_column(x_index, with_bleed=False) + right_x = layout.right_of_column(x_index, with_bleed=False) - 1 + top_y = layout.top_of_row(y_index, with_bleed=False) + bottom_y = layout.bottom_of_row(y_index, with_bleed=False) - 1 # Top left draw.line([(left_x, top_y), (left_x, top_y + guide_len)], fill = color, width = guide_thickness) @@ -166,8 +91,8 @@ def create_sheet(input_cards: List[InputCardImage], sheet_filepath: Path, sheet_ draw.line([(right_x, bottom_y), (right_x - guide_len, bottom_y)], fill = color, width = guide_thickness) # Save sheet - # sheet.save(sheet_filepath, 'JPEG', dpi=(sheet_layout.dpi, sheet_layout.dpi), quality=100, subsampling=0, icc_profile=icc_profile.tobytes()) - sheet.save(sheet_filepath, 'JPEG', dpi=(sheet_layout.dpi, sheet_layout.dpi), quality=100, subsampling=0) + # sheet.save(sheet_filepath, 'JPEG', dpi=(layout.dpi, layout.dpi), quality=100, subsampling=0, icc_profile=icc_profile.tobytes()) + sheet.save(sheet_filepath, 'JPEG', dpi=(layout.dpi, layout.dpi), quality=100, subsampling=0) def main(): @@ -177,15 +102,14 @@ def main(): all_input_cards = [InputCardImage(p) for p in all_input_cards_paths] - #sheet_layout = SheetLayout(1200, 11, 8.5) - sheet_layout = SheetLayout(1200, 8.5, 11) + layout = sheet_layout.letter_portrait_3x3_sheet_layout() sheets_dir = BASE_DIR / 'sheets' sheets_dir.mkdir(parents=True, exist_ok=True) - total_sheet_number = math.ceil(len(all_input_cards) / sheet_layout.cards_per_sheet) - for i, cards_in_sheet in enumerate(util.batched(all_input_cards, sheet_layout.cards_per_sheet), 1): + total_sheet_number = math.ceil(len(all_input_cards) / layout.cards_per_sheet) + for i, cards_in_sheet in enumerate(util.batched(all_input_cards, layout.cards_per_sheet), 1): print(f'Will create sheet {i}/{total_sheet_number}') sheet_filepath = sheets_dir / f'sheet{i:03d}.jpg' - create_sheet(cards_in_sheet, sheet_filepath, sheet_layout) + create_sheet(cards_in_sheet, sheet_filepath, layout) print(f'Output {sheet_filepath}') diff --git a/sheet_layout.py b/sheet_layout.py index e69de29..a6c60a2 100644 --- a/sheet_layout.py +++ b/sheet_layout.py @@ -0,0 +1,82 @@ +from typing import Tuple + +from base_proxy import CARD_W_INCHES, CARD_H_INCHES, LEAVE_BLEED_INCHES + + +class SheetLayout: + def __init__(self, dpi: int, w_inches: float, h_inches: float): + self.dpi = dpi + + self.card_w_inches = CARD_W_INCHES + self.card_h_inches = CARD_H_INCHES + self.card_w = self.in_to_px(self.card_w_inches) + self.card_h = self.in_to_px(self.card_h_inches) + + self.w_inches = w_inches + self.h_inches = h_inches + self.w = self.in_to_px(self.w_inches) + self.h = self.in_to_px(self.h_inches) + self.leave_bleed = self.in_to_px(LEAVE_BLEED_INCHES) + self.cropped_image_dest_w = self.card_w + self.leave_bleed * 2 + self.cropped_image_dest_h = self.card_h + self.leave_bleed * 2 + + self.spacing_inches = 0.05 + self.spacing = self.in_to_px(self.spacing_inches) + + self.min_padding_inches = 0.12 + self.min_padding = self.in_to_px(self.min_padding_inches) + + self.cards_per_row = ((self.w - self.min_padding * 2 + self.spacing) + // (self.cropped_image_dest_w + self.spacing)) + self.cards_per_column = ((self.h - self.min_padding * 2 + self.spacing) + // (self.cropped_image_dest_h + self.spacing)) + self.cards_per_sheet = self.cards_per_row * self.cards_per_column + + self.total_w_content = self.cropped_image_dest_w * self.cards_per_row + self.spacing * (self.cards_per_row - 1) + self.total_h_content = self.cropped_image_dest_h * self.cards_per_column + self.spacing * (self.cards_per_column - 1) + self.blank_space_w = (self.w - self.total_w_content) // 2 + self.blank_space_h = (self.h - self.total_h_content) // 2 + + print(self.__dict__) + + def in_to_px(self, inches: float) -> float: + return round(inches * self.dpi) + + def top_of_row(self, y_index: int, with_bleed: bool) -> int: + assert 0 <= y_index < self.cards_per_column + top = self.blank_space_h + (self.cropped_image_dest_h + self.spacing) * y_index + if not with_bleed: + top += self.leave_bleed + return top + + def bottom_of_row(self, y_index: int, with_bleed: bool) -> int: + if with_bleed: + return self.top_of_row(y_index, True) + self.cropped_image_dest_h + else: + return self.top_of_row(y_index, False) + self.card_h + + def left_of_column(self, x_index: int, with_bleed: bool) -> int: + assert 0 <= x_index < self.cards_per_row + left = self.blank_space_w + (self.cropped_image_dest_w + self.spacing) * x_index + if not with_bleed: + left += self.leave_bleed + return left + + def right_of_column(self, x_index: int, with_bleed: bool) -> int: + if with_bleed: + return self.left_of_column(x_index, True) + self.cropped_image_dest_w + else: + return self.left_of_column(x_index, False) + self.card_w + + def topleft_of_card_with_bleed(self, y_index: int, x_index: int) -> Tuple[int, int]: + x = self.left_of_column(x_index, with_bleed=True) + y = self.top_of_row(y_index, with_bleed=True) + return (x, y) + + +def letter_portrait_3x3_sheet_layout() -> SheetLayout: + return SheetLayout(1200, 8.5, 11) + + +def letter_landscape_2x4_sheet_layout() -> SheetLayout: + return SheetLayout(1200, 11, 8.5)