diff --git a/Makefile b/Makefile index 7be3e0d5..ca3c0fef 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ PYTHON_MAJOR ?= 3 PYTHON_MINOR ?= 4 # Test settings -UNIT_TEST_COVERAGE := 67 +UNIT_TEST_COVERAGE := 66 INTEGRATION_TEST_COVERAGE := 75 COMBINED_TEST_COVERAGE := 95 diff --git a/memegen/domain/image.py b/memegen/domain/image.py index bb91c903..472f3ec7 100644 --- a/memegen/domain/image.py +++ b/memegen/domain/image.py @@ -14,7 +14,7 @@ class Image: - """Meme JPEG generated from a template.""" + """JPEG generated by applying text to a template.""" def __init__(self, template, text, root=None): self.template = template @@ -36,74 +36,24 @@ def generate(self): make_meme(self.text.top, self.text.bottom, self.template.path, self.path) -def split_sentance(phrase): - ''' This function tries to split the phrase into two in as close to same size as possible''' - result = [phrase] - if len(phrase) >= 3 and ' ' in phrase[1:-1]: # can split this string - spaceindx=[i for i in range(len(phrase)) if phrase[i]==' '] #indicies of spaces - close = [abs(spacei-len(phrase)//2) for spacei in spaceindx] #space distance from center - for i, j in zip(close, spaceindx): - if i == min(close): - result = [phrase[:j],phrase[j+1:]] - break - return result - -def calc_largest_fontSize(phrase, max_size): - '''Find biggest font size that works''' - font_size = max_size - font = ImageFont.truetype(FONT, font_size) - text_size = font.getsize(phrase) - while text_size[0] > max_size: - font_size = font_size - 1 - font = ImageFont.truetype(FONT, font_size) - text_size = font.getsize(phrase) - return font_size - -def calc_font_size(top, bottom, max_font_size, min_font_size, max_text_len): - font_size = max_font_size - - # Check size when using smallest single line font size - font = ImageFont.truetype(FONT, min_font_size) - top_text_size = font.getsize(top) - bottom_text_size = font.getsize(bottom) - - #calculate font size for top text, split if necessary - if top_text_size[0] > max_text_len: - top_phrases = split_sentance(top) - else: - top_phrases = [top] - for phrase in top_phrases: - font_size = min(calc_largest_fontSize(phrase, max_text_len), font_size) - - #calculate font size for bottom text, split if necessary - if bottom_text_size[0] > max_text_len: - bottom_phrases = split_sentance(bottom) - else: - bottom_phrases = [bottom] - for phrase in bottom_phrases: - font_size = min(calc_largest_fontSize(phrase, max_text_len), font_size) - - #rebuild text with new lines - top = '\n'.join(top_phrases) - bottom = '\n'.join(bottom_phrases) - - return font_size, top, bottom # based on: https://github.com/danieldiekmeier/memegenerator def make_meme(top, bottom, background, path): + """Add text to an image and save it.""" img = ImageFile.open(background) # Resize to a maximum height and width img.thumbnail((500, 500)) image_size = img.size - + # Draw image draw = ImageDraw.Draw(img) max_font_size = int(image_size[1] / 5) - min_font_size = int(image_size[1] / 10) - max_text_len = image_size[0] - 20 - font_size, top, bottom = calc_font_size(top, bottom, max_font_size, min_font_size, max_text_len) + min_font_size = int(image_size[1] / 15) + max_text_len = image_size[0] - 10 + font_size, top, bottom = _optimize_font_size(top, bottom, max_font_size, + min_font_size, max_text_len) font = ImageFont.truetype(FONT, font_size) top_text_size = draw.multiline_textsize(top, font) @@ -124,13 +74,83 @@ def make_meme(top, bottom, background, path): for x in range(-outline_range, outline_range + 1): for y in range(-outline_range, outline_range + 1): pos = (top_text_position[0] + x, top_text_position[1] + y) - draw.multiline_text(pos, top, (0, 0, 0), font=font, align='center') + draw.multiline_text(pos, top, (0, 0, 0), + font=font, align='center') pos = (bottom_text_position[0] + x, bottom_text_position[1] + y) - draw.multiline_text(pos, bottom, (0, 0, 0), font=font, align='center') + draw.multiline_text(pos, bottom, (0, 0, 0), + font=font, align='center') # Draw inner white text - draw.multiline_text(top_text_position, top, (255, 255, 255), font=font, align='center') - draw.multiline_text(bottom_text_position, bottom, (255, 255, 255), font=font, align='center') + draw.multiline_text(top_text_position, top, (255, 255, 255), + font=font, align='center') + draw.multiline_text(bottom_text_position, bottom, (255, 255, 255), + font=font, align='center') log.info("generated: %s", path) return img.save(path) + + +def _optimize_font_size(top, bottom, max_font_size, min_font_size, + max_text_len): + """Calculate the optimal font size to fit text in a given size.""" + font_size = max_font_size + + # Check size when using smallest single line font size + font = ImageFont.truetype(FONT, min_font_size) + top_text_size = font.getsize(top) + bottom_text_size = font.getsize(bottom) + + # calculate font size for top text, split if necessary + if top_text_size[0] > max_text_len: + top_phrases = _split(top) + else: + top_phrases = [top] + for phrase in top_phrases: + font_size = min(_maximize_font_size(phrase, max_text_len), font_size) + + # calculate font size for bottom text, split if necessary + if bottom_text_size[0] > max_text_len: + bottom_phrases = _split(bottom) + else: + bottom_phrases = [bottom] + for phrase in bottom_phrases: + font_size = min(_maximize_font_size(phrase, max_text_len), font_size) + + # rebuild text with new lines + top = '\n'.join(top_phrases) + bottom = '\n'.join(bottom_phrases) + + return font_size, top, bottom + + +def _maximize_font_size(text, max_size): + """Find the biggest font size that will fit.""" + font_size = max_size + font = ImageFont.truetype(FONT, font_size) + text_size = font.getsize(text) + while text_size[0] > max_size: + font_size = font_size - 1 + font = ImageFont.truetype(FONT, font_size) + text_size = font.getsize(text) + return font_size + + +def _split(text): + """Split a line of text into two similarly sized pieces. + + >>> _split("Hello, world!") + ('Hello,', 'world!') + + >>> _split("This is a phrase that can be split.") + ('This is a phrase', 'that can be split.') + + """ + result = [text] + if len(text) >= 3 and ' ' in text[1:-1]: # can split this string + space_indices = [i for i in range(len(text)) if text[i] == ' '] + space_proximities = [abs(i - len(text) // 2) for i in space_indices] + for i, j in zip(space_proximities, space_indices): + if i == min(space_proximities): + result = (text[:j], text[j + 1:]) + break + return result