File size: 5,033 Bytes
c19ca42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#!/usr/bin/env python
"""
Create image grid
"""

import os
import argparse
import math
import logging
from pathlib import Path
import filetype
from PIL import Image, ImageDraw, ImageFont
from util import log


params = None


def wrap(text: str, font: ImageFont.ImageFont, length: int):
    lines = ['']
    for word in text.split():
        line = f'{lines[-1]} {word}'.strip()
        if font.getlength(line) <= length:
            lines[-1] = line
        else:
            lines.append(word)
    return '\n'.join(lines)


def grid(images, labels = None, width = 0, height = 0, border = 0, square = False, horizontal = False, vertical = False): # pylint: disable=redefined-outer-name
    if horizontal:
        rows = 1
    elif vertical:
        rows = len(images)
    elif square:
        rows = round(math.sqrt(len(images)))
    else:
        rows = math.floor(math.sqrt(len(images)))
    cols = math.ceil(len(images) / rows)
    size = [0, 0]
    if width == 0:
        w = max([i.size[0] for i in images])
        size[0] = cols * w + cols * border
    else:
        size[0] = width
        w = round(width / cols)
    if height == 0:
        h = max([i.size[1] for i in images])
        size[1] = rows * h + rows * border
    else:
        size[1] = height
        h = round(height / rows)
    size = tuple(size)
    image = Image.new('RGB', size = size, color = 'black') # pylint: disable=redefined-outer-name
    font = ImageFont.truetype('DejaVuSansMono', round(w / 40))
    for i, img in enumerate(images): # pylint: disable=redefined-outer-name
        x = (i % cols * w) + (i % cols * border)
        y = (i // cols * h) + (i // cols * border)
        img.thumbnail((w, h), Image.Resampling.HAMMING)
        image.paste(img, box=(x + int(border / 2), y + int(border / 2)))
        if labels is not None and len(images) == len(labels):
            ctx = ImageDraw.Draw(image)
            label = wrap(labels[i], font, w)
            ctx.text((x + 1 + round(w / 200), y + 1 + round(w / 200)), label, font = font, fill = (0, 0, 0))
            ctx.text((x, y), label, font = font, fill = (255, 255, 255))
    log.info({ 'grid': { 'images': len(images), 'rows': rows, 'cols': cols, 'cell': [w, h] } })
    return image


if __name__ == '__main__':
    log.info({ 'create grid' })
    parser = argparse.ArgumentParser(description='image grid utility')
    parser.add_argument("--square", default = False, action='store_true', help = "create square grid")
    parser.add_argument("--horizontal", default = False, action='store_true', help = "create horizontal grid")
    parser.add_argument("--vertical", default = False, action='store_true', help = "create vertical grid")
    parser.add_argument("--width", type = int, default = 0, required = False, help = "fixed grid width")
    parser.add_argument("--height", type = int, default = 0, required = False, help = "fixed grid height")
    parser.add_argument("--border", type = int, default = 0, required = False, help = "image border")
    parser.add_argument('--nolabels', default = False, action='store_true', help = "do not print image labels")
    parser.add_argument('--debug', default = False, action='store_true', help = "print extra debug information")
    parser.add_argument('output', type = str)
    parser.add_argument('input', type = str, nargs = '*')
    params = parser.parse_args()
    output = params.output if params.output.lower().endswith('.jpg') else params.output + '.jpg'
    if params.debug:
        log.setLevel(logging.DEBUG)
        log.debug({ 'debug': True })
    log.debug({ 'args': params.__dict__ })
    images = []
    labels = []
    for f in params.input:
        path = Path(f)
        if path.is_dir():
            files = [os.path.join(f, file) for file in os.listdir(f) if os.path.isfile(os.path.join(f, file))]
        elif path.is_file():
            files = [f]
        else:
            log.warning({ 'grid not a valid file/folder', f})
            continue
        files.sort()
        for file in files:
            if not filetype.is_image(file):
                continue
            if file.lower().endswith('.heic'):
                from pi_heif import register_heif_opener
                register_heif_opener()
            log.debug(file)
            img = Image.open(file)
            # img.verify()
            images.append(img)
            fp = Path(file)
            if not params.nolabels:
                labels.append(fp.stem)
    # log.info({ 'folder': path.parent, 'labels': labels })
    if len(images) > 0:
        image = grid(
            images = images,
            labels = labels,
            width = params.width,
            height = params.height,
            border = params.border,
            square = params.square,
            horizontal = params.horizontal,
            vertical = params.vertical)
        image.save(output, 'JPEG', optimize = True, quality = 60)
        log.info({ 'grid': { 'file': output, 'size': list(image.size) } })
    else:
        log.info({ 'grid': 'nothing to do' })