Grayscale Conversion In javaFX

Introduction 

 
The problem discussed in this post is quite common. In fact, you can find lots and lots of tutorials on it. You can take this post as one more addition to the available resources. The problem that we are going to discuss in this post is "Converting Colored Image To Black and White or to Grayscale".
 
In computer science terminology, color image is known as the RGB image. RGB stands for RED, GREEN, BLUE. Since these are the primary colors and all the other colors are made up of these colors, all the colored images that we click with our digital cameras fall into the category of RGB images.
 
Similarly, the images that are black and white, in computer terminology are known as Grayscale images. Grayscale means having different shades of gray. Basically, the black and white images do not have black and white colors, they have different shades of gray color. Below, I have explained algorithms for converting color images to black and white.
 

Algorithm for Grayscale Conversion
  

There are various algorithms to convert the color image to black and white. Here I am going to discuss two of them. One is naive and not very efficient whereas the second one is much more efficient. But before we begin the algorithm we must understand how does the algorithm work? All grayscale conversion includes these basic steps,
  • Get the red, green and blue value of pixels.
  • Use fancy maths to turn those numbers into a single gray value.
  • Replace the original red, green and blue values into a grayscale value.
Method 1 - Averaging (aka “quick and dirty”)
 
This method is the most boring. Averaging is the most common grayscale method and it works like this
 
GRAY = (RED + GREEN + BLUE) / 3
 
Basically in this algorithm, we calculate the value of gray color as shown above and assign it to red, green and blue values of the pixels. Fast, simple – no wonder this is a go-to method for the rookie programmers. This formula generates a reasonably nice grayscale equivalent, and its simplicity makes it easy to implement and optimize. However, this formula has its own shortcomings. While fast and simple this formula does a poor job of representing shades of gray relative to the way humans perceive luminosity (brightness). For that, we need a bit more complex.
 
Method 2 - Luminescence Method (Correction for human eye)
 
The first method fails because of the fact that cone density is not uniform across colors. Humans perceive green more strongly than red, and red more strongly than blue. This is because much of the natural world appear green, so humans have evolved greater sensitivity to green light. Because humans do not perceive all colors equally, the “average method” of grayscale is inaccurate.
 
In this method instead of treating red, blue and green light equally, a good grayscale conversion will weight each color based on how the human eye perceives it. A common formula found in image processor programs is shown below,
 
Gray = (Red * 0.3 + Green * 0.59 + Blue * 0.11)
 
This method is used in the code to make the color image black and white, or RGB image to Grayscale. The algorithm works in three basic steps as explained above.
 
Below you can see the source code,
  1. /*  
  2.  * To change this license header, choose License Headers in Project Properties.  
  3.  * To change this template file, choose Tools | Templates  
  4.  * and open the template in the editor.  
  5.  */  
  6. package grayscaleconversion;  
  7. import java.io.File;  
  8. import java.net.MalformedURLException;  
  9. import java.util.logging.Level;  
  10. import java.util.logging.Logger;  
  11. import javafx.application.Application;  
  12. import javafx.concurrent.Service;  
  13. import javafx.concurrent.Task;  
  14. import javafx.concurrent.WorkerStateEvent;  
  15. import javafx.event.ActionEvent;  
  16. import javafx.scene.Group;  
  17. import javafx.scene.Scene;  
  18. import javafx.scene.control.Button;  
  19. import javafx.scene.control.ProgressBar;  
  20. import javafx.scene.image.Image;  
  21. import javafx.scene.image.ImageView;  
  22. import javafx.scene.image.PixelReader;  
  23. import javafx.scene.image.PixelWriter;  
  24. import javafx.scene.image.WritableImage;  
  25. import javafx.scene.paint.Color;  
  26. import javafx.scene.text.Font;  
  27. import javafx.stage.FileChooser;  
  28. import javafx.stage.Stage;  
  29. /**  
  30.  *  
  31.  * @author Passionate Coder  
  32.  */  
  33. public class GrayScaleConversion extends Application {  
  34.  private Image image; //used to load the image    
  35.  private ImageView iv = new ImageView(); //ImageView to display the image    
  36.  private Button selectBTN = new Button(); //Button to select the Image in RGB    
  37.  private Button convertBTN = new Button(); //Button to convert the image in Grayscale    
  38.  private PixelReader preader; //Used to read the pixel of the image    
  39.  private PixelWriter pwriter; //Used to write the pixel    
  40.  private File filePath = null//Stores the file path    
  41.  private ProgressBar pbar = new ProgressBar(0); //Shows the progress of the conversion    
  42.  WritableImage wimage;  
  43.  @Override  
  44.  public void start(Stage primaryStage) {  
  45.   //font used in the program    
  46.   Font poorRichard = new Font("Poor Richard"16);  
  47.   //Settings of the UI Interface    
  48.   selectBTN.setText("Select Image");  
  49.   selectBTN.setFont(poorRichard);  
  50.   selectBTN.setLayoutX(20);  
  51.   selectBTN.setLayoutY(450);  
  52.   convertBTN.setText("Convert GrayScale");  
  53.   convertBTN.setFont(poorRichard);  
  54.   convertBTN.setLayoutX(200);  
  55.   convertBTN.setLayoutY(450);  
  56.   iv.setFitHeight(300);  
  57.   iv.setFitWidth(300);  
  58.   iv.setLayoutX(100);  
  59.   iv.setLayoutY(0);  
  60.   pbar.setLayoutX(20);  
  61.   pbar.setLayoutY(320);  
  62.   pbar.setPrefWidth(460);  
  63.   pbar.setVisible(false);  
  64.   //Event filed when the select Image button is pressed    
  65.   //Select the image in the RGB and display it    
  66.   selectBTN.setOnAction((ActionEvent ae) -> {  
  67.    FileChooser fc = new FileChooser();  
  68.    filePath = fc.showOpenDialog(primaryStage);  
  69.    if (filePath != null) {  
  70.     System.out.println();  
  71.     try {  
  72.      image = new Image(filePath.toURI().toURL().toExternalForm());  
  73.      iv.setImage(image);  
  74.     } catch (MalformedURLException ex) {  
  75.      Logger.getLogger(GrayScaleConversion.class.getName()).log(Level.SEVERE, null, ex);  
  76.     }  
  77.    }  
  78.   });  
  79.   //Event fires when the convert button is pressed    
  80.   //Converts the RGB image in the gray scale    
  81.   convertBTN.setOnAction((ActionEvent ae) -> {  
  82.    pbar.progressProperty().unbind();  
  83.    pbar.progressProperty().bind(RGBToGRAYSCALE.progressProperty());  
  84.    pbar.setVisible(true);  
  85.    //Service to convert the RGB into gray scale    
  86.    RGBToGRAYSCALE.restart();  
  87.   });  
  88.   //Event fired when the service succeeded successfully!!!    
  89.   RGBToGRAYSCALE.setOnSucceeded((WorkerStateEvent we) -> {  
  90.    iv.setImage(wimage);  
  91.    pbar.setVisible(false);  
  92.   });  
  93.   Group root = new Group();  
  94.   Scene scene = new Scene(root, 500500);  
  95.   root.getChildren().add(selectBTN);  
  96.   root.getChildren().add(convertBTN);  
  97.   root.getChildren().add(iv);  
  98.   root.getChildren().add(pbar);  
  99.   primaryStage.setTitle("Grayscale Conversion");  
  100.   primaryStage.setScene(scene);  
  101.   primaryStage.setResizable(false);  
  102.   primaryStage.show();  
  103.  }  
  104.  /*Service to convert the RGB into grayscale  
  105.  Copies the image pixel by pixel by converting each  
  106.  pixel into grayscale  
  107.  */  
  108.  Service < Void > RGBToGRAYSCALE = new Service < Void > () {  
  109.   @Override  
  110.   protected Task < Void > createTask() {  
  111.    return new Task < Void > () {  
  112.     @Override  
  113.     protected Void call() throws Exception {  
  114.      image = new Image(filePath.toURI().toURL().toExternalForm());  
  115.      preader = image.getPixelReader();  
  116.      wimage = new WritableImage((int) image.getWidth(), (int) image.getHeight());  
  117.      pwriter = wimage.getPixelWriter();  
  118.      int count = 0;  
  119.      for (int i = 0; i < (int) image.getHeight(); i++) {  
  120.       for (int j = 0; j < (int) image.getWidth(); j++) {  
  121.        count += 1;  
  122.        Color col = preader.getColor(j, i);  
  123.        //Reading each pixel and converting it into gray scale    
  124.        pwriter.setColor(j, i, new Color((col.getRed() * 0.3 + col.getGreen() * 0.59 + col.getBlue() * 0.11), (col.getRed() * 0.3 + col.getGreen() * 0.59 + col.getBlue() * 0.11), (col.getRed() * 0.3 + col.getGreen() * 0.59 + col.getBlue() * 0.11), 1.0));  
  125.        updateProgress(count, image.getHeight() * image.getWidth());  
  126.       }  
  127.      }  
  128.      return null;  
  129.     }  
  130.    };  
  131.   }  
  132.  };  
  133.  /**  
  134.   * @param args the command line arguments  
  135.   */  
  136.  public static void main(String[] args) {  
  137.   launch(args);  
  138.  }  
  139. }   
I hope you like it!