Blur an image using java.util.concurrent, however, the resulting image is entirely black
Asked Answered
T

1

6

I'm new to Java and is trying to learn the concept of high level concurrency. I saw this code at Java Tutorial Oracle. However, when I run the code, the IDE output an image that is entire black. Why is this happening? And also, how is the compute() method called?

import java.awt.image.BufferedImage;
import java.io.File;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import javax.imageio.ImageIO;

/**
 * ForkBlur implements a simple horizontal image blur. It averages pixels in the
 * source array and writes them to a destination array. The sThreshold value
 * determines whether the blurring will be performed directly or split into two
 * tasks.
 *
 * This is not the recommended way to blur images; it is only intended to
 * illustrate the use of the Fork/Join framework.
 */
public class ForkBlur extends RecursiveAction {

    private int[] mSource;
    private int mStart;
    private int mLength;
    private int[] mDestination;
    private int mBlurWidth = 15; // Processing window size, should be odd.

    public ForkBlur(int[] src, int start, int length, int[] dst) {
        mSource = src;
        mStart = start;
        mLength = length;
        mDestination = dst;
    }

    // Average pixels from source, write results into destination.
    protected void computeDirectly() {
        int sidePixels = (mBlurWidth - 1) / 2;
        for (int index = mStart; index < mStart + mLength; index++) {
            // Calculate average.
            float rt = 0, gt = 0, bt = 0;
            for (int mi = -sidePixels; mi <= sidePixels; mi++) {
                int mindex = Math.min(Math.max(mi + index, 0), mSource.length - 1);
                int pixel = mSource[mindex];
                rt += (float) ((pixel & 0x00ff0000) >> 16) / mBlurWidth;
                gt += (float) ((pixel & 0x0000ff00) >> 8) / mBlurWidth;
                bt += (float) ((pixel & 0x000000ff) >> 0) / mBlurWidth;
            }

            // Re-assemble destination pixel.
            int dpixel = (0xff000000)
                    | (((int) rt) << 16)
                    | (((int) gt) << 8)
                    | (((int) bt) << 0);
            mDestination[index] = dpixel;
        }
    }
    protected static int sThreshold = 10000;

    @Override
    protected void compute() {
        if (mLength < sThreshold) {
            computeDirectly();
            return;
        }

        int split = mLength / 2;

        invokeAll(new ForkBlur(mSource, mStart, split, mDestination),
                new ForkBlur(mSource, mStart + split, mLength - split, 
                mDestination));
    }

    // Plumbing follows.
    public static void main(String[] args) throws Exception {
        String srcName = "/Users/justin/NetBeansProjects/JavaTutorialOracle/src/JTOConcurrency/Screen Shot 2016-02-19 at 10.30.51 AM.jpg";
        File srcFile = new File(srcName);
        BufferedImage image = ImageIO.read(srcFile);

        System.out.println("Source image: " + srcName);

        BufferedImage blurredImage = blur(image);

        String dstName = "blurred-tulips.jpg";
        File dstFile = new File(dstName);
        ImageIO.write(blurredImage, "jpg", dstFile);

        System.out.println("Output image: " + dstName);

    }

    public static BufferedImage blur(BufferedImage srcImage) {
        int w = srcImage.getWidth();
        System.out.println("w: " + w);
        int h = srcImage.getHeight();
        System.out.println("h: " + h);

        int[] src = srcImage.getRGB(0, 0, w, h, null, 0, w);
        System.out.println("src[0]" + src[0]);
        System.out.println("src[src.length - 1]: " + src[src.length - 1]);
        int[] dst = new int[src.length];
        System.out.println("src.length: " + src.length);

        System.out.println("Array size is " + src.length);
        System.out.println("Threshold is " + sThreshold);

        int processors = Runtime.getRuntime().availableProcessors();
        System.out.println(Integer.toString(processors) + " processor"
                + (processors != 1 ? "s are " : " is ")
                + "available");

        ForkBlur fb = new ForkBlur(src, 0, src.length, dst);

        ForkJoinPool pool = new ForkJoinPool();

        long startTime = System.currentTimeMillis();
        pool.invoke(fb);

        long endTime = System.currentTimeMillis();

        System.out.println("Image blur took " + (endTime - startTime) + 
                " milliseconds.");

        BufferedImage dstImage =
                new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
        dstImage.setRGB(0, 0, w, h, dst, 0, w);

        return dstImage;
    }
}

Output:

Source image: /Users/justin/NetBeansProjects/JavaTutorialOracle/src/JTOConcurrency/Screen Shot 2016-02-19 at 10.30.51 AM.jpg
w: 454
h: 679
src[0]-5945082
src[src.length - 1]: -9673172
src.length: 308266
Array size is 308266
Threshold is 10000
4 processors are available
Image blur took 53 milliseconds.
Output image: blurred-tulips.jpg

I have attached some information about the photo I am using.

enter image description here

Toiletry answered 22/2, 2016 at 0:42 Comment(13)
Seems to work okay for mePandect
@Pandect do you know where in the code is the method "compute" invoked? I dont see any place where it is invoked. Sorry if my question sounds silly, I'm a beginner, learning my way.Toiletry
It's not invoked directly, but is invoked by the ForkJoinPoolPandect
how can ForkJoinPool invoke the "compute" method without directly calling it? Could you please explain it in a bit more detail? Thank you so muchToiletry
It's not invoked directly by anything in your code, it's invoked (indirectly) by ForkJoinPool itself (on your behavle), meaning, you don't need to do anything (so it's indirect)Pandect
@dzjustinli I believe that method is required to subclass RecursiveAction.Riker
What is the image you are using?Interspace
@Interspace just a screen shot of a tulip I got from the internet, its extension is jpg.Toiletry
Your specific image must be bad. Try to get what I got.Interspace
@Interspace I saved your image, used it and the result is still a black image. I dont get it, why does it seem to work on everyone else's computer =(Toiletry
Try to run is on another computer if you have one at hand, at work, or at a neighbor's. It could be something with the OS or JDK. I tried on Win7 with 1.8.0_25.Interspace
@Interspace Thanks for helping out! really appreciate it!Toiletry
It's a worthy question to get into. You should feel good since the problem is not in your code :)Interspace
J
7

Using Oracle JRE 1.7.0_71 on OS X 10.11, I can reproduce the issue with a completely black output image.

However, there's no bug in your code, as I can see that the dst array contains the expected values after the fork/join blur operation.

Instead, the problem is our old friend, the ImageIO JPEGImageWriter. It does not write ARGB data as per the JPEG conventions, thus other software will misinterpret the colors*.

Simply change the line:

BufferedImage dstImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);

to:

BufferedImage dstImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); // No alpha here

This will create an image without alpha. The JPEGImageWriter will just do the right thing, and the final output image looks as expected.

Another option, using a different file format, like PNG, also fixes the issue. I.e.:

ImageIO.write(blurredImage, "PNG", dstFile);

*) You can see this better if you use 0x00 instead of 0xff for alpha in the computeDirectly(...) method. You'll see an image with weird, probably pink/blueish colors.

Jewish answered 24/2, 2016 at 8:39 Comment(3)
Thank you so so much!! That solves my problem!! I will give you the +50 bounty as soon as Stackoverflow allows me (in 14 hours time). btw do you mind if I ask you how you came up with the solution? It would great if i can learn how you tried to solve the problem so next time when I encounter a problem I can have a better idea myself. Thanks again!!Toiletry
@TonyStark I started by verifying that the values in dst were actually as expected (not all black), by printing them to the console. Then I happen to know from experience that JPEG and alpha channel is trouble, so I tried using just RGB. You made it quite easy to make these small changes, by providing a sample program to reproduce the issue. If only more people did that... :-)Jewish
Thanks again for your help!!Toiletry

© 2022 - 2024 — McMap. All rights reserved.