-1

So, first of all, I'm only on my first year of my IT apprenticeship, so I'm no pro by any means. My current project is a meme generator with Python. The goal is to enter the top text and bottom text into fields, and then that text gets "drawn" on a .jpg or .png with PIL.

At the moment, I'm really stuck on this particular error message:

Traceback (most recent call last):
  File "C:\Users\erce\AppData\Local\Programs\Python\Python39\
    return self.func(*args)
  File "c:\Users\erce\Documents\meme-generator\appJar\appjar.
    return lambda *args: funcName(param)
  File "c:\Users\erce\Documents\meme-generator\memegenerator.
    draw = ImageDraw.Draw(im)
    draw = ImageDraw.Draw(im)
  File "C:\Users\erce\AppData\Local\Programs\Python\Python39\lib\site-packages\PIL\ImageDraw.py", line 684, in Draw
    return ImageDraw(im, mode)
  File "C:\Users\erce\AppData\Local\Programs\Python\Python39\lib\site-packages\PIL\ImageDraw.py", line 58, in __init__
    im.load()
AttributeError: 'tuple' object has no attribute 'load'

The particular lines of code are the following:

import textwrap
import tkinter as tk
import os
import random
from tkinter import filedialog
from PIL import Image, ImageDraw, ImageFont, ImageFile  # Pillow für öffnen, manipulieren und speichern von bildern
from appJar import gui  # für das GUI element


def generate_meme(stroke_width=5, font_path="C:/Windows/Fonts/Impact.ttf", font_size=9):
    im = filedialog.askopenfilenames(filetypes=[("images", "*.png, *.jpg")])
    draw = ImageDraw.Draw(im)
    image_width, image_height = im.size
    font = ImageFont.truetype(font=font_path, size=int(image_height * font_size) // 100)
    toptext = app.getEntry("Top Text")  # text aus feldern übernehmen
    bottomtext = app.getEntry("Bottom Text")
    toptext = toptext.upper()  # convert text to uppercase
    bottomtext = bottomtext.upper()
    char_width, char_height = font.getsize("A")  # text wrapping
    chars_per_line = image_width // char_width
    top_lines = textwrap.wrap(toptext, width=chars_per_line)
    bottom_lines = textwrap.wrap(bottomtext, width=chars_per_line)
    # draw top lines
    y = 10
    for line in top_lines:
        line_width, line_height = font.getsize(line)
        x = (image_width - line_width) / 2
        draw.text((x, y), line, fill="white", font=font, stroke_width=stroke_width, stroke_fill="black")
        y += line_height
    # draw bottom lines
    y = image_height - char_height * len(bottom_lines) - 15
    for line in bottom_lines:
        line_width, line_height = font.getsize(line)
        x = (image_width - line_width) / 2
        draw.text((x, y), line, fill="white", font=font, stroke_width=stroke_width, stroke_fill="black")
        y += line_height
    # save meme
    im.save("meme-" + im.filename.split("/")[-1])

I'd appreciate a couple of hints as to where this is going wrong. I've relied on the documentation of PIL a lot, and the particular line that seems to be affected (line 12) is written exactly like that in the documentation as well. Yet here it just gives me errors.

Caius Jard
  • 72,509
  • 5
  • 49
  • 80
  • 2
    `askopenfilenames` returns multiple filenames, for one; then, you need to load the image first, not just pass a filename to ImageDraw. – AKX Mar 15 '21 at 08:10
  • 1
    `im = filedialog.askopenfilenames(...)` most likely is no proper `Image` object, such that `draw = ImageDraw.Draw(im)` crashes. I guess, you'd need some `Image.open(...)` beforehand. – HansHirse Mar 15 '21 at 08:10

1 Answers1

0

Your issue here is that you can't directly pass a filename (or a tuple of filenames, since you're using getopenfilenames, plural) to ImageDraw; you'll need to open the image first with Image.open().

I would recommend refactoring your code into pieces you can more easily test, for instance like this:

import textwrap
from PIL import Image, ImageDraw, ImageFont, ImageFile 


def generate_meme(image, toptext, bottomtext, stroke_width=5, font_path="C:/Windows/Fonts/Impact.ttf", font_size=9):
    draw = ImageDraw.Draw(image)
    image_width, image_height = image.size
    font = ImageFont.truetype(font=font_path, size=int(image_height * font_size) // 100)
    toptext = toptext.upper()
    bottomtext = bottomtext.upper()
    char_width, char_height = font.getsize("A")  # text wrapping
    chars_per_line = image_width // char_width
    top_lines = textwrap.wrap(toptext, width=chars_per_line)
    bottom_lines = textwrap.wrap(bottomtext, width=chars_per_line)
    # draw top lines
    y = 10
    for line in top_lines:
        line_width, line_height = font.getsize(line)
        x = (image_width - line_width) / 2
        draw.text((x, y), line, fill="white", font=font, stroke_width=stroke_width, stroke_fill="black")
        y += line_height
    # draw bottom lines
    y = image_height - char_height * len(bottom_lines) - 15
    for line in bottom_lines:
        line_width, line_height = font.getsize(line)
        x = (image_width - line_width) / 2
        draw.text((x, y), line, fill="white", font=font, stroke_width=stroke_width, stroke_fill="black")
        y += line_height
    return image


def test_generate():
    image = Image.open("C:/Users/granapadano/Desktop/cheese.jpg")
    image = generate_meme(
        image=image,
        toptext="This is not",
        bottomtext="a meme",
    )
    image.save("C:/Users/granapadano/Desktop/cheesememe.jpg")


if __name__ == "__main__":
    test_generate()

You can run this script as-is, without having to click around to select files or type in text, and once it works satisfactorily, you can add the GUI stuff in a second function.


def generate_with_gui():
    filename = filedialog.askopenfilename(filetypes=[("images", "*.png, *.jpg")])

    image = Image.open(filename)
    image = generate_meme(
        image=image,
        toptext=app.getEntry("Top Text"),
        bottomtext=app.getEntry("Bottom Text"),
    )
    image.save(...)
AKX
  • 152,115
  • 15
  • 115
  • 172