Nowadays, most personal computers, workstations and servers are sold equipped with a multiple core architecture. In order to explore the benefits of these architectures, one technique is to divide the application into multiple threads, thus each one can run in a different core. In the case of image processing, each thread can handle a different image or a different segment of the same image.

In this tutorial is shown how to process a image using multiple threads. Novertheless, the principles are the same in the case of processing multiple images using different threads for them.

MarvinThread

MarvinThread provides a transparent way to use Java threads and Marvin plug-ins to process images. The MarvinThread constructor is presented as follow:

public MarvinThread(MarvinImagePlugin plg, MarvinImage imgIn, MarvinImage imgOut, MarvinImageMask mask)

plg is the plug-in used to process the image, imgIn is the input image, imgOut is the output image and mask contains the indexes of the pixels that must be processed. In the case of using a thread to process the entire image, this mask must be MarvinImageMask.NULL_MASK. On the other hand, when different threads process a different segment of the same image, the mask of each MarvinThread must contain the pixels of the image segment destined for each thread.

In order to listen MarvinThread events is needed to implement the MarvinThreadListener interface. This interface specifies the method threadFinished(MarvinThreadEvent e). Implementing this method an application can know when a thread has finished its job.

Example

To demonstrate the presented concepts in practice, lets start an example by its skeleton, as shown in Code 1.
import java.awt.BorderLayout; 
import java.awt.Container; 
import java.awt.event.ActionEvent; 
import java.awt.event.ActionListener; 

import javax.swing.JButton; 
import javax.swing.JFrame; 
import javax.swing.JLabel; 
import javax.swing.JPanel; 

import marvin.gui.MarvinImagePanel; 
import marvin.image.MarvinImage; 
import marvin.image.MarvinImageMask; 
import marvin.io.MarvinImageIO; 
import marvin.plugin.MarvinImagePlugin; 
import marvin.thread.MarvinThread; 
import marvin.thread.MarvinThreadEvent; 
import marvin.thread.MarvinThreadListener; 
import marvin.util.MarvinPluginLoader; 

/** 
 * Processing images single and multi threaded. 
 * @author Gabriel Ambrósio Archanjo 
 */ 
public class Multithread extends JFrame implements MarvinThreadListener{
     
    // Interface Components 
    private JButton                buttonSingleThread, 
                                buttonMultiThread, 
                                buttonResetImage; 
     
    private JLabel                labelPerformance; 
     
    // MarvinImagePanel to display the image 
    private MarvinImagePanel     imagePanel; 
     
    // MarvinImage objects used by the app 
    private MarvinImage         imageIn, imageOut, originalImage;
     
    private int                    threadsFinished;
    private long                processStartTime;
     
    /** 
     * Create the user interface and load the images. 
     */ 
    public Multithread(){ 
        super("Multithread Sample");
         
        // Buttons 
        ButtonHandler buttonHandler = new ButtonHandler(); 
        buttonSingleThread = new JButton("Single Thread");
        buttonMultiThread = new JButton("Multi Thread");
        buttonResetImage = new JButton("Reset Image");
         
        buttonSingleThread.addActionListener(buttonHandler); 
        buttonMultiThread.addActionListener(buttonHandler); 
        buttonResetImage.addActionListener(buttonHandler); 
         
        // Label 
        labelPerformance = new JLabel("Performance:");
         
        // Panels 
        JPanel panelIntern = new JPanel(); 
        panelIntern.add(buttonSingleThread); 
        panelIntern.add(buttonMultiThread); 
        panelIntern.add(buttonResetImage); 
         
        JPanel panelBottom = new JPanel(); 
        panelBottom.setLayout(new BorderLayout()); 
         
        panelBottom.add(panelIntern, BorderLayout.NORTH); 
        panelBottom.add(labelPerformance, BorderLayout.SOUTH); 
         
        imagePanel = new MarvinImagePanel(); 
                 
        // Container 
        Container con = getContentPane(); 
        con.setLayout(new BorderLayout()); 
        con.add(imagePanel, BorderLayout.NORTH); 
        con.add(panelBottom, BorderLayout.SOUTH); 
         
        // Load Image 
        loadImages(); 
         
        setSize(originalImage.getWidth()+20,690);
        setVisible(true); 
    } 
     
    /** 
     *     Load the image processed by the application. Create other two images (imageIn and imageOut)
     *    to be the input and output images. The image panel is set to display the original image.
     */ 
    private void loadImages(){
        originalImage = MarvinImageIO.loadImage("./res/senna.jpg");
        imageIn = new MarvinImage(originalImage.getWidth(), originalImage.getHeight());
        imageOut = new MarvinImage(originalImage.getWidth(), originalImage.getHeight());
        imagePanel.setImage(originalImage); 
    } 
         
    public void threadFinished(MarvinThreadEvent e){
    } 
     
    public static void main(String args[]){
        Multithread t = new Multithread(); 
        t.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
    } 
     
    private void singleThread(){    
    } 
     
    private void multiThread(){
    } 
     
    private class ButtonHandler implements ActionListener{
        public void actionPerformed(ActionEvent e){
            imageIn = originalImage.clone(); 
            if(e.getSource() == buttonSingleThread){ 
                singleThread(); 
            } 
            else if(e.getSource() == buttonMultiThread){
                multiThread(); 
            } 
            else if(e.getSource() == buttonResetImage){
                imagePanel.setImage(originalImage); 
            } 
        } 
    } 
}
Code 1. Application skeleton.

This source code only loads the target image and creates the user interface, shown in the image below.


Figure 1. Screenshot of the application skeleton shown in the Code 1.


The idea is when the user clicks on the button "Single Thread" the application apply a plug-in to the image using only one thread. On the other hand, when the user clicks on the button "Multi Thread", the application uses multiple threads. The time spent by these two methods must be shown in a label. First, letīs start implementing the single threaded method.

private void singleThread(){
    processStartTime = System.currentTimeMillis(); 
    MarvinImagePlugin plgImage = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.statistical.Maximum.jar");
    plgImage.process(imageIn, imageOut);
    imageOut.update(); 
    imagePanel.setImage(imageOut); 
    labelPerformance.setText("Performance: "+ (System.currentTimeMillis()-processStartTime)+ " milliseconds (Single Thread)"); 
    repaint();         
}


Now letīs implement the multithreaded method. For this, two plug-ins are loaded and two masks are created, one responsible for the top half of the image and the other for the lowe half. Two MarvinThreads are created too, each one receiving a reference to the same image, but with a different plug-in and mask. In this fashion, each MarvinThread uses a different plug-in to process the same image in a different region.

    private void multiThread(){
        processStartTime = System.currentTimeMillis(); 
         
        // Load Plug-ins 
        MarvinImagePlugin plgImage1 = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.statistical.Maximum.jar");
        MarvinImagePlugin plgImage2 = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.statistical.Maximum.jar");
         
        // Create masks 
        MarvinImageMask mask1 = new MarvinImageMask 
        ( 
            imageIn.getWidth(),            // width 
            imageIn.getHeight(),            // height 
            0,                    // x-start 
            0,                    // y-start 
            imageIn.getWidth(),            // regionīs width 
            imageIn.getHeight()/2            // regionīs height 
        ); 
         
        MarvinImageMask mask2 = new MarvinImageMask 
        ( 
            imageIn.getWidth(),            // width 
            imageIn.getHeight(),            // height 
            0,                    // x-start 
            imageIn.getHeight()/2,                // y-start 
            imageIn.getWidth(),            // regionīs width 
            imageIn.getHeight()/2            // regionīs height 
        ); 
         
         
        MarvinThread mvThread1 = new MarvinThread(plgImage1, imageIn, imageOut, mask1);
        MarvinThread mvThread2 = new MarvinThread(plgImage2, imageIn, imageOut, mask2);
         
        mvThread1.addThreadListener(this); 
        mvThread2.addThreadListener(this); 
         
        mvThread1.start(); 
        mvThread2.start(); 
         
        threadsFinished = 0; 
    }


Finally, we have the implementation of the method responsible to listen MarvinThread events. Everytime a MarvinThread fishishes its job, a MarvinThreadEvent is dispatched. In our example, the method threadFinished(...) will be invoked two times, one time after each thread finished its job. Therefore, in the second invokation, the application updates the image and show the spent time. The implementation of the method threadFinished(...) is presented below.

public void threadFinished(MarvinThreadEvent e){
    threadsFinished++;         
    if(threadsFinished == 2){
        imageOut.update(); 
        imagePanel.setImage(imageOut); 
        labelPerformance.setText("Performance: "+ (System.currentTimeMillis()-processStartTime)+ " milliseconds (Multi Thread)"); 
        repaint(); 
    } 
}


The final source code you can check out here: Multithread.java