copaint_fr / copaint /copaint.py
groueix's picture
fr + reco taille canvas fr
f95d001
# 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 = {}
@lru_cache(maxsize=1)
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)