Initial version of script
This commit is contained in:
parent
d437b56e8d
commit
ca8d110396
2 changed files with 174 additions and 0 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,3 +1,7 @@
|
||||||
|
input_cards/
|
||||||
|
cropped_cards/
|
||||||
|
sheets/
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
|
|
|
||||||
170
crop_card.py
Normal file
170
crop_card.py
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from PIL import Image, ImageDraw
|
||||||
|
|
||||||
|
W_INCHES_EXPECTED = 2.72
|
||||||
|
H_INCHES_EXPECTED = 3.7
|
||||||
|
BLEED_INCHES_EXPECTED = 0.12
|
||||||
|
LEAVE_BLEED_INCHES = 0.04
|
||||||
|
CORNER_RADIUS_INCHES = 0.12
|
||||||
|
|
||||||
|
BASE_DIR = Path(__file__).parent
|
||||||
|
|
||||||
|
|
||||||
|
def gather_input_cards() -> List[str]:
|
||||||
|
input_path = BASE_DIR / 'input_cards'
|
||||||
|
files_to_crop = []
|
||||||
|
for f in os.listdir(input_path):
|
||||||
|
possible_image_path = input_path / f
|
||||||
|
if not possible_image_path.is_file():
|
||||||
|
# Skip directory
|
||||||
|
continue
|
||||||
|
if any(s == '.crop' for s in possible_image_path.suffixes):
|
||||||
|
# Skip already cropped
|
||||||
|
continue
|
||||||
|
files_to_crop.append(possible_image_path.name)
|
||||||
|
|
||||||
|
num_files_to_crop = len(files_to_crop)
|
||||||
|
print(f'Found {num_files_to_crop} images to crop')
|
||||||
|
|
||||||
|
return files_to_crop
|
||||||
|
|
||||||
|
|
||||||
|
def crop(input_filepath: Path) -> Path:
|
||||||
|
out_dir = BASE_DIR / 'cropped_cards'
|
||||||
|
out_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
cropped_filepath = out_dir / (input_filepath.stem + ".crop" + input_filepath.suffix)
|
||||||
|
try:
|
||||||
|
with Image.open(input_filepath) as im:
|
||||||
|
# print(im.info)
|
||||||
|
rgb_im = im.convert('RGB')
|
||||||
|
w, h = rgb_im.width, rgb_im.height
|
||||||
|
dpi_by_w = w / W_INCHES_EXPECTED
|
||||||
|
dpi_by_h = h / H_INCHES_EXPECTED
|
||||||
|
print(f'By width dpi is {dpi_by_w} dpi')
|
||||||
|
if dpi_by_h != dpi_by_w:
|
||||||
|
print(f'dpi by height is different: {dpi_by_h} dpi')
|
||||||
|
crop_pixels = (BLEED_INCHES_EXPECTED - LEAVE_BLEED_INCHES) * dpi_by_w
|
||||||
|
crop_pixels_int = round(crop_pixels)
|
||||||
|
print(f'Image is {w} x {h}, will trim {crop_pixels} pixels (rounded to {crop_pixels_int})')
|
||||||
|
|
||||||
|
# Crop
|
||||||
|
crop_box = (crop_pixels_int, crop_pixels_int, w - crop_pixels_int, h - crop_pixels_int)
|
||||||
|
cropped_part = rgb_im.crop(crop_box)
|
||||||
|
cropped_w, cropped_h = cropped_part.width, cropped_part.height
|
||||||
|
|
||||||
|
# Draw where cuts should go
|
||||||
|
cut_pixels = LEAVE_BLEED_INCHES * dpi_by_w
|
||||||
|
cut_pixels_int = round(cut_pixels)
|
||||||
|
draw = ImageDraw.Draw(cropped_part)
|
||||||
|
LENGTH_CUT_GUIDE_INCHES = 0.06
|
||||||
|
CUT_GUIDE_WIDTH_INCHES = 0.004
|
||||||
|
guide_len = round(LENGTH_CUT_GUIDE_INCHES * dpi_by_w)
|
||||||
|
guide_thickness = round(CUT_GUIDE_WIDTH_INCHES * dpi_by_w)
|
||||||
|
cut_x_l = cut_pixels_int
|
||||||
|
cut_x_r = cropped_w - cut_pixels_int
|
||||||
|
cut_y_t = cut_pixels_int
|
||||||
|
cut_y_b = cropped_h - cut_pixels_int
|
||||||
|
|
||||||
|
color = 'magenta'
|
||||||
|
|
||||||
|
# Top left
|
||||||
|
draw.line([(cut_x_l, cut_y_t), (cut_x_l, cut_y_t + guide_len)], fill = color, width = guide_thickness)
|
||||||
|
draw.line([(cut_x_l, cut_y_t), (cut_x_l + guide_len, cut_y_t)], fill = color, width = guide_thickness)
|
||||||
|
# Bottom left
|
||||||
|
draw.line([(cut_x_l, cut_y_b), (cut_x_l, cut_y_b - guide_len)], fill = color, width = guide_thickness)
|
||||||
|
draw.line([(cut_x_l, cut_y_b), (cut_x_l + guide_len, cut_y_b)], fill = color, width = guide_thickness)
|
||||||
|
# Top right
|
||||||
|
draw.line([(cut_x_r, cut_y_t), (cut_x_r, cut_y_t + guide_len)], fill = color, width = guide_thickness)
|
||||||
|
draw.line([(cut_x_r, cut_y_t), (cut_x_r - guide_len, cut_y_t)], fill = color, width = guide_thickness)
|
||||||
|
# Bottom right
|
||||||
|
draw.line([(cut_x_r, cut_y_b), (cut_x_r, cut_y_b - guide_len)], fill = color, width = guide_thickness)
|
||||||
|
draw.line([(cut_x_r, cut_y_b), (cut_x_r - guide_len, cut_y_b)], fill = color, width = guide_thickness)
|
||||||
|
|
||||||
|
cropped_part.save(cropped_filepath, 'JPEG', quality=100, subsampling=0)
|
||||||
|
return cropped_filepath
|
||||||
|
except OSError as e:
|
||||||
|
print(e)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def crop_all_inputs(files_to_crop: List[str]) -> List[Path]:
|
||||||
|
cropped_filepaths = []
|
||||||
|
num_files_to_crop = len(files_to_crop)
|
||||||
|
for i, input_filename in enumerate(files_to_crop, 1):
|
||||||
|
print(f'[{i}/{num_files_to_crop}] Cropping {input_filename}')
|
||||||
|
input_filepath = BASE_DIR / 'input_cards' / input_filename
|
||||||
|
cropped_filepath = crop(input_filepath)
|
||||||
|
cropped_filepaths.append(cropped_filepath)
|
||||||
|
return cropped_filepaths
|
||||||
|
|
||||||
|
|
||||||
|
def create_sheet(cropped_filepaths: List[Path]) -> Path:
|
||||||
|
# Create sheet
|
||||||
|
LETTER_PAGE_W_INCHES = 11
|
||||||
|
LETTER_PAGE_H_INCHES = 8.5
|
||||||
|
CARD_W_INCHES = 2.48031 # 63mm
|
||||||
|
CARD_H_INCHES = 3.46457 # 88mm
|
||||||
|
DPI = 1200
|
||||||
|
PAGE_W = round(LETTER_PAGE_W_INCHES * DPI)
|
||||||
|
PAGE_H = round(LETTER_PAGE_H_INCHES * DPI)
|
||||||
|
CARD_W = round(CARD_W_INCHES * DPI)
|
||||||
|
CARD_H = round(CARD_H_INCHES * DPI)
|
||||||
|
CROPPED_IMAGE_DEST_W = round((CARD_W_INCHES + LEAVE_BLEED_INCHES) * DPI)
|
||||||
|
CROPPED_IMAGE_DEST_H = round((CARD_H_INCHES + LEAVE_BLEED_INCHES) * DPI)
|
||||||
|
SPACING_INCHES = 0.05
|
||||||
|
SPACING = round(SPACING_INCHES * DPI)
|
||||||
|
CARD_COLUMNS = 4
|
||||||
|
CARD_ROWS = 2
|
||||||
|
TOTAL_W_CONTENT = CROPPED_IMAGE_DEST_W * CARD_COLUMNS + SPACING * (CARD_COLUMNS - 1)
|
||||||
|
TOTAL_H_CONTENT = CROPPED_IMAGE_DEST_H * CARD_ROWS + SPACING * (CARD_ROWS - 1)
|
||||||
|
BLANK_SPACE_W = (PAGE_W - TOTAL_W_CONTENT) // 2
|
||||||
|
BLANK_SPACE_H = (PAGE_H - TOTAL_H_CONTENT) // 2
|
||||||
|
|
||||||
|
sheet = Image.new('RGB', (PAGE_W, PAGE_H), 'white')
|
||||||
|
|
||||||
|
# Draw cards
|
||||||
|
assert len(cropped_filepaths) == 8
|
||||||
|
filenames_iter = cropped_filepaths.__iter__()
|
||||||
|
for y in range(CARD_ROWS):
|
||||||
|
for x in range(CARD_COLUMNS):
|
||||||
|
filepath = filenames_iter.__next__()
|
||||||
|
with Image.open(filepath) as card_im:
|
||||||
|
card_im = card_im.resize((CROPPED_IMAGE_DEST_W, CROPPED_IMAGE_DEST_H), resample = Image.Resampling.LANCZOS)
|
||||||
|
sheet.paste(card_im, (BLANK_SPACE_W + (CROPPED_IMAGE_DEST_W + SPACING) * x, BLANK_SPACE_H + (CROPPED_IMAGE_DEST_H + SPACING) * y))
|
||||||
|
|
||||||
|
|
||||||
|
# Save sheet
|
||||||
|
out_dir = BASE_DIR / 'sheets'
|
||||||
|
out_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
outfile = out_dir / 'sheet.jpg'
|
||||||
|
sheet.save(outfile, 'JPEG', dpi=(DPI, DPI), quality=100, subsampling=0)
|
||||||
|
return outfile
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
all_input_cards = gather_input_cards()
|
||||||
|
print(all_input_cards)
|
||||||
|
|
||||||
|
all_cropped_cards = crop_all_inputs(all_input_cards)
|
||||||
|
|
||||||
|
cards = all_cropped_cards[:8]
|
||||||
|
# cards = [
|
||||||
|
# 'Svella, Ice Shaper (Extended Andrew Mar).crop.jpg',
|
||||||
|
# 'Icon of Ancestry.crop.png',
|
||||||
|
# 'Condescend.crop.png',
|
||||||
|
# 'Sulfur Falls (Cliff Childs).crop.png',
|
||||||
|
# 'Giant Killer.crop.png',
|
||||||
|
# 'Magda, Brazen Outlaw.crop.png',
|
||||||
|
# 'Rhox.crop.png',
|
||||||
|
# 'Visionary Augmenter.crop.png',
|
||||||
|
# ]
|
||||||
|
|
||||||
|
sheet_filepath = create_sheet(cards)
|
||||||
|
print(f'Output {sheet_filepath}')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue