Move SheetLayout, make InputCardImage to resize
This commit is contained in:
parent
902482abb0
commit
89e20fd61e
4 changed files with 123 additions and 108 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
140
crop_card.py
140
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}')
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue