|
| 1 | +"""class Border: add border to the image file(s) |
| 2 | +Copyright © 2025 John Liu |
| 3 | +""" |
| 4 | + |
| 5 | +from pathlib import Path |
| 6 | + |
| 7 | +import piexif |
| 8 | +import pillow_heif |
| 9 | +from loguru import logger |
| 10 | +from PIL import Image |
| 11 | + |
| 12 | +from batch_img.common import Common |
| 13 | + |
| 14 | +pillow_heif.register_heif_opener() # allow Pillow to open HEIC files |
| 15 | + |
| 16 | + |
| 17 | +class Border: |
| 18 | + @staticmethod |
| 19 | + def get_crop_box(width, height, border_width) -> tuple[float, float, float, float]: |
| 20 | + """Get the crop box tuple |
| 21 | +
|
| 22 | + Args: |
| 23 | + width: image width int |
| 24 | + height: image height int |
| 25 | + border_width: border width int |
| 26 | +
|
| 27 | + Returns: |
| 28 | + tuple[float, float, float, float] |
| 29 | + """ |
| 30 | + crop_left = border_width |
| 31 | + crop_top = border_width |
| 32 | + crop_right = width - border_width |
| 33 | + crop_bottom = height - border_width |
| 34 | + return crop_left, crop_top, crop_right, crop_bottom |
| 35 | + |
| 36 | + @staticmethod |
| 37 | + def add_border_1_image( |
| 38 | + in_path: Path, out_path: Path, border_width: int, border_color: str |
| 39 | + ) -> tuple: |
| 40 | + """Add internal border to an image file, not to expand the size |
| 41 | +
|
| 42 | + Args: |
| 43 | + in_path: input file path |
| 44 | + out_path: output dir path |
| 45 | + border_width: border width int |
| 46 | + border_color: border color str |
| 47 | +
|
| 48 | + Returns: |
| 49 | + tuple: bool, str |
| 50 | + """ |
| 51 | + try: |
| 52 | + with Image.open(in_path) as img: |
| 53 | + width, height = img.size |
| 54 | + box = Border.get_crop_box(width, height, border_width) |
| 55 | + cropped_img = img.crop(box) |
| 56 | + bd_img = Image.new(img.mode, (width, height), border_color) |
| 57 | + bd_img.paste(cropped_img, (border_width, border_width)) |
| 58 | + |
| 59 | + out_path.mkdir(parents=True, exist_ok=True) |
| 60 | + out_file = out_path |
| 61 | + if out_path.is_dir(): |
| 62 | + filename = f"{in_path.stem}_bw{border_width}{in_path.suffix}" |
| 63 | + out_file = Path(f"{out_path}/{filename}") |
| 64 | + exif_dict = None |
| 65 | + if "exif" in img.info: |
| 66 | + exif_dict = piexif.load(img.info["exif"]) |
| 67 | + if exif_dict: |
| 68 | + exif_bytes = piexif.dump(exif_dict) |
| 69 | + bd_img.save(out_file, img.format, optimize=True, exif=exif_bytes) |
| 70 | + else: |
| 71 | + bd_img.save(out_file, img.format, optimize=True) |
| 72 | + logger.info(f"Saved {out_file}") |
| 73 | + return True, out_file |
| 74 | + except (AttributeError, FileNotFoundError, ValueError) as e: |
| 75 | + return False, f"{in_path}:\n{e}" |
| 76 | + |
| 77 | + @staticmethod |
| 78 | + def add_border_all_in_dir( |
| 79 | + in_path: Path, out_path: Path, border_width: int, border_color: str |
| 80 | + ) -> bool: |
| 81 | + """Add border to all image files in the given dir |
| 82 | +
|
| 83 | + Args: |
| 84 | + in_path: input dir path |
| 85 | + out_path: output dir path |
| 86 | + border_width: border width int |
| 87 | + border_color: border color str |
| 88 | +
|
| 89 | + Returns: |
| 90 | + bool: True - Success. False - Error |
| 91 | + """ |
| 92 | + image_files = Common.prepare_all_files(in_path, out_path) |
| 93 | + if not image_files: |
| 94 | + logger.error(f"No image files at {in_path}") |
| 95 | + return False |
| 96 | + tasks = [(f, out_path, border_width, border_color) for f in image_files] |
| 97 | + files_cnt = len(tasks) |
| 98 | + |
| 99 | + logger.info(f"Add border to {files_cnt} image files in multiprocess ...") |
| 100 | + success_cnt = Common.multiprocess_progress_bar( |
| 101 | + Border.add_border_1_image, "Add border to image files", tasks |
| 102 | + ) |
| 103 | + logger.info(f"\nSuccessfully added border to {success_cnt}/{files_cnt} files") |
| 104 | + return True |
0 commit comments