Completed
Pull Request — master (#302)
by
unknown
03:18
created

make_meme()   A

Complexity

Conditions 4

Size

Total Lines 56

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 30
CRAP Score 4.026

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 4
c 1
b 1
f 0
dl 0
loc 56
rs 9.0544
ccs 30
cts 34
cp 0.8824
crap 4.026

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 1
    if img.mode not in ('RGB', 'RGBA'):
52 1
        if img.format == 'JPEG':
53
            img = img.convert('RGB')
54
            img.format = 'JPEG'
55
        else:
56 1
            img = img.convert('RGBA')
57 1
            img.format = 'PNG'
58
59
    # Resize to a maximum height and width
60 1
    img.thumbnail((500, 500))
61 1
    image_size = img.size
62
63
    # Draw image
64 1
    draw = ImageDraw.Draw(img)
65
66 1
    max_font_size = int(image_size[1] / 5)
67 1
    min_font_size_single_line = int(image_size[1] / 12)
68 1
    max_text_len = image_size[0] - 20
69 1
    top_font_size, top = _optimize_font_size(font, top, max_font_size,
70
                                             min_font_size_single_line,
71
                                             max_text_len)
72 1
    bottom_font_size, bottom = _optimize_font_size(font, bottom, max_font_size,
73
                                                   min_font_size_single_line,
74
                                                   max_text_len)
75
76 1
    if match_font_size is True:
77
        top_font_size = min(top_font_size, bottom_font_size)
78
        bottom_font_size = top_font_size
79
80 1
    top_font = ImageFont.truetype(font, top_font_size)
81 1
    bottom_font = ImageFont.truetype(font, bottom_font_size)
82
83 1
    top_text_size = draw.multiline_textsize(top, top_font)
84 1
    bottom_text_size = draw.multiline_textsize(bottom, bottom_font)
85
86
    # Find top centered position for top text
87 1
    top_text_position_x = (image_size[0] / 2) - (top_text_size[0] / 2)
88 1
    top_text_position_y = 0
89 1
    top_text_position = (top_text_position_x, top_text_position_y)
90
91
    # Find bottom centered position for bottom text
92 1
    bottom_text_size_x = (image_size[0] / 2) - (bottom_text_size[0] / 2)
93 1
    bottom_text_size_y = image_size[1] - bottom_text_size[1] * (7 / 6)
94 1
    bottom_text_position = (bottom_text_size_x, bottom_text_size_y)
95
96 1
    _draw_outlined_text(draw, top_text_position,
97
                        top, top_font, top_font_size)
98 1
    _draw_outlined_text(draw, bottom_text_position,
99
                        bottom, bottom_font, bottom_font_size)
100
101 1
    log.info("Generating image: %s", path)
102 1
    return img.save(path, format=img.format)
103
104
105 1
def _draw_outlined_text(draw_image, text_position, text, font, font_size):
106
    """Draw white text with black outline on an image."""
107
108
    # Draw black text outlines
109 1
    outline_range = max(1, font_size // 25)
110 1
    for x in range(-outline_range, outline_range + 1):
111 1
        for y in range(-outline_range, outline_range + 1):
112 1
            pos = (text_position[0] + x, text_position[1] + y)
113 1
            draw_image.multiline_text(pos, text, (0, 0, 0),
114
                                      font=font, align='center')
115
116
    # Draw inner white text
117 1
    draw_image.multiline_text(text_position, text, (255, 255, 255),
118
                              font=font, align='center')
119
120
121 1
def _optimize_font_size(font, text, max_font_size, min_font_size,
122
                        max_text_len):
123
    """Calculate the optimal font size to fit text in a given size."""
124
125
    # Check size when using smallest single line font size
126 1
    fontobj = ImageFont.truetype(font, min_font_size)
127 1
    text_size = fontobj.getsize(text)
128
129
    # Calculate font size for text, split if necessary
130 1
    if text_size[0] > max_text_len:
131 1
        phrases = _split(text)
132
    else:
133 1
        phrases = (text,)
134 1
    font_size = max_font_size // len(phrases)
135 1
    for phrase in phrases:
136 1
        font_size = min(_maximize_font_size(font, phrase, max_text_len),
137
                        font_size)
138
139
    # Rebuild text with new lines
140 1
    text = '\n'.join(phrases)
141
142 1
    return font_size, text
143
144
145 1
def _maximize_font_size(font, text, max_size):
146
    """Find the biggest font size that will fit."""
147 1
    font_size = max_size
148
149 1
    fontobj = ImageFont.truetype(font, font_size)
150 1
    text_size = fontobj.getsize(text)
151 1
    while text_size[0] > max_size and font_size > 1:
152 1
        font_size = font_size - 1
153 1
        fontobj = ImageFont.truetype(font, font_size)
154 1
        text_size = fontobj.getsize(text)
155
156 1
    return font_size
157
158
159 1
def _split(text):
160
    """Split a line of text into two similarly sized pieces.
161
162
    >>> _split("Hello, world!")
163
    ('Hello,', 'world!')
164
165
    >>> _split("This is a phrase that can be split.")
166
    ('This is a phrase', 'that can be split.')
167
168
    >>> _split("This_is_a_phrase_that_can_not_be_split.")
169
    ('This_is_a_phrase_that_can_not_be_split.',)
170
171
    """
172 1
    result = (text,)
173
174 1
    if len(text) >= 3 and ' ' in text[1:-1]:  # can split this string
175 1
        space_indices = [i for i in range(len(text)) if text[i] == ' ']
176 1
        space_proximities = [abs(i - len(text) // 2) for i in space_indices]
177 1
        for i, j in zip(space_proximities, space_indices):
178 1
            if i == min(space_proximities):
179 1
                result = (text[:j], text[j + 1:])
180 1
                break
181
182
    return result
183