(For more resources on Python, see here.)
Project: Thumbnail Maker
Let's take up a project now. We will apply some of the operations we learned in the previous article, to create a simple Thumbnail Maker utility. This application will accept an image as an input and will create a resized image of that image. Although we are calling it a thumbnail maker, it is a multi-purpose utility that implements some basic image-processing functionality.
Before proceeding further, make sure that you have installed all the packages discussed at the beginning of the previous article. The screenshot of the Thumbnail Maker dialog is show in the following illustration.
The Thumbnail Maker GUI has two components:
- The left panel is a 'control area', where you can specify certain image parameters along with options for input and output paths.
- A graphics area on the right-hand side where you can view the generated image.
In short, this is how it works:
- The application takes an image file as an input.
- It accepts user input for image parameters such as dimensions in pixel, filter for re-sampling and rotation angle in degrees.
- When the user clicks the OK button in the dialog, the image is processed and saved at a location indicated by the user in the specified output image format.
Time for action – play with Thumbnail Maker application
First, we will run the Thumbnail Maker application as an end user. This warm-up exercise intends to give us a good understanding of how the application works. This, in turn, will help us develop/learn the involved code quickly. So get ready for action!
- Download the files ThumbnailMaker.py, ThumbnailMakeDialog.py, and Ui_ThumbnailMakerDialog.py from Packt website. Place these files in some directory.
- From the command prompt, change to this directory location and type the following command:
The Thumbnail Maker dialog that pops up was shown in the earlier screenshot. Next, we will specify the input-output paths and various image parameters. You can open any image file of your choice. Here, the flower image shown in some previous sections will be used as an input image. To specify an input image, click on the small button with three dots …. It will open a file dialog. The following illustration shows the dialog with all the parameters specified.
- If "Maintain Aspect Ratio" checkbox is checked, internally it will scale the image dimension so that the aspect ratio of the output image remains the same. When the OK button is clicked, the resultant image is saved at the location specified by the Output Location field and the saved image is displayed in the right-hand panel of the dialog. The following screenshot shows the dialog after clicking OK button.
- You can now try modifying different parameters such as output image format or rotation angle and save the resulting image.
- See what happens when the Maintain Aspect Ratio checkbox is unchecked. The aspect ratio of the resulting image will not be preserved and the image may appear distorted if the width and height dimensions are not properly specified.
- Experiment with different re-sampling filters; you can notice the difference between the quality of the resultant image and the earlier image.
- There are certain limitations to this basic utility. It is required to specify reasonable values for all the parameters fields in the dialog. The program will print an error if any of the parameters is not specified.
What just happened?
We got ourselves familiar with the user interface of the thumbnail maker dialog and saw how it works for processing an image with different dimensions and quality. This knowledge will make it easier to understand the Thumbnail Maker code.
Generating the UI code
The Thumbnail Maker GUI is written using PyQt4 (Python bindings for Qt4 GUI framework). Detailed discussion on how the GUI is generated and how the GUI elements are connected to the main functions is beyond the scope of this article. However, we will cover certain main aspects of this GUI to get you going. The GUI-related code in this application can simply be used 'as-is' and if this is something that interests you, go ahead and experiment with it further! In this section, we will briefly discuss how the UI code is generated using PyQt4.
Time for action – generating the UI code
PyQt4 comes with an application called QT Designer. It is a GUI designer for QT-based applications and provides a quick way to develop a graphical user interface containing some basic widgets. With this, let's see how the Thumbnail Maker dialog looks in QT Designer and then run a command to generate Python source code from the .ui file.
- Download the thumbnailMaker.ui file from the Packt website.
- Start the QT Designer application that comes with PyQt4 installation.
- Open the file thumbnailMaker.ui in QT Designer. Notice the red-colored borders around the UI elements in the dialog. These borders indicate a 'layout' in which the widgets are arranged. Without a layout in place, the UI elements may appear distorted when you run the application and, for instance, resize the dialog. Three types of QLayouts are used, namely Horizontal, Vertical, and Grid layout.
- You can add new UI elements, such as a QCheckbox or a QLabel, by dragging and dropping it from the 'Widget Box' of QT Designer. It is located in the left panel by default.
- Click on the field next to the label "Input file". In the right-hand panel of QT Designer, there is a Property Editor that displays the properties of the selected widget (in this case it's a QLineEdit). This is shown in the following illustration. The Property Editor allows us to assign values to various attributes such as the objectName, width, and height of the widget, and so on.
Qt Designer shows the details of the selected widget in Property Editor.
- QT designer saves the file with extension .ui. To convert this into Python source code, PyQt4 provides a conversion utility called pyuic4. On Windows XP, for standard Python installation, it is present at the following location—C:\Python26\ Lib\site-packages\PyQt4\pyuic4.bat. Add this path to your environment variable. Alternatively specify the whole path each time you want to convert ui file to Python source file. The conversion utility can be run from the command prompt as:
pyuic4 thumbnailMaker.ui -o Ui_ThumbnailMakerDialog.py
- This script will generate Ui_ThumbnailMakerDialog.py with all the GUI elements defined. You can further review this file to understand how the UI elements are defined.
What just happened?
We learned how to autogenerate the Python source code defining UI elements of Thumbnail Maker Dialog from a Qt designer file.
Have a go hero – tweak UI of Thumbnail Maker dialog
Modify the thumbnailMaker.ui file in QT Designer and implement the following list of things in the Thumbnail Maker dialog.
- Change the color of all the line edits in the left panel to pale yellow.
- Tweak the default file extension displayed in the Output file Format combobox such that the first option is .png instead of .jpeg
Double click on this combobox to edit it.
- Add new option .tiff to the output format combobox.
- Align the OK and Cancel buttons to the right corner.
You will need to break layouts, move the spacer around, and recreate the layouts.
- Set the range of rotation angle 0 to 360 degrees instead of the current -180 to +180 degrees.
After this, create Ui_ThumbnailMakerDialog.py by running the pyuic4 script and then run the Thumbnail Maker application.
(For more resources on Python, see here.)
Connecting the widgets
In the earlier section, the Python source code representing UI was automatically generated using the pyuic4 script. This, however, only has the widgets defined and placed in a nice layout. We need to teach these widgets what they should do when a certain event occurs. To do this, QT's slots and signals will be used. A signal is emitted when a particular GUI event occurs. For example, when the user clicks on the OK button, internally, a clicked() signal is emitted. A slot is a function that is called when a particular signal is emitted. Thus, in this example, it will call a specified method, whenever the OK button is clicked.
Time for action – connecting the widgets
You will notice several different widgets in the dialog. For example, the field which accepts the input image path or the output directory path is a QLineEdit. The widget where image format is specified is a QCombobox. On similar lines, the OK and Cancel buttons are QPushButton. As an exercise, you can open up the thumbnailMaker.ui file and click on each element to see the associated QT class from the Property Editor.
With this, let's learn how the widgets are connected.
- Open the file ThumbnailMakerDialog.py. The _connect method of class ThumbnailMakerDialog is copied. The method is called in the constructor of this class.
Connect slots with signals.
- self._dialog is an instance of class Ui_ThumbnailMakerDialog.self.connect is the inherited method of Qt class QDialog. Here, it takes the following arguments (QObject, signal, callable), where QObject is any widget type (all inherit QObject), signal is the QT SIGNAL that tells us about what event occurred and callable is any method handling this event.
- For example, consider the highlighted lines of the code snippet. They connect the OK button to a method that handles image processing. The first argument , self._dialog.okPushButton refers to the button widget defined in class Ui_ThumbnailMakerDialog. Referring to QPushButton documentation, you will find there is a "clicked()" signal that it can emit. The second argument SIGNAL("clicked()") tells Qt that we want to know when that button is clicked by the user. The third argument is the method self._processImage that gets called when this signal is emitted.
- Similarly, you can review the other connections in this method. Each of these connects a widget to a method of the class ThumbnailMakerDialog.
What just happened?
We reviewed ThumbnailMakerDialog._connect() method to understand how the UI elements are connected to various internal methods. The previous two sections helped us learn some preliminary concepts of GUI programming using QT.
Developing the image processing code
The previous sections were intended to get ourselves familiar with the application as an end user and to understand some basic aspects of the GUI elements in the application. With all necessary pieces together, let's focus our attention on the class that does all the main image processing in the application.
The class ThumbnailMaker handles the pure image processing code. It defines various methods to achieve this. For example, the class methods such as _rotateImage, _makeThumbnail, and _resizeImage manipulate the given image to accomplish rotation, thumbnail generation, and resizing respectively. This class accepts input from ThumbnailMakerDialog. Thus, no QT related UI code is required here. If you want to use some other GUI framework to process input, you can do that easily. Just make sure to implement the public API methods defined in class ThumbnailMakerDialog, as those are used by the ThumbnailMaker class.
Time for action – developing image processing code
Thus, with ThumbnailMakerDialog at your disposal, you can develop your own code in scratch, in class ThumbnailMaker. Just make sure to implement the method processImage as this is the only method called by ThumbnailMakerDialog.
Let's develop some important methods of class ThumbnailMaker.
- Write the constructor for class ThumbnailMaker. It takes dialog as an argument. In the constructor, we only initialize self._dialog, which is an instance of class ThumbnailMakerDialog. Here is the code.
def __init__(self, dialog):
Constructor for class ThumbnailMaker.
# This dialog can be an instance of
# ThumbnailMakerDialog class. Alternatively, if
# you have some other way to process input,
# it will be that class. Just make sure to implement
# the public API methods defined in
# ThumbnailMakerDialog class!
self._dialog = dialog
- Next, write the processImage method in class ThumbnailMaker. The code is as follows:
Note: You can download the file ThumbnailMaker.py from Packt website. The code written is from this file. The only difference is that some code comments are removed here.
1 def processImage(self):
2 filePath = self._dialog.getInputImagePath()
3 imageFile = Image.open(filePath)
5 if self._dialog.maintainAspectRatio:
6 resizedImage = self._makeThumbnail(imageFile)
8 resizedImage = self._resizeImage(imageFile)
10 rotatedImage = self._rotateImage(resizedImage)
12 fullPath = self._dialog.getOutImagePath()
14 # Finally save the image.
- On line 2, it gets the full path of the input image file. Note that it relies on self._dialog to provide this information.
- Then the image file is opened the usual way. On line 4, it checks a flag that decides whether or not to process the image by maintaining the aspect ratio. Accordingly, _makeThumbnail or _resizeImage methods are called.
- On line 10, it rotates the image resized earlier, using the _rotateImage method.
- Finally, on line 15, the processed image is saved at a path obtained from the getOutImagePath method of class ThumbnailMakerDialog.
- We will now write the _makeThumbnail method.
1 def _makeThumbnail(self, imageFile):
2 foo = imageFile.copy()
3 size = self._dialog.getSize()
4 imageFilter = self._getImageFilter()
5 foo.thumbnail(size, imageFilter)
6 return foo
- First a copy of the original image is made. We will manipulate this copy and the method will return it for further processing.
- Then the necessary parameters such as the image dimension and filter for re-sampling are obtained from self._dialog and _getImageFilter respectively.
- Finally the thumbnail is created on line 5 and then method returns this image instance.
- We have already discussed how to resize and rotate image. The related code is straightforward to write and the readers are suggested to write it as an exercise. You will need to review the code from file ThumbnailMakerDialog.py for getting appropriate parameters. Write remaining routines namely, _resizeImage, _rotateImage and _getImageFilter.
- Once all methods are in place, run the code from the command line as
- It should show our application dialog. Play around with it to make sure everything works!
(For more resources on Python, see here.)
What just happened?
In the previous section, we completed an exciting project. Several things learned in the previous article Python Image Manipulation, such as image I/O, resizing and so on, were applied in the project. We developed a GUI application where some basic image manipulation features, such as creating thumbnails, were implemented. This project also helped us gain some insight into various aspects of GUI programming using QT.
Have a go hero – enhance the ThumbnailMaker application
Want to do something more with the Thumbnail Maker. Here you go! As you will add more features to this application, the first thing you would need to do is to change its name—at least from the caption of the dialog that pops up! Edit the thumbnailMaker.ui file in QT designer, change the name to something like "Image Processor", and recreate the corresponding .py file. Next, add the following features to this application.
If you don't want to deal with any UI code, that is fine too! You can write a class similar to ThumbnailMakerDialog. Do the input argument processing in your own way. All that class ThumbnailMaker requires is implementation of certain public methods in this new class, to get various input parameters.
- Accept output filename from the user. Currently, it gives the same name as the input file.
Edit the .ui file. You would need to break the layouts before adding a QLineEdit and its QLabel and then recreate the layouts.
- If there is a previously created output image file in the output directory, clicking OK would simply overwrite that file. Add a checkbox reading, "Overwrite existing file (if any)". If the checkbox in deselected, it should pop up a warning dialog and exit.
For the latter part, there is a commented out code block in ThumbnailMakerDialog._processImage. Just enable the code.
- Add a feature that can add specified text in the lower-left corner of the output image.
- Create an image with this text, and use the combination of crop and paste to achieve desired results. For user input, you will need to add a new QLineEdit for accepting text input and then connect signals with a callable method in ThumbnailMakerDialog._connect.
We created an interesting project implementing some image processing functionality.
- Python Image Manipulation [Article]
- Python 3 Object Oriented Programming [Book]
- Objects in Python [Article]
- Python 3: When to Use Object-oriented Programming [Article]
- Python 3 Object Oriented Programming: Managing objects [Article]