callpete
callpete

Reputation: 610

Why do I have to run this python script twice to format images correctly?

Goal:

I am trying to batch process images contained inside a folder to resize and optimize them for use online.

Problem:

The following script works, but I have to run it twice before I get the output I want. This is how I would expect it to work:

function 1: resize_aspect_fit()

Resizes each image in the target folder to a specific size, adds "_small.png" to the file name, and saves it as a new file in the subfolder "optimized_images", created in the same directory as the original group of images.

function2: png_conversion()

Takes the newly made images inside "optimized_images" ("_small.png") and applies a conversion that reduces the size of the original file, adding the "-opt.png" suffix to indicate it has been optimized.

function3: unoptimized_cleanup()

Takes the files built by function 1, which are no longer necessary (since they have been optimized) and deletes them, to reduce clutter.

When I run the script I get the expected response from function1, all files in the target file are resized appropriately and saved in the "optimized_images" folder. But I have to run the script a second time before function 2 and 3 take effect. It does work, but I have never encountered an issue like this before. Any idea why this is happening?

What I tried:

I thought this might be related to file open/close operations, but I think I am closing them all at the appropriate time. I swapped Image.open syntax to use "with Image.open(path) as image:" but that did not solve the problem.

I thought there might be some issue with os.listdir or os.path where it might have to be 'reset' in order to iterate through a directory of files twice, but I cannot find anything.

from PIL import Image
import os, sys

path = "../path/to/images/"
new_folder = '/optimized_images/'
optimized_path = path + new_folder[1:]

dirs = os.listdir( path )
optimized_dirs = os.listdir( optimized_path )

def resize_aspect_fit(final_size=250, dirs=dirs, optimized_path=optimized_path, optimized_dirs=optimized_dirs):
    for item in dirs:
        if item == '.DS_Store':
            continue
        if os.path.isfile(path+item):

        with Image.open(path+item) as im:
            f, e = os.path.splitext(path+item)
            size = im.size
            ratio = float(final_size) / max(size)
            new_image_size = tuple([int(x*ratio) for x in size])

            im = im.resize(new_image_size, Image.ANTIALIAS)

            new_im = Image.new("RGBA", (final_size, final_size), color=(255,255,255,0))

        new_im.paste(im, ((final_size-new_image_size[0])//2, (final_size-new_image_size[1])//2))

        new_path, new_filename = f.rsplit('/', 1)
        new_im.save(new_path + new_folder + new_filename + '_small.png', 'PNG', quality=10, optimize=True)
        new_im.close()

def png_conversion(optimized_dirs=optimized_dirs, optimized_path=optimized_path):
    for item in optimized_dirs:
        if item == '.DS_Store':
            continue

        f, e = os.path.splitext(optimized_path+item)

        with Image.open(f + e) as im:
            im.load()

            # Get the alpha band
            alpha = im.split()[-1]

            im = im.convert('RGB').convert('P', palette=Image.ADAPTIVE, colors=255)

            # Set all pixel values below 128 to 255,
            # and the rest to 0
            mask = Image.eval(alpha, lambda a: 255 if a <=128 else 0)

            # Paste the color of index 255 and use alpha as a mask
            im.paste(255, mask)

            # The transparency index is 255
            e = e.split('.png')[0]
            im.save(f + e + "-opt.png", transparency=255)
            im.close()

def unoptimized_cleanup(optimized_dirs=optimized_dirs, optimized_path=optimized_path):
    for item in optimized_dirs:
        if item.endswith('small.png'):
            os.remove(os.path.join(optimized_path, item))

#functions called in order

resize_aspect_fit(final_size=250, dirs=dirs)
png_conversion(optimized_dirs=optimized_dirs, optimized_path=optimized_path)
unoptimized_cleanup(optimized_dirs=optimized_dirs, optimized_path=optimized_path)

I expect that for the following folder structure:

folder/image1.png
folder/image2.png

the output should look like this, with the appropriately sized and smaller files:

folder/optimized_images/image1_small-opt.png
folder/optimized_images/image2_small-opt.png

Relevant Sources that I pulled from:

Converting PNG32 to PNG8 with PIL while preserving transparency

Python/PIL Resize all images in a folder

Sorry for the long question/code, and thanks in advance for any help!!

Upvotes: 1

Views: 96

Answers (1)

physicalattraction
physicalattraction

Reputation: 6858

The problem is that you create the variable optimized_dirs before you run step 1. So before step 1 is executed, you make a list of files in that directory, which is empty at that point. If you run it a second time, the files are in optimized_dirs, and hence then it works.

A solution would be to read the contents of optimized_dirs inside the function png_compression, i.e. moving os.listdir( optimized_path ) in there.

By the way: I see that you do some magic to build paths where you use [1:] to prevent double slashes. It is more robust to build paths using os.path.join, which will ensure there is always a single slash between directories, regardless of whether you specify them at the start or end of each of them.

Upvotes: 1

Related Questions