Saturday, March 26, 2011

Display a rotating Taichi Image.

The previous article contains source code for drawing a Taichi image. This article will create an animation thread that rotate the image in the graphics panel.

How to rotate a image in Java


A coordinate transformation can be applied to the Graphics2D object.
// gr is a java.awt.Graphics2D object
java.awt.geom.AffineTransform transform = gr.getTransform();

// rotate the image about the center of panel.
int xPanelCenter=this.getWidth()/2;
int yPanelCenter=this.getHeight()/2;
double pi=3.141592654;
double angle=rotatedDegree*pi/180;  // convert to radian
transform.rotate(angle,xPanelCenter,yPanelCenter);

gr.setTransform(transform);  // apply transformation

At this point, all coordinates in the gr object will be transformed. Calling the drawImage() method now will draw a transformed (rotated) image.
// draw the image
gr.drawImage(taichiImage,10,10,this);


How to create an animation thread


To create a new thread in Java, you must specified the thread entry point, which is typically know as the "run()" method.

Steps to start a running thread :
  1. Implements the run() method of a "Runnable" interface.
  2. Wrap the Runnable interface with a Thread object.
  3. Invoke the start() method of the Thread object.

Let's do it step by step now.
  1. Implements the run() method of a "Runnable" interface.
  2. // animation thread
    public void run()
    {
      while (true)
      {
        rotatedDegree=rotatedDegree+1;  // rotate one degree for each frame.
        repaint();
    
        // sleep for 40 ms, roughly 25 frames per second (1000/40=25)
        try { Thread.sleep(40); } catch (Exception ignore) {}
      }
    }
    
    /*
    The idea of the above animation is : repeatedly draw a new frame, repaint the screen 
    then delay for a while so that the frame can be seen.  
    To create a smooth animation, frame per second should between 20 and 30.  
    The above sample has a FPS value slightly less than 25.
    */
    
  3. Wrap the Runnable interface with a Thread object.
  4. // graphicsPanel is a Runnable interface
    Thread thread=new Thread(graphicsPanel);
    
  5. Invoke the start() method of the Thread object.
  6. thread.start();   // the run() method will be started
    


Full runnable demo

/******************************************************************************
* File : RotatingTaichi.java
* Author : http://java.macteki.com/
* Description :
*   Display a rotating Taichi image in a graphics panel.
* Tested with : JDK 1.6
******************************************************************************/

import java.awt.image.BufferedImage;
import java.awt.Point;

class RotatingTaichi extends javax.swing.JPanel implements Runnable
{
  private BufferedImage taichiImage;
  private int rotatedDegree=0;

  public static RotatingTaichi getInstance()
  {
    RotatingTaichi panel=new RotatingTaichi();
    panel.setPreferredSize(new java.awt.Dimension(320,320));

    panel.setBackground(java.awt.Color.WHITE);

    panel.taichiImage = new BufferedImage(300,300,BufferedImage.TYPE_INT_RGB);
    panel.drawTaichi(panel.taichiImage,150,150,100);

    return panel;
  }

  // draw a taichi image
  public void drawTaichi(BufferedImage image,int xCenter,int yCenter, int radius)
  {
    java.awt.Graphics2D gr = (java.awt.Graphics2D) image.getGraphics();
    gr.setColor(java.awt.Color.WHITE);
    gr.fillRect(0,0,image.getWidth(this),image.getWidth(this));

    gr.setStroke(new java.awt.BasicStroke(2));  // pen width
    gr.setColor(java.awt.Color.BLACK);

    // draw the big circle
    int r=radius;  // radius
    int xc=xCenter, yc=yCenter;  // center
    // the bounding rectangle (x,y,w,h) of the circle is defined by :
    // x=xc-r,  y=yc-r,  w=r*2,  h=r*2;
    gr.drawArc(xc-r,yc-r,r*2,r*2,0,360);  // (x,y,w,h,startDegree,arcDegree)

    // draw a half circle inside the big circle
    xc=xCenter; yc=yCenter-radius/2; r=radius/2;
    gr.drawArc(xc-r,yc-r,r*2,r*2,270,180);  // (x,y,w,h,startDegree,arcDegree)

    // draw another half circle inside the big circle
    xc=xCenter; yc=yCenter+radius/2; r=radius/2;
    gr.drawArc(xc-r,yc-r,r*2,r*2,90,180);  // (x,y,w,h,startDegree,arcDegree)

    // draw two more small circles
    xc=xCenter; yc=yCenter-radius/2; r=radius/8;
    gr.drawArc(xc-r,yc-r,r*2,r*2,0,360);  // (x,y,w,h,startDegree,arcDegree)

    xc=xCenter; yc=yCenter+radius/2; r=radius/8+1;
    gr.drawArc(xc-r,yc-r,r*2,r*2,0,360);  // (x,y,w,h,startDegree,arcDegree)

    // fill with appropriate color
    int black=packRgb(0,0,0);
    floodFill(image,xCenter,yCenter-radius/2,black);

    floodFill(image,xCenter,yCenter+radius/16,black);
  }


  // override the paint method
  // just paint the taichi image here
  public void paintComponent(java.awt.Graphics graphics)
  {
    super.paintComponent(graphics);  

    // Graphics2D is a better choice to the default Graphics object
    java.awt.Graphics2D gr=(java.awt.Graphics2D) graphics;

    java.awt.geom.AffineTransform transform = gr.getTransform();

    // rotate the image about the center of panel.
    int xPanelCenter=this.getWidth()/2;
    int yPanelCenter=this.getHeight()/2;
    double pi=3.141592654;
    double angle=rotatedDegree*pi/180;  // convert to radian
    transform.rotate(angle,xPanelCenter,yPanelCenter);
 
    gr.setTransform(transform);
    
    gr.drawImage(taichiImage,10,10,this);
  }

  public static int packRgb(int r,int g,int b)
  {
    return (r*256+g)*256+b;
  }


  // implements the flood fill algorithm
  public static void floodFill(BufferedImage image, int x,int y, int fillColor)
  {
    java.util.ArrayList<Point> examList=new java.util.ArrayList<Point>();

    int initialColor=image.getRGB(x,y);
    examList.add(new Point(x,y));

    while (examList.size()>0)
    {
      Point p = examList.remove(0);  // get and remove the first point in the list
      if (image.getRGB(p.x,p.y)==initialColor) 
      {
        x = p.x;  y = p.y;
        image.setRGB(x, y, fillColor);  // fill current pixel

        if (image.getRGB(x-1,y)==initialColor) // check west neighbor
        {
          examList.add(new Point(x-1,y));        
        }
        if (image.getRGB(x+1,y)==initialColor) // check east neighbor
        {
          examList.add(new Point(x+1,y));        
        }
        if (image.getRGB(x,y-1)==initialColor) // check north neighbor
        {
          examList.add(new Point(x,y-1));        
        }
        if (image.getRGB(x,y+1)==initialColor) // check south neighbor
        {
          examList.add(new Point(x,y+1));       
        }

      }
    }

  }


  // animation thread
  public void run()
  {
    while (true)
    {
      rotatedDegree=rotatedDegree+1;  // rotate one degree for each frame.
      repaint();

      // sleep for 40 ms, roughly 25 frames per second (1000/40=25)
      try { Thread.sleep(40); } catch (Exception ignore) {}
    }
  }

  public static void main(String[] args) throws Exception
  {
    RotatingTaichi graphicsPanel = RotatingTaichi.getInstance();

    javax.swing.JFrame window=new javax.swing.JFrame();
    window.add(graphicsPanel);
    window.pack();

    window.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE);
    window.setTitle("Macteki Taichi Panel");
    window.setVisible(true);

    // start the animation thread
    new Thread(graphicsPanel).start();
  }
}


Thanks for reading. Comments are welcome.

No comments:

Post a Comment