From ca8d110396c4bf5c035c63e830bb0ff8230113fc Mon Sep 17 00:00:00 2001 From: henriquenakashima Date: Tue, 27 Jun 2023 03:10:28 -0400 Subject: [PATCH] Initial version of script --- .gitignore | 4 ++ crop_card.py | 170 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 crop_card.py diff --git a/.gitignore b/.gitignore index 2dc53ca..15815be 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +input_cards/ +cropped_cards/ +sheets/ + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/crop_card.py b/crop_card.py new file mode 100644 index 0000000..6d41ce4 --- /dev/null +++ b/crop_card.py @@ -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()