Draw a Circle in Java Group

This chapter shows you how y'all can paint your own custom drawing (such every bit graphs, charts, drawings and, in detail, reckoner game avatars) because you cannot discover standard GUI components that meets your requirements. I shall stress that you should attempt to reuse the standard GUI components as far as possible and leave custom graphics every bit the last resort. Nonetheless, custom graphics is crucial in game programming.

Read "Swing Tutorial" trail "Performing Custom Painting".

The java.awt.Graphics Class: Graphics Context and Custom Painting

A graphics context provides the capabilities of cartoon on the screen. The graphics context maintains states such every bit the color and font used in drawing, as well as interacting with the underlying operating system to perform the cartoon. In Coffee, custom painting is done via the coffee.awt.Graphics class, which manages a graphics context, and provides a set up of device-contained methods for drawing texts, figures and images on the screen on dissimilar platforms.

The coffee.awt.Graphics is an abstract class, equally the actual deed of drawing is system-dependent and device-dependent. Each operating platform will provide a subclass of Graphics to perform the actual drawing nether the platform, only adjust to the specification defined in Graphics.

AWT_GraphicsClassDiagram.png

Graphics Class' Drawing Methods

The Graphics class provides methods for drawing iii types of graphical objects:

  1. Text strings: via the drawString() method. Accept note that Organisation.out.println() prints to the system panel, not to the graphics screen.
  2. Vector-graphic primitives and shapes: via methods drawXxx() and fillXxx(), where Xxx could exist Line, Rect, Oval, Arc, PolyLine, RoundRect, or 3DRect.
  3. Bitmap images: via the drawImage() method.
            drawString(String          str, int          xBaselineLeft, int          yBaselineLeft);    drawLine(int          x1, int          y1, int          x2, int          y2); drawPolyline(int[]          xPoints, int[]          yPoints, int          numPoint);    drawRect(int          xTopLeft, int          yTopLeft, int          width, int          height); drawOval(int          xTopLeft, int          yTopLeft, int          width, int          height); drawArc(int          xTopLeft, int          yTopLeft, int          width, int          height, int          startAngle, int          arcAngle); draw3DRect(int          xTopLeft, int,          yTopLeft, int          width, int          acme, boolean          raised); drawRoundRect(int          xTopLeft, int          yTopLeft, int          width, int          elevation, int          arcWidth, int          arcHeight) drawPolygon(int[]          xPoints, int[]          yPoints, int          numPoint);    fillRect(int          xTopLeft, int          yTopLeft, int          width, int          meridian); fillOval(int          xTopLeft, int          yTopLeft, int          width, int          summit); fillArc(int          xTopLeft, int          yTopLeft, int          width, int          height, int          startAngle, int          arcAngle); fill3DRect(int          xTopLeft, int,          yTopLeft, int          width, int          height, boolean          raised); fillRoundRect(int          xTopLeft, int          yTopLeft, int          width, int          height, int          arcWidth, int          arcHeight) fillPolygon(int[]          xPoints, int[]          yPoints, int          numPoint);    drawImage(Image          img, int          xTopLeft, int          yTopLeft, ImageObserver          obs);   drawImage(Image          img, int          xTopLeft, int          yTopLeft, int          width, int          height, ImageObserver          o);          

These drawing methods is illustrated below. The drawXxx() methods draw the outlines; while fillXxx() methods make full the internal. Shapes with negative width and elevation volition non exist painted. The drawImage() will be discussed afterwards.

Graphics_drawXxx.png

Graphics Course' Methods for Maintaining the Graphics Context

The graphic context maintains states (or attributes) such as the current painting color, the current font for drawing text strings, and the electric current painting rectangular expanse (called clip). Y'all tin use the methods getColor(), setColor(), getFont(), setFont(), getClipBounds(), setClip() to get or set the color, font, and clip area. Any painting outside the clip area is ignored.

            void setColor(Colour          c) Color getColor()    void setFont(Font          f) Font getFont()   void setClip(int          xTopLeft, int          yTopLeft, int          width, int          height) void setClip(Shape          rect) public abstract void clipRect(int 10, int y, int width, int height)  Rectangle getClipBounds()   Shape getClip()        

Graphics Class' Other Methods

void clearRect(int          x, int          y, int          width, int          height)     void copyArea(int          x, int          y, int          width, int          height, int          dx, int          dy)     void translate(int          x, int          y)     FontMetrics getFontMetrics() FontMetrics getFontMetrics(Font          f)        

Graphics Coordinate Organisation

Graphics_Coordinates.png

In Coffee Windowing Subsystem (like most of the 2D Graphics systems), the origin (0,0) is located at the top-left corner.

EACH component/container has its own coordinate system, ranging for (0,0) to (width-1, meridian-1) as illustrated.

You can use method getWidth() and getHeight() to recollect the width and height of a component/container. Yous can use getX() or getY() to get the height-left corner (x,y) of this component's origin relative to its parent.

Custom Painting Template

Under Swing, custom painting is usually performed by extending (i.due east., subclassing) a JPanel as the drawing canvas and override the paintComponent(Graphics thousand) method to perform your own drawing with the drawing methods provided by the Graphics class. The Java Windowing Subsystem invokes (calls back) paintComponent(thou) to return the JPanel by providing the current graphics context k, which can be used to invoke the drawing methods. The extended JPanel is often programmed every bit an inner grade of a JFrame application to facilitate access of individual variables/methods. Although we typically draw on the JPanel, you lot tin in fact draw on whatever JComponent (such as JLabel, JButton).

The custom painting code template is as follows:

1 2 iii 4 five six 7 8 9 ten 11 12 thirteen fourteen fifteen 16 17 18 xix 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
import java.awt.*;        import java.awt.event.*;  import javax.swing.*;         public class CGTemplate extends JFrame {       public static last int CANVAS_WIDTH  = 640;    public static last int CANVAS_HEIGHT = 480;         private DrawCanvas canvas;          public CGTemplate() {       canvas = new DrawCanvas();          sheet.setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT));               Container cp = getContentPane();       cp.add(canvas);               setDefaultCloseOperation(EXIT_ON_CLOSE);         pack();                    setTitle("......");        setVisible(true);       }                        private grade DrawCanvas extends JPanel {             @Override       public void paintComponent(Graphics chiliad) {          super.paintComponent(one thousand);              setBackground(Colour.BLACK);                               g.setColor(Color.Yellow);             g.drawLine(30, twoscore, 100, 200);          yard.drawOval(150, 180, ten, 10);          k.drawRect(200, 210, twenty, 30);          g.setColor(Color.RED);                1000.fillOval(300, 310, 30, 50);          g.fillRect(400, 350, threescore, 50);                   g.setColor(Colour.WHITE);          g.setFont(new Font("Monospaced", Font.PLAIN, 12));          g.drawString("Testing custom cartoon ...", 10, 20);       }    }                      public static void main(String[] args) {             SwingUtilities.invokeLater(new Runnable() {          @Override          public void run() {             new CGTemplate();          }       });    } }
Dissecting the Program
  • Custom painting is performed past extending a JPanel (chosen DrawCanvas) and overrides the paintComponent(Graphics g) method to do your own drawing with the cartoon methods provided by the Graphics class.
  • DrawCanvas is designed as an inner grade of this JFrame awarding, so as to facilitate admission of the private variables/methods.
  • Java Windowing Subsystem invokes (calls dorsum) paintComponent(g) to render the JPanel, with the current graphics context in k, whenever there is a need to refresh the display (due east.g., during the initial launch, restore, resize, etc). You can employ the drawing methods (thou.drawXxx() and one thousand.fillXxx()) on the current graphics context g to perform custom painting on the JPanel.
  • The size of the JPanel is set via the setPreferredSize(). The JFrame does not set its size, but packs the components contained via pack().
  • In the primary(), the constructor is called in the result-dispatch thread via static method javax.swing.SwingUtilities.invokeLater() (instead of running in the main thread), to ensure thread-rubber and avert deadlock, as recommended past the Swing developers.
(Avant-garde) Anonymous Inner Class for Drawing Canvas

Instead of a named-inner course called DrawCanvas in the previous case, you tin likewise employ an bearding inner class for the drawing canvas, if the painting code is brusk. For example,

            JPanel canvass = new JPanel() {    @Override    public void paintComponent(Graphics m) {       super.paintComponent(g);          
...... } }; ......
(Advanced) Getting the Graphics Context

Y'all tin think the Graphics context of a JComponent via the getGraphics() method. This is, however, non ordinarily used. For example,

JPanel console = new JPanel(); Graphics graphics = panel.getGraphics();
Custom Painting in AWT (Obsolete)

Under AWT, yous tin can perform custom painting past extending java.awt.Canvass, and override the paint(Graphics g) method, in a java.awt.Frame awarding. Similarly, you can explicitly invoke repaint() to update the graphics.

Refreshing the Brandish via repaint()

At times, we demand to explicitly refresh the display (east.g., in game and blitheness). Nosotros shall Not invoke paintComponent(Graphics) straight. Instead, we invoke the JComponent's repaint() method. The Windowing Subsystem will in plow remember the paintComponent() with the current Graphics context and execute it in the upshot-dispatching thread for thread safety. You lot tin repaint() a detail JComponent (such equally a JPanel) or the unabridged JFrame. The children contained within the JComponent volition besides be repainted.

Colors and Fonts

java.awt.Color

Graphics_StandardColors.png

The class java.awt.Colour provides 13 standard colors equally named-constants. They are: Colour.RED, Green, BLUE, MAGENTA, CYAN, Yellowish, Black, WHITE, Grayness, DARK_GRAY, LIGHT_GRAY, Orange, and PINK. (In JDK one.1, these abiding names are in lowercase, e.k., red. This violates the Java naming convention for constants. In JDK ane.two, the majuscule names are added. The lowercase names were non removed for backward compatibility.)

You can use the toString() to impress the RGB values of these color (e.m., Organization.out.println(Colour.Ruby-red)):

RED       : java.awt.Color[r=255, g=0,   b=0] GREEN     : java.awt.Color[r=0,   one thousand=255, b=0] BLUE      : java.awt.Color[r=0,   g=0,   b=255] Yellow    : java.awt.Color[r=255, g=255, b=0] MAGENTA   : java.awt.Color[r=255, 1000=0,   b=255] CYAN      : java.awt.Color[r=0,   thousand=255, b=255] WHITE     : java.awt.Color[r=255, g=255, b=255] Black     : coffee.awt.Color[r=0,   chiliad=0,   b=0] Grayness      : java.awt.Color[r=128, g=128, b=128] LIGHT_GRAY: coffee.awt.Color[r=192, k=192, b=192] DARK_GRAY : java.awt.Color[r=64,  1000=64,  b=64] Pink      : java.awt.Color[r=255, g=175, b=175] ORANGE    : java.awt.Color[r=255, one thousand=200, b=0]

You can also utilize the RGB values or RGBA value (A for alpha to specify transparency/opaque) to construct your own color via constructors:

Colour(int          r, int          g, int          b);              Color(float          r, float          thou, float          b);        Color(int          r, int          chiliad, int          b, int          alpha);          Color(float          r, float          g, bladder          b, float          blastoff);         

For case:

Color myColor1 = new Color(123, 111, 222); Color myColor2 = new Color(0.5f, 0.3f, 0.1f); Color myColor3 = new Colour(0.5f, 0.3f, 0.1f, 0.5f);        

To retrieve the individual components, you can use getRed(), getGreen(), getBlue(), getAlpha(), etc.

To fix the background and foreground (text) color of a component/container, y'all can invoke:

JLabel label = new JLabel("Test"); label.setBackground(Color.LIGHT_GRAY); label.setForeground(Color.Ruby-red);

To set the color of the Graphics context g (for drawing lines, shapes, and texts), utilise chiliad.setColor(color):

k.setColor(Color.Crimson); g.drawLine(10, xx, 30, twoscore);    Colour myColor = new Color(123, 111, 222); g.setColor(myColor); grand.drawRect(x, 10, xl, 50);           

(Avant-garde) JColorChooser Example

Graphics_JColorChooser.png

This example uses the javax.swing.JColorChooser to ready the groundwork color of the JPanel.

1 2 iii 4 5 6 seven viii 9 10 11 12 13 14 fifteen 16 17 xviii 19 20 21 22 23 24 25 26 27 28 29 xxx 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 l
import coffee.awt.*;         import java.awt.event.*;   import javax.swing.*;         @SuppressWarnings("serial") public class JColorChooserDemo extends JFrame {    JPanel panel;    Colour bgColor = Color.LIGHT_GRAY;            public JColorChooserDemo() {       console = new JPanel(new BorderLayout());         JButton btnColor = new JButton("Change Colour");       panel.add(btnColor, BorderLayout.Due south);       btnColor.addActionListener(new ActionListener() {          @Override          public void actionPerformed(ActionEvent evt) {             Color colour = JColorChooser.showDialog(JColorChooserDemo.this,                   "Choose a color", bgColor);             if (colour != null) {                 bgColor = color;             }             console.setBackground(bgColor);           }       });         setContentPane(panel);         setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);       setTitle("JColorChooser Demo");       setSize(300, 200);       setLocationRelativeTo(null);         setVisible(true);                 }          public static void principal(String[] args) {              SwingUtilities.invokeLater(new Runnable() {          @Override          public void run() {             new JColorChooserDemo();            }       });    } }

java.awt.Font

The grade java.awt.Font represents a specific font face, which tin can be used for rendering texts. You can use the post-obit constructor to construct a Font instance:

public Font(Cord          name, int          style, int          size);         

You lot tin can use the setFont() method to set the current font for the Graphics context m for rendering texts. For example,

Font myFont1 = new Font(Font.MONOSPACED, Font.Patently, 12); Font myFont2 = new Font(Font.SERIF, Font.BOLD | Font.ITALIC, 16);   JButton btn = new JButton("RESET"); btn.setFont(myFont1); JLabel lbl = new JLabel("Hello"); lbl.setFont(myFont2); ...... grand.drawString("In default Font", 10, xx);      Font myFont3 = new Font(Font.SANS_SERIF, Font.ITALIC, 12); thou.setFont(myFont3); g.drawString("Using the font set up", 10, fifty);          
Font's Family Proper name vs. Font Name

A font could accept many faces (or style), e.g., plain, bold or italic. All these faces accept like typographic pattern. The font confront proper name, or font name for short, is the proper noun of a detail font confront, like "Arial", "Arial Bold", "Arial Italic", "Arial Bold Italic". The font family name is the name of the font family that determines the typographic design across several faces, like "Arial". For example,

coffee.awt.Font[family unit=Arial,name=Arial,style=plain,size=ane] java.awt.Font[family=Arial,proper noun=Arial Bold,style=obviously,size=ane] java.awt.Font[family=Arial,proper name=Arial Bold Italic,style=plain,size=1] java.awt.Font[family=Arial,name=Arial Italic,style=plain,size=1]
Logical Font vs. Physical Font

JDK supports these logical font family names: "Dialog", "DialogInput", "Monospaced", "Serif", or "SansSerif". JDK 1.half dozen provides these String constants: Font.DIALOG, Font.DIALOG_INPUT, Font.MONOSPACED, Font.SERIF, Font.SANS_SERIF.

Physical font names are actual font libraries such as "Arial", "Times New Roman" in the system.

GraphicsEnvironment's getAvailableFontFamilyNames() and getAllFonts()

You can use GraphicsEnvironment'south getAvailableFontFamilyNames() to listing all the font famiy names; and getAllFonts() to construct all Font instances (with font size of ane pt). For example,

GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();         Cord[] fontNames = env.getAvailableFontFamilyNames(); for (String fontName : fontNames) {    System.out.println(fontName); }         Font[] fonts = env.getAllFonts(); for (Font font : fonts) {    System.out.println(font); }
Font's deriveFont()

You can utilize Font'southward deriveFont() to derive a new Font instance from this Font with varying size, style and others.

public Font deriveFont(bladder size) public Font deriveFont(int style) public Font deriveFont(AffineTransform trans) public Font deriveFont(int style, float size) public Font deriveFont(int fashion, AffineTransform trans)

For case,

Font font = new Font(Font.MONOSPACED, Font.BOLD, 12); System.out.println(font);     Font fontDerived = font.deriveFont(twenty);  System.out.println(fontDerived);        

(Advanced) java.awt.FontMetrics

The java.awt.FontMetrics class can be used to mensurate the exact width and pinnacle of the cord for a particular font face, so that y'all tin position the string equally you desire (such as at the center of the screen).

To create a FontMetrics, employ getFontMetrics() methods of the Graphics class, equally follows:

            public abstract FontMetrics getFontMetrics(Font f)     public abstruse FontMetrics getFontMetrics()            

Graphics_FontMetrics.png

            public int getHeight() public int getLeading() public int getAscent() public int getDescent()

The well-nigh commonly-used function for FontMetrics is to mensurate the width of a given String displayed in a certain font.

public int stringWidth(String str)        

To centralize a string on the drawing canvass (e.one thousand., JPanel):

public void paintComponent(Graphics thou) {    super.paintComponent(one thousand);    g.setFont(new Font("Arial", Font.BOLD, thirty));        FontMetrics fm = thousand.getFontMetrics();        String msg = "Hi, world!";    int msgWidth = fm.stringWidth(msg);    int msgAscent = fm.getAscent();            int msgX = getWidth() / 2 - msgWidth / 2;    int msgY = getHeight() / two + msgAscent / 2;    g.drawString(msg, msgX, msgY); }

Custom Graphics Examples

Example 1: Moving an Object via Cardinal/Button Activity

Graphics_MoveALine.png

This example illustrates how to re-pigment the screen in response to a KeyEvent or ActionEvent.

The display consists of two JPanel in a JFrame, arranged in BorderLayout. The summit console is used for custom painting; the lesser panel holds two JButton arranged in FlowLayout. Clicking the "Motility Right" or "Move Left" buttons moves the line. The JFrame listens to the "Left-arrow" and "Right-arrow" keys, and responses by moving the line left or right.

1 ii 3 4 5 6 7 8 ix 10 eleven 12 thirteen 14 xv 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
import java.awt.*;        import java.awt.event.*;  import javax.swing.*;      @SuppressWarnings("serial") public class CGMoveALine extends JFrame {        public static last int CANVAS_WIDTH = 400;    public static terminal int CANVAS_HEIGHT = 140;    public static terminal Color LINE_COLOR = Colour.Blackness;    public static terminal Colour CANVAS_BACKGROUND = Colour.CYAN;          private int x1 = CANVAS_WIDTH / two;    private int y1 = CANVAS_HEIGHT / 8;    private int x2 = x1;    private int y2 = CANVAS_HEIGHT / 8 * 7;      private DrawCanvas sheet;           public CGMoveALine() {              JPanel btnPanel = new JPanel(new FlowLayout());       JButton btnLeft = new JButton("Move Left ");       btnPanel.add(btnLeft);       btnLeft.addActionListener(new ActionListener() {          public void actionPerformed(ActionEvent evt) {             x1 -= x;             x2 -= x;             canvas.repaint();             requestFocus();           }       });       JButton btnRight = new JButton("Move Correct");       btnPanel.add together(btnRight);       btnRight.addActionListener(new ActionListener() {          public void actionPerformed(ActionEvent evt) {             x1 += 10;             x2 += ten;             canvas.repaint();             requestFocus();           }       });                canvas = new DrawCanvas();       sail.setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT));                Container cp = getContentPane();       cp.setLayout(new BorderLayout());       cp.add(canvas, BorderLayout.CENTER);       cp.add(btnPanel, BorderLayout.Due south);                addKeyListener(new KeyAdapter() {          @Override          public void keyPressed(KeyEvent evt) {             switch(evt.getKeyCode()) {                instance KeyEvent.VK_LEFT:                   x1 -= 10;                   x2 -= 10;                   repaint();                   break;                instance KeyEvent.VK_RIGHT:                   x1 += 10;                   x2 += 10;                   repaint();                   break;             }          }       });         setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);        setTitle("Move a Line");       pack();                  setVisible(true);        requestFocus();       }          class DrawCanvas extends JPanel {       @Override       public void paintComponent(Graphics m) {          super.paintComponent(g);          setBackground(CANVAS_BACKGROUND);          1000.setColor(LINE_COLOR);          g.drawLine(x1, y1, x2, y2);        }    }          public static void master(Cord[] args) {              SwingUtilities.invokeLater(new Runnable() {          @Override          public void run() {             new CGMoveALine();           }       });    } }
Dissecting the Program
  • To practise custom painting, yous have to decide which superclass to use. It is recommended that y'all use a JPanel (or a more specialized Swing component such as JButton or JLabel). In this instance, we extend the JPanel to do our custom painting, in an inner class, as follows:
    class DrawCanvas extends JPanel {    @Override       public void paintComponent(Graphics m) {       super.paintComponent(g);         setBackground(CANVAS_BACKGROUND);       g.setColor(LINE_COLOR);       g.drawLine(x1, y1, x2, y2);    } }
  • The paintComponent() method is overridden to provide the custom drawing codes. We use the drawLine() method to depict a line from (x1,y1) to (x2, y2).
  • The paintComponent() method cannot be called directly from your code, because it requires a Graphics object as argument.
  • paintComponent() is a so-called "call-back" method. The Windowing subsystem invokes this method and provides a pre-configured Graphics object to stand for its country (e.g., current colour, font, clip area and etc). There are two kinds of painting: system-triggered painting and application-triggered painting. In a system-trigger painting, the system request a component to render its content when the component is first made visible on the screen, or the component is resized, or the component is damaged that needs to be repaint. In an application-triggered painting, the awarding invokes a repaint() request. Nether both cases, the Windowing subsystem will call-back the paintComponent() to render the contents of the component with a proper Graphics object as statement.
  • In this case, the application requests for a repaint() in the KeyEvent and MouseEvent handlers, which triggers the paintComponent() with an appropriate Graphics object every bit the argument.
  • To be precise, when you lot invoke the repaint() method to repaint a JComponent, the Windowing subsystem calls-back paint() method. The paint() method and then calls-dorsum three methods: paintComponent(), paintBorder() and paintChilden().
  • In the overridden paintComponent() method, we call super.paintComponent() to paint the background of the JComponent. If this phone call is omitted, you must either pigment the background yourself (via a fillRect() call) or use setOpaque(false) to make the JComponent transparent. This will inform Swing arrangement to paint those JComponents backside the transparent component.
  • We choose the JFrame as the source of the KeyEvent. JFrame shall exist "in focus" when the central is pressed. The requestFocus() method (of "this" JFrame) is invoked to request for the keyboard focus.

[TODO]: may need to revise.

Try

Graphics_MoveABall.png

Modifying the programme to motion a ball in response to up/downwards/left/correct buttons, besides every bit the four arrow and "wasd" keys , as shown:

Instance 2: Moving Sprites

In game programming, nosotros take moving game objects called sprites. Each sprite is normally modeled in its own grade, with its own properties, and information technology tin can paint itself.

Sprite.java

This class models a sprite, with its own backdrop, and it tin can paint itself via the paint() method provided given a Graphics context. A rectangle is used here.

1 2 3 4 5 6 7 8 9 ten xi 12 13 fourteen 15 16 17 eighteen nineteen 20 21 22 23 24 25
import java.awt.*;     public form Sprite {        int ten, y, width, peak;     Colour colour = Color.RED;           public Sprite(int x, int y, int width, int height, Color color) {       this.x = x;       this.y = y;       this.width = width;       this.height = height;       this.color = color;    }          public void pigment(Graphics thousand) {       k.setColor(color);       g.fillRect(x, y, width, superlative);     } }
MoveASprite.java

Instead of repainting the entire display, we just repaint the affected areas (clips), for efficiency, via the repaint(x, y, width, height) method. In moveLeft() and moveRight(), we save united states, move the object, repaint the saved clip-area with the background color, and repaint the new clip-surface area occupied past the sprite. Repainting is washed by asking the sprite to paint itself at the new location, and erase from the old location.

i 2 3 4 v 6 7 8 9 x 11 12 13 14 xv 16 17 eighteen xix 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 twoscore 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
import java.awt.*;        import java.awt.consequence.*;  import javax.swing.*;      public class CGMoveASprite extends JFrame {        public static final int CANVAS_WIDTH = 400;    public static final int CANVAS_HEIGHT = 140;    public static final Color CANVAS_BG_COLOR = Color.CYAN;      private DrawCanvas canvas;     private Sprite sprite;               public CGMoveASprite() {              sprite = new Sprite(CANVAS_WIDTH / two - 5, CANVAS_HEIGHT / 2 - 40,             10, 80, Color.RED);                JPanel btnPanel = new JPanel(new FlowLayout());       JButton btnLeft = new JButton("Motion Left ");       btnPanel.add(btnLeft);       btnLeft.addActionListener(new ActionListener() {          @Override          public void actionPerformed(ActionEvent evt) {             moveLeft();             requestFocus();           }       });       JButton btnRight = new JButton("Move Right");       btnPanel.add together(btnRight);       btnRight.addActionListener(new ActionListener() {          @Override          public void actionPerformed(ActionEvent evt) {             moveRight();             requestFocus();           }       });                canvas = new DrawCanvas();       sail.setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT));                Container cp = getContentPane();       cp.setLayout(new BorderLayout());       cp.add(canvas, BorderLayout.Middle);       cp.add together(btnPanel, BorderLayout.Due south);                addKeyListener(new KeyAdapter() {          @Override          public void keyPressed(KeyEvent evt) {             switch(evt.getKeyCode()) {                case KeyEvent.VK_LEFT:  moveLeft();  break;                case KeyEvent.VK_RIGHT: moveRight(); break;             }          }       });         setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);       setTitle("Move a Sprite");       pack();                   setVisible(true);         requestFocus();        }          private void moveLeft() {              int savedX = sprite.10;              sprite.x -= 10;              canvas.repaint(savedX, sprite.y, sprite.width, sprite.meridian);        canvas.repaint(sprite.x, sprite.y, sprite.width, sprite.height);     }          private void moveRight() {              int savedX = sprite.x;              sprite.x += 10;              canvas.repaint(savedX, sprite.y, sprite.width, sprite.height);        sheet.repaint(sprite.10, sprite.y, sprite.width, sprite.height);     }          class DrawCanvas extends JPanel {       @Override       public void paintComponent(Graphics g) {          super.paintComponent(g);          setBackground(CANVAS_BG_COLOR);          sprite.paint(g);         }    }          public static void main(String[] args) {              SwingUtilities.invokeLater(new Runnable() {          @Override          public void run() {             new CGMoveASprite();           }       });    } }

Example 3: Paint

Graphics_MyPaint.gif

MyPaint.java
1 2 3 4 5 half dozen 7 8 ix 10 eleven 12 13 fourteen 15 16 17 eighteen nineteen 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 forty 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
import coffee.util.List; import java.util.ArrayList; import java.awt.*;        import java.awt.effect.*;  import javax.swing.*;        public class MyPaint extends JFrame {        public static concluding int CANVAS_WIDTH = 500;    public static terminal int CANVAS_HEIGHT = 300;    public static final Color LINE_COLOR = Colour.Ruby;          private List<PolyLine> lines = new ArrayList<PolyLine>();    private PolyLine currentLine;            public MyPaint() {       DrawCanvas sheet = new DrawCanvas();       canvas.setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT));       sail.addMouseListener(new MouseAdapter() {          @Override          public void mousePressed(MouseEvent evt) {                          currentLine = new PolyLine();             lines.add(currentLine);             currentLine.addPoint(evt.getX(), evt.getY());          }       });       canvas.addMouseMotionListener(new MouseMotionAdapter() {          @Override          public void mouseDragged(MouseEvent evt) {             currentLine.addPoint(evt.getX(), evt.getY());             repaint();            }       });         setContentPane(canvas);       setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);       setTitle("Paint");       pack();       setVisible(true);    }          private form DrawCanvas extends JPanel {       @Override       protected void paintComponent(Graphics g) {           super.paintComponent(g);          g.setColor(LINE_COLOR);          for (PolyLine line: lines) {             line.draw(thousand);          }       }    }          public static void main(String[] args) {       SwingUtilities.invokeLater(new Runnable() {                    @Override          public void run() {             new MyPaint();           }       });    } }
PolyLine.java
1 2 three iv five vi 7 8 ix x 11 12 13 fourteen fifteen 16 17 18 nineteen 20 21 22 23 24 25 26 27 28 29
import java.awt.Graphics; import java.util.*;  public class PolyLine {    private List<Integer> xList;      private List<Integer> yList;            public PolyLine() {       xList = new ArrayList<Integer>();       yList = new ArrayList<Integer>();    }          public void addPoint(int x, int y) {       xList.add(10);       yList.add together(y);    }          public void describe(Graphics thousand) {        for (int i = 0; i < xList.size() - i; ++i) {          g.drawLine((int)xList.go(i), (int)yList.get(i), (int)xList.get(i + 1),                (int)yList.get(i + 1));       }    } }
Dissecting the Program

[TODO]

Drawing Images

javax.swing.ImageIcon

The javax.swing.ImageIcon class represents an icon, which is a fixed-size picture, typically small-size and used to decorate components. To create an ImageIcon:

            String imgNoughtFilename = "images/nought.gif"; ImageIcon iconNought = null; URL imgURL = getClass().getClassLoader().getResource(imgNoughtFilename); if (imgURL != nix) {    iconNought = new ImageIcon(imgURL); } else {    Arrangement.err.println("Couldn't detect file: " + imgNoughtFilename); }

Graphics Class' drawImage()

ImageIcon is fixed-in-sized and cannot be resized in brandish. You can utilise Graphics's drawImage() to resize a source image in display.

The java.awt.Graphics class declares six overloaded versions of abstruse method drawImage().

public abstract boolean drawImage(Image          img, int          x, int          y, ImageObserver          observer) public abstract boolean drawImage(Prototype          img, int          10, int          y, int          width, int          height, ImageObserver          observer) public abstract boolean drawImage(Image          img, int          x, int          y, Color          bgcolor, ImageObserver          observer) public abstruse boolean drawImage(Image          img, int          x, int          y, int          width, int          height, Colour          bgcolor, ImageObserver          observer)      public abstract boolean drawImage(Image          img, int          destX1, int          destY1, int          destX2, int          destY2,       int          srcX1, int          srcY1, int          srcX2, int          srcY2, ImageObserver          observer) public abstruse boolean drawImage(Image          img, int          destX1, int          destY1, int          destX2, int          destY2,       int          srcX1, int          srcY1, int          srcX2, int          srcY2, Color          bgcolor, ImageObserver          observer)        

Graphics_DrawImage.png

The coordinates involved is shown in the above diagram. The ImageObserver receives notification about the Image as information technology is loaded. In nearly purposes, you lot tin set up it to goose egg or this.

The drawImage() method requires an Image instance, which can be obtained via ImageIcon's getImage() method; or via static method ImageIO.read() (read "Reading Images into your plan"). For example,

            ImageIcon icon = null; String imgFilename = "images/duke.gif"; coffee.net.URL imgURL = getClass().getClassLoader().getResource(imgFilename); if (imgURL != zippo) {          icon =  new ImageIcon(imgURL); } else {    Organization.err.println("Couldn't find file: " + imgFilename); }    final          Epitome img = icon.getImage();  JLabel lbl4 = new JLabel() {    @Override public void paintComponent(Graphics grand) {       super.paintComponent(thousand);         grand.drawImage(img, 0, 0, 200, 200, null);    } }; lbl4.setPreferredSize(new Dimension(200, 200)); cp.add(lbl4);

Example

Graphics_DrawImageDemo.png

Images:

cross.gif nought.gif

i 2 iii 4 5 6 7 8 nine 10 11 12 13 14 fifteen 16 17 18 xix twenty 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
import java.awt.*;      import java.net.URL; import javax.swing.*;   import java.util.Random;    @SuppressWarnings("serial") public course CGDrawImageDemo extends JFrame {        public static final int ROWS = 3;    public static final int COLS = 3;    public static final int IMAGE_SIZE = 50;    public static final int PADDING = xx;      public static last int CELL_SIZE = IMAGE_SIZE + 2 * PADDING;    public static final int CANVAS_SIZE = CELL_SIZE * ROWS;      individual DrawCanvas sail;        private Random random = new Random();           individual String imgCrossFilename = "images/cross.gif";    private Cord imgNoughtFilename = "images/nought.gif";    private Paradigm imgCross;       individual Prototype imgNought;          public CGDrawImageDemo() {              ImageIcon iconCross = null;       ImageIcon iconNought = null;       URL imgURL = getClass().getClassLoader().getResource(imgCrossFilename);       if (imgURL != null) {          iconCross = new ImageIcon(imgURL);       } else {          System.err.println("Couldn't discover file: " + imgCrossFilename);       }       imgCross = iconCross.getImage();         imgURL = getClass().getClassLoader().getResource(imgNoughtFilename);       if (imgURL != nix) {          iconNought = new ImageIcon(imgURL);       } else {          Organisation.err.println("Couldn't notice file: " + imgNoughtFilename);       }       imgNought = iconNought.getImage();         sail = new DrawCanvas();       canvas.setPreferredSize(new Dimension(CANVAS_SIZE, CANVAS_SIZE));       setContentPane(sheet);         setDefaultCloseOperation(EXIT_ON_CLOSE);       pack();         setTitle("Test drawImage()");       setVisible(true);    }          individual course DrawCanvas extends JPanel {       @Override       public void paintComponent(Graphics chiliad) {          super.paintComponent(1000);          setBackground(Color.WHITE);                      for (int row = 0; row < ROWS; ++row) {             for (int col = 0; col < COLS; ++col) {                boolean useCross = random.nextBoolean();                Prototype img = useCross ? imgCross : imgNought;                g.drawImage(img,                      CELL_SIZE * col + PADDING, CELL_SIZE * row + PADDING,                      IMAGE_SIZE, IMAGE_SIZE, null);             }          }                    g.fill3DRect(CELL_SIZE - 2, 0, 4, CELL_SIZE * three, truthful);          yard.fill3DRect(CELL_SIZE * two - two, 0, iv, CELL_SIZE * 3, true);          thousand.fill3DRect(0, CELL_SIZE - ii, CELL_SIZE * three, iv, true);          g.fill3DRect(0, CELL_SIZE * 2 - 2, CELL_SIZE * 3, 4, true);       }    }          public static void main(String[] args) {              SwingUtilities.invokeLater(new Runnable() {          @Override          public void run() {             new CGDrawImageDemo();           }       });    } }

This example places absolute numbers in the draw methods, which is difficult to maintain and reuse. Yous should define name-constants such as CELL_WIDTH, BORDER_WIDTH, etc, and compute the numbers based on these constants.

Animation

Blitheness using javax.swing.Timer

Graphics_BouncingBall.png

Creating an animation (such as a bouncing brawl) requires repeatedly running an updating chore at a regular interval. Swing provides a javax.swing.Timer class which can be used to fire ActionEvent to its registered ActionListeners at regular interval.

The Timer grade has one constructor:

public Timer(int          delay, ActionListener          listener)

Y'all are required to override the actionPerformed() method of the ActionListener to specify your task'southward beliefs. The Timer fires an ActionEvent to the ActionListener later the (initial) filibuster, and and so at regular interval afterwards delay.

You can start and finish the Timer via the Timer'southward start() and stop() methods. For example,

int delay = 500;   ActionListener updateTask = new ActionListener() {    @Override    public void actionPerformed(ActionEvent evt) {           } };  new Timer(filibuster, updateTask).commencement();

You lot tin can employ method setRepeats(imitation) to prepare the Timer to fire only once, after the delay. You tin can ready the initial filibuster via setInitialDelay() and regular filibuster via setDelay().

A Timer can fire the ActionEvent to more than 1 ActionListenersouth. Yous can annals more ActionListeners via the addActionListener() method.

The actionPerformed() runs on the event-dispatching thread, simply like all the result handlers. You can be relieved of the multi-threading bug.

JDK 1.3 introduced some other timer course called coffee.util.Timer, which is more general, but javax.swing.Timer is sufficient (and easier) to run animation in Swing application.

Instance: A Bouncing Ball
1 2 3 4 5 6 7 viii 9 x 11 12 13 14 15 16 17 eighteen 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 twoscore 41 42 43 44 45 46 47 48 49 fifty 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
import coffee.awt.*;        import java.awt.effect.*;  import javax.swing.*;        @SuppressWarnings("serial") public class CGBouncingBallSwingTimer extends JFrame {        individual static last int CANVAS_WIDTH = 640;    private static final int CANVAS_HEIGHT = 480;                  individual static concluding int UPDATE_PERIOD = 50;                        private DrawCanvas canvas;            individual int x = 100, y = 100;      individual int size = 250;            individual int xSpeed = 3, ySpeed = five;           public CGBouncingBallSwingTimer() {       canvas = new DrawCanvas();       canvas.setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT));       this.setContentPane(sail);       this.setDefaultCloseOperation(EXIT_ON_CLOSE);       this.pack();       this.setTitle("Bouncing Ball");       this.setVisible(true);                           ActionListener updateTask = new ActionListener() {          @Override          public void actionPerformed(ActionEvent evt) {             update();                                    repaint();                                    }       };                                    new Timer(UPDATE_PERIOD, updateTask).beginning();                  }          public void update() {       10 += xSpeed;       y += ySpeed;       if (x > CANVAS_WIDTH - size || 10 < 0) {          xSpeed = -xSpeed;       }       if (y > CANVAS_HEIGHT - size || y < 0) {          ySpeed = -ySpeed;       }    }          individual course DrawCanvas extends JPanel {       @Override       public void paintComponent(Graphics g) {          super.paintComponent(g);            setBackground(Colour.Black);          1000.setColor(Colour.BLUE);          thousand.fillOval(x, y, size, size);         }    }          public static void chief(String[] args) {              SwingUtilities.invokeLater(new Runnable() {          @Override          public void run() {             new CGBouncingBallSwingTimer();           }       });    } }

javax.swing.Timer does not provide very accurate timing due to the overhead of issue-handling. Information technology probaly cannot be used for real-fourth dimension application such as displaying a clock.

[TODO] Terminate the Timer later x steps

(Avant-garde) Animation using a new Thread

Graphics_BouncingBall.png

Animation unremarkably involves multi-threading, and so that the GUI refreshing operations does not interfere with the programming logic. Multi-threading is an advanced topics. Read "Multithreading & Concurrent Programming"

In the previous example, we use javax.swing.Timer, which run the updating task at regular interval on the event-dispatching thread. In this example, we shall create a new thread to run the update.

To create a new thread, define a (anonymous and inner) subclass of Thread and override the run() method to specify the behavior of the task. Create an instance and start the example via the start() method, which calls back the run() defined earlier.

To ensure the new thread does not starve the other threads, in particular the event-dispatching thread, the thread shall yield control via the sleep(mills) method, which also provides the necessary filibuster.

1 2 iii 4 five 6 vii viii 9 x xi 12 13 14 15 xvi 17 eighteen nineteen 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
import java.awt.*;     import javax.swing.*;     public class CGBouncingBall extends JFrame {        private static final int CANVAS_WIDTH = 640;    private static final int CANVAS_HEIGHT = 480;                  individual static final int UPDATE_INTERVAL = l;                                    private DrawCanvas sail;            private int x = 100;         individual int y = 100;    private int size = 250;      private int xSpeed = iii;      private int ySpeed = 5;            public CGBouncingBall() {       canvas = new DrawCanvas();       canvas.setPreferredSize(new Dimension(CANVAS_WIDTH, CANVAS_HEIGHT));       this.setContentPane(canvas);       this.setDefaultCloseOperation(EXIT_ON_CLOSE);       this.pack();       this.setTitle("Bouncing Ball");       this.setVisible(truthful);                                      Thread updateThread = new Thread() {          @Override          public void run() {             while (true) {                update();                   repaint();                  endeavour {                                      Thread.sleep(UPDATE_INTERVAL);                  } take hold of (InterruptedException ignore) {}             }          }       };       updateThread.starting time();                                    }          public void update() {       ten += xSpeed;       y += ySpeed;       if (x > CANVAS_WIDTH - size || x < 0) {          xSpeed = -xSpeed;       }       if (y > CANVAS_HEIGHT - size || y < 0) {          ySpeed = -ySpeed;       }    }          class DrawCanvas extends JPanel {       @Override       public void paintComponent(Graphics g) {          super.paintComponent(one thousand);            setBackground(Colour.BLACK);          g.setColor(Color.BLUE);          m.fillOval(x, y, size, size);         }    }          public static void master(Cord[] args) {              SwingUtilities.invokeLater(new Runnable() {          @Override          public void run() {             new CGBouncingBall();           }       });    } }
  • To update the display regularly, nosotros explicitly invoke the repaint() method of the JFrame, which will callback the paintComponent(one thousand) of all the components contained in this JFrame.
  • The display refreshing code is run in its own thread, and so as to avoid the infamous unresponsive user interface problem. Information technology is programmed every bit an anonymous inner class, extends class Thread, by overriding the run() method to provide the programmed operations (i.e., repaint()). The offset() method is use to start the thread, which will callback the run().
  • Inside the overridden run(), the repaint() is programmed inside an space loop, followed by a Thread.sleep(milliseconds) method, which suspends the thread for the given milliseconds. This functioning provides the necessary delay and also yield command to other thread to perform their intended operations.

[TODO] Stopping the thread afterward 10 steps

(Advanced) A Closer Look at repaint()

Reference: "Painting in AWT and Swing" @ http://world wide web.oracle.com/technetwork/coffee/painting-140037.html. I summarize some of the important points hither.

Heavyweight AWT Components vs. Lightweight Swing Components

The original AWT components are heavyweight components. "Heavyweight" ways that the component has it'southward own opaque native window. Heavyweight components, such equally java.awt.Push, are mapped to the platform-specific components. Information technology relies on the windowing subsystem in each native platform to accept care of details such as damage detection, clip adding, and z-ordering. On the other hand, the newer Swing JComponentsouth (such equally javax.swing.JButton) are lightweight components. A "lightweight" component does non own its screen resources but reuses the native window of its closest heavyweight antecedent. Swing JComponents do not rely on the native platform and are written purely in Coffee, . The pinnacle-level containers, such as JFrame, JApplet and JDialog, which are non subclass of JComponent, remain heavyweight. It is because the lightweight Swing JComponents need to attach to a heavyweight ancestor.

Painting Mechanism

Painting is carried out via a "call-back" machinery. A program shall put its painting codes in a overridden method (paint() for AWT components or paintComponent() for Swing component), and the windowing subsystem volition retrieve this method when it's time to pigment.

System-triggered vs. Awarding-triggered Painting Requests

There are two types of paint (or repaint) requests:

  1. System-triggered: e.yard., the component is first made visible, the componet is resized, etc. The windowing subsystem will schedule paint() or paintComponent() on the upshot-dispatching thread.
  2. Application-triggered: application has modified the appearance of the component and requested to repaint the component. Withal, Awarding shall non invoke pigment() or paintComponent() directly. Instead, information technology shall invoke a special method chosen repaint(), which will in turn invoke paint() or paintComponent(). Multiple repaint() requests may be collapsed into a single paint() call.

Instead of issuing repaint() to paint the entire component, for efficiency, y'all can selectively repaint a rectangular clip expanse. Y'all can also specify a maximum time limit for painting to take place.

public void repaint()        public void repaint(long timeMax)          public void repaint(int x, int y, int width, int height)          public void repaint(long timeMax, int x, int y, int width, int meridian)        
Painting the Lightweight Swing Components

A lightweight needs a heavyweight somewhere up the containment hierarchy in lodge to accept a identify to pigment, equally only heavyweight components accept their own opaque window. When this heavyweight antecedent is asked to pigment its window, information technology must too paint all of its lightweight descendants. This is handled by java.awt.Container's paint() method, which calls paint() on any of its visible, lightweight children which intersect with the rectangle to be painted. Hence, information technology is crucial for all Container subclasses (lightweight or heavyweight) that override paint() to place a super.paint() call in the paint() method. This super.paint() call invoke Container's (super) pigment() method, which in turn invoke pigment() on all its descendants. If the super.pigment() phone call is missing, some of the lightweight descendants will exist shown upward.

Opaque and Transparent

Lightweight components does not ain its opaque window and "borrow" the screen real manor of its heavyweight antecedent. As a result, they could exist made transparent, by leaving their background pixels unpainted to allow the underlying component to show through.

To ameliorate performance of opaque components, Swing adds a property called opaque to all JComponentsouth. If opaque is set to true, the component agrees to paint all of the pixels contained within its rectangular premises. In order words, the windowing subsystem does not have to exercise anything within these bounds such as painting its ancestors. It opaque is prepare to false, the component makes no guarantees about painting all the bits inside its rectangular bounds, and the windowing subsystem has more work to do.

Swing farther factor the paint() method into 3 methods, which are invoked in the following order:

protected void paintComponent(Graphics 1000) protected void paintBorder(Graphics thousand) protected void paintChildren(Graphics g)

Swing programs should override paintComponent() instead of paint().

Most of the standard Swing components (in item, JPanel) have their expect and feel implemented by carve up look-and-feel objects (chosen "UI delegates") for Swing's Pluggable look and feel feature. This means that about or all of the painting for the standard components is delegated to the UI delegate and this occurs in the following way:

  1. paint() invokes paintComponent().
  2. If the ui property is non-zilch, paintComponent() invokes ui.update().
  3. If the component's opaque property is true, ui.udpate() fills the component's background with the background color and invokes ui.paint().
  4. ui.paint() renders the content of the component.

This ways that subclasses of Swing components which have a UI delegate (such every bit JPanel), should invoke super.paintComponent() within their overridden paintComponent(), so that ui.update() fills the background (of the superclass such as JPanel) provided opaque is truthful.

public class MyPanel extends JPanel {    @Override    protected void paintComponent(Graphics g) {       // Let UI delegate paint first        // (including background filling, if I'yard opaque)              super.paintComponent(g);                // paint my contents next....    } }

Endeavor removing the super.paintComponent() from a Swing plan that does animation (e.g., bouncing ball). The groundwork will not exist painted, and the previous screen may not be cleared. You can also paint the background yourself by filling a Rectangle with background color.

@Override protected void paintComponent(Graphics g) {    g.setColor(backgroundColor);    k.fillRect(0, 0, getWidth() - ane, getHeight() - 1); }

Furthermore, if you lot gear up the opaque to faux (via setOpaque(false)) for the subclass of JPanel, the super.paintComponent(g) does non make full the background.

REFERENCES & Resources

  • "The Swing Tutorial" @ http://docs.oracle.com/javase/tutorial/uiswing/, in particular, the section on "Performing Custom Graphics".
  • "Painting in AWT and Swing" @ http://www.oracle.com/technetwork/java/painting-140037.html.

thorntongracts1957.blogspot.com

Source: https://www3.ntu.edu.sg/home/ehchua/programming/java/J4b_CustomGraphics.html

0 Response to "Draw a Circle in Java Group"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel