Search code examples
javaeclipseframe-rate

How to rotate BufferedImage as object itself


I have trouble with performance when I am rotating every image, when drawing it FPS has dropped from 1400 to 6. Can I rotate BufferedImage object just after creating it, so the calculation happens only once?

There is my Grid Control class: (Drawing class, problem method is drawRotated())

package cs.meowxiik.universes.graphics;

import java.awt.Canvas;
import java.awt.Graphics;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.util.Observer;

import cs.meowxiik.universes.map.Map;
import cs.meowxiik.universes.map.Tile;

public class GridControl {

    int width;
    int height;
    int xcells;
    int ycells;
    int cellWidth;
    int cellHeight;

    int maxZoom = 80;
    int minZoom = 30;
    private Map activeMap;

    public GridControl(int width, int height, int xcells, int ycells) {
            this.width = width;
            this.height = height;
            this.xcells = xcells;
            this.ycells = ycells;
            cellWidth = width / xcells;
            cellHeight= height/ ycells;
    }

    public GridControl(int width, int height, int cellWidth, int cellHeight, int thisDoesntMatterJustForOverloading) {
            this.width = width;
            this.height = height;
            this.cellWidth = cellWidth;
            this.cellHeight = cellHeight;
            xcells = width / cellWidth;
            ycells = height / cellHeight;
    }

    public void drawGrid(Graphics g){
            for(int i = 0; i < xcells; i++){
                    for(int ii = 0; ii <ycells; ii++){
                            g.drawRect(i * cellWidth, ii * cellHeight, cellWidth, cellHeight);
                    }
            }
    }

    public int getCellSize() {
            return cellWidth;
    }

    public void editCellSize(int i) {
            if(i == 0)return;
            System.out.println("Resizing: " + i);
            //Pokud je i menší jak 0 -> člověk se pokouší přibližovat
            if(i < 0 && xcells > maxZoom)return;
            if(i > 0 && xcells < minZoom)return;
            this.cellWidth += i;
            this.cellHeight += i;
            xcells = width / cellWidth;
            ycells = height / cellHeight;
    }

    public void setActiveMap(Map map){
            this.activeMap = map;
    }

    public void drawMap(Graphics g, Canvas canvas){
            if(activeMap != null){
                    for(int i = 0; i < xcells; i++){
                            for(int ii = 0; ii <ycells; ii++){
                                    drawRotated(activeMap.getTiles()[i][ii].getTexture(),activeMap.getTiles()[i][ii].getDegrees(), i*cellWidth, ii*cellWidth,canvas, g, null);
                                    //ImageObserver observer = null;
                                    //g.drawImage(activeMap.getTiles()[i][ii].getTexture(), i*cellWidth, ii*cellWidth, observer);
                            }
                    }
            }
    }

    public Tile getTileByCoordinate(int x, int y){
            int cellx = x / this.cellWidth;
            int celly = y / this.cellHeight;
            return activeMap.getTiles()[cellx][celly];
    }

    public boolean isWalkable(int x, int y){
            return getTileByCoordinate(x,y).getWalkable();
    }

    public void drawRotated(BufferedImage img,double rotationRequired,int x,int y,Canvas canvas, Graphics g, Observer observer){
            //double rotationRequired = Math.toRadians(degrees);
            double locationX = img.getWidth() / 2;
            double locationY = img.getHeight() / 2;
            AffineTransform tx = AffineTransform.getRotateInstance(rotationRequired, locationX, locationY);
            AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);

            // Drawing the rotated image at the required drawing locations
            g.drawImage(op.filter(img, null), x, y, null);
    }}

My Tile method:

public class Tile {

    boolean walkable;
    BufferedImage image;
    double radians;
    int ID; //0 - floor, 1 - wall

    public Tile(boolean walkable, BufferedImage image, int ID) {
            this.walkable = walkable;
            this.image = image;
            this.ID = ID;
    }

    public BufferedImage getTexture() {
            return image;
    }

    public boolean getWalkable() {
            return walkable;
    }

    public int getDegrees() {
            return (int) Math.toDegrees(radians);
    }

    public int getID() {
            return ID;
    }

    public void setTexture(BufferedImage image, int degrees) {
            this.image = image;
            this.radians = Math.toRadians(degrees);
    }
}

And my Map creator: (the rotating part is broken, I'll fix that later, now I need to solve my 6-FPS problem)

package cs.meowxiik.universes.map;

import cs.meowxiik.universes.graphics.GraphicsManager;


public class MapCreator {

    GraphicsManager graphics;

    public MapCreator(GraphicsManager graphics) {
            this.graphics = graphics;
    }

    public Map getSpaceStationMap(){
            Map map;
            int x = 32;
            int y = 18;
            Tile[][] tiles = new Tile[x][y];
            for(int i = 0; i < x; i++){
                    for(int ii = 0; ii < y; ii++){
                            tiles[i][ii] = new Tile(true, graphics.floor, 0);
                    }
            }

            for(int i = 0; i < x; i++){
                    for(int ii = 0; ii < y; ii++){
                            if(i == 0 || ii == 0 || i == x-1 || ii == y-1)tiles[i][ii] = new Tile(false,graphics.stWallZero, 1); //tiles.get(i).set(ii, new Tile(false,graphics.wall));
                    }
            }

            for(int i = 0; i < x; i++){
                    for(int ii = 0; ii < y; ii++){
                            if(tiles[i][ii].getID() == 0)break;
                            boolean connectedUp = false;
                            boolean connectedLeft = false;
                            boolean connectedRight = false;
                            boolean connectedBottom = false;
                            if(i + 1 < tiles.length)
                                    if(tiles[i + 1][ii + 0].getID() == 1)
                                            connectedRight = true;
                            if(i != 0)                                     
                                    if(tiles[i - 1][ii + 0].getID() == 1)
                                            connectedLeft = true;
                            if(ii + 1 < tiles[i].length)
                                    if(tiles[i + 0][ii + 1].getID() == 1)
                                            connectedBottom = true;
                            if(ii != 0)                                    
                                    if(tiles[i + 0][ii - 1].getID() == 1)
                                            connectedUp = true;

                            if(connectedUp && !(connectedLeft || connectedRight || connectedBottom))tiles[i][ii].setTexture(graphics.stWallOne, 0);
                            if(connectedLeft && !(connectedUp || connectedRight || connectedBottom))tiles[i][ii].setTexture(graphics.stWallOne, 90);
                            if(connectedRight && !(connectedUp || connectedLeft || connectedBottom))tiles[i][ii].setTexture(graphics.stWallOne, 180);
                            if(connectedBottom && !(connectedUp || connectedRight || connectedLeft))tiles[i][ii].setTexture(graphics.stWallOne, 270);

                            if((connectedUp && connectedBottom) && !(connectedLeft || connectedRight))tiles[i][ii].setTexture(graphics.stWallTwo, 90);
                            if(!(connectedUp && connectedBottom) && !(connectedLeft || connectedRight))tiles[i][ii].setTexture(graphics.stWallTwo, 0);
                    }
            }

            map = new Map(x,y,tiles);
            return map;
    }

}


Solution

  • Given this code

      public void drawRotated(BufferedImage img,double rotationRequired,int x,int y,Canvas canvas, Graphics g, Observer observer) {
            //double rotationRequired = Math.toRadians(degrees);
            double locationX = img.getWidth() / 2;
            double locationY = img.getHeight() / 2;
            AffineTransform tx = AffineTransform.getRotateInstance(rotationRequired, locationX, locationY);
            AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
    
            BufferedImage rotatedImage = op.filter(img, null); // my edit here
    
            // Drawing the rotated image at the required drawing locations
            g.drawImage(rotatedImage , x, y, null);
        }
    

    The rotation of the image to get rotatedImage is only dependant upon it's size (width/height) and the required rotation.

    From my reading of your code the required rotation is largely fixed. The rotation is known when the setTexture method of Title is called.

    Sine this is the case, rotatedImage can be computed in the setTexture method. Just create a new attribute that stores the computed rotated image. Don't destroy the original.

    If setTexture is called as often as drawRotated then this won't deliver the performance increase you desire.