Java: Example - Buffered Analog Clock

The following is an analog (uses hands) clock that uses buffered drawing to increase speed. In buffered drawing some or all of an images is computed once and stored so that it doesn't have to be redrawn later. In this program the clock face is drawn only once, and the hands are computed.
// An analog clock -- Using Timer and Calendar.
// Uses a BufferedImage and antialiasing.
// The second hand creep is from the Timer, which seems to take
// a little longer than the requested time to call the listener.
//  -- Fred Swartz, 1 May 1999, 2002-02-21 from applet to application

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
import javax.swing.event.*;
import java.util.*;

///////////////////////////////////////////////////////////// ClockAnalogBuf
public class ClockAnalogBuf extends JFrame {

    Clock clockFace;
    
    //================================================================= main
    public static void main(String[] args) {
        JFrame windo = new ClockAnalogBuf();
        windo.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        windo.setVisible(true);
    }//end main

    //========================================================== constructor
    public ClockAnalogBuf() {
        Container content = this.getContentPane();
        content.setLayout(new BorderLayout());
        clockFace = new Clock();
        content.add(clockFace, BorderLayout.CENTER);
        
        this.setTitle("Analog Clock");
        this.pack();
        
        clockFace.start();
    }//end constructor
}//end class ClockAnalogBuf


//////////////////////////////////////////////////////////////// Clock class
class Clock extends JPanel {

    private int hours   = 0;
    private int minutes = 0;
    private int seconds = 0;
    private int millis  = 0;

    private static final int   spacing = 10;
    private static final float twoPi = (float)(2.0 * Math.PI);
    private static final float threePi = (float)(3.0 * Math.PI);
    // Angles for the trigonometric functions are measured in radians.
    // The following in the number of radians per sec or min.
    private static final float radPerSecMin = (float)(Math.PI / 30.0);

    private int size;       // height and width of clock face
    private int centerX;    // x coord of middle of clock
    private int centerY;    // y coord of middle of clock
    private BufferedImage clockImage;
    private javax.swing.Timer t;

    //==================================================== Clock constructor
    public Clock() {
        this.setPreferredSize(new Dimension(300,300));
        this.setBackground(Color.white);
        this.setForeground(Color.black);

        t = new javax.swing.Timer(1000,
              new ActionListener() {
                  public void actionPerformed(ActionEvent e) {
                      update();
                  }
              });
    }//end constructor

    //=============================================================== update
        // Replace the default update so that the plain background
        // doesn't get drawn.
    public void update() {
        // Hmmm, is this the right way to do this? Or do we just
        // call paintComponent?  Does this coalesce calls, not that
        // this updates often enuf for that to be important.
        this.repaint();
    }//end update

    //================================================================ start
    public void start() {
        t.start();  // start the timer
    }//end start

    //================================================================= stop
    public void stop() {
        t.stop();  // start the timer
    }//end stop

    //======================================================= paintComponent

    public void paintComponent(Graphics g) {
        super.paintComponent(g);  // paint background, borders
        Graphics2D g2 = (Graphics2D)g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                            RenderingHints.VALUE_ANTIALIAS_ON);

        // The panel may have been resized, get current dimensions
        int w = getWidth();
        int h = getHeight();
        size = ((w<h) ? w : h) - 2*spacing;
        centerX = size/2 + spacing;
        centerY = size/2 + spacing;

        // Create the clock face background image if this is the first time,
        // or if the size of the panel has changed
        if (clockImage == null
                || clockImage.getWidth() != w
                || clockImage.getHeight() != h) {

            clockImage = (BufferedImage)(this.createImage(w, h));
            // now get a graphics context from this image
            Graphics2D gc = clockImage.createGraphics();
            gc.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                RenderingHints.VALUE_ANTIALIAS_ON);
            drawClockFace(gc);
        }
        
        // Now get the time and draw the hands.
        Calendar now = Calendar.getInstance();
        hours   = now.get(Calendar.HOUR);
        minutes = now.get(Calendar.MINUTE);
        seconds = now.get(Calendar.SECOND);
        millis  = now.get(Calendar.MILLISECOND);

        // Draw the clock face from the precomputed image
        g2.drawImage(clockImage, null, 0, 0);

        // Draw the clock hands
        drawClockHands(g);
    }//end paintComponent


    //======================================================= drawClockHands
    private void drawClockHands(Graphics g) {
        int secondRadius = size/2;
        int minuteRadius = secondRadius * 3/4;
        int hourRadius   = secondRadius/2;

        // second hand
        float fseconds = seconds + (float)millis/1000;
        float secondAngle = threePi - (radPerSecMin * fseconds);
        drawRadius(g, centerX, centerY, secondAngle, 0, secondRadius);

        // minute hand
        float fminutes = (float)(minutes + fseconds/60.0);
        float minuteAngle = threePi - (radPerSecMin * fminutes);
        drawRadius(g, centerX, centerY, minuteAngle, 0, minuteRadius);

        // hour hand
        float fhours = (float)(hours + fminutes/60.0);
        float hourAngle = threePi - (5 * radPerSecMin * fhours);
        drawRadius(g, centerX, centerY, hourAngle, 0, hourRadius);
    }//end drawClockHands

    //======================================================== drawClockFace
    private void drawClockFace(Graphics g) {
        // clock face
        g.setColor(Color.pink);
        g.fillOval(spacing, spacing, size, size);
        g.setColor(Color.black);
        g.drawOval(spacing, spacing, size, size);

        // tic marks
        for (int sec = 0; sec<60; sec++) {
            int ticStart;
            if (sec%5 == 0) {
                ticStart = size/2-10;
            } else {
                ticStart = size/2-5;
            }
            drawRadius(g, centerX, centerY, radPerSecMin*sec, ticStart , size/2);
        }
    }//endmethod drawClockFace

    //=========================================================== drawRadius
    private void drawRadius(Graphics g, int x, int y, double angle,
                    int minRadius, int maxRadius) {
        float sine   = (float)Math.sin(angle);
        float cosine = (float)Math.cos(angle);

        int dxmin = (int)(minRadius * sine);
        int dymin = (int)(minRadius * cosine);

        int dxmax = (int)(maxRadius * sine);
        int dymax = (int)(maxRadius * cosine);
        g.drawLine(x+dxmin, y+dymin, x+dxmax, y+dymax);
    }//end drawRadius

}//endclass Clock