Thursday, 28 August 2014

Converting and Resizing Images Using Python

As part of my wife's job a few years ago, she had to convert images from various file types to .jpg and then resize them to make them smaller. Sure this could be done in programs like GIMP or Photoshop, but it was a time consuming process. To speed things up I wrote her a Python program which would automatically convert and resize any images which were in the same folder as the Python program. It saved her a lot of time.

Since then there have been a number of occasion when I have had to convert images to .jpg and then resize them, such as when I am using eBay. I always find this a cumbersome task, and often fall back on my Python program to do this for me.

As I am sure this is something you will find useful I have made it the basis of my latest blog post.

The program is quite simple, and does three things.
  1. Converts images to .jpg. 
  2. Deletes the original images. 
  3. Resizes images keeping the same aspect ratio.
Let us have a look at the whole program first and then we will go through it line by line.

This is written in Python 2.7.

## Required Modules
import Image
import glob
import os

## Global Variables
FILETYPES = ['*.tiff','*.jpeg','*.png']
NEWIMAGESIZE = 400

## Functions
def convert2jpg():
    for types in FILETYPES:
        openFiles = glob.glob(types)
        
        for files in openFiles:
            inFile = Image.open(files) 
            fileName = os.path.splitext(files)[0] # gets filename
            outFile = fileName + ".jpg" 
            inFile.save(outFile)
            print fileName + " ... converted"
            
    print "\n"
    return None

def delOldFileTypes():
    for types in FILETYPES:
        openFiles = glob.glob(types)
        
        for files in openFiles:
            os.remove(files)
            print files + " ... deleted"
            
    print "\n"
    return None
    

def resize(): 
    openFiles = glob.glob('*.jpg')
    
    for files in openFiles:
        inFile = Image.open(files)
        fileName = os.path.splitext(files)[0] # gets filename
        outFile = fileName + ".jpg"
        print fileName
        print "Origonal size ",inFile.size
        xDim = inFile.size[0]
        yDim = inFile.size[1]        
        newSize = aspectRatio(xDim, yDim)       
        inFile = inFile.resize((int(newSize[0]),int(newSize[1])),Image.ANTIALIAS)
        inFile.save(outFile)
        print "New Size ",inFile.size, "\n"
        
    return None

def aspectRatio(xDim, yDim):
    
    if xDim <= NEWIMAGESIZE and yDim <= NEWIMAGESIZE: #ensures images already correct size are not enlarged.
        return(xDim, yDim)
    
    elif xDim > yDim:
        divider = xDim/float(NEWIMAGESIZE)
        xDim = float(xDim/divider)
        yDim = float(yDim/divider)
        return(xDim, yDim)
        
    elif yDim > xDim:
        divider = yDim/float(NEWIMAGESIZE)
        xDim = float(xDim/divider)
        yDim = float(yDim/divider)
        return(xDim, yDim)
       
    elif xDim == yDim:
        xDim = NEWIMAGESIZE
        yDim = NEWIMAGESIZE
        return(xDim, yDim)

convert2jpg()
delOldFileTypes()
resize()

print ('All Done!!!')
raw_input('Images Resized... Press any key to continue')

The first thing we need to do is to import three libraries, which are required for the program.
  • Image - Helps us do certain things with the image files.
  • glob - Helps us with filenames when loading files.
  • os - Allows us to access operating system dependant functionality. 
For those new to programming, these would not be the first lines I would type into my program, but are added as and when required. 

## Required Modules
import Image
import glob
import os

Next I have assigned some Global Variables.

## Global Variables
FILETYPES = ['*.tiff','*.jpeg','*.png']
NEWIMAGESIZE = 400

  • FILETYPES - This is a list of the types of files our program will look for. 
  • NEWIMAGESIZE - This is the maximum number of pixels in either X or Y that our image will become. 
The advantage of global variables is you can change the value in one location, and it will be changed throughout your program.

Now we will write the first of the four functions in our program. The first function converts the images to .jpg format.

Lets look at the whole function, and then I will break it down line by line.

## Functions
def convert2jpg():
    for types in FILETYPES:
        openFiles = glob.glob(types)
        
        for files in openFiles:
            inFile = Image.open(files) 
            fileName = os.path.splitext(files)[0] # gets filename
            outFile = fileName + ".jpg" 
            inFile.save(outFile)
            print fileName + " ... converted"
            
    print "\n"
    return None

The first line defines our function. It names the function and, as the brackets are empty, states we are not passing anything into the function.

def convert2jpg():

Earlier we created a global variable, which was a list of all the filetypes we wanted our program to work on.  The next line iterates through that list one filetype at a time.

    for types in FILETYPES:

Next we use the glob module, which we imported at the start of our program,  to create a list of all the filenames associated with the current 'type' of file we are dealing with. For example, if we are currently running through the FILETYPES and are currently looking at the .tiff files, openFiles will become a list of all the .tiff files in our folder.

        openFiles = glob.glob(types)

Now we have our list of files associated with the current file type, we can start to do something with them. The next line iterates through each of the file names, in the list, one at a time.

        for files in openFiles:

We then use the Image module to open the file.

            inFile = Image.open(files) 

The next line, although it looks complicated, isn't at all.

            fileName = os.path.splitext(files)[0] # gets filename

Lets look at the individual aspects of this line:
  • os.path.splitext(files) - Splits the filename from the extension. This creates a list containing [filename , extension]. This uses the os module we imported at the start of our program. 
  • [0] - Ensures we only take the first part of the list. i.e. the filename, and not the extension. 
  • fileName - A variable we store the result in. 
See I said it was not difficult!

Now we create the name of the file we will be saving. This will be the same as the current filename, but with a different extension. 


            outFile = fileName + ".jpg" 

Then we save the file we opened with the new name.

            inFile.save(outFile)

We now print the fileName and the words " ... converted" to the screen so we can see which files have been converted.

            print fileName + " ... converted"

Finally we print a blank line, and exit from the function.

    print "\n"
    return None

Remember that we have a few nested for loops in our function. So we will only get to these two lines once we have been through all iterations within the for loops.

Now we move into the second function in our program, which is to delete the original files. The full function is as follows.

def delOldFileTypes():
    for types in FILETYPES:
        openFiles = glob.glob(types)
        
        for files in openFiles:
            os.remove(files)
            print files + " ... deleted"
            
    print "\n"
    return None

The first thing you will see is there are a lot of similarities between this function and the previous. It would be possible to add the additional functionality, of deleting the files, into the first function. However I have decided to keep it separate, as this allows greater flexibility in my program. I may not always call the function to delete the old files.

The difference between the functions occurs in the inner for loop.

        for files in openFiles:
            os.remove(files)
            print files + " ... deleted"

Rather than saving the files as a different type, we instead use

            os.remove(files)

to delete the files, and then print some information saying the files have been deleted.

            print files + " ... deleted"

Onto our third function.

def resize(): 
    openFiles = glob.glob('*.jpg')
    
    for files in openFiles:
        inFile = Image.open(files)
        fileName = os.path.splitext(files)[0] # gets filename
        outFile = fileName + ".jpg"
        print fileName
        print "Origonal size ",inFile.size
        xDim = inFile.size[0]
        yDim = inFile.size[1]        
        newSize = aspectRatio(xDim, yDim)       
        inFile = inFile.resize((int(newSize[0]),int(newSize[1])),Image.ANTIALIAS)
        inFile.save(outFile)
        print "New Size ",inFile.size, "\n"
        
    return None

Now that we have changed the file type and deleted the old files we can resize the files.

Again the start of the function is similar to what we have seen previously, although we are only working with .jpg files and not other types, so only need one for loop.

def resize(): 
    openFiles = glob.glob('*.jpg')
    
    for files in openFiles:
        inFile = Image.open(files)
        fileName = os.path.splitext(files)[0] # gets filename
        outFile = fileName + ".jpg"

There is nothing new in that section of the program.

We then print the file name to the screen, and also the size of the original file, which will allow us to compare with the final file size later.

        print fileName
        print "Origonal size ",inFile.size

infile.size gives the number of pixels in the image in the format (xPixels, yPixels), which is a handy reference.

We then separate the X and the Y number of pixels and store them in their own variables, xDim and yDim.

        xDim = inFile.size[0]
        yDim = inFile.size[1]  

We send these to another function, whose purpose is to maintain the correct aspect ratio of the image, after is has been resized. We will look at this function in more detail in a minute.

        newSize = aspectRatio(xDim, yDim)    

The result is stored in a variable called newSize.

Using our new size of image we can resize the image.

        inFile = inFile.resize((int(newSize[0]),int(newSize[1])),Image.ANTIALIAS)

This resizes the current inFile and saves the result also as inFile using the resize command. Effectively overwriting what is being stored as inFile.

It looks a little complicated to the right of resize, but it isn't.

(int(newSize[0]),int(newSize[1]))

This refers to the size that we want to resize the image to, in the format (X dimension ,Y dimension).
  • newsize[0] takes the first item from the newSize list, which is the X value, and then ensures it is a whole number by converting it to an integer (int).
  • newsize[1] does the same for the Y value. 

Finally Image.ANTIALIAS improves the image quality. What exactly AntiAlias does is outside the scope of this blog!

        inFile.save(outFile)
        print "New Size ",inFile.size, "\n"

Next we save the file, and again print some information showing the new file size.

We then return from the function.

    return None

The fourth function is the function we referred to in the third function, the apectRatio function. As always lets look at the full function and then break it down into more detail.

def aspectRatio(xDim, yDim):
    
    if xDim <= NEWIMAGESIZE and yDim <= NEWIMAGESIZE: #ensures images already correct size are not enlarged.
        return(xDim, yDim)
    
    elif xDim > yDim:
        divider = xDim/float(NEWIMAGESIZE)
        xDim = float(xDim/divider)
        yDim = float(yDim/divider)
        return(xDim, yDim)
        
    elif yDim > xDim:
        divider = yDim/float(NEWIMAGESIZE)
        xDim = float(xDim/divider)
        yDim = float(yDim/divider)
        return(xDim, yDim)
       
    elif xDim == yDim:
        xDim = NEWIMAGESIZE
        yDim = NEWIMAGESIZE
        return(xDim, yDim)


Firstly why is this function needed? Well, if you are resizing images, you need to maintain the ratio between the X and the Y dimension, otherwise the image will look distorted. This function helps work out what the new values should be, ensuring a good relationship is maintained.

First of all we define the function name, and pass in the current size of the image, xDim and yDim.

def aspectRatio(xDim, yDim):

Right at the start of our program we created a global variable called NEWIMAGESIZE. This defined the largest value, in terms of the number of pixels in either X or Y, that our resized image should have.

The first two lines checks if either the X or Y dimension of our image is less than or equal to (<=) NEWIMAGESIZE. If it is, then we don't need to modify the size of our image.

    if xDim <= NEWIMAGESIZE and yDim <= NEWIMAGESIZE: #ensures images already correct size are not enlarged.
        return(xDim, yDim)

We just return xDim and yDim unchanged.

We now do another check using elif (else if).

    elif xDim > yDim:

This checks to see if the image is a landscape image i.e. there are more pixels in X than in Y.

If this statement is true, we need to reduce the X size down to the value in NEWIMAGESIZE. We can do this by dividing xDim by a value we will store in a variable called divider. Firstly we need to determine the value of divider.

By dividing the size of X (xDim) by the value we need it to become, i.e. NEWIMAGESIZE, we get a the value of divider, we can use this to determine the new size of X and Y. I have also made this a float to ensure some decimal points for accuracy.


        divider = xDim/float(NEWIMAGESIZE)

It figures if we now take xDim and divide it by our value stored in divider, we will get a new value of xDim, which should be the same as the value in NEWIMAGESIZE.

        xDim = float(xDim/divider)
        yDim = float(yDim/divider)

If we also divide yDIM by divider, we will get a new value of yDim. As yDim was smaller than xDim, then it follows that the new value of yDim should be less than NEWIMAGESIZE.

As both xDim and yDim have been divided by the same value, they will maintain the same aspect ratio as the original image.

Now we just return the values of xDim and yDim.

        return(xDim, yDim)

The next section is almost identical.

    elif yDim > xDim:
        divider = yDim/float(NEWIMAGESIZE)
        xDim = float(xDim/divider)
        yDim = float(yDim/divider)
        return(xDim, yDim)

However this is for the case where the image is larger in Y than X. i.e. the image is portrait rather than landscape. In this case we want the yDim to become equal to the size of NEWSIZEIMAGE. Therefore yDim is used to calculate the value of divider.

The final section covers if the image is square. In that case both xDim and yDim can be made equal to NEWSIZEIMAGE.

    elif xDim == yDim:
        xDim = NEWIMAGESIZE
        yDim = NEWIMAGESIZE
        return(xDim, yDim)

Right that is all the functions written. We now just have to call them.

convert2jpg()
delOldFileTypes()
resize()

Finally I have added a message to say that everything is complete, and asking for the user to press a key to continue.

print ('All Done!!!')
raw_input('Images Resized... Press any key to continue')

That brings us to the end of the program. 

I hope you have found this blog post useful. It is certainly a Python program that is my first port of call when I need to resize images.