0
\$\begingroup\$

I'm making a simple game engine using Java and have found a major issue. To create accurate collision detection I decided to convert a transparent image into a java.awt.geom.Area object. However the method I'm doing this might be wrong or unoptimized.

To test it out, I tried loading an almost 200x200 (exactly 195x196) pixels image. I ran the program 5 times and this is the time it took

  • 2900ms = 2s
  • 2848ms = 2s
  • 2999ms = 2s
  • 2887ms = 2s
  • 2871ms = 2s

Average time: 2,901ms
This isn't very good especially considering the size of the image.

Here is the code I am using:

public Entity(BufferedImage image) {
    this.image = image;

    entityArea = new Area();
    heightmap = new int[image.getWidth()];
        
    boolean heightFound = false;

    for (int x = 0; x < image.getWidth(); x++) {

        for (int y = 0; y < image.getHeight(); y++) {
                
            if((image.getRGB(x, y) & 0xFF000000) != 0) {
                entityArea.add(new Area(new Rectangle(x, y, 1, 1)));

                if(!heightFound) {
                    heightmap[x] = y;
                    heightFound = true;
                }
            }

        }

    }
}

heightmap is used to refer the top pixels for an entity

\$\endgroup\$
1
  • 1
    \$\begingroup\$ You may want to consider adding areas for spans of vertically connected pixels, rather than one per pixel. For an image that's mostly a solid shape, this reduces the number of Add(new Area...) calls from O(w * h) to O(w) \$\endgroup\$
    – DMGregory
    Mar 24 at 21:28

3 Answers 3

1
\$\begingroup\$

Using the java.awt packages will likely be a problem when it comes to performance.

Even if you ignore the reading of the image (which I doubt is the bottleneck here), trying to add 200 * 200 = 40,000 Areas into a single Area will run slow.

This cut down example with high-light the performance problem (note that no image is used here);

    long start = System.currentTimeMillis();
    Area entityArea = new Area();
    for(int y = 0; y < 200; ++y) {
        for (int x = 0; x < 200; ++x) {
            entityArea.add(new Area(new Rectangle(x, y, 1, 1)));
        }
    }
    System.out.println(System.currentTimeMillis() - start);

For game-related code you are likely better off moving away from the java.awt packages and go for something optimized for gaming.

\$\endgroup\$
1
  • \$\begingroup\$ Thank you, however could you also suggest some alternatives to Area? I have a plan in my head, to store the transparency in a transparency map, and use the X and Y and check if they are colliding. It sounds really confusing and I have no idea how to explain it :D \$\endgroup\$ Mar 25 at 9:53
0
\$\begingroup\$

.getRGB() as a per pixel operation is slow. It can be okay if you're only sampling a few pixels, but looping over it for processing all the pixels in an image is significant performance bottleneck.

Instead of working directly on the BufferedImage, convert the image to an array and work with that:

byte[] pixels = ((DataBufferByte)image.getRaster().getDataBuffer()).getData();

Or consider wrapping the conversion in a helper class as shown here:

import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;

public class FastRGB
{
    private int width;
    private int height;
    private boolean hasAlphaChannel;
    private int pixelLength;
    private byte[] pixels;

    FastRGB(BufferedImage image)
    {
        pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
        width = image.getWidth();
        height = image.getHeight();
        hasAlphaChannel = image.getAlphaRaster() != null;
        pixelLength = 3;
        if (hasAlphaChannel)
        {
            pixelLength = 4;
        }

    }

    int getRGB(int x, int y)
    {
        int pos = (y * pixelLength * width) + (x * pixelLength);
        int argb = -16777216; // 255 alpha
        if (hasAlphaChannel)
        {
            argb = (((int) pixels[pos++] & 0xff) << 24); // alpha
        }

        argb += ((int) pixels[pos++] & 0xff); // blue
        argb += (((int) pixels[pos++] & 0xff) << 8); // green
        argb += (((int) pixels[pos++] & 0xff) << 16); // red
        return argb;
    }
}
\$\endgroup\$
4
  • \$\begingroup\$ There might be an issue into how im implementing it, because it does reduce the time from 2.9s to 2.8s. Moreover I found out after some debugging that the source of the large amount of time is infact entityArea.add(new Area(new Rectangle(x, y, 1, 1))) as suggested my bornander \$\endgroup\$ Mar 25 at 9:58
  • \$\begingroup\$ @GrindingForReputation typically, memory allocation operations are slow slow, and should be avoided in tight loops. \$\endgroup\$
    – Vaillancourt
    Mar 25 at 14:18
  • \$\begingroup\$ @Vaillancourt ok thanks for letting me know \$\endgroup\$ Mar 25 at 14:29
  • \$\begingroup\$ It's entirely possible; the best way to determine that is to profile your code & I should have led off with that. I was answering based on my own experience with using awt's .getRGB() for image processing, but as others have correctly pointed out, the first thing to do in addressing performance problems is to run the profiler. \$\endgroup\$
    – Pikalek
    Mar 25 at 15:20
0
\$\begingroup\$

This answer is based of off bornander's answer.

"Using the java.awt packages will likely be a problem when it comes to performance." - The cause of the performance issue was entityArea.add(new Area(new Rectangle(x, y, 1, 1))). Instead of using an Area object I used an array to create a transparency map as shown below

public Entity(BufferedImage image) {
    this.image = image;

    transmap = new int[image.getWidth()][image.getHeight()];
    heightmap = new int[image.getWidth()];

    boolean heightFound = false;

    for (int x = 0; x < image.getWidth(); x++) {

        for (int y = 0; y < image.getHeight(); y++) {

            if((image.getRGB(x, y) & 0xFF000000) != 0) {
                    
                transmap[x][y] = 1;

                if(!heightFound) {
                    heightmap[x] = y;
                    heightFound = true;
                }
            }
            else
                transmap[x][y] = 0;
        }
    }
}

The average time when running the program 5 times changed from 2,901ms to 167ms.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Fwiw, always run a profiler on your code to help figure out what areas need improvement. Also, calls to getWidth and getHeight will always return the same values, so you could save a couple of operations there by storing the values to a local variable and use this instead; finally, depending on how the data is organized internally in image, it may be more efficient to have x as the inner loop instead of y, as this may reduce the amount of cache misses. \$\endgroup\$
    – Vaillancourt
    Mar 25 at 13:47

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .