Home > OS >  Java small jittering in graphical movement of an image
Java small jittering in graphical movement of an image

Time:07-01

I have a buffered image (width = 66, height = 75) that seems to jitter (as if the image is shaking at a small scale) when trying to translate it when it is at an angle. This is the set up that shows the problem (A and D keys to rotate, W and S keys to translate):


import java.awt.Color;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Test extends JPanel {

    BufferedImage image;
    
    public double angle = 0;
    public Point center = new Point(160, 160);
    
    public Test() {
        
        try {
            image =  ImageIO.read(new File("filePath"));
        } catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println(image.getWidth()   ", "   image.getHeight());

        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
         
        this.setPreferredSize(new Dimension(1000, 500));
        frame.add(this); 
        this.setBackground(Color.gray);
        
        frame.addKeyListener(new KeyListener() {

            @Override
            public void keyTyped(KeyEvent e) {
                
            }

            @Override
            public void keyPressed(KeyEvent e) {
                
                double deltaAngle = 3;
                int speed = 3;
                
                if(e.getKeyCode() == KeyEvent.VK_A)
                    angle -= deltaAngle;
                else if(e.getKeyCode() == KeyEvent.VK_D)
                    angle  = deltaAngle;
                else if(e.getKeyCode() == KeyEvent.VK_W)
                    center.translate(speed*Math.sin(Math.toRadians(angle)), -speed*Math.cos(Math.toRadians(angle)));
                else if(e.getKeyCode() == KeyEvent.VK_S)
                    center.translate(-speed*Math.sin(Math.toRadians(angle)), speed*Math.cos(Math.toRadians(angle)));
                
                frame.repaint();
            }

            @Override
            public void keyReleased(KeyEvent e) {
                
            }
            
        });

        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }
    
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2 = (Graphics2D) g;
        
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);   
        
        g2.drawString("Angle: "   Double.toString(angle), 20, 20);
        drawImageCentered(g2, rotateImage(image, angle), center);
        
    }
    
    public void drawImageAccurate(Graphics2D g, BufferedImage image, Point location) {
        AffineTransform transform = new AffineTransform();
        transform.translate(location.x, location.y);
        //transform.scale(1, 1);
        g.drawImage(image, transform, null);
    }
    
    public void drawImageCentered(Graphics2D g, BufferedImage image, Point location) {
        drawImageAccurate(g, image, new Point(location.x - image.getWidth()/2.0, location.y - image.getHeight()/2.0));
    }
    
    public static BufferedImage rotateImage(BufferedImage image, double angle) {
        if(angle == 0 || Double.isNaN(angle))
            return image;
        else {
            angle = Math.toRadians(angle);
            double x = Math.abs(Math.cos(angle));
            double y = Math.abs(Math.sin(angle));
            int width = image.getWidth();
            int height = image.getHeight();
            int nWidth = (int) Math.floor(width*x   height*y);
            int nHeight = (int) Math.floor(height*x   width*y);
            
            BufferedImage rotated = new BufferedImage(nWidth, nHeight, image.getType());
            Graphics2D tool = rotated.createGraphics();
            
            AffineTransform transformer = new AffineTransform();
            transformer.translate((nWidth - width)/2.0, (nHeight - height)/2.0);

            transformer.rotate(angle, width/2, height/2);
            
            tool.drawImage(image, transformer, null);
            tool.dispose();
            
            return rotated;
        }   
    }
    
    private class Point {
        public double x, y;
        
        public Point(double x, double y) {
            this.x = x;
            this.y = y;
        }
        
        public void translate(double x, double y) {
            this.x  = x;
            this.y  = y;
        }
    }

    public static void main(String[] args) {
        new Test();
    }

}

I notice that the jittering of the image is more noticeable as the image get smaller in size and when the image is at 87 degrees and other close to "straight" angles. I have used rendering hints:

g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);   

and also an AffineTransform:

public void drawImageAccurate(Graphics2D g, BufferedImage image, Point location) {
    AffineTransform transform = new AffineTransform();
    transform.translate(location.x, location.y);
    //transform.scale(1, 1);
    g.drawImage(image, transform, null);
}

Which, I think, lowered the intensity of the jittering but I still can see some small jittering. Maybe my expectations are too high, but I do not think I have ever seen jittering in a 2D game which makes me think there can be more done. Or perhaps some solutions are to increase the movement speed of images or prevent using small images for movement. However, it would nice if there is another direct solution I have not been able to find through searches that could remove this image jittering.

CodePudding user response:

I assume the image quality keeps changing as you keep rotating the image into the various angles repeatedly and each time you may perceive conversion losses.

Probably it is better to draw the image once only by applying one AffineTransform only. This AffineTransform is a contatencation of three single transformations:

  1. translate image center to origin
  2. rotate image acount origin
  3. translate image to actual position

So when keys are pressed you recalculate the AffineTransform and ask for repaint. When repainting, just draw the image using the existing AffineTransform.

I played around a bit and the code looks like this now.

package com.mycompany.test;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Test extends JPanel {
    /** rotation speed in radians. */
    final double deltaAngle = 3.0d * Math.PI/180.0d;
    final int speed = 3;

    BufferedImage image;

    /** angle in radians. */
    public double angle = 0;
    public Point center = new Point(160, 160);
    AffineTransform at;

    private void updateTransform() {
        at = AffineTransform.getRotateInstance(angle);
        at.concatenate(AffineTransform.getTranslateInstance(-image.getWidth()/2, -image.getHeight()/2));
        at.concatenate(AffineTransform.getTranslateInstance(center.x, center.y));
    }

    public Test() {
        try {
            image =  ImageIO.read(new File("filepath"));
        } catch (IOException e) {
            e.printStackTrace();
        }

        System.out.println(image.getWidth()   ", "   image.getHeight());
    
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     
        this.setPreferredSize(new Dimension(1000, 500));
        frame.add(this); 
        this.setBackground(Color.gray);
    
        frame.addKeyListener(new KeyListener() {

            @Override
            public void keyTyped(KeyEvent e) {
            }

            @Override
            public void keyPressed(KeyEvent e) {
                if(e.getKeyCode() == KeyEvent.VK_A)
                    angle -= deltaAngle;
                else if(e.getKeyCode() == KeyEvent.VK_D)
                    angle  = deltaAngle;
                else if(e.getKeyCode() == KeyEvent.VK_W)
                    center.translate(speed*Math.sin(Math.toRadians(angle)), -speed*Math.cos(Math.toRadians(angle)));
                else if(e.getKeyCode() == KeyEvent.VK_S)
                    center.translate(-speed*Math.sin(Math.toRadians(angle)), speed*Math.cos(Math.toRadians(angle)));
            
                updateTransform();
            
                frame.repaint();
            }

            @Override
            public void keyReleased(KeyEvent e) {
            }
        
        });

        frame.pack();

        updateTransform();

        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2 = (Graphics2D) g;
    
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);   
    
        g2.drawString("Angle: "   Double.toString(angle), 20, 20);
        g2.drawImage(image, at, null);
    
    }

    private class Point {
        public double x, y;
    
        public Point(double x, double y) {
            this.x = x;
            this.y = y;
        }
    
        public void translate(double x, double y) {
            this.x  = x;
            this.y  = y;
        }
    }

    public static void main(String[] args) {
        new Test();
    }

}

However the artifacts are not fully resolved. This is since a bitmap is rotated - such artifacts are to be expected. Not even scaling down the image helps. The only chance may be to use SVG as source. Check out Apache Batik.

  • Related