4

I have been working on a nonogram solver in Java, and all of my algorithm works fine, but I have been struggling with the visualization a little bit.

During the execution of the algorithm, I have access to two "solution-arrays". One is of type int[][], and contains values -1 for "certainly white", 0 for "uncertain" and 1 for "certainly black". Another array is of type float[][] which contains values between 0 and 1, here 0 is for certainly white, 1 is for certainly black and a value of .2 indicates it is more likely for the cell to be white than it to be black.

Since I've recently switched from PHP to Java programming (without proper introduction), I don't know much about visualizing this array properly. Of course I have first tried to simply print the first type of array to the console with characters like X, . and ?, but this is far from nice. I then found something about BufferedImage's, and I created the following function (for the float[][], the int[][] is similar):

public void toImage(int w, int h, float[][] solution) throws IOException {
    int[] data = toImage1(w, h, solution);
    BufferedImage img = toImage2(data, w, h);
    toImage3(img);
}

public int[] toImage1(int w, int h, float[][] solution) throws IOException {
    int[] data = new int[w * h];
    int i = 0;
    for (int y = 0; y < h; y++) {
        for (int x = 0; x < w; x++) {
            int a = y / 100;
            int b = x / 100;
            int z = (int) (255 * Math.sqrt(1 - solution[a][b]));
            if (solution[a][b] == 1 && ((((y % 100 >= 10 && y % 100 <= 15) || (y % 100 >= 85 && y % 100 <= 90)) && x % 100 >= 10 && x % 100 <= 90) || (((x % 100 >= 10 && x % 100 <= 15) || (x % 100 >= 85 && x % 100 <= 90)) && y % 100 >= 10 && y % 100 <= 90))) {
                z = 100;
            } else if (solution[a][b] == 0 && ((((y % 100 >= 10 && y % 100 <= 15) || (y % 100 >= 85 && y % 100 <= 90)) && x % 100 >= 10 && x % 100 <= 90) || (((x % 100 >= 10 && x % 100 <= 15) || (x % 100 >= 85 && x % 100 <= 90)) && y % 100 >= 10 && y % 100 <= 90))) {
                z = 230;
            }
            data[i++] = z << 16 | z << 8 | z;
        }
    }
    return data;
}

public BufferedImage toImage2(int[] data, int w, int h) {
    BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_BYTE_GRAY);
    img.setRGB(0, 0, w, h, data, 0, w);
    return img;
}

public void toImage3(BufferedImage img) throws IOException {
    File f = new File("Nonogram.png");
    ImageIO.write(img, "PNG", f);
}

Here, w and h are supposed to be the amount of cells in each column resp. row multiplied by 100 (I want each cell to be represented by a block of 100x100 pixels). I then also want an extra gray box in cells that are certain, that's what the if and else if are for.

This works beautifully, and creates images like this: nonogram

However, I run into two problems:

  1. This function is super slow. After profiling the execution, I see 90% of the execution time goes to this function. After breaking down my function into two bits, as suggested in the comments, I get the following profile:profile
  2. Instead of writing to a .png file, I would like a JFrame to display my image, but (again since I missed my proper introduction to Java), JFrame's don't seem to be my best friends and I can't seem to work out how to use them.

Is there a possibility to fill entire 100x100 px cells at once? Is there a way to not have to create the entire BufferedImage each time over, but just modify the bits that have changed in another method? Should I use something else than BufferedImage? What elements does my code need, could someone code out an example method, or the needed code snippets?

durron597
  • 31,968
  • 17
  • 99
  • 158
konewka
  • 620
  • 8
  • 21
  • What you probably want to do is create a `JPanel` inside of your `JFrame`, and add this `BufferedImage` to the `JPanel`. – NoseKnowsAll Aug 27 '15 at 19:52
  • 1
    **Simplify these `if` queries!!!**. Computing `y % 100` and `x % 100` *dozens* of times is just ugly (and slow, although this may not affect the overall performance sooo much after the JIT has kicked in) – Marco13 Aug 27 '15 at 20:43

1 Answers1

2

Okay, so it looks like writing to a file is not actually your biggest problem, it looks like your biggest problem is that you're dumping the pixels individually. Here are some things I might do:

  1. Make a smaller image. 100x100 is a lot. Why not 20x20? You can always zoom in with an image editor.
  2. Skip the int[] step entirely, and write to the BufferedImage directly.
    • Use bufferedImage.setRGB(startX, startY, w, h, rgbArray, offset, scansize) as you've been doing, but only for the section of the image you're drawing in bulk.
  3. Do everything based on the values of a and b (as opposed to w and h, including and especially the loops, see point 4.
  4. Fill the the boxes completely solidly, then add the inner rectangle by overwriting those lines separately. All these complicated if checks are killing your performance. Do it branchless (no if statements), and it will run much faster.
  5. Put the code that's inside the for loops into a separate method that makes it more clear. Call it something like drawSingleBox.

Remember, more methods with good names make it easier to follow what's going on. writeImageToFile is preferred over toImage3. convertArrayToImage is preferred over toImage2.

Also, you asked about how to put an image as the background of a JFrame; once you have your fully drawn BufferedImage object, you can use the information in JFrame background image to do that part of it.

Community
  • 1
  • 1
durron597
  • 31,968
  • 17
  • 99
  • 158