Graphics

AWT's Component base class defines a method paint(). This method is called automatically when the contents of the component should be painted in response to the component first being shown or damage needing repair. paint() can be overridden to provide for customization of a component. Notice that the Button derived class doesn't draw on "itself", it draws on the Graphics object passed as an argument. The origin for all Graphics primitives is the top-left corner.
   import java.awt.*;
   
   public class GraphicsDemos extends Button {
      private int count = 1;
      public GraphicsDemos( String label ) {
         super( label );
         Frame f = new FrameClose( "GraphicsDemos" );
         f.add( this );   f.setSize( 300, 200 );   f.setVisible( true );
      }
      public void paint( Graphics g ) {
         System.out.println( "paint" + count++ );
         int endX  = getSize().width - 1;
         int endY = getSize().height - 1;
         g.drawLine( 0, 0, endX, endY );
         g.drawLine( endX, 0, 0, endY );
      }
      public static void main( String[] args ) {
         new GraphicsDemos( "The label for GraphicsDemos" );
   }  }
A Canvas component represents a blank rectangular area of the screen onto which the application can draw or from which the application can trap input events from the user. An application must subclass the Canvas class in order to provide useful functionality such as creating a custom component. The paint() method must be overridden in order to perform custom graphics on the Canvas.
   import java.awt.*;
   
   public class GraphicsDemos extends Canvas {
      private int count = 1;
      public GraphicsDemos() {
         Frame f = new FrameClose( "GraphicsDemos" );
         f.add( this );   f.setSize( 300, 200 );   f.setVisible( true );
      }
      public void paint( Graphics g ) {
         System.out.println( "paint" + count++ );
         int endX  = getSize().width - 1;
         int endY = getSize().height - 1;
         g.drawLine( 0, 0, endX, endY );
         g.drawLine( endX, 0, 0, endY );
      }
      public static void main( String[] args ) {
         new GraphicsDemos();
   }  }
drawString() is another primitive offered by the Graphics class. The origin of a drawn String is its bottom-left corner. If you forget this fact and draw a String at (0,0), it will be drawn off the top of the Canvas and all you will see is the bottoms of characters like: g, j, p, q, and y. If you want to computer the width of a String, the commented code is provided.
   import java.awt.*;
   import java.awt.font.*;
   import java.awt.geom.AffineTransform;
   
   public class GraphicsDemos extends Canvas {
      private int count = 1;
      public GraphicsDemos() {
         Frame f = new FrameClose( "GraphicsDemos" );
         f.add( this );  f.setSize( 300, 200 );  f.setVisible( true );
      }
      public void paint( Graphics g ) {
         System.out.println( "paint" + count++ );
         String line = "here is a String";
         g.drawString( line, 20, 20 );
         // FontRenderContext frc = new FontRenderContext(
         //                            new AffineTransform(), false, false );
         // int lineWidth = (int) getFont().getStringBounds( line,frc ).getWidth();
         // g.drawString( line, (getSize().width - lineWidth) / 2, 20 );
         // LineMetrics lm = getFont().getLineMetrics( line, frc );
         // lineHeight = (int) lm.getHeight();
         // lineAscent = (int) lm.getAscent();
      }
      public static void main( String[] args ) {
         new GraphicsDemos();
   }  }
Here is code that demonstrates how you might respond to resize events from the user. This is a good time to present the architecture that makes it possible to respond to expose, resize, and un-iconifying events. The next several examples will step through each of these.

paint( Graphics g ) - called by the system whenever the component needs to redraw itself. Includes a "clipping rectangle" capability that causes updates to be limited to the region that has been "damaged".

update( Graphics g ) - clears this component by filling it with the background color, and, calls this component's paint() method to completely redraw itself.

repaint() - called by the programmer to tell the system to call the referenced component's update() method.

Normally, the default implementation for update() is sufficient. The programmer is routinely responsible for writing the paint() method. If a call to paint() is desired - don't call it directly. Instead, call repaint() to have the paint() method called indirectly.

   import java.awt.*;
   
   public class GraphicsDemos extends Canvas {
      private int count = 1;
      public GraphicsDemos() {
         Frame f = new FrameClose( "GraphicsDemos" );
         f.add( this );  f.setSize( 300, 300 );  f.setVisible( true );
      }
      public void paint( Graphics g ) {
         System.out.println( "paint" + count++ );
         int dx = getSize().width / 10;
         int dy = getSize().height / 10;
         g.setColor( Color.red );
         for (int i=1; i < 10; i++)
            g.drawLine( dx * i, 0, dx * i, getSize().height );
         g.setColor( Color.blue );
         for (int i=1; i < 10; i++)
            g.drawLine( 0, dy * i, getSize().width, dy * i );
      }
      public static void main( String[] args ) {
         new GraphicsDemos();
   }  }
GUI programming requires a fairly unique design. The logic in the aplication that needs to affect its presentation may be distributed throughout the application, but all the actual "drawing" needs to happen in a single place (the paint() method in Java's AWT). All the pieces of the application that need to "draw" should not actually draw, they should modify some data structures instead, and then cause the paint() method to be invoked. Below, the "mouse pressed" event handler modifies the matrix data structure, and then calls repaint() (which will ultimately call paint()).
   import java.awt.*;
   import java.awt.event.*;
   
   public class GraphicsDemos extends Canvas implements MouseListener {
      private int count = 1, dx, dy;
      private boolean[][] matrix = new boolean[10][10];
      public GraphicsDemos() {
         Frame f = new FrameClose( "GraphicsDemos" );
         f.add( this );  f.setSize( 300, 300 );  f.setVisible( true );
         addMouseListener( this );
      }
      public void paint( Graphics g ) {
         System.out.println( "paint" + count++ );
         dx = getSize().width / 10;
         dy = getSize().height / 10;
         g.setColor( Color.red );
         for (int i=0; i < 10; i++)
            for (int j=0; j < 10; j++)
               if (matrix[i][j]) g.fillRect( i*dx, j*dy, dx, dy );
         g.setColor( Color.black );
         for (int i=1; i < 10; i++)
            g.drawLine( dx * i, 0, dx * i, getSize().height );
         for (int i=1; i < 10; i++)
            g.drawLine( 0, dy * i, getSize().width, dy * i );
      }
      public void mousePressed( MouseEvent e ) {
         matrix[e.getX()/dx][e.getY()/dy] = ! matrix[e.getX()/dx][e.getY()/dy];
         repaint();
      }
      public void mouseClicked(  MouseEvent e ) { }
      public void mouseEntered(  MouseEvent e ) { }
      public void mouseExited(   MouseEvent e ) { }
      public void mouseReleased( MouseEvent e ) { }
      public static void main( String[] args ) {
         new GraphicsDemos();
   }  }
Here is some code from Core Java, volume II that demonstrates animation and an "association" relationship with a Canvas object. Now that we are not inheriting from class Canvas (or any other Component), we have to design some replacement for paint(). Notice that both draw() and move() call getGraphics() on the Canvas object. This provides the Graphics object that will allow all drawing primitives to be accessed. Because there are possibly platform-specific scarce resources associated with a Graphics object, the programmer should be diligent to call dispose() on the object to retire it.

The animation is a result of successive "undraws" and "draws". "Undraw" is routinely accomplished with an "exclusive or draw". In AWT, use setXORMode() followed by the appropriate drawing primitive. Notice the use of an "anonymous inner class" in main().

   import java.awt.*;
   import java.awt.event.*;
   
   public class GraphicsDemos extends Thread {
      private static final int XSIZE = 10, YSIZE = 10;
      private int x = 0, y = 0, dx = 2, dy = 2;
      private static Canvas box = new Canvas();
      public void draw() {
         Graphics g = box.getGraphics();
         g.setColor( Color.red );
         g.fillOval( x, y, XSIZE, YSIZE );
         g.dispose();
      }
      public void move() {
         if ( ! box.isVisible() ) return;
         // without the lines above (and in main), on exiting the window you get -
         //    java.lang.NullPointerException at GraphicsDemos.move(Compiled Code)
         Graphics g = box.getGraphics();
         g.setColor( Color.red );
         g.setXORMode( box.getBackground() );
         g.fillOval( x, y, XSIZE, YSIZE );
         x += dx;   y += dy;
         Dimension d = box.getSize();
         if (x < 0)                 { x = 0; dx = -dx; }
         if (x + XSIZE >= d.width)  { x = d.width - XSIZE;  dx = -dx; }
         if (y < 0)                 { y = 0; dy = -dy; }
         if (y + YSIZE >= d.height) { y = d.height - YSIZE; dy = -dy; }
         g.fillOval( x, y, XSIZE, YSIZE );
         g.dispose();
      }
      public void run() {
         draw();
         while (true) {
            move();
            try { Thread.sleep( 5 ); }
            catch( InterruptedException e ) { }
       }  }
       public static void main( String[] args ) {
         final Frame f = new Frame( "GraphicsDemos" );
         f.addWindowListener( new WindowAdapter() {
            public void windowClosing( WindowEvent e ) {
               box.setVisible( false );  f.dispose();  System.exit( 0 ); } } );
         f.add( box );   f.setSize( 300, 200 );   f.setVisible( true );
         new GraphicsDemos().start();
   }  }
To optimize the performance of the paint() method, instead of redrawing the entire component, just the "damaged" portion could be redrawn. getClipBounds() returns the bounding rectangle of the current clipping area (the area uncovered or exposed as a result of user interaction).
   import java.awt.*;
   
   public class GraphicsDemos extends Canvas {
      private int count = 1;
      public GraphicsDemos() {
         Frame f = new FrameClose( "GraphicsDemos" );
         f.add( this );  f.setSize( 300, 300 );  f.setVisible( true );
         this.setBackground( Color.yellow );
      }
      public void paint( Graphics g ) {
         int x = g.getClipBounds().x;
         int y = g.getClipBounds().y;
         int w = g.getClipBounds().width;
         int h = g.getClipBounds().height;
         System.out.println( "paint" + count + ", width is " + w + ", height is " + h );
         g.drawLine( x, y, x+w, y+h );
         g.drawLine( x+w, y, x, y+h );
         if (count++ < 3) {
            g.setColor( Color.red );
            g.fillRect( 0, 0, 300, 300 );
      }  }
      public static void main( String[] args ) {
         new GraphicsDemos();
   }  }
Previously, it was described that repaint() doesn't actually call paint(), it calls update(), which calls paint(). Here is an attempt to document that flow of control. The "new background" button calls repaint(). The update() method has been overridden. It sends a message to standard-out and delegates to the real inherited method.
   import java.awt.*;
   import java.awt.event.*;
   
   public class GraphicsDemos extends Canvas {
      private int count = 1;
      private Button back = new Button( "new background" );
      private Button fore = new Button( "fill foreground" );
   
      private class BBL implements ActionListener {
         private int     index = 0;
         private Canvas  drawingArea;
         private Color[] colors = { Color.black, Color.blue, Color.cyan,
            Color.darkGray, Color.gray, Color.green, Color.lightGray,
            Color.magenta, Color.orange, Color.pink, Color.white, Color.yellow };
         public BBL( Canvas c ) { drawingArea = c; }
         public void actionPerformed( ActionEvent e ) {
            drawingArea.setBackground( colors[index] );
            index = (index+1) % colors.length;
            repaint();
      }  }
   
      private class FBL implements ActionListener {
         private Canvas drawingArea;
         public FBL( Canvas c ) { drawingArea = c; }
         public void actionPerformed( ActionEvent e ) {
            Graphics g = drawingArea.getGraphics();
            g.setColor( Color.red );
            g.fillRect( 0, 0, 300, 300 );
            g.dispose();
      }  }
   
      public GraphicsDemos() {
         this.setBackground( Color.yellow );
         Frame f = new FrameClose( "GraphicsDemos" );
         f.setSize( 300, 300 );
         f.setLayout( new BorderLayout() );
         f.add( this );
         Panel p = new Panel();
         p.setLayout( new GridLayout() );
         p.add( back );
         p.add( fore );
         f.add( p, BorderLayout.SOUTH );
         f.setVisible( true );
         back.addActionListener( new BBL( this ) );
         fore.addActionListener( new FBL( this ) );
      }
      public void paint( Graphics g ) {
         System.out.println( "paint" + count );
         count++;
      }
      public void update( Graphics g ) {
         System.out.print( "update - " );
         super.update( g );
      }
      public static void main( String[] args ) {
         new GraphicsDemos();
   }  }