Only external bleed
This commit is contained in:
parent
89e20fd61e
commit
088181085e
3 changed files with 165 additions and 73 deletions
|
|
@ -3,7 +3,7 @@ from pathlib import Path
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from base_proxy import LEAVE_BLEED_INCHES
|
from base_proxy import LEAVE_BLEED_INCHES
|
||||||
from sheet_layout import SheetLayout
|
from sheet_layout import SheetLayout, CardImageTransfer
|
||||||
|
|
||||||
|
|
||||||
W_INCHES_EXPECTED = 2.72
|
W_INCHES_EXPECTED = 2.72
|
||||||
|
|
@ -14,28 +14,44 @@ BLEED_INCHES_EXPECTED = 0.12
|
||||||
class InputCardImage:
|
class InputCardImage:
|
||||||
def __init__(self, path: Path):
|
def __init__(self, path: Path):
|
||||||
self.path = path
|
self.path = path
|
||||||
|
self.input_dpi = None
|
||||||
|
self.original_im = None
|
||||||
|
|
||||||
def crop(self) -> Image:
|
def load(self) -> None:
|
||||||
with Image.open(self.path) as im:
|
with Image.open(self.path) as im:
|
||||||
# print(im.info)
|
# print(im.info)
|
||||||
rgb_im = im.convert('RGB')
|
rgb_im = im.convert('RGB')
|
||||||
w, h = rgb_im.width, rgb_im.height
|
w, h = rgb_im.width, rgb_im.height
|
||||||
dpi_by_w = w / W_INCHES_EXPECTED
|
dpi_by_w = w / W_INCHES_EXPECTED
|
||||||
dpi_by_h = h / H_INCHES_EXPECTED
|
dpi_by_h = h / H_INCHES_EXPECTED
|
||||||
print(f'By width dpi is {dpi_by_w} dpi')
|
print(f'Image is {w} x {h}, {dpi_by_w} dpi (by width)')
|
||||||
if dpi_by_h != dpi_by_w:
|
if dpi_by_h != dpi_by_w:
|
||||||
print(f'dpi by height is different: {dpi_by_h} dpi')
|
print(f'By height dpi is different: {dpi_by_h} dpi')
|
||||||
crop_pixels = BLEED_INCHES_EXPECTED * dpi_by_w - LEAVE_BLEED_INCHES * dpi_by_w
|
self.input_dpi = dpi_by_w
|
||||||
crop_pixels_int = round(crop_pixels)
|
self.original_im = im
|
||||||
print(f'Image is {w} x {h}, will trim {crop_pixels} pixels (rounded to {crop_pixels_int})')
|
|
||||||
|
|
||||||
# Crop
|
def get_cropped_and_resized(self, layout: SheetLayout, transfer: CardImageTransfer) -> Image:
|
||||||
crop_box = (crop_pixels_int, crop_pixels_int, w - crop_pixels_int, h - crop_pixels_int)
|
new_dpi = layout.dpi
|
||||||
cropped_part = rgb_im.crop(crop_box)
|
|
||||||
|
|
||||||
return cropped_part
|
self.load()
|
||||||
|
im = self.original_im
|
||||||
|
|
||||||
|
# Resize to match sheet DPI
|
||||||
|
new_w = layout.in_to_px(W_INCHES_EXPECTED)
|
||||||
|
new_h = layout.in_to_px(H_INCHES_EXPECTED)
|
||||||
|
im = im.resize((new_w, new_h), resample=Image.Resampling.LANCZOS)
|
||||||
|
|
||||||
|
# Crop
|
||||||
|
top_bleed, bottom_bleed, left_bleed, right_bleed = transfer.bleeds
|
||||||
|
crop_pixels_leaving_bleed = layout.in_to_px(BLEED_INCHES_EXPECTED - LEAVE_BLEED_INCHES)
|
||||||
|
crop_pixels_no_bleed = layout.in_to_px(BLEED_INCHES_EXPECTED)
|
||||||
|
top_crop = crop_pixels_leaving_bleed if top_bleed else crop_pixels_no_bleed
|
||||||
|
bottom_crop = crop_pixels_leaving_bleed if bottom_bleed else crop_pixels_no_bleed
|
||||||
|
left_crop = crop_pixels_leaving_bleed if left_bleed else crop_pixels_no_bleed
|
||||||
|
right_crop = crop_pixels_leaving_bleed if right_bleed else crop_pixels_no_bleed
|
||||||
|
|
||||||
|
# Crop
|
||||||
|
crop_box = (left_crop, top_crop, new_w - right_crop, new_h - bottom_crop)
|
||||||
|
im = im.crop(crop_box)
|
||||||
|
|
||||||
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
|
return im
|
||||||
|
|
|
||||||
35
crop_card.py
35
crop_card.py
|
|
@ -8,7 +8,7 @@ from PIL import Image, ImageDraw, ImageCms
|
||||||
from base_proxy import BASE_DIR
|
from base_proxy import BASE_DIR
|
||||||
from card_images import InputCardImage
|
from card_images import InputCardImage
|
||||||
import sheet_layout
|
import sheet_layout
|
||||||
from sheet_layout import SheetLayout
|
from sheet_layout import SheetLayout, CardImageTransfer
|
||||||
|
|
||||||
import util
|
import util
|
||||||
|
|
||||||
|
|
@ -44,16 +44,20 @@ def create_sheet(input_cards: List[InputCardImage], sheet_filepath: Path, layout
|
||||||
guide_thickness = layout.in_to_px(CUT_GUIDE_WIDTH_INCHES)
|
guide_thickness = layout.in_to_px(CUT_GUIDE_WIDTH_INCHES)
|
||||||
|
|
||||||
for x_index in range(layout.cards_per_column):
|
for x_index in range(layout.cards_per_column):
|
||||||
left_x = layout.left_of_column(x_index, with_bleed=False)
|
left_x = layout.card_area_left_xs[x_index]
|
||||||
right_x = layout.right_of_column(x_index, with_bleed=False)
|
right_x = layout.card_area_right_xs[x_index]
|
||||||
draw.line([(left_x, 0), (left_x, layout.h)], fill=color, width=guide_thickness)
|
min_y = layout.min_padding
|
||||||
draw.line([(right_x, 0), (right_x, layout.h)], fill=color, width=guide_thickness)
|
max_y = layout.h - 1 - layout.min_padding
|
||||||
|
draw.line([(left_x, min_y), (left_x, max_y)], fill=color, width=guide_thickness)
|
||||||
|
draw.line([(right_x, min_y), (right_x, max_y)], fill=color, width=guide_thickness)
|
||||||
|
|
||||||
for y_index in range(layout.cards_per_row):
|
for y_index in range(layout.cards_per_row):
|
||||||
top_y = layout.top_of_row(y_index, with_bleed=False)
|
top_y = layout.card_area_top_ys[y_index]
|
||||||
bottom_y = layout.bottom_of_row(y_index, with_bleed=False)
|
bottom_y = layout.card_area_bottom_ys[y_index]
|
||||||
draw.line([(0, top_y), (layout.w, top_y)], fill=color, width=guide_thickness)
|
min_x = layout.min_padding
|
||||||
draw.line([(0, bottom_y), (layout.w, bottom_y)], fill=color, width=guide_thickness)
|
max_x = layout.w - 1 - layout.min_padding
|
||||||
|
draw.line([(min_x, top_y), (max_x, top_y)], fill=color, width=guide_thickness)
|
||||||
|
draw.line([(min_x, bottom_y), (max_x, bottom_y)], fill=color, width=guide_thickness)
|
||||||
|
|
||||||
# Draw cards
|
# Draw cards
|
||||||
input_cards_iter = input_cards.__iter__()
|
input_cards_iter = input_cards.__iter__()
|
||||||
|
|
@ -64,18 +68,19 @@ def create_sheet(input_cards: List[InputCardImage], sheet_filepath: Path, layout
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
break
|
break
|
||||||
|
|
||||||
card_im = input_card.get_cropped_and_resized(layout)
|
image_transfer: CardImageTransfer = layout.create_card_image_transfer(y_index, x_index)
|
||||||
sheet.paste(card_im, layout.topleft_of_card_with_bleed(y_index, x_index))
|
card_im = input_card.get_cropped_and_resized(layout, image_transfer)
|
||||||
|
sheet.paste(card_im, image_transfer.topleft_dest)
|
||||||
|
|
||||||
# Draw internal guidelines
|
# Draw internal guidelines
|
||||||
color = 'magenta'
|
color = 'magenta'
|
||||||
guide_len = 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]
|
# -1 because the pixels are in [0, W - 1] and [0, H - 1]
|
||||||
left_x = layout.left_of_column(x_index, with_bleed=False)
|
left_x = layout.card_area_left_xs[x_index]
|
||||||
right_x = layout.right_of_column(x_index, with_bleed=False) - 1
|
right_x = layout.card_area_right_xs[x_index] - 1
|
||||||
top_y = layout.top_of_row(y_index, with_bleed=False)
|
top_y = layout.card_area_top_ys[y_index]
|
||||||
bottom_y = layout.bottom_of_row(y_index, with_bleed=False) - 1
|
bottom_y = layout.card_area_bottom_ys[y_index] - 1
|
||||||
|
|
||||||
# Top left
|
# Top left
|
||||||
draw.line([(left_x, top_y), (left_x, top_y + guide_len)], fill = color, width = guide_thickness)
|
draw.line([(left_x, top_y), (left_x, top_y + guide_len)], fill = color, width = guide_thickness)
|
||||||
|
|
|
||||||
159
sheet_layout.py
159
sheet_layout.py
|
|
@ -1,10 +1,18 @@
|
||||||
from typing import Tuple
|
import dataclasses
|
||||||
|
import math
|
||||||
|
from typing import List, Tuple
|
||||||
|
|
||||||
from base_proxy import CARD_W_INCHES, CARD_H_INCHES, LEAVE_BLEED_INCHES
|
from base_proxy import CARD_W_INCHES, CARD_H_INCHES, LEAVE_BLEED_INCHES
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class CardImageTransfer:
|
||||||
|
topleft_dest: Tuple[int, int]
|
||||||
|
bleeds: List[int]
|
||||||
|
|
||||||
|
|
||||||
class SheetLayout:
|
class SheetLayout:
|
||||||
def __init__(self, dpi: int, w_inches: float, h_inches: float):
|
def __init__(self, dpi: int, w_inches: float, h_inches: float, internal_bleed: bool):
|
||||||
self.dpi = dpi
|
self.dpi = dpi
|
||||||
|
|
||||||
self.card_w_inches = CARD_W_INCHES
|
self.card_w_inches = CARD_W_INCHES
|
||||||
|
|
@ -17,66 +25,129 @@ class SheetLayout:
|
||||||
self.w = self.in_to_px(self.w_inches)
|
self.w = self.in_to_px(self.w_inches)
|
||||||
self.h = self.in_to_px(self.h_inches)
|
self.h = self.in_to_px(self.h_inches)
|
||||||
self.leave_bleed = self.in_to_px(LEAVE_BLEED_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_inches = 0.05 if internal_bleed else 0
|
||||||
self.spacing = self.in_to_px(self.spacing_inches)
|
self.spacing = self.in_to_px(self.spacing_inches)
|
||||||
|
|
||||||
self.min_padding_inches = 0.12
|
self.min_padding_inches = 0.12
|
||||||
self.min_padding = self.in_to_px(self.min_padding_inches)
|
self.min_padding = self.in_to_px(self.min_padding_inches)
|
||||||
|
|
||||||
self.cards_per_row = ((self.w - self.min_padding * 2 + self.spacing)
|
paste_area_left_xs = []
|
||||||
// (self.cropped_image_dest_w + self.spacing))
|
paste_area_right_xs = []
|
||||||
self.cards_per_column = ((self.h - self.min_padding * 2 + self.spacing)
|
card_area_left_xs = []
|
||||||
// (self.cropped_image_dest_h + self.spacing))
|
card_area_right_xs = []
|
||||||
self.cards_per_sheet = self.cards_per_row * self.cards_per_column
|
cur_x = 0
|
||||||
|
x_index = 0
|
||||||
|
|
||||||
self.total_w_content = self.cropped_image_dest_w * self.cards_per_row + self.spacing * (self.cards_per_row - 1)
|
while True:
|
||||||
self.total_h_content = self.cropped_image_dest_h * self.cards_per_column + self.spacing * (self.cards_per_column - 1)
|
paste_area_left = cur_x
|
||||||
self.blank_space_w = (self.w - self.total_w_content) // 2
|
if x_index == 0 or internal_bleed:
|
||||||
self.blank_space_h = (self.h - self.total_h_content) // 2
|
# Left side bleed
|
||||||
|
card_area_left = cur_x + self.leave_bleed
|
||||||
|
else:
|
||||||
|
card_area_left = cur_x
|
||||||
|
card_area_right = card_area_left + self.card_w
|
||||||
|
if internal_bleed:
|
||||||
|
# Right side bleed (added to last card of row in break block)
|
||||||
|
paste_area_right = card_area_right + self.leave_bleed
|
||||||
|
else:
|
||||||
|
paste_area_right = card_area_right
|
||||||
|
|
||||||
|
if paste_area_right >= self.w - 2 * self.min_padding - (self.leave_bleed if not internal_bleed else 0):
|
||||||
|
if not internal_bleed:
|
||||||
|
# Right side bleed of previous card
|
||||||
|
paste_area_right_xs[-1] += self.leave_bleed
|
||||||
|
|
||||||
|
self.cards_per_row = x_index
|
||||||
|
self.total_w_content = paste_area_right_xs[-1]
|
||||||
|
|
||||||
|
# Center content horizontally
|
||||||
|
self.blank_space_w = (self.w - self.total_w_content) // 2
|
||||||
|
self.paste_area_left_xs = [x + self.blank_space_w for x in paste_area_left_xs]
|
||||||
|
self.paste_area_right_xs = [x + self.blank_space_w for x in paste_area_right_xs]
|
||||||
|
self.card_area_left_xs = [x + self.blank_space_w for x in card_area_left_xs]
|
||||||
|
self.card_area_right_xs = [x + self.blank_space_w for x in card_area_right_xs]
|
||||||
|
break
|
||||||
|
|
||||||
|
paste_area_left_xs.append(paste_area_left)
|
||||||
|
paste_area_right_xs.append(paste_area_right)
|
||||||
|
card_area_left_xs.append(card_area_left)
|
||||||
|
card_area_right_xs.append(card_area_right)
|
||||||
|
cur_x = paste_area_right + self.spacing
|
||||||
|
x_index += 1
|
||||||
|
|
||||||
|
paste_area_top_ys = []
|
||||||
|
paste_area_bottom_ys = []
|
||||||
|
card_area_top_ys = []
|
||||||
|
card_area_bottom_ys = []
|
||||||
|
cur_y = 0
|
||||||
|
y_index = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
paste_area_top = cur_y
|
||||||
|
if y_index == 0 or internal_bleed:
|
||||||
|
# Top bleed
|
||||||
|
card_area_top = cur_y + self.leave_bleed
|
||||||
|
else:
|
||||||
|
card_area_top = cur_y
|
||||||
|
card_area_bottom = card_area_top + self.card_h
|
||||||
|
if internal_bleed:
|
||||||
|
# Bottom bleed (added to last card of column in break block)
|
||||||
|
paste_area_bottom = card_area_bottom + self.leave_bleed
|
||||||
|
else:
|
||||||
|
paste_area_bottom = card_area_bottom
|
||||||
|
|
||||||
|
if paste_area_bottom >= self.h - 2 * self.min_padding - (self.leave_bleed if not internal_bleed else 0):
|
||||||
|
if not internal_bleed:
|
||||||
|
# Bottom bleed of previous card
|
||||||
|
paste_area_bottom_ys[-1] += self.leave_bleed
|
||||||
|
|
||||||
|
self.cards_per_column = y_index
|
||||||
|
self.total_h_content = paste_area_bottom_ys[-1]
|
||||||
|
|
||||||
|
# Center content vertically
|
||||||
|
self.blank_space_h = (self.h - self.total_h_content) // 2
|
||||||
|
self.paste_area_top_ys = [y + self.blank_space_h for y in paste_area_top_ys]
|
||||||
|
self.paste_area_bottom_ys = [y + self.blank_space_h for y in paste_area_bottom_ys]
|
||||||
|
self.card_area_top_ys = [y + self.blank_space_h for y in card_area_top_ys]
|
||||||
|
self.card_area_bottom_ys = [y + self.blank_space_h for y in card_area_bottom_ys]
|
||||||
|
break
|
||||||
|
|
||||||
|
paste_area_top_ys.append(paste_area_top)
|
||||||
|
paste_area_bottom_ys.append(paste_area_bottom)
|
||||||
|
card_area_top_ys.append(card_area_top)
|
||||||
|
card_area_bottom_ys.append(card_area_bottom)
|
||||||
|
cur_y = paste_area_bottom + self.spacing
|
||||||
|
y_index += 1
|
||||||
|
|
||||||
|
self.cards_per_sheet = self.cards_per_row * self.cards_per_column
|
||||||
|
|
||||||
print(self.__dict__)
|
print(self.__dict__)
|
||||||
|
|
||||||
def in_to_px(self, inches: float) -> float:
|
def in_to_px(self, inches: float) -> float:
|
||||||
return round(inches * self.dpi)
|
return round(inches * self.dpi)
|
||||||
|
|
||||||
def top_of_row(self, y_index: int, with_bleed: bool) -> int:
|
def create_card_image_transfer(self, y_index: int, x_index: int) -> CardImageTransfer:
|
||||||
assert 0 <= y_index < self.cards_per_column
|
top_row = y_index == 0
|
||||||
top = self.blank_space_h + (self.cropped_image_dest_h + self.spacing) * y_index
|
bottom_row = y_index == self.cards_per_column - 1
|
||||||
if not with_bleed:
|
left_col = x_index == 0
|
||||||
top += self.leave_bleed
|
right_col = x_index == self.cards_per_row - 1
|
||||||
return top
|
|
||||||
|
|
||||||
def bottom_of_row(self, y_index: int, with_bleed: bool) -> int:
|
paste_x = self.paste_area_left_xs[x_index]
|
||||||
if with_bleed:
|
paste_y = self.paste_area_top_ys[y_index]
|
||||||
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:
|
bleeds = [
|
||||||
assert 0 <= x_index < self.cards_per_row
|
self.leave_bleed if top_row else 0,
|
||||||
left = self.blank_space_w + (self.cropped_image_dest_w + self.spacing) * x_index
|
self.leave_bleed if bottom_row else 0,
|
||||||
if not with_bleed:
|
self.leave_bleed if left_col else 0,
|
||||||
left += self.leave_bleed
|
self.leave_bleed if right_col else 0]
|
||||||
return left
|
return CardImageTransfer((paste_x, paste_y), bleeds)
|
||||||
|
|
||||||
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:
|
def letter_portrait_3x3_sheet_layout() -> SheetLayout:
|
||||||
return SheetLayout(1200, 8.5, 11)
|
return SheetLayout(1200, 8.5, 11, False)
|
||||||
|
|
||||||
|
|
||||||
def letter_landscape_2x4_sheet_layout() -> SheetLayout:
|
def letter_landscape_2x4_sheet_layout() -> SheetLayout:
|
||||||
return SheetLayout(1200, 11, 8.5)
|
return SheetLayout(1200, 11, 8.5, False)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue