Create function tile().
[toast/gimp_passport.git] / gimp_passport.py
1 import argparse
2 from itertools import count
3 from numbers import Integral
4 from PIL import Image
5
6
7 MM_PER_INCH = 25.4
8
9
10 def mm_to_pixel(mm: float, dpi: Integral) -> int:
11     return int(round(mm / MM_PER_INCH * dpi))
12
13
14 def make_passport(img_rgba: Image, fpr: float, width_mm: float, height_mm: float) -> Image:
15     """
16     :param fpr: face to picture ratio, e.g. 2/3
17     """
18     # get bounding box of head
19     bands = img_rgba.getbands()
20     if 'A' not in bands:
21         raise ValueError('Image has no transparency (needed for marking the head).')
22     alpha = img_rgba.getdata(bands.index('A'))
23     bbox_head = alpha.getbbox()  # bounding box of head
24
25     # get bounding box of passport image ("pic")
26     left, top, right, bottom = bbox_head
27     height = bottom - top
28     width = right - left
29     pic_height = int(round(height / fpr))
30     pic_width = int(round(pic_height * width_mm / height_mm))
31     pic_top = top - (pic_height - height) // 2
32     pic_bottom = pic_top + pic_height
33     pic_left = left - (pic_width - width) // 2
34     pic_right = pic_left + pic_width
35     bbox_pic = pic_left, pic_top, pic_right, pic_bottom
36
37     # cut image
38     img = img_rgba.convert('RGB')  # drop alpha channel
39     pic = img.crop(bbox_pic)
40     dpi = int(round(pic_height / (height_mm / MM_PER_INCH)))
41     pic.info['dpi'] = (dpi, dpi)
42     return pic
43
44
45 def tile(img: Image, paper_width_mm: float, paper_height_mm: float, margin_mm: float) -> Image:
46     dpi = img.info['dpi'][0]
47     paper_width_pixel = int(round(paper_width_mm / MM_PER_INCH * dpi))
48     paper_height_pixel = int(round(paper_height_mm / MM_PER_INCH * dpi))
49     margin_pixel = mm_to_pixel(margin_mm, dpi)
50     paper = Image.new('RGB', (paper_width_pixel, paper_height_pixel), 'white')
51     paper.info['dpi'] = (dpi, dpi)
52     for iy in count():
53         top = margin_pixel + iy * img.height
54         bottom = top + img.height
55         if bottom > paper_height_pixel - margin_pixel:
56             break
57         for ix in count():
58             left = margin_pixel + ix * img.width
59             right = left + img.width
60             if right > paper_width_pixel - margin_pixel:
61                 break
62             paper.paste(img, (left, top))
63     return paper
64
65
66 def main(source_image: str, dest_image: str):
67     img = Image.open(source_image)
68     pic = make_passport(img, 0.75, 35., 45.)
69     pic.save(dest_image, dpi=pic.info['dpi'])
70
71
72 if __name__ == '__main__':
73     description = 'Convert image with alpha mask marking the face to passport image.'
74     parser = argparse.ArgumentParser(description=description)
75     parser.add_argument('source', help='sourse image')
76     parser.add_argument('dest', help='destination image')
77     args = parser.parse_args()
78     main(args.source, args.dest)
79