banner



How To Make Drawing Board In Java

Using Drawing Techniques

Having learned to walk, permit's effort a jog. In this department, we'll look at some techniques for doing fast and flicker-free drawing and painting. If y'all're interested in animation or polish updating, this is for you.

Cartoon operations take time, and time spent cartoon leads to delays and imperfect results. Our goal is to minimize the amount of drawing work we do and, every bit much as possible, to practise that work away from the eyes of the user. To see how to eliminate flicker and blinking problems, we'll look at an application that animates very badly. The good news is that Swing automatically solves a lot of flicker problems.

TerribleFlicker illustrates some of the problems of updating a display. Like many animations, information technology has two parts: a abiding groundwork and a changing object in the foreground. In this example, the background is a checkerboard pattern and the object is a small, scaled prototype we can drag effectually on pinnacle of information technology, equally shown in Figure 17.4. Our outset version of TerribleFlicker lives up to its proper name, doing a very poor job of updating.

The TerribleFlicker application

Figure 17-4. The TerribleFlicker application

//file: TerribleFlicker.coffee import java.awt.*; import coffee.awt.issue.*; import javax.swing.*;  public class TerribleFlicker extends JComponent                              implements MouseMotionListener {   int grid = 10;   int imageX, imageY;   Paradigm image;   int imageWidth = 60, imageHeight = 60;      public TerribleFlicker(Image i) {     image = i;     addMouseMotionListener(this);   }      public void mouseDragged(MouseEvent east) {     imageX = e.getX( );     imageY = e.getY( );     repaint( );   }      public void mouseMoved(MouseEvent due east) {}    public void pigment(Graphics g) {     Graphics2D g2 = (Graphics2D)1000;          int due west = getSize( ).width / grid;     int h = getSize( ).height / grid;     boolean black = faux;     for (int y = 0; y <= filigree; y++)       for (int 10 = 0; x <= filigree; x++) {         g2.setPaint(black ? Color.black : Color.white);         black = !black;         g2.fillRect(x * w, y * h, w, h);       }     g2.drawImage(prototype, imageX, imageY,                  imageWidth, imageHeight, this);   }    public static void main(String[] args) {     String imageFile = "L1-Light.jpg";     if (args.length > 0)       imageFile = args[0];     Image i = Toolkit.getDefaultToolkit( ).getImage(         TerribleFlicker.class.getResource(imageFile));     JFrame f = new JFrame("TerribleFlicker");     Container content = new Panel(new BorderLayout( ));     content.add(new TerribleFlicker(i), BorderLayout.CENTER);     f.setContentPane(content);     f.setSize(300, 300);     f.setLocation(100, 100);     f.addWindowListener(new WindowAdapter( ) {       public void windowClosing(WindowEvent e) { Organisation.exit(0); }     });     f.setVisible(true);   } }

Run the awarding by specifying an image file equally a command-line argument. Endeavour dragging the image; you'll notice both the groundwork and foreground flicker equally they are repeatedly redrawn. What is TerribleFlicker doing, and what is it doing incorrect?

TerribleFlicker is a custom component that is shown in the content pane of a JFrame. In the master( ) method, a TerribleFlicker is created and put in a Panel. The Panel is gear up to exist the content pane of the applet. A Panel is used in place of one of the Swing containers for the purposes of analogy. At the terminate of this department, we'll employ a JPanel, which will have care of all of our problems in 1 dramatic step.

As the mouse is dragged, TerribleFlicker keeps rail of its position in two example variables, imageX and imageY. On each phone call to mouseDragged( ), the coordinates are updated, and repaint( ) is called to ask that the brandish be updated. When paint( ) is called, information technology looks at some parameters, draws the checkerboard pattern to fill the applet's surface area, and finally paints a pocket-sized version of the image at the latest coordinates.

Our kickoff, and biggest, problem is that we are updating, but we have neglected to implement a good strategy. The console that contains TerribleFlicker is using the default implementation of the update( ) method, which looks something like this:

public void update( Graphics g ) {       setColor ( backgroundColor );       fillRect( 0, 0, getSize().width, getSize( ).height );       paint ( g );   }

This method simply clears the display to the groundwork color and calls the paint( ) method, which somewhen calls the paint( ) method of TerribleFlicker. This is almost never the best strategy, but is the merely appropriate default for update( ), which doesn't know how much of the screen we're really going to paint.

Our application paints its own background in its entirety, then we can provide a simpler version of update( ) that doesn't bother to articulate the display. Here's some other example that uses the TerribleFlicker form. This time, nosotros create a Panel with a modified update( ) method:

//file: UpdateFlicker.java import java.awt.*; import java.awt.effect.*; import javax.swing.*;  public class UpdateFlicker {   public static void primary(String[] args) {     String imageFile = "L1-Light.jpg";     if (args.length > 0) imageFile = args[0];     Image i = Toolkit.getDefaultToolkit( ).getImage(         TerribleFlicker.grade.getResource(imageFile));     JFrame f = new JFrame("UpdateFlicker");     Container content = new Panel(new BorderLayout( )) {         public void update(Graphics g) { paint(thousand); }     };     content.add(new TerribleFlicker(i), BorderLayout.Centre);     f.setContentPane(content);     f.setSize(300, 300);     f.setLocation(100, 100);     f.addWindowListener(new WindowAdapter( ) {       public void windowClosing(WindowEvent e) { Organisation.leave(0); }     });     f.setVisible(true);   } }

This awarding works better because we take eliminated 1 big, unnecessary, and (in fact) annoying graphics operation. However, although nosotros have eliminated a fillRect( ) call, we're still doing a lot of wasted cartoon. Most of the groundwork stays the same each time information technology'southward fatigued. Yous might call back of trying to make paint( ) smarter, so that it wouldn't redraw these areas, but remember that paint( ) has to be able to draw the entire scene, because it might be called in situations when the display isn't intact. The solution is to draw only part of the picture whenever the mouse moves.

Limited Redrawing

Whenever the mouse is dragged, TerribleFlicker responds by updating its coordinates and calling repaint( ) . Merely repaint( ) draws the unabridged component. Most of this drawing is unnecessary. It turns out that there's another version of repaint( ) that lets you specify a rectangular surface area that should exist fatigued—in essence, a clipping region.

Why does it help to restrict the drawing surface area? Well, foremost, drawing operations that fall outside of the clipping region are not displayed. If a cartoon operation overlaps the clipping region, we encounter simply the role that's inside. A 2nd result is that, in a good implementation, the graphics context tin recognize drawing operations that fall completely exterior the clipping region and ignore them altogether. Eliminating unnecessary operations can relieve fourth dimension if nosotros're doing something complex, like filling a bunch of polygons. This doesn't save the fourth dimension our application spends calling the drawing methods, but the overhead of calling these kinds of drawing methods is usually negligible compared to the time it takes to execute them. (If we were generating an image pixel by pixel, this would not be the case, equally the calculations would be the major time sink, not the drawing.)

So we tin save time in our application by redrawing only the affected portion of the brandish. We can selection the smallest rectangular surface area that includes both the old image position and the new image position, as shown in Figure 17.5. This is the merely portion of the brandish that really needs to change; everything else stays the same.

Determining the clipping region

Figure 17-5. Determining the clipping region

A smarter algorithm could save even more time by redrawing just those regions that take inverse. However, the simple clipping strategy we've implemented hither can exist applied to many kinds of cartoon, and gives quite expert performance, particularly if the surface area being changed is small.

1 of import thing to annotation is that, in addition to looking at the new position, our updating operation at present has to think the final position at which the image was fatigued. Let's fix our application so it volition use a specified clipping region. To keep this brusk and emphasize the changes, we'll take some liberties with design and make our next instance a subclass of TerribleFlicker. Permit'south call it LimitedFlicker.

//file: LimitedFlicker.coffee import java.awt.*; import java.awt.consequence.*; import javax.swing.*;  public form LimitedFlicker extends TerribleFlicker {   int oldX, oldY;      public LimitedFlicker(Image i) { super(i); }      public void mouseDragged(MouseEvent e) {     imageX = e.getX( );     imageY = e.getY( );     Rectangle r = getAffectedArea(oldX, oldY, imageX, imageY,         imageWidth, imageHeight);     // update merely the affected part of the component     repaint(r);     oldX = imageX;     oldY = imageY;   }      individual Rectangle getAffectedArea(int oldx, int oldy,       int newx, int newy, int width, int height) {      int 10 = Math.min(oldx, newx);      int y = Math.min(oldy, newy);      int w = (Math.max(oldx, newx) + width) - ten;      int h = (Math.max(oldy, newy) + height) - y;      return new Rectangle(10, y, w, h);    }    public static void main(String[] args) {     String imageFile = "L1-Light.jpg";     if (args.length > 0)       imageFile = args[0];     Image i = Toolkit.getDefaultToolkit( ).getImage(         TerribleFlicker.class.getResource(imageFile));     JFrame f = new JFrame("LimitedFlicker");     Container content = new Console(new BorderLayout( )) {         public void update(Graphics thou) { paint(g); }     };     content.add(new LimitedFlicker(i), BorderLayout.CENTER);     f.setContentPane(content);     f.setSize(300, 300);     f.setLocation(100, 100);     f.addWindowListener(new WindowAdapter( ) {       public void windowClosing(WindowEvent e) { System.leave(0); }     });     f.setVisible(true);   } }

You lot may find that LimitedFlicker is significantly faster, though it still flickers. (You might not notice the speed-up on a fast machine.) We'll make one more alter in the side by side department to eliminate that.

Then, what have we inverse? First, we've overridden mouseDragged( ) so that instead of setting the current coordinates of the image, it figures out the expanse that has changed. A new, private method helps it practice this. getAffectedArea( ) takes as arguments the new and one-time coordinates and the width and pinnacle of the prototype. Information technology determines the bounding rectangle as shown in Effigy 17.6, then calls repaint( ) to draw only the affected area of the screen. mouseDragged( ) also saves the current position away, past setting the oldX and oldY variables.

Double Buffering

Now permit'due south get to the virtually powerful technique in our toolbox: double buffering . Double buffering is a technique that fixes our flickering problems completely. It's easy to practice and gives us almost flawless updates. We'll combine it with our clipping technique for better performance. In general, you lot tin can employ double buffering with or without clipping.

Double buffering our display means drawing into an offscreen buffer and then copying our completed work to the display in a single painting operation, as shown in Figure 17.6. It takes the same corporeality of fourth dimension to draw a frame, but double buffering instantaneously updates our display when it's fix.

Double buffering

Figure 17-6. Double buffering

Although y'all could implement this technique yourself, in that location'south not much point—Swing supplies double buffering for complimentary. All you need to do is use a Swing component in a Swing container. Swing takes care of the details. Instead of using an AWT Console as a container, then, let'southward run across how it works with a Swing JPanel:

//file: Smoothie.java import java.awt.*; import java.awt.event.*; import javax.swing.*;  public grade Smoothie {   public static void main(Cord[] args) {     String imageFile = "L1-Low-cal.jpg";     if (args.length > 0)       imageFile = args[0];     Image i = Toolkit.getDefaultToolkit( ).getImage(         TerribleFlicker.course.getResource(imageFile));     JFrame f = new JFrame("Smoothie");     Container content = new JPanel(new BorderLayout( )) {         public void update(Graphics thousand) { paint(g); }     };     content.add(new LimitedFlicker(i), BorderLayout.CENTER);     f.setContentPane(content);     f.setSize(300, 300);     f.setLocation(100, 100);     f.addWindowListener(new WindowAdapter( ) {       public void windowClosing(WindowEvent eastward) { System.exit(0); }     });     f.setVisible(true);   } }

Now, when you elevate the image, y'all shouldn't run into any flickering. The update rate should be about the aforementioned every bit in the previous instance (or marginally slower), but the image should move from position to position without noticeable repainting.

Annotation that we're still limiting repaints, considering we're still using LimitedFlicker. Yous could use TerribleFlicker if you lot want. Information technology should perform a petty more slowly just you lot even so won't run across any flickering.

Offscreen Drawing

In addition to serving as buffers for double buffering, offscreen images are useful for saving circuitous, difficult-to-produce, background information. We'll look at a simple instance: the "doodle pad." DoodlePad is a uncomplicated drawing tool that lets u.s. scribble by dragging the mouse, every bit shown in Figure 17.seven. It draws into an offscreen image; its paint( ) method only copies the epitome to the display area.

The DoodlePad application

Figure 17-7. The DoodlePad application

//file: DoodlePad.java import java.awt.*; import java.awt.event.*; import javax.swing.*;  public form DoodlePad extends JFrame {    public DoodlePad( ) {     super("DoodlePad");     Container content = getContentPane( );     content.setLayout(new BorderLayout( ));     final DrawPad drawPad = new DrawPad( );     content.add(drawPad, BorderLayout.Middle);     JPanel p = new JPanel( );     JButton clearButton = new JButton("Clear");     clearButton.addActionListener(new ActionListener( ) {       public void actionPerformed(ActionEvent e) {         drawPad.articulate( );       }     });     p.add(clearButton);     content.add(p, BorderLayout.South);     setSize(280, 300);     setLocation(100, 100);     addWindowListener(new WindowAdapter( ) {       public void windowClosing(WindowEvent due east) {         System.exit(0);       }     });     setVisible(true);   }    public static void main(String[] args) {     new DoodlePad( );   } } // end of grade DoodlePad  grade DrawPad extends JComponent {   Prototype image;   Graphics2D graphics2D;   int currentX, currentY, oldX, oldY;      public DrawPad( ) {     setDoubleBuffered(false);     addMouseListener(new MouseAdapter( ) {       public void mousePressed(MouseEvent e) {         oldX = e.getX( );         oldY = e.getY( );       }     });     addMouseMotionListener(new MouseMotionAdapter( ) {       public void mouseDragged(MouseEvent e) {         currentX = e.getX( );         currentY = e.getY( );         if (graphics2D != null)           graphics2D.drawLine(oldX, oldY, currentX, currentY);         repaint( );         oldX = currentX;         oldY = currentY;       }     });   }      public void paintComponent(Graphics one thousand) {     if (image == null) {       image = createImage(getSize().width, getSize( ).height);       graphics2D = (Graphics2D)image.getGraphics( );       graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,           RenderingHints.VALUE_ANTIALIAS_ON);       clear( );     }     g.drawImage(image, 0, 0, null);   }      public void clear( ) {     graphics2D.setPaint(Colour.white);     graphics2D.fillRect(0, 0, getSize().width, getSize( ).height);     graphics2D.setPaint(Color.black);     repaint( );   } }

Give it a effort. Draw a nice moose, or a sunset. We just drew a lovely cartoon of Bill Gates. If y'all make a fault, hit the Clear button and first over.

The parts should exist familiar past now. Nosotros have made a type of JComponent called DrawPad. The new DrawPad component uses inner classes to supply handlers for the MouseListener and MouseMotionListener interfaces. Mouse dragging is handled by drawing lines into an offscreen image and calling repaint( ) to update the display. DrawPad'southward paint( ) method simply does a drawImage( ) to copy the offscreen drawing expanse to the display. In this way, DrawPad saves our sketch information.

What is unusual about DrawPad is that it does some drawing outside of paint( ). In this instance, nosotros desire to let the user scribble with the mouse, so nosotros should respond to every mouse movement. Therefore, we practice our piece of work in mouseDragged( ) itself. As a dominion, we should be conscientious well-nigh doing heavy piece of work in effect-handling methods because we don't want to interfere with other tasks the windowing organisation'due south painting thread is performing. In this instance, our line-drawing option should not exist a burden, and our main business organisation is getting every bit close a coupling equally possible betwixt the mouse motion events and the sketch on the screen.

In addition to drawing a line as the user drags the mouse, the mouseDragged( ) handler maintains a set of sometime coordinates, to be used as a starting point for the adjacent line segment. The mousePressed( ) handler resets the old coordinates to the current mouse position whenever the user moves the mouse. Finally, DrawPad provides a clear( ) method that clears the offscreen buffer and calls repaint( ) to update the brandish. The DoodlePad application ties the articulate( ) method to an appropriately labeled push through another anonymous inner grade.

What if we wanted to do something with the image subsequently the user has finished scribbling on it? Every bit we'll run into in the next chapter, we could go the pixel data for the image and work with that. It wouldn't be hard to create a save facility that stores the pixel data and reproduces it after. Recall nearly how you lot might go about creating a networked "bathroom wall," where people could scribble on your spider web pages.

Source: https://www.oreilly.com/library/view/learning-java/1565927184/ch17s08.html

Posted by: robbfarome.blogspot.com

0 Response to "How To Make Drawing Board In Java"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel