As a developer, have you ever wanted to create an image editing tool that lets you seamlessly apply a variety of filters, adjust dimensions, and even perform advanced features like background removal? In this comprehensive tutorial, we will harness the capabilities of DelphiFMX and merge them with image management platforms to craft an image editing application that leverages the incredible potential of AI tools with an amazing GUI. So, let’s get started right away!
Table of Contents
How to create an image editing app with DelphiFMX?
Developing an image editing application with DelphiFMX involves a series of steps. First, install Delphi Integrated Development Environment (IDE) and the Python FireMonkey GUI framework on your computer. These are essential tools for creating the image editor. Additionally, you’ll need a text editor or an IDE that supports Python, such as PyScripter, along with the Delphi4PythonExporter tool. If you don’t have these tools already, we suggest referring to the article “Setting Up a Powerful Python GUI Project” article to help you get started.
Furthermore, it’s crucial to have the requests and cloudinary Python libraries installed. You can achieve this by executing the following commands in your command prompt:
1 2 3 |
pip install cloudinary pip install requests |
To access the source code for this tutorial, you can download it from our GitHub repository using the following link:
https://github.com/Embarcadero/PythonBlogExamples/tree/main/Image_Editor_App
What kind of forms do we need for an image editing app?
Let’s begin by launching the Delphi application and initiating a new project. Go to the File menu, select New, then choose Multi-Device Application, and finally, opt for Blank Application and click OK. In this tutorial, we’ve named our project “ImageEditor.”
If you’re not well-acquainted with the different sections of the Delphi IDE, we suggest referring to our complimentary eBook bundle. This resource comprehensively covers the Delphi Python EcoSystem and all Python GUI offerings.
Now, let’s provide a name for our form. Right-click on the form and select QuickEdit. Here, we’ll name our form MainForm
with the caption “Image Editor.”
We’ll also rename the source file for our MainForm
to Main.pas
to simplify tracking. Next, adjust the form’s dimensions in the Object Inspector using ClientWidth
and ClientHeight
under the properties tab or simply using the mouse.
We can use the TTabControl
component of this IDE to add two tabs to our form, one for image editing and the other for background removal. Search for the component in the Palette and drop this component onto the form.
Next, stretch out the component so it occupies the whole form. Here is what our form looks like:
Right-click on the component and click on AddTabItem to add a tab to the form. As mentioned, we will add two tabs, one for image editing and the second for background removal. Next, we’ll use QuickEdit to rename the tabs to Edit
and BackgroundRemoval
.
Now, let’s incorporate a few labels that will be required for our application. To achieve this, access the standard Palette and locate the TLabel
component. Then, drag and drop this component onto the form.
Next, we’ll customize the text style by utilizing the TextSettings
property of each label. To do this, navigate to the Object Inspector and search for the TextSettings
property. Click on the ellipsis button (...
) adjacent to the Font
property to explore options for adjusting the label according to your preferences. In this instance, we’ve configured our font family as SemiBold Segoe UI with varying font sizes for each label. Here’s what our form looks like:
Now, let’s add a couple of buttons to our form. For this purpose, we’ll utilize the TButton
component. We’ll include two buttons in this tab: FileButton
to facilitate file selection for image editing and a TransformButton
to edit and display the transformed image.
Next, we’ll add a TImage
component to the tab where the selected and edited images will be displayed. A TComboBox
will also be used for giving options of all possible image filters to the user. At the same time, a TSpinBox
next to it will allow the user to input the intensity of the selected filter. We will also add a TCheckBox
component that, when checked, will enable users to change the image’s dimensions by specifying the height and width. We will also add two TEdit
components for the user to specify the new image dimensions.
To complete our form, we’ll introduce the TOpenDialog
component, enabling us to open a dialog box for file selection from our system. Access the Palette, search for this component, and then drag and drop it onto our form. Here is what the tab looks like:
Now that our Edit
tab is complete, we will head to the BackgroundRemoval
tab. Here, we will add a TImage
component and a TButton
component. Here is what the tab looks like:
Finally, let’s infuse some style into our form. To achieve this, you’ll need to obtain the free eBook bundle, which includes various styles. Download the eBook and extract the FMX styles ZIP file to your preferred directory. Select the style you wish to use; in this instance, we’ll opt for GoldenGraphite.style
from the bundle. You can explore more DelphiFMX styles online, such as at delphistyles.com.
Go to the Palette and add the TStyleBook
component to your form.
Double-click on the StyleBook1
component to access the Style Designer. Now click the open button, which will prompt a dialog box to appear.
Navigate to the .style
file within the dialog box, select the file, and then click exit.
Now, select the form and access the Object Inspector. Locate the StyleBook property, choose the drop-down menu, and select StyleBook1.
Here’s what our form looks like:
Now that the form is fully configured, the last step involves adding functionality to each button within the form. To achieve this, double-click on each button. This action will generate an OnClick
method for that button. This will create a procedure in the .pas
file corresponding to the .fmx
form, enabling you to incorporate unique functionality for each button when clicked.
Due to the Delphi4PythonExporter’s lack of runtime implementation, including at least one comment (//
) in each procedure to preserve its function when exporting the form is essential.
How can I export these forms into Python code?
Now that our form is ready, we can export the project by navigating to Tools > Export To Python > Export Current Entire Project
.
Here, we will give the project the title Image Editor App
. Next, select the directory of your choosing and click on Export:
Because we had one form, Delphi generated two Python files and one .pyfmx
file. The ImageEditor.py
file will be used to run the application, whereas the Main.py
file contains the class for the MainForm
. The Main.pyfmx
file contains the visual information for the MainForm
. Here are the contents for each file.
ImageEditor.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
from delphifmx import * from Main import MainFrom def main(): Application.Initialize() Application.Title = 'Image Editor App' Application.MainForm = MainFrom(Application) Application.MainForm.Show() Application.Run() Application.MainForm.Destroy() if __name__ == '__main__': main() |
Main.py:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
import os from delphifmx import * class MainFrom(Form): def __init__(self, owner): self.TabControl1 = None self.Edit = None self.FilenameLabel = None self.EffectLabel = None self.IntensityLabel = None self.WidthLabel = None self.HeightLabel = None self.TransformButton = None self.FileButton = None self.EffectComboBox = None self.SpinBox1 = None self.DimensionsCheckBox = None self.EditImage = None self.BackgroundRemoval = None self.RemoveBgImage = None self.RemoveButton = None self.OpenDialog1 = None self.StyleBook1 = None self.WidthEdit = None self.HeightEdit = None self.LoadProps(os.path.join(os.path.dirname(os.path.abspath(__file__)), "Main.pyfmx")) def TransformButtonClick(self, Sender): pass def FileButtonClick(self, Sender): pass def RemoveButtonClick(self, Sender): pass |
How can I add functionality to our image editing form?
The only thing we need to do now is start adding functionality to our forms. Start by opening up Main.py
in the text editor of your choice and importing some libraries:
1 2 3 4 5 6 7 |
# Additional Imports import cloudinary from cloudinary import uploader from cloudinary import CloudinaryImage import requests import time |
Next, navigate to the __init__
function. Here, we will define our effect_options
in a dictionary and use it to populate our EffectComboBox
. Next, we will define our Cloudinary API using our cloud credentials. Here is what our __init__
function looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
def __init__(self, owner): self.TabControl1 = None self.Edit = None self.FilenameLabel = None self.EffectLabel = None self.IntensityLabel = None self.WidthLabel = None self.HeightLabel = None self.TransformButton = None self.FileButton = None self.EffectComboBox = None self.SpinBox1 = None self.DimensionsCheckBox = None self.EditImage = None self.BackgroundRemoval = None self.RemoveBgImage = None self.RemoveButton = None self.OpenDialog1 = None self.StyleBook1 = None self.WidthEdit = None self.HeightEdit = None self.LoadProps(os.path.join(os.path.dirname(os.path.abspath(__file__)), "Main.pyfmx")) # Defining different effect options self.effect_options = {0:"--", 1:"cartoonify", 2:"pixelate", 3:"sepia", 4:"vignette", 5:"grayscale"} # Populating ComboBox for option in list(self.effect_options.values()): self.EffectComboBox.Items.Add(option) # Configure Cloudinary with your account details cloudinary.config( cloud_name = "your_cloud_name", api_key = "your_api_key", api_secret = "your_api_secret" ) |
Next, let’s head over to the FileButtonClick
function. Here, we will start by defining the title of the Dialog Box and filter the results only to show image formats:
1 2 3 4 5 6 7 |
def FileButtonClick(self, Sender): # Filepicker window title self.OpenDialog1.Title = "Select an Image" # Filtering only image formats to show self.OpenDialog1.Filter = "Image files (*.jpg;*.jpeg;*.png;*.bmp,*.webp)|*.jpg;*.jpeg;*.png;*.bmp;*.webp|All files (*.*)|*.*" |
Once the user selects the image, we open it up and extract its information, including its location and name. Here is what our FilebuttonClick
function looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def FileButtonClick(self, Sender): # Filepicker window title self.OpenDialog1.Title = "Select an Image" # Filtering only image formats to show self.OpenDialog1.Filter = "Image files (*.jpg;*.jpeg;*.png;*.bmp,*.webp)|*.jpg;*.jpeg;*.png;*.bmp;*.webp|All files (*.*)|*.*" if self.OpenDialog1.Execute(): self.FilenameLabel.Text = os.path.basename(self.OpenDialog1.FileName) # Display selected image name self.EditImage.Bitmap.LoadFromFile(self.OpenDialog1.FileName) self.RemoveBgImage.Bitmap.LoadFromFile(self.OpenDialog1.FileName) self.public_id = self.FilenameLabel.Text.split('.')[0] |
Next, we will create a BoxChecked
function to turn our Width
and Height
edit labels on or off. Here is what the function looks like:
1 2 3 4 5 6 7 8 9 10 |
def BoxChecked(self, Sender): if self.DimensionsCheckBox.IsChecked: # Enable the WidthEdit and HeightEdit controls self.WidthEdit.Enabled = True self.HeightEdit.Enabled = True else: # Disable the WidthEdit and HeightEdit controls self.WidthEdit.Enabled = False self.HeightEdit.Enabled = False |
Before moving forward, we will define a helper function to help us download our images from the Cloudinary cloud. Start by creating a download
function inside the MainForm
class. Next, we will use requests
to retrieve the image using a url
provided in the results
parameter. The image is then saved in the path defined in the local_file_path
parameter. Finally, it is then loaded onto the form using the image
parameter. Here is what our final function looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
def Download(self, result, local_file_path, image): # Retrieving Image response = requests.get(result, stream=True) if response.status_code == 200: with open(local_file_path, "wb") as file: for chunk in response.iter_content(1024): file.write(chunk) print(f"Transformed image saved to: {local_file_path}") # Load and display the downloaded image in the DownloadedImage control image.Bitmap.LoadFromFile(local_file_path) else: print("Failed to download the image.") |
Now, let’s define our TransformButtonClick
function. We will start by defining an empty list called transformations
that will be used to pass our transformation parameters to the Cloudinary API. Next, we will check if the DimensionsCheckBox
has been checked. If it has, we will add a width
and height
transformation to the list:
1 2 3 4 5 6 |
def TransformButtonClick(self, Sender): transformations = [] # Adding Height and Width Parameters if Checkbox is checked if self.DimensionsCheckBox.IsChecked: if self.WidthEdit.Text.isdigit() and self.HeightEdit.Text.isdigit(): transformations.append({"height": int(self.HeightEdit.Text), "width": int(self.WidthEdit.Text), "crop": "fill"}) |
Next, we will retrieve the transformation selected in the combo box as well as its intensity from the spin box. We will then add these transformations to the list and call the uploader
function to upload the image with said transformation. Next, we will download the transformed image using our download
function. Here is what our complete function looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
def TransformButtonClick(self, Sender): transformations = [] # Adding Height and Width Parameters if Checkbox is checked if self.DimensionsCheckBox.IsChecked: if self.WidthEdit.Text.isdigit() and self.HeightEdit.Text.isdigit(): transformations.append({"height": int(self.HeightEdit.Text), "width": int(self.WidthEdit.Text), "crop": "fill"}) # Adding Effect Paramets along with Intensity if self.EffectComboBox.ItemIndex in list(self.effect_options.keys()) and self.effect_options[self.EffectComboBox.ItemIndex] != "--" and self.SpinBox1.Text.isdigit(): if 0 < int(self.SpinBox1.Text) <= 100: transformations.append({"effect": "{}:{}".format(self.effect_options[self.EffectComboBox.ItemIndex], self.SpinBox1.Text)}) # Upload the image to Cloudinary with the specified public ID result = uploader.upload(self.OpenDialog1.FileName, public_id=self.public_id, transformation=transformations)['secure_url'] # Define the local file path where you want to save the image local_file_path = ".Imagesedited_"+self.FilenameLabel.Text # Change to your desired local path # Download the transformed image and save it locally self.Download(result, local_file_path, self.EditImage) |
Finally, we can complete our app by defining the RemoveButtonClick
function. Like the TransformButtonClick
function, we will make a simple API call to the Cloudinary cloud with a background_removal
parameter. This parameter uses the Cloudinary AI Background Removal addon which removes the background from our image. You can learn more about this addon here. Here is what our final function looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
def RemoveButtonClick(self, Sender): self.public_id+='_removal' # Uploading Image with background removal API result = uploader.upload(self.OpenDialog1.FileName, public_id=self.public_id, background_removal = "cloudinary_ai") print(result) result= result['secure_url'] image = CloudinaryImage(self.public_id) # Retrieving Image URL result = image.url + ".png" print(result) # Defining Local Save Path local_file_path = ".Imagesbg_removed_"+self.public_id+".png" # Change to your desired local path # Adding Time for background removal completion time.sleep(5) # Downloading Image self.Download(result, local_file_path, self.RemoveBgImage) |
Here is what our final Main.py
file looks like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
import os from delphifmx import * # Additional Imports import cloudinary from cloudinary import uploader from cloudinary import CloudinaryImage import requests import time class MainFrom(Form): def __init__(self, owner): self.TabControl1 = None self.Edit = None self.FilenameLabel = None self.EffectLabel = None self.IntensityLabel = None self.WidthLabel = None self.HeightLabel = None self.TransformButton = None self.FileButton = None self.EffectComboBox = None self.SpinBox1 = None self.DimensionsCheckBox = None self.EditImage = None self.BackgroundRemoval = None self.RemoveBgImage = None self.RemoveButton = None self.OpenDialog1 = None self.StyleBook1 = None self.WidthEdit = None self.HeightEdit = None self.LoadProps(os.path.join(os.path.dirname(os.path.abspath(__file__)), "Main.pyfmx")) # Defining different effect options self.effect_options = {0:"--", 1:"cartoonify", 2:"pixelate", 3:"sepia", 4:"vignette", 5:"grayscale"} # Populating ComboBox for option in list(self.effect_options.values()): self.EffectComboBox.Items.Add(option) # Configure Cloudinary with your account details cloudinary.config( cloud_name = "dzte1natd", api_key = "558364876947471", api_secret = "Llc8TFU56g17mjPWK_kEgNd--mE" ) def BoxChecked(self, Sender): if self.DimensionsCheckBox.IsChecked: # Enable the WidthEdit and HeightEdit controls self.WidthEdit.Enabled = True self.HeightEdit.Enabled = True else: # Disable the WidthEdit and HeightEdit controls self.WidthEdit.Enabled = False self.HeightEdit.Enabled = False def TransformButtonClick(self, Sender): transformations = [] # Adding Height and Width Parameters if Checkbox is checked if self.DimensionsCheckBox.IsChecked: if self.WidthEdit.Text.isdigit() and self.HeightEdit.Text.isdigit(): transformations.append({"height": int(self.HeightEdit.Text), "width": int(self.WidthEdit.Text), "crop": "fill"}) # Adding Effect Paramets along with Intensity if self.EffectComboBox.ItemIndex in list(self.effect_options.keys()) and self.effect_options[self.EffectComboBox.ItemIndex] != "--" and self.SpinBox1.Text.isdigit(): if 0 < int(self.SpinBox1.Text) <= 100: transformations.append({"effect": "{}:{}".format(self.effect_options[self.EffectComboBox.ItemIndex], self.SpinBox1.Text)}) # Upload the image to Cloudinary with the specified public ID result = uploader.upload(self.OpenDialog1.FileName, public_id=self.public_id, transformation=transformations)['secure_url'] # Define the local file path where you want to save the image local_file_path = ".Imagesedited_"+self.FilenameLabel.Text # Change to your desired local path # Download the transformed image and save it locally self.Download(result, local_file_path, self.EditImage) def FileButtonClick(self, Sender): # Filepicker window title self.OpenDialog1.Title = "Select an Image" # Filtering only image formats to show self.OpenDialog1.Filter = "Image files (*.jpg;*.jpeg;*.png;*.bmp,*.webp)|*.jpg;*.jpeg;*.png;*.bmp;*.webp|All files (*.*)|*.*" if self.OpenDialog1.Execute(): self.FilenameLabel.Text = os.path.basename(self.OpenDialog1.FileName) # Display selected image name self.EditImage.Bitmap.LoadFromFile(self.OpenDialog1.FileName) self.RemoveBgImage.Bitmap.LoadFromFile(self.OpenDialog1.FileName) self.public_id = self.FilenameLabel.Text.split('.')[0] def RemoveButtonClick(self, Sender): self.public_id+='_removal' # Uploading Image with background removal API result = uploader.upload(self.OpenDialog1.FileName, public_id=self.public_id, background_removal = "cloudinary_ai") print(result) result= result['secure_url'] image = CloudinaryImage(self.public_id) # Retrieving Image URL result = image.url + ".png" print(result) # Defining Local Save Path local_file_path = ".Imagesbg_removed_"+self.public_id+".png" # Change to your desired local path # Adding Time for background removal completion time.sleep(5) # Downloading Image self.Download(result, local_file_path, self.RemoveBgImage) def Download(self, result, local_file_path, image): # Retrieving Image response = requests.get(result, stream=True) if response.status_code == 200: with open(local_file_path, "wb") as file: for chunk in response.iter_content(1024): file.write(chunk) print(f"Transformed image saved to: {local_file_path}") # Load and display the downloaded image in the DownloadedImage control image.Bitmap.LoadFromFile(local_file_path) else: print("Failed to download the image.") |
What does the final Python image editing app look like?
Now that everything is complete, all we need to do is open up the ImageEditor.py
file and run it.
Next, click on the File button and load up an image file. For now, we will be loading using an image of a teacher as a reference.
Once you’ve selected your image, you can add an effect to your image. Here, we will add a pixelate
effect with an intensity of 30
.
Finally, click the Transform
button to add the effect to your image.
Next, let’s head over to the Background Removal
tab.
Now click on Remove
to remove the background of the subject.
Are you ready to create more apps with DelphiFMX?
Creating an image editing application with DelphiFMX empowers developers to leverage AI tools and elevate their image manipulation capabilities. By seamlessly integrating the Delphi IDE with the DelphiFMX Python library, you can lay the foundation for a robust image editor. Moreover, Delphi’s flexibility allows you to harness external APIs like Cloudinary for enhanced functionality, enabling features like advanced filters, dimension adjustments, and even background removal while managing to provide one of the best GUI tools.
What are some important FAQs about creating great apps with Python and Delphi?
What are the benefits of using Delphi for image editing apps?
Delphi offers a powerful and flexible development environment for building image editing applications. It provides access to various libraries, APIs, and tools that streamline the development process and enable rich functionality.
Is DelphiFMX suitable for cross-platform development?
Yes, DelphiFMX is known for its cross-platform compatibility. You can develop apps for Windows, macOS, iOS, and Android using a single codebase, which is advantageous for reaching a wider audience.
Can I use external APIs like Cloudinary in my DelphiFMX app?
DelphiFMX allows you to integrate external APIs like Cloudinary to enhance your app’s capabilities. You can use APIs for image storage, management, and manipulation.
Can I monetize my image editing app created with DelphiFMX?
Yes, you can monetize your app through various means, such as offering it as a paid download, integrating ads, or offering in-app purchases for premium features