In this blog post, we’ll walk through the creation of a basic image processing in Java using Swing. This editor will allow you to load, save, and perform various Image manipulation such as cropping, resizing, rotating, applying filters, adjusting brightness and contrast, inverting colors, applying blur, and undoing changes.
Overview of the Application
The Image Editor application is built using Java’s Swing library, which provides a robust framework for creating graphical user interfaces (GUIs). The application consists of a single panel, ImageEditorPanel
, which houses all the necessary components for image editing.
Setting Up the Project
First, make sure you have a development environment set up with Java installed. You can use any IDE you prefer, such as IntelliJ IDEA, Eclipse, or NetBeans.
Creating the Image Manipulation
We start by creating a class ImageEditorPanel
that extends JPanel
. This panel will hold the image and the control buttons. The panel is designed to use a BorderLayout
to manage its components. A JLabel
is used to display the image, and a JScrollPane
wraps this label to enable scrolling for larger images.
User Interface in Java
The user interface of the Image Editor application is designed to be intuitive and easy to use. The ImageEditorPanel
is divided into two main sections:
- Button panel
- The button panel contains a range of buttons, each corresponding to a specific image editing tool. The buttons are organized into two rows, with the top row containing buttons for loading, saving, and undoing changes, and the bottom row containing buttons for image manipulation and adjustment.
- Image display panel.
- The image display panel is a
JLabel
component that displays the loaded image. The panel is surrounded by aJScrollPane
, allowing users to zoom in and out of the image.
- The image display panel is a
NOTE:
You can get the source code for project of image editor in java from this GitHub repository
From download Zip File
Adding Control Buttons
The control buttons for the image editor are added using a JPanel
with a GridBagLayout
. This layout manager provides flexibility for arranging the buttons in a grid-like format with adjustable spacing.
Here are the buttons included in the editor:
File Handling in java
The image processing in java handles the files by using JFileChooser
to load an image into a BufferedImage
for display and to save the current image to a specified file location with ImageIO.write
. This ensures easy management of image files.
- Load Image
- The Load Image feature allows users to select an image file from their file system and load it into the image editor. This is handled by the
LoadButtonListener
, which usesJFileChooser
to provide a user-friendly interface for navigating the file system and choosing an image file. Once selected, the image is displayed in the editor for further manipulation.
- The Load Image feature allows users to select an image file from their file system and load it into the image editor. This is handled by the
- Save Image
- The Save Image feature enables users to save the current state of the edited image back to their file system. The
SaveButtonListener
is responsible for this action, prompting the user to specify a file name and location for saving the image. This ensures that users can preserve their edited images for future use or sharing.
- The Save Image feature enables users to save the current state of the edited image back to their file system. The
Crop Image
Crop Image allows users to remove unwanted parts of the image by defining specific dimensions for cropping. The CropButtonListener
manages this feature by prompting the user to enter the desired crop dimensions. The image is then cropped according to these specifications, focusing on the most important part of the image as defined by the user.
Resize Image
The Resize Image feature lets users change the dimensions of the image. This is useful for scaling images to fit specific requirements. The ResizeButtonListener
handles this operation, asking the user to input the new width and height. The image is then resized to these dimensions, maintaining the aspect ratio if desired.
Rotate Image
Rotate Image allows users to rotate the image by a user-specified angle. This feature is particularly useful for correcting the orientation of images. The RotateButtonListener
prompts the user to enter the desired rotation angle, and the image is rotated accordingly to achieve the desired orientation.
Apply Filter
Apply Filter enables users to enhance their images with various filters, such as grayscale or sepia. The FilterButtonListener
is responsible for this feature, providing options for different filters. Once a filter is selected, it is applied to the image, altering its appearance based on the chosen effect.
Adjust Brightness
The Adjust Brightness feature allows users to increase or decrease the brightness of the image. The BrightnessButtonListener
manages this operation by prompting the user to specify the desired brightness level. The image is then adjusted accordingly, making it brighter or darker based on the user’s input.
Adjust Contrast
Adjust Contrast enables users to modify the contrast levels of the image. This feature is handled by the ContrastButtonListener
, which asks the user to define the desired contrast level. The image is then adjusted to enhance or reduce the contrast, making details more or less pronounced.
Invert Colors
Invert Colors allows users to invert the colors of the image, creating a negative-like effect. The InvertButtonListener
manages this operation, applying the color inversion to the entire image. This effect can create visually striking images with a unique appearance.
Apply Blur
Apply Blur lets users apply a blur effect to the image, softening its appearance. The BlurButtonListener
is responsible for this feature, allowing the user to specify the intensity of the blur. The image is then blurred accordingly, which can be useful for creating artistic effects or obscuring details.
Undo Feature in Java
The Undo feature enables users to revert the image to its previous state, effectively undoing the last change made. This is handled by the UndoButtonListener
, which retrieves the last saved state of the image from the history stack. This feature is crucial for allowing users to experiment with different edits without the risk of permanently altering the image.
Managing Image History
To allow for undo functionality, we maintain a stack of image states. Before performing any operation that modifies the image, we save the current state to this stack. The UndoButtonListener
pops the last state from the stack and sets it as the current image.
Running the Application
Finally, the main method sets up the application window and adds the ImageEditorPanel
to it. By invoking SwingUtilities.invokeLater
, we ensure that the GUI creation is done on the Event Dispatch Thread, which is the standard practice for Swing applications.

Implementation Details of image manipulation
The ImageEditorPanel
class is responsible for creating the user interface and handling user interactions. The class extends JPanel
and overrides the paintComponent
method to display the loaded image.
The image editing tools are implemented as separate classes, each extending Action Listener
in java. These classes handle the logic for each image editing tool, such as cropping, resizing, and applying filters.
The ImageEditor
class is the main entry point of the application, responsible for creating the JFrame
and adding the ImageEditorPanel
to it.
Conclusion
The project of image editor in java demonstrates how to use Java Swing for creating a graphical user interface and performing basic image manipulation. It’s a great way to learn about image processing and GUI development in Java. Feel free to extend this project with more advanced features such as additional filters, support for different image formats, or more sophisticated undo/redo functionality.
Source Code of Image Processing
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import java.util.Stack;
class ImageEditorPanel extends JPanel {
private BufferedImage image;
private JLabel imageLabel;
private Stack<BufferedImage> imageHistory = new Stack<>();
public ImageEditorPanel() {
setLayout(new BorderLayout());
setBackground(Color.BLUE); // Set panel background color
JPanel buttonPanel = new JPanel(new GridBagLayout());
// buttonPanel.setBackground(Color.BLUE);
buttonPanel.setBackground(new Color(51, 153, 255)); // RGB for skyblue
// RGB for light blue
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(5, 5, 5, 5);
gbc.fill = GridBagConstraints.HORIZONTAL;
JButton loadButton = createButton("Load Image");
JButton saveButton = createButton("Save Image");
JButton cropButton = createButton("Crop Image");
JButton resizeButton = createButton("Resize Image");
JButton rotateButton = createButton("Rotate Image");
JButton filterButton = createButton("Apply Filter");
JButton brightnessButton = createButton("Adjust Brightness");
JButton contrastButton = createButton("Adjust Contrast");
JButton invertButton = createButton("Invert Colors");
JButton blurButton = createButton("Apply Blur");
JButton undoButton = createButton("Undo");
loadButton.addActionListener(new LoadButtonListener());
saveButton.addActionListener(new SaveButtonListener());
cropButton.addActionListener(new CropButtonListener());
resizeButton.addActionListener(new ResizeButtonListener());
rotateButton.addActionListener(new RotateButtonListener());
filterButton.addActionListener(new FilterButtonListener());
brightnessButton.addActionListener(new BrightnessButtonListener());
contrastButton.addActionListener(new ContrastButtonListener());
invertButton.addActionListener(new InvertButtonListener());
blurButton.addActionListener(new BlurButtonListener());
undoButton.addActionListener(new UndoButtonListener());
JPanel topRow = new JPanel(new FlowLayout());
// topRow.setBackground(new Color(51, 153, 255));
topRow.setBackground(new Color(51, 153, 255)); // RGB for skyblue
topRow.add(loadButton);
topRow.add(saveButton);
JPanel editRow = new JPanel(new GridLayout(2, 5, 5, 5));
editRow.setBackground(new Color(51, 153, 255)); // RGB for skyblue
editRow.add(cropButton);
editRow.add(resizeButton);
editRow.add(rotateButton);
editRow.add(filterButton);
editRow.add(brightnessButton);
editRow.add(contrastButton);
editRow.add(invertButton);
editRow.add(blurButton);
editRow.add(undoButton);
gbc.gridx = 0;
gbc.gridy = 0;
buttonPanel.add(topRow, gbc);
gbc.gridx = 0;
gbc.gridy = 1;
buttonPanel.add(editRow, gbc);
imageLabel = new JLabel();
imageLabel.setHorizontalAlignment(JLabel.CENTER);
JScrollPane scrollPane = new JScrollPane(imageLabel);
add(buttonPanel, BorderLayout.NORTH);
add(scrollPane, BorderLayout.CENTER);
setPreferredSize(new Dimension(800, 600));
setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
}
private JButton createButton(String text) {
JButton button = new JButton(text);
button.setFocusPainted(false);
// button.setBackground(new Color(51, 153, 255));
button.setBackground(new Color(173, 216, 230)); // RGB for light blue
button.setForeground(Color.WHITE);
button.setFont(new Font("Arial", Font.BOLD, 12));
return button;
}
private void displayImage() {
if (image != null) {
imageLabel.setIcon(new ImageIcon(image));
revalidate();
repaint();
} else {
imageLabel.setIcon(null);
}
}
private void saveToHistory() {
if (image != null) {
BufferedImage copy = new BufferedImage(image.getWidth(), image.getHeight(), image.getType());
Graphics g = copy.getGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
imageHistory.push(copy);
}
}
private class LoadButtonListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
JFileChooser fileChooser = new JFileChooser();
if (fileChooser.showOpenDialog(ImageEditorPanel.this) == JFileChooser.APPROVE_OPTION) {
File file = fileChooser.getSelectedFile();
try {
image = ImageIO.read(file);
imageHistory.clear();
saveToHistory();
displayImage();
} catch (IOException ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(ImageEditorPanel.this, "Error loading image: " + ex.getMessage());
}
}
}
}
private class SaveButtonListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
if (image != null) {
JFileChooser fileChooser = new JFileChooser();
if (fileChooser.showSaveDialog(ImageEditorPanel.this) == JFileChooser.APPROVE_OPTION) {
File file = fileChooser.getSelectedFile();
try {
ImageIO.write(image, "png", file);
} catch (IOException ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(ImageEditorPanel.this, "Error saving image: " + ex.getMessage());
}
}
}
}
}
private class CropButtonListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
if (image != null) {
JTextField xField = new JTextField(5);
JTextField yField = new JTextField(5);
JTextField widthField = new JTextField(5);
JTextField heightField = new JTextField(5);
JPanel cropPanel = new JPanel();
cropPanel.add(new JLabel("X:"));
cropPanel.add(xField);
cropPanel.add(new JLabel("Y:"));
cropPanel.add(yField);
cropPanel.add(new JLabel("Width:"));
cropPanel.add(widthField);
cropPanel.add(new JLabel("Height:"));
cropPanel.add(heightField);
int result = JOptionPane.showConfirmDialog(null, cropPanel, "Enter Crop Dimensions", JOptionPane.OK_CANCEL_OPTION);
if (result == JOptionPane.OK_OPTION) {
try {
int x = Integer.parseInt(xField.getText());
int y = Integer.parseInt(yField.getText());
int width = Integer.parseInt(widthField.getText());
int height = Integer.parseInt(heightField.getText());
if (x + width <= image.getWidth() && y + height <= image.getHeight()) {
saveToHistory();
image = image.getSubimage(x, y, width, height);
displayImage();
} else {
JOptionPane.showMessageDialog(ImageEditorPanel.this, "Crop dimensions are out of bounds");
}
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(ImageEditorPanel.this, "Invalid dimensions");
}
}
}
}
}
private class ResizeButtonListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
if (image != null) {
JTextField widthField = new JTextField(5);
JTextField heightField = new JTextField(5);
JPanel resizePanel = new JPanel();
resizePanel.add(new JLabel("New Width:"));
resizePanel.add(widthField);
resizePanel.add(new JLabel("New Height:"));
resizePanel.add(heightField);
int result = JOptionPane.showConfirmDialog(null, resizePanel, "Enter New Dimensions", JOptionPane.OK_CANCEL_OPTION);
if (result == JOptionPane.OK_OPTION) {
try {
int newWidth = Integer.parseInt(widthField.getText());
int newHeight = Integer.parseInt(heightField.getText());
saveToHistory();
Image tmp = image.getScaledInstance(newWidth, newHeight, Image.SCALE_SMOOTH);
BufferedImage resized = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = resized.createGraphics();
g2d.drawImage(tmp, 0, 0, null);
g2d.dispose();
image = resized;
displayImage();
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(ImageEditorPanel.this, "Invalid dimensions");
}
}
}
}
}
private class RotateButtonListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
if (image != null) {
String degrees = JOptionPane.showInputDialog("Enter degrees to rotate:");
try {
int angle = Integer.parseInt(degrees);
saveToHistory();
image = rotateImage(image, angle);
displayImage();
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(ImageEditorPanel.this, "Invalid input for degrees");
}
}
}
private BufferedImage rotateImage(BufferedImage img, int angle) {
int w = img.getWidth();
int h = img.getHeight();
BufferedImage rotated = new BufferedImage(w, h, img.getType());
Graphics2D g2d = rotated.createGraphics();
g2d.rotate(Math.toRadians(angle), w / 2, h / 2);
g2d.drawImage(img, null, 0, 0);
g2d.dispose();
return rotated;
}
}
private class FilterButtonListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
if (image != null) {
String[] options = {"Grayscale", "Sepia"};
int choice = JOptionPane.showOptionDialog(null, "Choose a filter", "Filter Options",
JOptionPane.DEFAULT_OPTION, JOptionPane.INFORMATION_MESSAGE, null, options, options[0]);
if (choice != -1) {
saveToHistory();
image = applyFilter(image, options[choice]);
displayImage();
}
}
}
private BufferedImage applyFilter(BufferedImage img, String filter) {
int width = img.getWidth();
int height = img.getHeight();
BufferedImage filteredImage = new BufferedImage(width, height, img.getType());
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
Color color = new Color(img.getRGB(x, y));
int r = color.getRed();
int g = color.getGreen();
int b = color.getBlue();
int newPixel;
switch (filter) {
case "Grayscale":
int gray = (r + g + b) / 3;
newPixel = new Color(gray, gray, gray).getRGB();
break;
case "Sepia":
int tr = (int)(0.393 * r + 0.769 * g + 0.189 * b);
int tg = (int)(0.349 * r + 0.686 * g + 0.168 * b);
int tb = (int)(0.272 * r + 0.534 * g + 0.131 * b);
newPixel = new Color(Math.min(tr, 255), Math.min(tg, 255), Math.min(tb, 255)).getRGB();
break;
default:
newPixel = color.getRGB();
}
filteredImage.setRGB(x, y, newPixel);
}
}
return filteredImage;
}
}
private class BrightnessButtonListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
if (image != null) {
String input = JOptionPane.showInputDialog("Enter brightness adjustment value (-255 to 255):");
try {
int brightness = Integer.parseInt(input);
saveToHistory();
image = adjustBrightness(image, brightness);
displayImage();
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(ImageEditorPanel.this, "Invalid input for brightness");
}
}
}
private BufferedImage adjustBrightness(BufferedImage img, int value) {
int width = img.getWidth();
int height = img.getHeight();
BufferedImage adjustedImage = new BufferedImage(width, height, img.getType());
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
Color color = new Color(img.getRGB(x, y));
int r = clamp(color.getRed() + value);
int g = clamp(color.getGreen() + value);
int b = clamp(color.getBlue() + value);
adjustedImage.setRGB(x, y, new Color(r, g, b).getRGB());
}
}
return adjustedImage;
}
private int clamp(int value) {
return Math.max(0, Math.min(value, 255));
}
}
private class ContrastButtonListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
if (image != null) {
String input = JOptionPane.showInputDialog("Enter contrast adjustment value (-100 to 100):");
try {
int contrast = Integer.parseInt(input);
saveToHistory();
image = adjustContrast(image, contrast);
displayImage();
} catch (NumberFormatException ex) {
JOptionPane.showMessageDialog(ImageEditorPanel.this, "Invalid input for contrast");
}
}
}
private BufferedImage adjustContrast(BufferedImage img, int value) {
int width = img.getWidth();
int height = img.getHeight();
BufferedImage adjustedImage = new BufferedImage(width, height, img.getType());
float scaleFactor = (259 * (value + 255)) / (255 * (259 - value));
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
Color color = new Color(img.getRGB(x, y));
int r = clamp((int) (scaleFactor * (color.getRed() - 128) + 128));
int g = clamp((int) (scaleFactor * (color.getGreen() - 128) + 128));
int b = clamp((int) (scaleFactor * (color.getBlue() - 128) + 128));
adjustedImage.setRGB(x, y, new Color(r, g, b).getRGB());
}
}
return adjustedImage;
}
private int clamp(int value) {
return Math.max(0, Math.min(value, 255));
}
}
private class InvertButtonListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
if (image != null) {
saveToHistory();
image = invertColors(image);
displayImage();
}
}
private BufferedImage invertColors(BufferedImage img) {
int width = img.getWidth();
int height = img.getHeight();
BufferedImage invertedImage = new BufferedImage(width, height, img.getType());
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
Color color = new Color(img.getRGB(x, y));
int r = 255 - color.getRed();
int g = 255 - color.getGreen();
int b = 255 - color.getBlue();
invertedImage.setRGB(x, y, new Color(r, g, b).getRGB());
}
}
return invertedImage;
}
}
private class BlurButtonListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
if (image != null) {
saveToHistory();
image = applyBlur(image);
displayImage();
}
}
private BufferedImage applyBlur(BufferedImage img) {
int radius = 5;
int size = radius * 2 + 1;
int width = img.getWidth();
int height = img.getHeight();
BufferedImage blurredImage = new BufferedImage(width, height, img.getType());
int[] pixels = new int[width * height];
img.getRGB(0, 0, width, height, pixels, 0, width);
int[] blurredPixels = new int[width * height];
for (int y = radius; y < height - radius; y++) {
for (int x = radius; x < width - radius; x++) {
int r = 0, g = 0, b = 0;
for (int ky = -radius; ky <= radius; ky++) {
for (int kx = -radius; kx <= radius; kx++) {
int pixel = pixels[(y + ky) * width + (x + kx)];
Color color = new Color(pixel);
r += color.getRed();
g += color.getGreen();
b += color.getBlue();
}
}
int numPixels = size * size;
Color newColor = new Color(r / numPixels, g / numPixels, b / numPixels);
blurredPixels[y * width + x] = newColor.getRGB();
}
}
blurredImage.setRGB(0, 0, width, height, blurredPixels, 0, width);
return blurredImage;
}
}
private class UndoButtonListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
if (!imageHistory.isEmpty()) {
image = imageHistory.pop();
displayImage();
}
}
}
}
class ImageEditor {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Image Editor");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new ImageEditorPanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}