Table of Contents
1. Introduction
This article is going to cover how to use Java to create grayscale images from their colour counter-parts. When I started looking into this topic I did a quick Google search and found many different answers using many different libraries with considerably different outputs. So I decided to put together this post describing the outcome of my Quest For the Holy Gray.
2. Getting Started on Using Java to Create Grayscale Images
For the purposes of this article I’m going to use the same method signatures I used in my code, so I will receive the image as a byte array and return the image as a byte array. You can easily change this to accept a BufferedImage and return one too. The code will also convert the image mime type to PNG but you could use any converter you like (e.g. bmp, jpg, png or gif).
Below is the base line code without any of the gray goodies yet:
public byte[] convertToGrayAndPng( byte[] imageBytes ) throws IOException { // Turn the byte array into a BufferedImage InputStream in = new ByteArrayInputStream( imageBytes ); BufferedImage sourceImg = ImageIO.read(in); // We'll be doing some gray scale magic here soon... // Write the icon as a PNG ByteArrayOutputStream out = new ByteArrayOutputStream(); ImageIO.write( image, "png", out ); // Return the grayscale PNG as a byte array return out.toByteArray(); }
Now onto the different options.
3. Option 1
The first option is to use the ColorConvertOp class which will perform a pixel-by-pixel color conversion of the source image using the gray color space.
BufferedImageOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null); BufferedImage image = op.filter(sourceImg, null);
The results of this option were the following:
Source | Output | Execution Time |
---|---|---|
158ms |
4. Option 2
The second option is to use the GrayFilter on the source image and then draw it into a new BufferedImage. Notice that there is a second parameter to the GrayFilter, this is a number between 0 and 100 which defines the percentage of gray to use (100 being the darkest and 0 the lightest). The results table below contains the output of using 80 (as below) and using 50.
ImageFilter filter = new GrayFilter( true, 80 ); ImageProducer producer = new FilteredImageSource( sourceImg.getSource(), filter ); Image grayImg = Toolkit.getDefaultToolkit().createImage( producer ); BufferedImage image = new BufferedImage( sourceImg.getWidth(), sourceImg.getHeight(), BufferedImage.TYPE_INT_ARGB ); Graphics g = image.getGraphics(); g.drawImage( grayImg, 0, 0, null ); g.dispose();
The results of this option were the following:
Source | Output | Execution Time | % of Gray |
---|---|---|---|
828ms | 80 | ||
827ms | 50 |
5. Option 3
The third option is a variation of option 2 which makes use of the createDisabledImage() utility method. Notice that there is a huge improvement in execution time compared to option 2.
Image grayImg = GrayFilter.createDisabledImage( sourceImg ); BufferedImage image = new BufferedImage( sourceImg.getWidth(), sourceImg.getHeight(), BufferedImage.TYPE_INT_ARGB ); Graphics g = image.getGraphics(); g.drawImage( grayImg, 0, 0, null ); g.dispose();
The results of this option were the following:
Source | Output | Execution Time |
---|---|---|
221ms |
6. Option 4
The fourth option is to write the source image into a new BufferedImage which is initialised with a TYPE_BYTE_GRAY image type. This is incredibly fast but unfortunately doesn’t maintain the transparent background of the source image. If anyone knows how to change that behaviour I would love to hear about it in the comments.
BufferedImage image = new BufferedImage( sourceImg.getWidth(), sourceImg.getHeight(), BufferedImage.TYPE_BYTE_GRAY ); Graphics g = image.getGraphics(); g.drawImage( sourceImg, 0, 0, null ); g.dispose();
The results of this option were the following:
Source | Output | Execution Time |
---|---|---|
14ms |
7. Option 5
The fifth option is to once again write the source image into a new BufferedImage, except this time we use the ColorConvertOp to do that. Once again, the performance is very good but the transparency is lost.
BufferedImage image = new BufferedImage( sourceImg.getWidth(), sourceImg.getHeight(), BufferedImage.TYPE_BYTE_GRAY ); ColorConvertOp op = new ColorConvertOp( sourceImg.getColorModel().getColorSpace(), image.getColorModel().getColorSpace(), null ); op.filter( sourceImg, image );
The results of this option were the following:
Source | Output | Execution Time |
---|---|---|
17ms |
8. Option 6
The sixth option is to write the source image into a new BufferedImage which uses a TYPE_4BYTE_ABGR_PRE image type. This image is then passed through a ColorConvertOp filter using the CS_GRAY colour space as the source colour space.
Integer width = sourceImg.getWidth(); Integer height = sourceImg.getHeight(); BufferedImage image = new BufferedImage( width, height, BufferedImage.TYPE_4BYTE_ABGR_PRE ); image.createGraphics().drawImage( sourceImg, 0, 0, width, height, null ); ColorSpace grayColorSpace = ColorSpace.getInstance( ColorSpace.CS_GRAY ); ColorConvertOp op = new ColorConvertOp( grayColorSpace, image.getColorModel().getColorSpace(), null ); op.filter( image, image );
The results of this option were the following:
Source | Output | Execution Time |
---|---|---|
531ms |
9. Conclusions
So we used many different methods to try and achieve the same goal. Overall the best quality gray scale image was the one from Option 6, however this was by no means the fastest. I couldn’t find anything on avoiding the transparent to black issue, otherwise I would have probably opted for Option 4. To summarise I’ve put all the options in a single table so that you can compare and decide which is the best option for you:
Option | Source | Output | Execution Time | % of Gray |
---|---|---|---|---|
1 | 158ms | N/A | ||
2a | 828ms | 80 | ||
2b | 827ms | 50 | ||
3 | 221ms | N/A | ||
4 | 14ms | N/A | ||
5 | 17ms | N/A | ||
6 | 531ms | N/A |
- Spring Security Tutorial: Form Login Java Configuration - September 27, 2014
- Spring Security Tutorial: 3-Legged OAuth 1.0 - June 9, 2014
- Spring Security Tutorial: 2-Legged OAuth 1.0 - June 3, 2014