Search code examples
javaimageperformanceswingrotation

Lightweight image modification in Java


As my CS project, I am creating a full Touhou-esque bullet dodging game that involves rendering thousands of bullet images on their proper coordinates on JPanel. Fortunately, the JVM could hold tens of thousands of bufferedImage without any noticeable frame drop, so I wasn't expecting this giant roadblock I hit: rotating images.

What I initially wanted to achieve is rotating the enemy bullet's BufferedImage; I used the rotation methods on other Stack Overflow question with a small sample, and they worked just fine. The problem arose when I tried to rotate thousands of bullet sprites in the ArrayList of bullet objects. Tens of thousands of new BufferedImage and Graphics2D creation completely halted JVM upon running.

I looked into all the questions relating to Java's image rotation to find a lightweight method that wouldn't cause severe frame drops or downright heap space issues. However, the methods all included at least some form of Object creation or manipulation, and the program simply couldn't take it.

I did attempt to make a lightweight rotation method myself by sacrificing two weeks and at least seven IQ points. Still, without any knowledge of more inherent understanding of computer science, the "best" performance I could get was this method, modifying the field images:

public Bullet(... , double deg, ... , BufferedImage shape /*actual bullet sprite*/, String tag, BufferedImage emp /*empty bufferedimage to act as a template to modify image then redraw*/ ) throws IOException
    {
        rotor = emp;
        img = shape;
        rotate(deg);
        setDeg(deg);
        this.deg = deg;
        ...
    }
public void rotate(double angle) { //tried AffineTransform and image Op and everything but all the same...
        Graphics2D g = rotor.createGraphics();
        g.setBackground(new Color(255, 255, 255, 0));
        g.clearRect(0,0, rotor.getWidth(), rotor.getHeight());
        g.rotate(Math.toRadians(angle), img.getWidth() / 2, img.getHeight() / 2);
        g.drawImage(img, null, img.getWidth() - rotor.getWidth(), img.getHeight() - rotor.getHeight());
        g.dispose();
        img = rotor;
}

Still, with so many bullets to render(at least 10,000), the method makes no innovative difference. Is there any way to make the image rotation as light as possible so as not to add a relevant weight to rendering (and hopefully salvage the project from destined doom)?

Without the rotation the knives look so wrong. Pls help :c


Solution

  • Consider pre-computing and storing rotated images during the game's initialization to efficiently rotate. This approach minimizes the real-time computational load to enhance performance. Here’s how you can implement this:

    Pre-computation Phase:

    Pre-compute rotated versions of each bullet sprite at fixed intervals (e.g., every 1 degree). Store these pre-rotated images in an array or a map, where each index/key represents a rotation angle. Implementation Example:

    // Precompute rotated images
    BufferedImage[] preRotatedImages = new BufferedImage[360];
    for (int i = 0; i < 360; i++) {
        preRotatedImages[i] = rotateImage(originalImage, i);
    }
    
    // Rotate function
    private BufferedImage rotateImage(BufferedImage img, int angle) {
        int w = img.getWidth();
        int h = img.getHeight();
        int newW = (int) Math.ceil(Math.sqrt(w * w + h * h));
        BufferedImage rotated = new BufferedImage(newW, newW, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = rotated.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2d.rotate(Math.toRadians(angle), newW / 2, newW / 2);
        g2d.drawImage(img, (newW - w) / 2, (newW - h) / 2, null);
        g2d.dispose();
        return rotated;
    }
    
    // During gameplay, fetch pre-rotated images
    public void render(Graphics g, int angle, int x, int y) {
        g.drawImage(preRotatedImages[angle % 360], x, y, null);
    }
    

    Optimizations:

    As @Abra suggested, rotate and store images only for visible bullets, reducing memory usage. Use hardware-accelerated image drawing with Java’s built-in capabilities if you haven't already. Test memory usage, loading times, and performance to see if this approach works for you.