Home  Contents

Images in Java 2D

In this part of the Java 2D tutorial, we will work with images. Images are really a complex topic. Here we only provide some basics.

BufferedImage plays a crucial role when we work with images in Java 2D. It is used to manipulate with images. It is created in memory for efficiency. The process is as follows. We copy the image pixels into the BufferedImage, manipulate the pixels and draw the result on the panel.

Displaying an Image

In the first example, we display an image on the panel.

DisplayImage.java
package com.zetcode;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

class Surface extends JPanel {

    private Image mshi;

    public Surface() {
        
        loadImage();
        determineAndSetSize();
    }

    private void loadImage() {

        mshi = new ImageIcon("mushrooms.jpg").getImage();
    }
    
    private void determineAndSetSize() {
        
        Dimension d = new Dimension();
        d.width = mshi.getWidth(null);
        d.height = mshi.getHeight(null);
        setPreferredSize(d);        
    }

    private void doDrawing(Graphics g) {

        Graphics2D g2d = (Graphics2D) g;
        g2d.drawImage(mshi, 1, 1, null);
    }

    @Override
    public void paintComponent(Graphics g) {

        super.paintComponent(g);
        doDrawing(g);
    }
}

public class DisplayImage extends JFrame {

    public DisplayImage() {

        initUI();
    }

    private void initUI() {

        setTitle("Mushrooms");

        add(new Surface());

        pack();
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                DisplayImage ex = new DisplayImage();
                ex.setVisible(true);
            }
        });
    }
}

In the example, we display an image on the panel. The window is resized to fit the size of the image.

private void loadImage() {

    mshi = new ImageIcon("mushrooms.jpg").getImage();
}

We use the ImageIcon class to load the image. The image is located in the current working directory.

private void determineAndSetSize() {
    
    Dimension d = new Dimension();
    d.width = mshi.getWidth(null);
    d.height = mshi.getHeight(null);
    setPreferredSize(d);        
}

We determine the size of the loaded image. With the setPreferredSize() method, we set the preferred size of the Surface panel. The pack() method of the JFrame container will cause the frame to fit the size of its children. In our case the Surface panel. As a consequence, the window will be sized to exactly display the loaded image.

private void doDrawing(Graphics g) {

    Graphics2D g2d = (Graphics2D) g;
    g2d.drawImage(mshi, 1, 1, null);
}

The image is drawn on the panel using the drawImage() method. The last parameter is the ImageObserver class. It is sometimes used for asynchronous loading. When we do not need asynchronous loading of our images, we can just put null there.

private void initUI() {
    ...
    pack();
    ...
}

The pack() method sizes the contaniner to fit the size of the child panel.

Grayscale image

In computing, a grayscale digital image is an image in which the value of each pixel is a single sample, that is, it carries the full (and only) information about its intensity. Images of this sort are composed exclusively of shades of neutral gray, varying from black at the weakest intensity to white at the strongest. (Wikipedia)

In the next example, we will create a grayscale image with Java 2D library.

GrayScaleImage.java
package com.zetcode;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

class Surface extends JPanel {

    private Image mshi;
    private BufferedImage bufimg;
    private Dimension d;

    public Surface() {

        loadImage();
        determineAndSetSize();
        createGrayImage();
    }

    private void determineAndSetSize() {

        d = new Dimension();
        d.width = mshi.getWidth(null);
        d.height = mshi.getHeight(null);
        setPreferredSize(d);
    }
    
    private void createGrayImage() {
        
        bufimg = new BufferedImage(d.width, d.height, 
                BufferedImage.TYPE_BYTE_GRAY);

        Graphics2D g2d = bufimg.createGraphics();
        g2d.drawImage(mshi, 1, 1, null);
        g2d.dispose();        
    }

    private void loadImage() {

        mshi = new ImageIcon("mushrooms.jpg").getImage();
    }

    private void doDrawing(Graphics g) {

        Graphics2D g2d = (Graphics2D) g;
        g2d.drawImage(bufimg, null, 2, 2);
    }

    @Override
    public void paintComponent(Graphics g) {

        super.paintComponent(g);
        doDrawing(g);
    }
}

public class GrayScaleImage extends JFrame {

    public GrayScaleImage() {
        
        initUI();
    }

    public final void initUI() {

        Surface dpnl = new Surface();
        add(dpnl);

        pack();
        setTitle("GrayScale Image");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                GrayScaleImage ex = new GrayScaleImage();
                ex.setVisible(true);
            }
        });
    }
}

Creating a grayscale image is very easy in Java 2D.

bufimg = new BufferedImage(d.width, d.height, 
        BufferedImage.TYPE_BYTE_GRAY);

We create a BufferedImage class of type BufferedImage.TYPE_BYTE_GRAY.

Graphics2D g2d = bufimg.createGraphics();
g2d.drawImage(mshi, 1, 1, null);

Here we draw the mushrooms image into the buffered image.

g2d.dispose();      

Graphics objects are disposed by JVM automatically. In some cases, calling the dispose() method will create a more efficient code. According to the documentation, the method should be called only when the graphics objec was created directly from a component or another Graphics object. Graphics objects which are provided as arguments to the paint() and update() methods of components are automatically released by the system when those methods return.

private void doDrawing(Graphics g) {

    Graphics2D g2d = (Graphics2D) g;
    g2d.drawImage(bufimg, null, 2, 2);
}

The buffered image is drawn on the panel.

Flipped image

Next we are going to flip an image. We are going to filter the image. There is a filter() method, which is transforming images.

FlippedImage.java
package com.zetcode;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

class Surface extends JPanel {

    private Image mshi;
    private BufferedImage bufimg;
    private final int SPACE = 10;

    public Surface() {

        loadImage();
        createFlippedImage();
        setSurfaceSize();
    }

    private void loadImage() {

        mshi = new ImageIcon("mushrooms.jpg").getImage();
    }

    private void createFlippedImage() {
        
        bufimg = new BufferedImage(mshi.getWidth(null),
                mshi.getHeight(null), BufferedImage.TYPE_INT_RGB);
        
        Graphics gb = bufimg.getGraphics();
        gb.drawImage(mshi, 0, 0, null);
        gb.dispose();

        AffineTransform tx = AffineTransform.getScaleInstance(-1, 1);
        tx.translate(-mshi.getWidth(null), 0);
        AffineTransformOp op = new AffineTransformOp(tx,
                AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
        bufimg = op.filter(bufimg, null);        
    }
    
    private void setSurfaceSize() {
                
        int w = bufimg.getWidth();
        int h = bufimg.getHeight();
        
        Dimension d = new Dimension(3*SPACE+2*w, h+2*SPACE);
        
        setPreferredSize(d);
    }

    private void doDrawing(Graphics g) {

        Graphics2D g2d = (Graphics2D) g;

        g2d.drawImage(mshi, SPACE, SPACE, null);
        g2d.drawImage(bufimg, null, 2*SPACE+bufimg.getWidth(), SPACE);
    }

    @Override
    public void paintComponent(Graphics g) {

        super.paintComponent(g);
        doDrawing(g);
    }
}

public class FlippedImage extends JFrame {

    public FlippedImage() {

        initUI();
    }

    private void initUI() {

        setTitle("Flipped image");

        add(new Surface());
        pack();

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                FlippedImage fi = new FlippedImage();
                fi.setVisible(true);
            }
        });
    }
}

In our code example, we horizontally flip an image.

AffineTransform tx = AffineTransform.getScaleInstance(-1, 1);
tx.translate(-castle.getWidth(null), 0);

Flipping an image means scaling it and translating it. So we do an AffineTransform operation.

AffineTransformOp op = new AffineTransformOp(tx, 
                        AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
bufferedImage = op.filter(bufferedImage, null)

This is one of the filtering operations available. This could be also done by pixel manipulation. But Java 2D provides high level classes, that make it easier to manipulate images. In our case, the AffineTransformOp class performs scaling and translation on the image pixels.

private void doDrawing(Graphics g) {

    Graphics2D g2d = (Graphics2D) g;

    g2d.drawImage(mshi, SPACE, SPACE, null);
    g2d.drawImage(bufimg, null, 2*SPACE+bufimg.getWidth(), SPACE);
}

Both images are painted on the panel.

private void setSurfaceSize() {
            
    int w = bufimg.getWidth();
    int h = bufimg.getHeight();
    
    Dimension d = new Dimension(3*SPACE+2*w, h+2*SPACE);
    
    setPreferredSize(d);
}

We set the preferred size for the panel. We calculate the size so that we can place two images on the panel and put some space between them and the images and the window borders.

Blurred image

The next code example will blur an image. A blur means an unfocused image. To blur an image, we use a convolution operation. It is a mathematical operation which is also used in edge detection or noise elimination. Blur operations can be used in various graphical effects. For example creating speed illusion, showing an unfocused vision of a human being etc.

The blur filter operation replaces each pixel in image with an average of the pixel and its neighbours. Convolutions are per-pixel operations. The same arithmetic is repeated for every pixel in the image. A kernel can be thought of as a two-dimensional grid of numbers that passes over each pixel of an image in sequence, performing calculations along the way. Since images can also be thought of as two-dimensional grids of numbers, applying a kernel to an image can be visualized as a small grid (the kernel) moving across a substantially larger grid (the image). (developer.apple.com)

BlurredImage.java
package com.zetcode;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

class Surface extends JPanel {

    private BufferedImage mshi;
    private BufferedImage databuf;

    public Surface() {

        loadImage();
        createBlurredImage();
        setSurfaceSize();
    }

    private void loadImage() {
        
        try {
            
            mshi = ImageIO.read(new File("mushrooms.jpg"));
        } catch (IOException ex) {
            
            Logger.getLogger(Surface.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    private void createBlurredImage() {
        
        databuf = new BufferedImage(mshi.getWidth(null),
                mshi.getHeight(null),
                BufferedImage.TYPE_INT_BGR);

        Graphics g = databuf.getGraphics();
        g.drawImage(mshi, 455, 255, null);

        float[] blurKernel = {
            1 / 9f, 1 / 9f, 1 / 9f,
            1 / 9f, 1 / 9f, 1 / 9f,
            1 / 9f, 1 / 9f, 1 / 9f
        };

        BufferedImageOp blur = new ConvolveOp(new Kernel(3, 3, blurKernel));
        mshi = blur.filter(mshi, new BufferedImage(mshi.getWidth(),
                mshi.getHeight(), mshi.getType()));
        g.dispose();        
    }
    
    private void setSurfaceSize() {
        
        Dimension d = new Dimension();
        d.width = mshi.getWidth(null);
        d.height = mshi.getHeight(null);
        setPreferredSize(d);        
    }

    private void doDrawing(Graphics g) {

        Graphics2D g2d = (Graphics2D) g;
        g2d.drawImage(mshi, null, 3, 3);
    }

    @Override
    public void paintComponent(Graphics g) {

        super.paintComponent(g);
        doDrawing(g);
    }
}

public class BlurredImage extends JFrame {

    public BlurredImage() {

        setTitle("Blurred image");

        add(new Surface());

        pack();
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                BlurredImage bi = new BlurredImage();
                bi.setVisible(true);
            }
        });
    }
}

In the code example, we load an image from the disk, perform a blur operation on the image and display the result on the window.

private void loadImage() {
    
    try {
        
        mshi = ImageIO.read(new File("mushrooms.jpg"));
    } catch (IOException ex) {
        
        Logger.getLogger(Surface.class.getName()).log(Level.SEVERE, null, ex);
    }
}

Here we use an alternative method of loading an image in Java 2D. We use the read() method of the ImageIO class. This way we directly have a BufferedImage class.

float[] blurKernel = {
    1 / 9f, 1 / 9f, 1 / 9f,
    1 / 9f, 1 / 9f, 1 / 9f,
    1 / 9f, 1 / 9f, 1 / 9f
};

This matrix is called a kernel. The values are weights that are applied to the neighbouring values of the pixel being changed.

BufferedImageOp blur = new ConvolveOp(new Kernel(3, 3, blurKernel));
mshi = blur.filter(mshi, new BufferedImage(mshi.getWidth(),
        mshi.getHeight(), mshi.getType()));

Here we apply the blur filter to the image.

Reflection

In the next example we show a reflected image. This effect makes an illusion as if the image was reflected in water. The following code example was inspired by the code from jhlabs.com.

Reflection.java
package com.zetcode;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

class Surface extends JPanel {

    private BufferedImage image;
    private BufferedImage refImage;
    private int img_w;
    private int img_h;
    private final int SPACE = 30;

    public Surface() {

        loadImage();
        getImageSize();
        createReflectedImage();
    }

    private void loadImage() {

        try {

            image = ImageIO.read(new File("rotunda.jpg"));
        } catch (Exception ex) {

            Logger.getLogger(Surface.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private void getImageSize() {

        img_w = image.getWidth();
        img_h = image.getHeight();
    }

    private void createReflectedImage() {
        
        float opacity = 0.4f;
        float fadeHeight = 0.3f;
        
        refImage = new BufferedImage(img_w, img_h,
                BufferedImage.TYPE_INT_ARGB);        
        Graphics2D rg = refImage.createGraphics();
        rg.drawImage(image, 0, 0, null);
        rg.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_IN));
        rg.setPaint(new GradientPaint(0, img_h * fadeHeight,
                new Color(0.0f, 0.0f, 0.0f, 0.0f), 0, img_h,
                new Color(0.0f, 0.0f, 0.0f, opacity)));

        rg.fillRect(0, 0, img_w, img_h);
        rg.dispose();
    }

    private void doDrawing(Graphics g) {

        Graphics2D g2d = (Graphics2D) g;

        int win_w = getWidth();
        int win_h = getHeight();

        int gap = 20;

        g2d.setPaint(new GradientPaint(0, 0, Color.black, 0, 
                win_h, Color.darkGray));
        g2d.fillRect(0, 0, win_w, win_h);
        g2d.translate((win_w - img_w) / 2, win_h / 2 - img_h);
        g2d.drawImage(image, 0, 0, null);
        g2d.translate(0, 2 * img_h + gap);
        g2d.scale(1, -1);

        g2d.drawImage(refImage, 0, 0, null);
    }

    @Override
    public void paintComponent(Graphics g) {

        super.paintComponent(g);
        doDrawing(g);
    }

    @Override
    public Dimension getPreferredSize() {

        return new Dimension(img_w + 2 * SPACE, 2 * img_h + 3 * SPACE);
    }
}

public class Reflection extends JFrame {

    public Reflection() {

        initUI();
    }

    private void initUI() {

        setTitle("Reflection");

        add(new Surface());

        pack();
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                Reflection bi = new Reflection();
                bi.setVisible(true);
            }
        });
    }
}

In the example, we create an illusion of a reflected image.

refImage = new BufferedImage(img_w, img_h,
        BufferedImage.TYPE_INT_ARGB);        
Graphics2D rg = refImage.createGraphics();
rg.drawImage(image, 0, 0, null);

A copy of a loaded image is created.

rg.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_IN));
rg.setPaint(new GradientPaint(0, img_h * fadeHeight,
        new Color(0.0f, 0.0f, 0.0f, 0.0f), 0, img_h,
        new Color(0.0f, 0.0f, 0.0f, opacity)));

rg.fillRect(0, 0, img_w, img_h);

This is the most important part of the code. We make the second image transparent. But the transparency is not constant. The image gradually fades out. This is achieved with the GradientPaint.

g2d.setPaint(new GradientPaint(0, 0, Color.black, 0, 
        win_h, Color.darkGray));
g2d.fillRect(0, 0, win_w, win_h);

The background of the window is filled with a gradient paint. The paint is a smooth blending from black to dark gray.

g2d.translate((win_w - img_w) / 2, win_h / 2 - img_h);
g2d.drawImage(image, 0, 0, null);

The normal image is moved to the center of the window and drawn.

g2d.translate(0, 2 * imageHeight + gap);
g2d.scale(1, -1);

This code flips the image and translates it below the original image. The translation operation is necessary, because the scaling operation makes the image upside down and translates the image up. To understand what happens, simply take a photograph and place it on the table and flip it.

g2d.drawImage(refImage, 0, 0, null);

The reflected image is drawn.

@Override
public Dimension getPreferredSize() {

    return new Dimension(img_w + 2 * SPACE, 2 * img_h + 3 * SPACE);
}

Another way to set a preferred size for a component is to override the getPreferredSize() method.

Reflection
Figure: Reflection

In this part of the Java2D tutorial we have worked with images.