Completed
Push — master ( 80d6ff...d0b076 )
by Jace
16s
created

Image.generate()   A

Complexity

Conditions 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
dl 0
loc 7
ccs 6
cts 6
cp 1
crap 2
rs 9.4285
c 0
b 0
f 0
1 1
import os
2 1
import hashlib
3 1
import logging
4
5 1
from PIL import Image as ImageFile, ImageFont, ImageDraw
0 ignored issues
show
Configuration introduced by
The import PIL could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
6
7
8 1
log = logging.getLogger(__name__)
9
10
11 1
class Image:
12
    """JPEG generated by applying text to a template."""
13
14 1
    def __init__(self, template, text, style=None, font=None, root=None):
15 1
        self.template = template
16 1
        self.style = style
17 1
        self.font = font
18 1
        self.text = text
19 1
        self.root = root
20
21 1
    @property
22
    def path(self):
23 1
        if not self.root:
24
            return None
25
26 1
        sha = hashlib.md5()
27 1
        sha.update((self.style or "").encode('utf-8'))
28 1
        sha.update(str(self.font).encode('utf-8'))
29
30 1
        base = os.path.join(self.root, self.template.key, self.text.path)
31
32 1
        if self.style or self.font:
33 1
            return "{}#{}.img".format(base, sha.hexdigest())
34
        else:
35 1
            return base + ".img"
36
37 1
    def generate(self):
38 1
        directory = os.path.dirname(self.path)
39 1
        if not os.path.isdir(directory):
40 1
            os.makedirs(directory)
41 1
        background = self.template.get_path(self.style)
42 1
        make_meme(self.text.top, self.text.bottom, self.font.path,
43
                  background, self.path)
44
45
46
# based on: https://github.com/danieldiekmeier/memegenerator
47 1
def make_meme(top, bottom, font, background, path, match_font_size=False):
48
    """Add text to an image and save it."""
49 1
    log.info("Loading background: %s", background)
50 1
    img = ImageFile.open(background)
51
52
    # Resize to a maximum height and width
53 1
    img.thumbnail((500, 500))
54 1
    image_size = img.size
55
56
    # Draw image
57 1
    draw = ImageDraw.Draw(img)
58
59 1
    max_font_size = int(image_size[1] / 5)
60 1
    min_font_size_single_line = int(image_size[1] / 12)
61 1
    max_text_len = image_size[0] - 20
62 1
    top_font_size, top = _optimize_font_size(font, top, max_font_size,
63
                                             min_font_size_single_line,
64
                                             max_text_len)
65 1
    bottom_font_size, bottom = _optimize_font_size(font, bottom, max_font_size,
66
                                                   min_font_size_single_line,
67
                                                   max_text_len)
68
69 1
    if match_font_size is True:
70
        top_font_size = min(top_font_size, bottom_font_size)
71
        bottom_font_size = top_font_size
72
73 1
    top_font = ImageFont.truetype(font, top_font_size)
74 1
    bottom_font = ImageFont.truetype(font, bottom_font_size)
75
76 1
    top_text_size = draw.multiline_textsize(top, top_font)
77 1
    bottom_text_size = draw.multiline_textsize(bottom, bottom_font)
78
79
    # Find top centered position for top text
80 1
    top_text_position_x = (image_size[0] / 2) - (top_text_size[0] / 2)
81 1
    top_text_position_y = 0
82 1
    top_text_position = (top_text_position_x, top_text_position_y)
83
84
    # Find bottom centered position for bottom text
85 1
    bottom_text_size_x = (image_size[0] / 2) - (bottom_text_size[0] / 2)
86 1
    bottom_text_size_y = image_size[1] - bottom_text_size[1] * (7 / 6)
87 1
    bottom_text_position = (bottom_text_size_x, bottom_text_size_y)
88
89 1
    _draw_outlined_text(draw, top_text_position,
90
                        top, top_font, top_font_size)
91 1
    _draw_outlined_text(draw, bottom_text_position,
92
                        bottom, bottom_font, bottom_font_size)
93
94 1
    log.info("Generating image: %s", path)
95 1
    return img.save(path, format=img.format)
96
97
98 1
def _draw_outlined_text(draw_image, text_position, text, font, font_size):
99
    """Draw white text with black outline on an image."""
100
101
    # Draw black text outlines
102 1
    outline_range = max(1, font_size // 25)
103 1
    for x in range(-outline_range, outline_range + 1):
104 1
        for y in range(-outline_range, outline_range + 1):
105 1
            pos = (text_position[0] + x, text_position[1] + y)
106 1
            draw_image.multiline_text(pos, text, (0, 0, 0),
107
                                      font=font, align='center')
108
109
    # Draw inner white text
110 1
    draw_image.multiline_text(text_position, text, (255, 255, 255),
111
                              font=font, align='center')
112
113
114 1
def _optimize_font_size(font, text, max_font_size, min_font_size,
115
                        max_text_len):
116
    """Calculate the optimal font size to fit text in a given size."""
117
118
    # Check size when using smallest single line font size
119 1
    fontobj = ImageFont.truetype(font, min_font_size)
120 1
    text_size = fontobj.getsize(text)
121
122
    # Calculate font size for text, split if necessary
123 1
    if text_size[0] > max_text_len:
124 1
        phrases = _split(text)
125
    else:
126 1
        phrases = (text,)
127 1
    font_size = max_font_size // len(phrases)
128 1
    for phrase in phrases:
129 1
        font_size = min(_maximize_font_size(font, phrase, max_text_len),
130
                        font_size)
131
132
    # Rebuild text with new lines
133 1
    text = '\n'.join(phrases)
134
135 1
    return font_size, text
136
137
138 1
def _maximize_font_size(font, text, max_size):
139
    """Find the biggest font size that will fit."""
140 1
    font_size = max_size
141
142 1
    fontobj = ImageFont.truetype(font, font_size)
143 1
    text_size = fontobj.getsize(text)
144 1
    while text_size[0] > max_size and font_size > 1:
145 1
        font_size = font_size - 1
146 1
        fontobj = ImageFont.truetype(font, font_size)
147 1
        text_size = fontobj.getsize(text)
148
149 1
    return font_size
150
151
152 1
def _split(text):
153
    """Split a line of text into two similarly sized pieces.
154
155
    >>> _split("Hello, world!")
156
    ('Hello,', 'world!')
157
158
    >>> _split("This is a phrase that can be split.")
159
    ('This is a phrase', 'that can be split.')
160
161
    >>> _split("This_is_a_phrase_that_can_not_be_split.")
162
    ('This_is_a_phrase_that_can_not_be_split.',)
163
164
    """
165 1
    result = (text,)
166
167 1
    if len(text) >= 3 and ' ' in text[1:-1]:  # can split this string
168 1
        space_indices = [i for i in range(len(text)) if text[i] == ' ']
169 1
        space_proximities = [abs(i - len(text) // 2) for i in space_indices]
170 1
        for i, j in zip(space_proximities, space_indices):
171 1
            if i == min(space_proximities):
172 1
                result = (text[:j], text[j + 1:])
173 1
                break
174
175
    return result
176