0

I made a script that loops through an Excel table which looks like this:

item name namefontsize country code
sample.jpg Apple 142 US 1564
sample2.jpg Orange 142 US 1562

then loops through a folder of .jpg images.

When it matches it writes some text on the image and saves it as a new one.

How do complete this loop so that it uses the data from columns 2, 3, 4, and 5 in the place of the variables txt1, txt2, txt3, fontsize, etc. when the image names match?

I'm a newbie so feel free to point out how ugly my code is.

import os
from PIL import Image,ImageFont,ImageDraw, features
import pandas as pd

path='./'

txt1= "name..."
W1 = 1200
H1 = 200
fontSize1 = 142

txt2= "country..."
W2 = 1200
H2 = 400
fontSize2 = 132

txt3= "code..."
W3 = 1200
H3 = 600
fontSize3 = 124

def setText(name,file,txt,fontSize,w,h):
    arial = ImageFont.truetype(r'./font.ttf', fontSize)
    draw = ImageDraw.Draw(file)
    draw.text((w, h), txt, font=arial, fill='#ff0000',
        direction="rtl",align="right",anchor="rm",features='rtla')
    file.save(f'done {name}')

df = pd.read_excel (r'./data.xlsx')
files = []
for (dirpath, dirnames, filenames) in os.walk(path):
    files.extend(filenames)
items= []
for index, row in df.iterrows():
    items.append(row["item"])

for i in items:
    if i in files:
        imageName = i
        imgfile = Image.open(f"./{imageName}")
        setText(imageName,imgfile,txt1,fontSize1,W1,H1)
        setText(imageName,imgfile,txt2,fontSize2,W2,H2)
        setText(imageName,imgfile,txt3,fontSize3,W3,H3)
David Z
  • 128,184
  • 27
  • 255
  • 279

1 Answers1

1

Any time you have a repeating series of variable declarations like this:

txt1= "name..."
W1 = 1200
H1 = 200
fontSize1 = 142

txt2= "country..."
W2 = 1200
H2 = 400
fontSize2 = 132

txt3= "code..."
W3 = 1200
H3 = 600
fontSize3 = 124

it's a good clue that you'd be better served by a list:

text_props = [
    ("name...", 1200, 200, 142),
    ("country...", 1200, 400, 132),
    ("code...", 1200, 600, 124),
]

Now you can set these properties in a loop, so that this:

        setText(imageName,imgfile,txt1,fontSize1,W1,H1)
        setText(imageName,imgfile,txt2,fontSize2,W2,H2)
        setText(imageName,imgfile,txt3,fontSize3,W3,H3)

becomes this:

        for txt, w, h, font in text_props:
            setText(imageName, imgfile, txt, font, w, h)

In my own code I'd probably use a NamedTuple instead of a plain tuple, but that's another topic. :)

(edit) If you want to swap the CSV data in for the txt values, I think what you want to do is stick the key names in there (without the dots), like this:

text_props = [
    ("name", 1200, 200, 142),
    ("country", 1200, 400, 132),
    ("code", 1200, 600, 124),
]

and then just do this in the iterrows loop (where you already have all the data) instead of building the items list:

for _, row in df.iterrows():
    item = row["item"]
    if item not in files:
        continue
    imgfile = Image.open(f"./{item}")
    for key, w, h, default_font_size in text_props:
        # key is one of 'name', 'country', or 'code'.
        # Not all keys have a font size, so check for one but
        # use default_font_size if none is in the table.
        font_size = row.get(key+'fontsize', default_font_size)
        setText(
            item,      # name (the image filename)
            imgfile,   # file (from Image.open())
            row[key],  # txt (from the table, e.g. row['name'])
            font_size, # fontSize (see above where this is determined)
            w,         # w (width)
            h          # h (height)
        )
Samwise
  • 68,105
  • 3
  • 30
  • 44
  • That's very neat. But how does it grab the data of the second row ? height and width are fixed for the most part but name, country, code, change with each image. – FoodHunter Jun 25 '21 at 18:51
  • oh, I think I misunderstood what you were getting at -- instead of "name..." you want the name for that row, etc? – Samwise Jun 25 '21 at 18:53
  • Yes exactly, I want to grab all the data from that row and put it on the image then save it. I plan to add all the variables including height and width to excel as well but It seem too complicated for now. – FoodHunter Jun 25 '21 at 18:59
  • Updated the answer. There's some ambiguity in your question around how you want to handle the font sizes (since you have a `namefontsize` but not a `countryfontsize` etc), but hopefully the updated answer at least gets you pointed in the right direction of how to handle this in a data-driven way. – Samwise Jun 25 '21 at 19:21
  • Its my bad, I have been testing this all day. I should've added `namefontsize`, `nameHeight`, `nameWidth`, and so on for country and code as well. but I didn't know how to use those with pillow. If I got that right you used `row[key],` and `row.get(key+'fontsize', default_font_size),` to link the data to each image but I don't have those parameters inside my `setText` function can you explain how it work please? – FoodHunter Jun 25 '21 at 19:58
  • I don't understand the question -- the code in my answer uses the exact same six parameters that your question did (mostly with the same values as well) so the statement "I don't have those parameters inside my `setText` function" doesn't make sense to me. Did you try just running the code? – Samwise Jun 25 '21 at 22:34
  • I think you might be confusing the names in the caller with the names in the function definition -- maybe some comments in the code will help? I'll add some. – Samwise Jun 25 '21 at 23:08
  • Added a bunch of comments to remind you of what each parameter is. Note that some of the values come from our iteration over `text_props` and some of them come from `row`, which is produced by our `iterrows()` iteration. – Samwise Jun 25 '21 at 23:14
  • Thanks alot man I made follow up post [here](https://stackoverflow.com/questions/68146069/how-to-make-a-function-dynamic-and-accept-new-parameters-in-python) . I couldn't wrap my head around `iterrows()` to work but I managed to make a working code. – FoodHunter Jun 26 '21 at 20:40