1

So, I recently asked a question on how to preload images in Java (preloading images in Java) and it worked great! Until I went to play the game. Framerate dropped drastically. I don't know what it was, but basically, I have a whole sprite map loaded into an array. Each image corresponds to a three-degree rotation. So, 5 degrees would become the 3-degree image, 6 would stay the 6, and so on (I tried Math.round and it actually made the 5 and 4-degree images go to the 6-degree image, and that's more desirable, however, it's slower)
I am looking for some ways to optimize the code. Here are my angle calculations:

private float getAngle(double x, double y) {
    float angle = (float) Math.toDegrees(Math.atan2(y - this.getCenterY(), x - this.getCenterX()));
    if(angle < 0){
        angle += 360;
    }
    return angle;
}

The x and y values inputted into the method are the center x and y values of the enemy being targeted by the tower performing this calculation. Then this code is executed in the draw method:

if(airID==Value.towerCannon && (airRow>0 && airRow<5) && angle>=0) {
    if(angle==Canvas.rotatedAirMap.length) angle = 0;
        g.drawImage(Canvas.rotatedAirMap[angle][level-1], x, y, width, height, null); 
    } else {
        g.drawImage(Canvas.airMap[airRow][airID], x, y, width, height, null); 
    }
}

This will draw the appropriate, preloaded image rotated at the specified angle (The "angle" - or image identifier - is calculated when the tower shoots by dividing the result of the angle calculation by three and then casting that to an int - I could also round that value)
Any suggestions on how to optimize this so I don't get such massive frame drops? I assume the frame drops are due to the VM heap size being too small, but I've increased it and still, nothing significant happens. Any help would be greatly appreciated.
Thanks!

@VGR here is what I did with your response:

public void paintComponent(Graphics g) {
    if(isFirst) { //Define stuff when isFirst is true
        define(); //Sets up the image arrays

        GraphicsConfiguration config = getGraphicsConfiguration();
        if(config == null) {
            GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
            config = env.getDefaultScreenDevice().getDefaultConfiguration();
        }

        BufferedImage compatibleImage = config.createCompatibleImage(image.getWidth(), image.getHeight(), image.getTransparency());

        g = compatibleImage.createGraphics();

        isFirst = false;
    }
}

This works a little bit faster, but I had to do some workarounds. repaint() is called in the game loop (this class implements runnable) So the graphics component created by the repaint method (however that works) is the graphics I use for the whole game. Would this be the correct way to do it?

Luca Kukuk
  • 39
  • 4
  • Make sure the image matches the window’s GraphicsConfiguration. One way to do this is to [create a compatible image](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/java/awt/GraphicsConfiguration.html#createCompatibleImage(int,int,int)), then draw your image onto that, and paint only the compatible image. – VGR Dec 17 '18 at 18:26
  • @VGR So should I use BufferedImages rather than just Images? EDIT: Or would the image container hold the BufferedImage that the method returns? – Luca Kukuk Dec 17 '18 at 18:48
  • In this case, yes, you should use GraphicsConfiguration compatible BufferedImages. – VGR Dec 17 '18 at 19:05
  • @VGR To create my images, I use the ImageIO.read method. Would that be able to make my BufferedImages as well or would I have to do something else? – Luca Kukuk Dec 17 '18 at 19:18
  • It is not sufficient. Call the method I linked to in my first comment, then draw your loaded image into it. – VGR Dec 17 '18 at 19:55
  • @VGR I don't understand. The method you linked is not a static method and can only be called if I have an object of that class....which I don't and can't create since that class can't be instantiated. Could you please post some example code so I can understand how to use this method because I've been searching through the Java docs and I'm lost. – Luca Kukuk Dec 17 '18 at 19:57
  • @VGR I've posted some code to see if that was what you were thinking – Luca Kukuk Dec 17 '18 at 20:07

1 Answers1

2

Translating images from their inherent color model to the color model of the current video mode can slow down rendering. To avoid this, you can make sure each image is compatible with the screen where your window resides:

BufferedImage image = ImageIO.read(/* ... */);

GraphicsConfiguration config = getGraphicsConfiguration();
if (config == null) {
    GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment;
    config = env.getDefaultScreenDevice().getDefaultConfiguration();
}

BufferedImage compatibleImage = config.createCompatibleImage(
    image.getWidth(), image.getHeight(), image.getTransparency());

Graphics2D g = compatibleImage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();

image = compatibleImage;

If an image has the same color model as the current video mode, there is no translation needed when drawing it. It’s even possible that painting of such an image may be done entirely by the GPU.

VGR
  • 40,506
  • 4
  • 48
  • 63
  • I've never seen the necessity for these efforts. Particularly, I have never seen that an image that is a default ARGB image was "slow". In fact, my rule of thumb is to simply convert **every** image to ARGB (using a method like ´convertToARGB` as in this answer https://stackoverflow.com/a/23397515/3182664 ), and that should be enough. Did you notice any particular speedup (or did you make a benchmark on different platforms) comparing the performance of ARGB images and "compatible" images? – Marco13 Dec 17 '18 at 20:28
  • @Marco13 It certainly is not a necessity. But I have observed tangible speed increases with it in the past. I admit I haven’t tried it on a recent system. – VGR Dec 17 '18 at 20:32
  • @VGR I edited my post to show you what I have done with your code. – Luca Kukuk Dec 17 '18 at 20:57
  • @Marco13 I will attempt your method at one point, thank you for your input – Luca Kukuk Dec 17 '18 at 20:57
  • @LucaKukuk That is not the same thing. You should never save a Graphics or Graphics2D object; in this case, the goal is to copy your loaded image into a compatible image. Once that copy is finished, the Graphics object has no further use. And painting a component should only be done using the Graphics object passed to the paintComponent method. – VGR Dec 17 '18 at 21:01
  • @VGR I understand now, thanks for your patience. It goes SO much smoother now, thanks a bunch! – Luca Kukuk Dec 17 '18 at 21:07