Spaces:
Running
Running
# cc @2024 COPAINT | |
# troubleshooting: groueix@copaint.com | |
""" | |
# usage | |
python copaint.py --input data/input_design.png --back data/back_design.png --outputfolder output | |
# install dependencies | |
pip install torch torchvision reportlab PyPDF2 Pillow argparse | |
# if you are using a mac, you might need to install cairosvg and cairo to load SVG files | |
pip install cairosvg ; brew install cairo libffi | |
export PKG_CONFIG_PATH="/usr/local/lib/pkgconfig:/opt/homebrew/lib/pkgconfig:$PKG_CONFIG_PATH" | |
export DYLD_LIBRARY_PATH="/usr/local/lib:/opt/homebrew/lib:$DYLD_LIBRARY_PATH" | |
""" | |
import argparse | |
import os | |
import numpy as np | |
import torchvision | |
import torch | |
import time # Add this import for timing | |
from reportlab.pdfgen import canvas | |
from reportlab.lib.pagesizes import letter, A4 | |
from reportlab.lib.units import inch | |
import PyPDF2 | |
import logging | |
from functools import lru_cache | |
from matplotlib import font_manager | |
from PIL import Image, ImageDraw, ImageFont | |
# Configure logging | |
logging.basicConfig( | |
level=logging.INFO, | |
format='%(asctime)s - %(levelname)s - %(message)s', | |
datefmt='%Y-%m-%d %H:%M:%S' | |
) | |
logger = logging.getLogger(__name__) | |
# Configure debug logging based on environment variable | |
logger.setLevel(logging.DEBUG) | |
Image.MAX_IMAGE_PIXELS = None # Removes the limit entirely | |
fromPIltoTensor = torchvision.transforms.ToTensor() | |
fromTensortoPIL = torchvision.transforms.ToPILImage() | |
pre_loaded_images = {} | |
def get_font(debug=False) -> str: | |
""" | |
Get the path to the Bradley Hand font, cached after first call. | |
""" | |
start_time = time.time() | |
good_font_options = ["Avenir Next", "HelveticaNeue", "AdobeClean-Regular", "Arial"] # "Bradley Hand" | |
font_paths = ["/System/Library/Fonts/Avenir Next.ttc"] | |
for font_path in font_paths: | |
if os.path.exists(font_path): | |
logger.info(f"Found '{font_path}' font") | |
return font_path | |
available_fonts = font_manager.findSystemFonts(fontpaths=None, fontext='ttf') | |
font_path = None | |
for good_font in good_font_options: | |
font_path = next((font for font in available_fonts if good_font in font), None) | |
if font_path: | |
logger.info(f"Found '{good_font}' font: {font_path}") | |
break | |
if font_path is None: | |
font_path = available_fonts[0] | |
logger.warning(f"No good fonts found. Using default: {font_path}") | |
logger.warning("Please install one of the recommended fonts.") | |
if debug: | |
logger.debug(f"Font loading took {time.time() - start_time:.4f} seconds") | |
return font_path | |
font_path = get_font() | |
def load_image(image_path, debug=False): | |
""" Load an image from a file path and return a tensor. """ | |
start_time = time.time() | |
# check if the path exists | |
assert os.path.exists(image_path), f"File not found: {image_path}" | |
# check if the file is an SVG | |
if image_path.endswith(".svg"): | |
import cairosvg | |
import io | |
# Convert SVG to PNG | |
with open(image_path, "rb") as svg_file: | |
png_data = cairosvg.svg2png(file_obj=svg_file) | |
# Load the PNG data into a Pillow Image | |
image = Image.open(io.BytesIO(png_data)) | |
else: | |
image = Image.open(image_path) | |
# convert to RGBA | |
image = image.convert("RGBA") | |
# Apply a white background | |
background = Image.new("RGB", image.size, (255, 255, 255)) | |
background.paste(image, mask=image.split()[3]) | |
image = background | |
image_open_time = time.time() | |
if debug: | |
logger.debug(f"Image opening took {image_open_time - start_time:.4f} seconds") | |
image = fromPIltoTensor(image).unsqueeze(0) | |
logger.info(f"Loaded image of shape {image.shape}, from {image_path}") | |
if debug: | |
logger.debug(f"Image to tensor conversion took {time.time() - image_open_time:.4f} seconds") | |
logger.debug(f"Total image loading took {time.time() - start_time:.4f} seconds") | |
return image | |
def save_image(tensor, image_path, debug=False): | |
""" Save a tensor to an image file. """ | |
start_time = time.time() | |
logger.info(f"Saving image of shape {tensor.shape} to {image_path}") | |
image = fromTensortoPIL(tensor.squeeze(0)) | |
conversion_time = time.time() | |
if debug: | |
logger.debug(f"Tensor to PIL conversion took {conversion_time - start_time:.4f} seconds") | |
image.save(image_path) | |
if debug: | |
logger.debug(f"Image saving took {time.time() - conversion_time:.4f} seconds") | |
logger.debug(f"Total save_image took {time.time() - start_time:.4f} seconds") | |
def save_tensor_to_pdf(tensor, pdf_path, is_front=True, margin=0.25, img_small_side_in_cm=None, a4=False, high_res=False, scale=None, debug=False): | |
""" | |
Save a tensor to a PDF, the tensor is assumed to be a single image, and is centered on the page. | |
""" | |
start_time = time.time() | |
image = fromTensortoPIL(tensor.squeeze(0)) | |
img_width, img_height = image.size | |
# 1 Inch = 72 Points : ad-hoc metric used in typography and the printing industry. | |
# The US Letter format is US Letter size: 8.5 by 11 inches | |
W, H = 8.5, 11 # the unit is inch | |
if a4: | |
logger.info("Using A4 format") | |
W, H = 8.27, 11.69 # the unit is inch | |
page_width_in_pt = (W - 2*margin) * inch | |
page_height_in_pt = (H - 2*margin) * inch | |
img_small_side_in_pt = None | |
img_large_side_in_pt = None | |
if img_small_side_in_cm is not None: | |
img_small_side_in_pt = img_small_side_in_cm * inch * 0.393701 # 1 cm = 0.393701 inches | |
img_large_side_in_pt = img_small_side_in_pt * max(img_width, img_height) / min(img_width, img_height) | |
assert(img_small_side_in_pt < page_width_in_pt and img_large_side_in_pt < page_height_in_pt), f"Cell size in cm is too large for the page, max in pt unit is {page_width_in_pt}x{page_height_in_pt}, got {img_small_side_in_pt}x{img_large_side_in_pt}. It looks you manually set the size of the cell in cm, but the image is too large for the page, try a smaller cell size." | |
logger.info(f"Saving tensor of shape {tensor.shape} to {pdf_path}") | |
# Convert tensor to image | |
t1 = time.time() | |
if debug: | |
logger.debug(f"Tensor to PIL conversion took {time.time() - t1:.4f} seconds") | |
t2 = time.time() | |
# Check if image should be rotated | |
scale_1, rotated = None, True | |
if scale is not None: | |
scale_1, rotated = scale | |
if image.width > image.height and rotated: | |
logger.info(f"Rotating image. Size in pixels: {image.width}, {image.height}") | |
image = image.rotate(90, expand=True) | |
logger.info(f"Rotated image. Size in pixels: {image.width}, {image.height}") | |
img_width, img_height = image.width, image.height | |
rotated = True | |
else: | |
rotated = False | |
# check if it's better to maxout width or height | |
if scale_1 is None: | |
if img_small_side_in_pt is not None: | |
scale_1 = img_small_side_in_pt / min(img_width, img_height) # this might go over the page | |
else: | |
# Calculate the scaling factor to fit the image within the page | |
scale_width = page_width_in_pt / img_width | |
scale_height = page_height_in_pt / img_height | |
scale_1 = min(scale_width, scale_height) # Choose the smaller scale to preserve aspect ratio | |
# Calculate the resized image dimensions | |
new_width = img_width * scale_1 | |
new_height = img_height * scale_1 | |
# Calculate offsets to center the image on the page | |
x_offset = (page_width_in_pt - new_width) // 2 | |
y_offset = (page_height_in_pt - new_height) // 2 | |
if debug: | |
logger.debug(f"Image calculations took {time.time() - t2:.4f} seconds") | |
# Save image to PDF | |
t3 = time.time() | |
# Use PNG for high-res mode instead of JPG | |
image_path = "temp.png" if high_res else "temp.jpg" | |
ram_folder_linux = "/dev/shm/" | |
if os.path.exists(ram_folder_linux): | |
image_path = os.path.join(ram_folder_linux, image_path) | |
image.save(image_path) | |
if debug: | |
logger.debug(f"Temporary image saving took {time.time() - t3:.4f} seconds") | |
# Create a PDF | |
t4 = time.time() | |
if a4: | |
c = canvas.Canvas(pdf_path, pagesize = A4) | |
else: | |
c = canvas.Canvas(pdf_path, pagesize = letter) | |
c.drawImage(image_path, x_offset+margin*inch, y_offset+margin*inch, width=new_width, height=new_height, preserveAspectRatio=True) | |
c.save() | |
if debug: | |
logger.debug(f"PDF creation took {time.time() - t4:.4f} seconds") | |
os.remove(image_path) | |
if debug: | |
logger.debug(f"Total PDF saving took {time.time() - start_time:.4f} seconds") | |
return pdf_path, (scale_1, rotated) | |
def merge_pdf_list(pdfs, output_path, debug=False): | |
""" Merge a list of PDFs into a single PDF. """ | |
start_time = time.time() | |
merger = PyPDF2.PdfMerger() | |
for pdf in pdfs: | |
merger.append(pdf) | |
merger.write(output_path) | |
merger.close() | |
if debug: | |
logger.debug(f"PDF merging took {time.time() - start_time:.4f} seconds") | |
return output_path | |
def create_image_with_text(text: str = "1", size: int = 400, underline: bool = True, debug=False) -> torch.Tensor: | |
""" Create an image with text using PIL. Returns a torch tensor. """ | |
start_time = time.time() | |
# Create a blank image (200x200 pixels, white background) | |
if isinstance(size, int): | |
size = (size, size) | |
image = Image.new("RGB", size, "white") | |
# Create a drawing object | |
draw = ImageDraw.Draw(image) | |
# Set the font (optional) | |
try: | |
font = ImageFont.truetype(font_path, size=int(size[1]/1.3)) # Ensure the font is available | |
except IOError: | |
font = ImageFont.load_default() | |
# turn size to 100 | |
# Use textbbox to measure the text dimensions | |
visual_bbox = draw.textbbox((0, 0), text, font=font) | |
# (-4, 101, 340, 260) | |
text_width = visual_bbox[2] - visual_bbox[0] # Width of the text | |
text_height = visual_bbox[3] - visual_bbox[1] # Height of the text | |
center_point = (size[0] // 2, size[1] // 2) | |
top_left_of_BB = (center_point[0] - text_width // 2, center_point[1] - text_height // 2) | |
baseline = (top_left_of_BB[0] - visual_bbox[0], top_left_of_BB[1] - visual_bbox[1]) | |
visual_bbox = draw.textbbox(baseline, text, font=font) | |
# draw.rectangle(visual_bbox, outline="red", width=2) | |
# print(f" text {text} Text width: {text_width}, Text height: {text_height}", f"Image width: {image.width}, Image height: {image.height}", f"Text position: {baseline}") | |
# # Draw the text | |
draw.text(baseline, text, fill="black", font=font) | |
if underline: | |
# # Add a line under the text | |
x = baseline[0] | |
y = visual_bbox[3] + 20 | |
draw.line((x, y, x+text_width, y), fill="black", width=5) | |
tensor = fromPIltoTensor(image).unsqueeze(0) | |
if debug and len(text) <= 2: # Only log for short texts (cell numbers) when debugging | |
logger.debug(f"Creating image with text '{text}' took {time.time() - start_time:.4f} seconds") | |
return tensor | |
def create_back_image(h, w, h_cells, w_cells, logo_image, logo_insta_image, unique_identifier, list_of_cell_idx=None, debug=False): | |
""" | |
Create back image tensor, of size hxw - | |
Black pixels at the separation of cells to draw the lines | |
The logo is in each cell, with the cell number underlined | |
logo_image : tensor of size 1x3xhxw | |
""" | |
logger.info(f"Creating back image of size {h}x{w} for {h_cells}x{w_cells} cells") | |
start_time = time.time() | |
num_channels = 3 # do not consider the alpha channel | |
back_image = torch.ones(1, num_channels, h, w) | |
# cell size in pixels | |
cell_h = h // h_cells | |
cell_w = w // w_cells | |
# hyperparameters controlling the thickness of the lines and the logo size | |
line_thickness = min(cell_h, cell_w) // 100 | |
logo_size = min(cell_h, cell_w) // 4 | |
logo_offset = min(cell_h, cell_w) // 50 | |
number_size = min(cell_h, cell_w) // 2 | |
if debug: | |
logger.debug(f"thickness of the lines: {line_thickness}") | |
logger.debug(f"Initialization took {time.time() - start_time:.4f} seconds") | |
# Create the grid lines | |
grid_start_time = time.time() | |
line_half_thickness = line_thickness // 2 | |
for i in range(h_cells): | |
for j in range(w_cells): | |
h0 = i * cell_h # height start | |
h1 = (i + 1) * cell_h # height end | |
w0 = j * cell_w # width start | |
w1 = (j + 1) * cell_w # width end | |
if h0+line_half_thickness < h: | |
back_image[:, :num_channels, h0:(h0+line_half_thickness), :] = 0 | |
if w0+line_half_thickness < w: | |
back_image[:, :num_channels, :, w0:(w0+line_half_thickness)] = 0 | |
if h1 - line_half_thickness > 0: | |
back_image[:, :num_channels, (h1-line_half_thickness):h1, :] = 0 | |
if w1 - line_half_thickness > 0: | |
back_image[:, :num_channels, :, (w1-line_half_thickness):w1] = 0 | |
if debug: | |
logger.debug(f"Creating grid lines took {time.time() - grid_start_time:.4f} seconds") | |
# Resize logo for all cells | |
logo_resize_time = time.time() | |
_, _, h, w = logo_image.size() | |
scale_logo = min(logo_size / h, logo_size / w) | |
new_h, new_w = int(h * scale_logo), int(w * scale_logo) | |
logo_image_resized = torch.nn.functional.interpolate(logo_image, size=(new_h, new_w), mode='bilinear') | |
t_insta = time.time() | |
_, _, h_insta, w_insta = logo_insta_image.size() | |
scale_insta = min(logo_size / h_insta, logo_size / w_insta) / 5 | |
new_h_insta, new_w_insta = int(h_insta * scale_insta), int(w_insta * scale_insta) | |
logo_insta_image_resized = torch.nn.functional.interpolate(logo_insta_image, size=(new_h_insta, new_w_insta), mode='bilinear') | |
if debug: | |
logger.debug(f"Logo resizing took {time.time() - logo_resize_time:.4f} seconds ({time.time() - t_insta:.4f} for insta and {t_insta - logo_resize_time:.4f} for copaint logo)") | |
# save logo_insta_image_resized | |
save_image(logo_insta_image_resized, "logo_insta_image_resized.png", debug=debug) | |
# Add content to cells | |
cell_content_time = time.time() | |
letscopaint = create_image_with_text("copaint.art", underline=False, | |
size=(int(0.8*number_size), number_size//8), | |
debug=debug) | |
# add unique identifier | |
unique_identifier_size_w = number_size | |
unique_identifier_size_h = number_size // 4 | |
image_with_unique_identifier = create_image_with_text(unique_identifier, underline=False, | |
size=(unique_identifier_size_w, unique_identifier_size_h), | |
debug=debug) | |
for i in range(h_cells): | |
for j in range(w_cells): | |
h0 = i * cell_h # height start | |
h1 = (i + 1) * cell_h # height end | |
w0 = j * cell_w # width start | |
w1 = (j + 1) * cell_w # width end | |
# add logo at the bottom right of the cell | |
logo_size_h, logo_size_w = logo_image_resized.shape[2:] | |
back_image[:, :, h1-logo_size_h-logo_offset:h1-logo_offset, w1-logo_size_w-logo_offset:w1-logo_offset] = logo_image_resized[:, :num_channels, :, :] | |
# add cell number at the center of the cell | |
# invert cell number to match the order of the canvas. 1 is at the top right, and w_cells is at the top left | |
if list_of_cell_idx is not None: | |
logger.info(f"list_of_cell_idx: {list_of_cell_idx}") | |
if list_of_cell_idx is not None: | |
cell_number = list_of_cell_idx[i*w_cells+j] | |
else: | |
cell_number = i*w_cells+(w_cells-j) | |
image_with_number = create_image_with_text(f"{cell_number}", size=number_size, debug=debug) | |
start_h_big = h0 + (h1 - h0) // 2 - number_size // 2 | |
start_w_big = w0 + (w1 - w0) // 2 - number_size // 2 | |
back_image[:, :, start_h_big:start_h_big+number_size, start_w_big:start_w_big+number_size] = image_with_number[:, :num_channels, :, :] | |
start_h = h0 + unique_identifier_size_h // 2 # Fix | |
start_w = w0 + unique_identifier_size_h // 2 # Fix | |
back_image[:, :, start_h:start_h+unique_identifier_size_h, start_w:start_w+unique_identifier_size_w] = image_with_unique_identifier[:, :num_channels, :, :] | |
start_letscopaint_h = h1-logo_offset # Fix | |
start_letscopaint_w = w0 + unique_identifier_size_h // 16 # Fix | |
back_image[:, :, start_letscopaint_h-(number_size//8):start_letscopaint_h, start_letscopaint_w:start_letscopaint_w+(int(0.8*number_size))] = letscopaint[:, :num_channels, :, :] | |
# add instagram logo at the bottom left of the cell | |
_, _, h_insta, w_insta = logo_insta_image_resized.shape | |
start_insta_h = h1-logo_offset # Fix | |
start_insta_w = w0 + unique_identifier_size_h // 6 # Fix | |
back_image[:, :, start_insta_h-(number_size//8):start_insta_h-(number_size//8)+h_insta, start_insta_w:start_insta_w+w_insta] = logo_insta_image_resized[:, :num_channels, :, :] | |
if debug: | |
logger.debug(f"Adding content to cells took {time.time() - cell_content_time:.4f} seconds") | |
logger.debug(f"Created back image of shape {back_image.shape}") | |
logger.debug(f"Total back image creation took {time.time() - start_time:.4f} seconds") | |
return back_image | |
def image_to_pdf_core(input_image, file_name, logo_image, outputfolder, h_cells, w_cells, unique_identifier="Mauricette", cell_size_in_cm=None, a4=False, high_res=False, list_of_cell_idx=None, scale=None, debug=False): | |
overall_start_time = time.time() | |
os.makedirs(outputfolder, exist_ok=True) | |
scale_1, scale_2, scale_3, scale_4 = None, None, None, None | |
if scale is not None: | |
scale_1, scale_2, scale_3, scale_4 = scale | |
# Load image | |
t1 = time.time() | |
if not isinstance(input_image, torch.Tensor): | |
if input_image in pre_loaded_images: | |
image = pre_loaded_images[input_image] | |
logger.info(f"Loaded image from cache: {input_image}") | |
else: | |
image = load_image(input_image, debug=debug) | |
pre_loaded_images[input_image] = image | |
else: | |
image = input_image | |
if debug: | |
logger.debug(f"Image loading took {time.time() - t1:.4f} seconds") | |
_, c, h, w = image.shape | |
logger.info(f"Image shape: {image.shape}") | |
t1_2 = time.time() | |
if logo_image in pre_loaded_images: | |
logo_image = pre_loaded_images[logo_image] | |
logger.info(f"Loaded logo copaint image from cache: {logo_image}") | |
else: | |
logo_image = load_image(logo_image, debug=debug) | |
pre_loaded_images[logo_image] = logo_image | |
if debug: | |
logger.debug(f"Logo copaint Image loading took {time.time() - t1_2:.4f} seconds") | |
t1_3 = time.time() | |
logo_insta_path = "./copaint/static/logo_instagram.png" | |
if logo_insta_path in pre_loaded_images: | |
logo_insta_image = pre_loaded_images[logo_insta_path] | |
logger.info(f"Loaded logo instagram image from cache: {logo_insta_path}") | |
else: | |
logo_insta_image = load_image(logo_insta_path, debug=debug) | |
pre_loaded_images[logo_insta_path] = logo_insta_image | |
if debug: | |
logger.debug(f"Logo instagram Image loading took {time.time() - t1_3:.4f} seconds") | |
# # Quick check that the greatest dimension corresponds to the greatest number of cells | |
# if h > w and h_cells < w_cells: | |
# print("Swapping h_cells and w_cells") | |
# h_cells, w_cells = w_cells, h_cells | |
# elif w > h and w_cells < h_cells: | |
# print("Swapping h_cells and w_cells") | |
# h_cells, w_cells = w_cells, h_cells | |
# Create back image | |
t2 = time.time() | |
multiplier_w = max(1, 10000 // w) | |
multiplier_h = max(1, 10000 // h) | |
if scale_3 is None: | |
scale_3 = max(multiplier_w, multiplier_h) | |
logger.info(f"Creating back image with {h*scale_3} x {w*scale_3} pixels for {h_cells} x {w_cells} cells") | |
back_image = create_back_image(h*scale_3, w*scale_3, h_cells, w_cells, logo_image, logo_insta_image, | |
unique_identifier=unique_identifier, list_of_cell_idx=list_of_cell_idx, debug=debug) | |
if debug: | |
save_image(back_image, os.path.join(outputfolder, "back_image.png"), debug=debug) | |
logger.debug(f"Back image creation and saving took {time.time() - t2:.4f} seconds") | |
# Save to PDF | |
t3 = time.time() | |
os.makedirs(outputfolder, exist_ok=True) | |
output_path_front = os.path.join(outputfolder, "output_front.pdf") | |
output_path_back = os.path.join(outputfolder, "output_back.pdf") | |
img_small_side_in_cm = None | |
if cell_size_in_cm is not None: | |
# Why Min? Cells are not neccearily square, depending on the aspect ratio of the image, and the number of H and W cells, so we assume cell_size_in_cm is the smallest side of the cell. | |
logger.info(f"cell_size_in_cm: {cell_size_in_cm}") | |
min_cells = min(h_cells, w_cells) | |
img_small_side_in_cm = cell_size_in_cm * min_cells # smallest side in cm. | |
# print image and back image shapes | |
if debug: | |
logger.debug(f"Image shape: {image.shape}") | |
logger.debug(f"Back image shape: {back_image.shape}") | |
# Only resize back image if not high-res | |
if not high_res: | |
back_image_h, back_image_w = back_image.shape[2:] | |
scale_h = 4096 / back_image_h | |
if scale_4 is None: | |
scale_4 = scale_h | |
back_image = torch.nn.functional.interpolate(back_image, scale_factor=scale_4, mode='bilinear') | |
_, scale_1 = save_tensor_to_pdf(image, output_path_front, is_front=True, img_small_side_in_cm=img_small_side_in_cm, a4=a4, high_res=high_res, scale=scale_1, debug=debug) | |
_, scale_2 = save_tensor_to_pdf(back_image, output_path_back, is_front=False, img_small_side_in_cm=img_small_side_in_cm, a4=a4, high_res=high_res, scale=scale_2, debug=debug) | |
scale = (scale_1 , scale_2, scale_3, scale_4) | |
if debug: | |
logger.debug(f"PDF creation took {time.time() - t3:.4f} seconds") | |
# concatenate pdfs | |
t4 = time.time() | |
logger.info("Concatenating PDFs") | |
output_path = os.path.join(outputfolder, f"{file_name}_{h_cells}x{w_cells}_copaint.pdf") | |
merge_pdf_list([output_path_front, output_path_back], output_path, debug=debug) | |
# clean unnecessary files | |
os.remove(output_path_front) | |
os.remove(output_path_back) | |
if debug: | |
logger.debug(f"PDF concatenation and cleanup took {time.time() - t4:.4f} seconds") | |
logger.info(f"Total processing time: {time.time() - overall_start_time:.4f} seconds") | |
logger.info(f"Done! Output saved to {output_path}") | |
return output_path, scale | |
def image_to_pdf(input_image, logo_image, outputfolder, h_cells, w_cells, unique_identifier="Mauricette", cell_size_in_cm=None, a4=False, high_res=False, min_cell_size_in_cm=2, list_of_cell_idx=None, debug=False): | |
""" | |
Create a copaint PDF from an image and a logo. | |
""" | |
logger.info(f"h_cells: {h_cells}, w_cells: {w_cells}, a4: {a4}") | |
image = load_image(input_image, debug=debug) | |
_, c, h, w = image.shape | |
file_name = os.path.basename(input_image) | |
# Check if the image needs to be split to fit in the page. | |
if cell_size_in_cm is not None: | |
min_cell_size_in_cm = cell_size_in_cm | |
# The US Letter format is US Letter size: 8.5 by 11 inches | |
W, H = 8.5, 11 # the unit is inch | |
if a4: | |
logger.info("Using A4 format") | |
W, H = 8.27, 11.69 # the unit is inch | |
margin = 0.25 # hardcoded margin | |
page_width_in_pt = (W - 2 * margin) * inch | |
page_height_in_pt = (H - 2 * margin) * inch | |
max_cell_per_page_h = h_cells | |
max_cell_per_page_w = w_cells | |
established_cell_size = False | |
while not established_cell_size: | |
img_small_side_in_pt = min(max_cell_per_page_h, max_cell_per_page_w) * min_cell_size_in_cm * inch * 0.393701 # 1 cm = 0.393701 inches | |
minimum_is_width = min(w, h) == w | |
img_large_side_in_pt = img_small_side_in_pt * max(w, h) / min(w, h) | |
logger.info(f"img_small_side_in_pt: {img_small_side_in_pt}, img_large_side_in_pt: {img_large_side_in_pt}") | |
logger.info(f"page_width_in_pt: {page_width_in_pt}, page_height_in_pt: {page_height_in_pt}") | |
if img_large_side_in_pt < page_height_in_pt and img_small_side_in_pt < page_width_in_pt: | |
established_cell_size = True | |
else: | |
max_cell_per_page_h = max_cell_per_page_h // 2 | |
max_cell_per_page_w = max_cell_per_page_w // 2 | |
logger.info(f"Decreasing max_cell_per_page to {max_cell_per_page_h}x{max_cell_per_page_w}") | |
divide_factor_h = int(np.ceil(h_cells / max_cell_per_page_h)) | |
divide_factor_w = int(np.ceil(w_cells / max_cell_per_page_w)) | |
logger.info(f"divide_factor_h: {divide_factor_h}, divide_factor_w: {divide_factor_w}") | |
copaint_pdfs = [] | |
scale = None | |
for i in range(divide_factor_h): | |
for j in range(divide_factor_w): | |
cell_h_start = i * max_cell_per_page_h | |
cell_h_end = min((i + 1) * max_cell_per_page_h, h_cells) | |
cell_w_start = j * max_cell_per_page_w | |
cell_w_end = min((j + 1) * max_cell_per_page_w, w_cells) | |
list_of_cell_idx = [cell_h_idx * w_cells + (w_cells-cell_w_idx) for cell_h_idx in range(cell_h_start, cell_h_end) for cell_w_idx in range(cell_w_start, cell_w_end)] | |
logger.info(f"cell_h_start: {cell_h_start}, cell_h_end: {cell_h_end}, cell_w_start: {cell_w_start}, cell_w_end: {cell_w_end}") | |
h_cells_new = cell_h_end - cell_h_start | |
w_cells_new = cell_w_end - cell_w_start | |
file_name_new = f"{file_name}_{i}x{j}" | |
px_h_start = int(cell_h_start * h / h_cells) | |
px_h_end = int(cell_h_end * h / h_cells) | |
px_w_start = int(cell_w_start * w / w_cells) | |
px_w_end = int(cell_w_end * w / w_cells) | |
image_new = image[:, :, px_h_start:px_h_end, px_w_start:px_w_end] | |
pdf_path, new_scale = image_to_pdf_core(image_new, file_name_new, logo_image, outputfolder, h_cells_new, w_cells_new, unique_identifier, cell_size_in_cm, a4, high_res, list_of_cell_idx=list_of_cell_idx, scale=scale, debug=debug) | |
if scale is None: | |
scale = new_scale | |
copaint_pdfs.append(pdf_path) | |
# Merge the copaint PDFs | |
output_path = os.path.join(outputfolder, "copaint-design.pdf") | |
merge_pdf_list(copaint_pdfs, output_path, debug=debug) | |
# clean unnecessary files | |
for pdf in copaint_pdfs: | |
os.remove(pdf) | |
logger.info(f"Done! Final output saved to {output_path}") | |
return output_path | |
if __name__ == "__main__": | |
parser = argparse.ArgumentParser(description='CoPaint') | |
parser.add_argument('--input_image', type=str, default='./data/bear.png', help='input image') | |
parser.add_argument('--copaint_logo', type=str, default='./data/logo_copaint.png', help='copaint logo') | |
parser.add_argument('--outputfolder', type=str, default='output/', help='output image') | |
parser.add_argument('--h_cells', type=int, help='number of cells in height', default=9) | |
parser.add_argument('--w_cells', type=int, help='number of cells in width', default=6) | |
parser.add_argument('--debug', action='store_true', help='show timing information') | |
# done adding arguments | |
args = parser.parse_args() | |
image_to_pdf(args.input_image, args.copaint_logo, args.outputfolder, args.h_cells, args.w_cells, cell_size_in_cm=None, debug=args.debug) | |