I want to be able to take the data of an image as an array, modify it, and then use that array to create a modified image. Here is what I attempted:
public class Blue { public static void main (String [] args) throws AWTException, IOException { Robot robot = new Robot (); Dimension d = Toolkit.getDefaultToolkit().getScreenSize(); BufferedImage img = robot.createScreenCapture(new Rectangle(0,0,d.width,d.height)); int[] pixels = ((DataBufferInt)img.getRaster().getDataBuffer()).getData(); int[] newPixels = IntStream.range(0,pixels.length).parallel().filter(i ->{ int p = pixels[i]; // get red int r = (p >> 16) & 0xff; // get green int g = (p >> 8) & 0xff; // get blue int b = p & 0xff; return b >= 200; }).toArray(); int[] output = new int[pixels.length]; for(int i = 0; i<newPixels.length; i++) { output[newPixels[i]] = 0x0000FF; } File f = new File("Result.jpg"); ByteBuffer byteBuffer = ByteBuffer.allocate(output.length * 4); for (int i = 0; i< output.length; i++) { byteBuffer.putInt(output[i]); } byte[] array = byteBuffer.array(); InputStream stream = new ByteArrayInputStream(array); BufferedImage image1 = ImageIO.read(stream); System.out.println(image1.getWidth()); ImageIO.write(image1, "png", f); } }
Here is how it works.
- The robot takes a screen capture of the screen, which is then stored into a BufferedImage.
- The data of the image is stored in an integer array
- An int stream is used to select all pixel locations that correspond to sufficiently blue pixels
- These blue pixels are placed in an array called output at the same locations they were taken from. However, the rest of the array has value 0.
- A destination file for my modified image is created
- I create a byte buffer that is 4 times the length of the output array, and data from the output array is placed in it.
- I create a byte array from the buffer then create an input stream with it
- Finally, I read the stream to create an Image from it
- I use System.out.println() to print some data from the image to see if the image exists.
Step 9 is where the problem shows up. I keep getting a NullPointerException, meaning that the image doesn’t exist, it is null. I don’t understand what I did wrong. I tried using ByteArrayInputStream instead of InputStream, but that doesn’t work as well. Then, I thought that maybe the first couple of bytes encode the coding information for the image, so I tried copying that over to the output array, but that didn’t solve the problem either. I am not sure why my byte array isn’t turning into an image.
Advertisement
Answer
Yo summarize the comments in an answer, the problem is that you have an array of “raw” pixels, and try to pass that to ImageIO.read()
. ImageIO.read()
reads images stored in a defined file format, like PNG, JPEG or TIFF (while the pixel array is just pixels, it does not contain information on image dimension, color model, compression etc.). If no plugin is found for the input, the method will return null
(thus the NullPointerException
).
To create a BufferedImage
from the pixel array, you could create a raster around the array, pick a suitable color model and create a new BufferedImage
using the constructor taking a Raster
and ColorModel
parameter. You can see how to do that in one of my other answers.
However, as you already have a BufferedImage
and access to its pixels, it’s much easier (and cheaper CPU/memory wise) to just reuse that.
You can replace your code with the following (see comments for details and relation to your steps):
public class Blue { public static void main (String [] args) throws AWTException, IOException { // 1. Create screen capture Robot robot = new Robot (); Dimension d = Toolkit.getDefaultToolkit().getScreenSize(); BufferedImage img = robot.createScreenCapture(new Rectangle(0, 0, d.width, d.height)); // 2: Get backing array int[] pixels = ((DataBufferInt) img.getRaster().getDataBuffer()).getData(); // 3: Find all "sufficiently blue" pixels int[] bluePixels = IntStream.range(0, pixels.length).parallel() .filter(i -> pixels[i] & 0xff >= 200).toArray(); // 4a: Clear all pixels to opaque black for (int i = 0; i < pixels.length; i++) { pixels[i] = 0xFF000000; } // 4b: Set all blue pixels to opaque blue for (int i = 0; i < bluePixels.length; i++) { pixels[bluePixels[i]] = 0xFF0000FF; } // 5: Make sure the file extension matches the file format for less confusion... 😀 File f = new File("result.png"); // 9: Print & write image (steps 6-8 is not needed) System.out.println(img); ImageIO.write(img, "png", f); } }