epubjs
Version:
Render ePub documents in the browser, across many devices
335 lines (313 loc) • 45.3 kB
HTML
<html xmlns="http://www.w3.org/1999/xhtml"><head><title>Drawing Techniques</title><link rel="stylesheet" href="core.css" type="text/css"/><meta name="generator" content="DocBook XSL Stylesheets V1.74.0"/></head><body><div class="sect1" title="Drawing Techniques"><div class="titlepage"><div><div><h1 class="title"><a id="learnjava3-CHP-20-SECT-8"/>Drawing Techniques</h1></div></div></div><p>Now that we’ve learned about the basic tools, let’s put a few of
them together. In this section, we’ll look at some techniques for doing
fast and flicker-free drawing and painting. If you’re interested in
animation, this is for you. Drawing operations take time, and time spent
drawing leads to delays and imperfect results. Our goals are to minimize
the amount of drawing work we do and, as much as possible, to do that work
away from the eyes of the user. To do this, we use two techniques:
clipping and double buffering. Fortunately, Swing now handles double
buffering by default. You won’t have to implement this logic on your own,
but it’s useful to understand it.</p><p>Our first example, <code class="literal">DragImage</code>,
illustrates some of the issues in updating a display. Like many
animations, it has two parts: a constant background and a changing object
in the foreground. In this case, the background is a checkerboard pattern,
and the object is a small, scaled that image we can drag around on top of
it, as shown in <a class="xref" href="ch20s08.html#learnjava3-CHP-20-FIG-4" title="Figure 20-4. The DragImage application">Figure 20-4</a>:</p><a id="I_20_tt1166"/><pre class="programlisting"> <code class="kn">import</code> <code class="nn">java.awt.*</code><code class="o">;</code>
<code class="kn">import</code> <code class="nn">java.awt.event.*</code><code class="o">;</code>
<code class="kn">import</code> <code class="nn">javax.swing.*</code><code class="o">;</code>
<code class="kd">public</code> <code class="kd">class</code> <code class="nc">DragImage</code> <code class="kd">extends</code> <code class="n">JComponent</code>
<code class="kd">implements</code> <code class="n">MouseMotionListener</code>
<code class="o">{</code>
<code class="kd">static</code> <code class="kt">int</code> <code class="n">imageWidth</code><code class="o">=</code><code class="mi">60</code><code class="o">,</code> <code class="n">imageHeight</code><code class="o">=</code><code class="mi">60</code><code class="o">;</code>
<code class="kt">int</code> <code class="n">grid</code> <code class="o">=</code> <code class="mi">10</code><code class="o">;</code>
<code class="kt">int</code> <code class="n">imageX</code><code class="o">,</code> <code class="n">imageY</code><code class="o">;</code>
<code class="n">Image</code> <code class="n">image</code><code class="o">;</code>
<code class="kd">public</code> <code class="nf">DragImage</code><code class="o">(</code><code class="n">Image</code> <code class="n">i</code><code class="o">)</code> <code class="o">{</code>
<code class="n">image</code> <code class="o">=</code> <code class="n">i</code><code class="o">;</code>
<code class="n">addMouseMotionListener</code><code class="o">(</code><code class="k">this</code><code class="o">);</code>
<code class="o">}</code>
<code class="kd">public</code> <code class="kt">void</code> <code class="nf">mouseDragged</code><code class="o">(</code><code class="n">MouseEvent</code> <code class="n">e</code><code class="o">)</code> <code class="o">{</code>
<code class="n">imageX</code> <code class="o">=</code> <code class="n">e</code><code class="o">.</code><code class="na">getX</code><code class="o">();</code>
<code class="n">imageY</code> <code class="o">=</code> <code class="n">e</code><code class="o">.</code><code class="na">getY</code><code class="o">();</code>
<code class="n">repaint</code><code class="o">();</code>
<code class="o">}</code>
<code class="kd">public</code> <code class="kt">void</code> <code class="nf">mouseMoved</code><code class="o">(</code><code class="n">MouseEvent</code> <code class="n">e</code><code class="o">)</code> <code class="o">{}</code>
<code class="kd">public</code> <code class="kt">void</code> <code class="nf">paint</code><code class="o">(</code><code class="n">Graphics</code> <code class="n">g</code><code class="o">)</code> <code class="o">{</code>
<code class="n">Graphics2D</code> <code class="n">g2</code> <code class="o">=</code> <code class="o">(</code><code class="n">Graphics2D</code><code class="o">)</code><code class="n">g</code><code class="o">;</code>
<code class="kt">int</code> <code class="n">w</code> <code class="o">=</code> <code class="n">getSize</code><code class="o">().</code><code class="na">width</code> <code class="o">/</code> <code class="n">grid</code><code class="o">;</code>
<code class="kt">int</code> <code class="n">h</code> <code class="o">=</code> <code class="n">getSize</code><code class="o">().</code><code class="na">height</code> <code class="o">/</code> <code class="n">grid</code><code class="o">;</code>
<code class="kt">boolean</code> <code class="n">black</code> <code class="o">=</code> <code class="kc">false</code><code class="o">;</code>
<code class="k">for</code> <code class="o">(</code><code class="kt">int</code> <code class="n">y</code> <code class="o">=</code> <code class="mi">0</code><code class="o">;</code> <code class="n">y</code> <code class="o"><=</code> <code class="n">grid</code><code class="o">;</code> <code class="n">y</code><code class="o">++)</code>
<code class="k">for</code> <code class="o">(</code><code class="kt">int</code> <code class="n">x</code> <code class="o">=</code> <code class="mi">0</code><code class="o">;</code> <code class="n">x</code> <code class="o"><=</code> <code class="n">grid</code><code class="o">;</code> <code class="n">x</code><code class="o">++)</code> <code class="o">{</code>
<code class="n">g2</code><code class="o">.</code><code class="na">setPaint</code><code class="o">(</code><code class="n">black</code> <code class="o">?</code> <code class="n">Color</code><code class="o">.</code><code class="na">black</code> <code class="o">:</code> <code class="n">Color</code><code class="o">.</code><code class="na">white</code><code class="o">);</code>
<code class="n">black</code> <code class="o">=</code> <code class="o">!</code><code class="n">black</code><code class="o">;</code>
<code class="n">g2</code><code class="o">.</code><code class="na">fillRect</code><code class="o">(</code><code class="n">x</code> <code class="o">*</code> <code class="n">w</code><code class="o">,</code> <code class="n">y</code> <code class="o">*</code> <code class="n">h</code><code class="o">,</code> <code class="n">w</code><code class="o">,</code> <code class="n">h</code><code class="o">);</code>
<code class="o">}</code>
<code class="n">g2</code><code class="o">.</code><code class="na">drawImage</code><code class="o">(</code><code class="n">image</code><code class="o">,</code> <code class="n">imageX</code><code class="o">,</code> <code class="n">imageY</code><code class="o">,</code> <code class="k">this</code><code class="o">);</code>
<code class="o">}</code>
<code class="kd">public</code> <code class="kd">static</code> <code class="kt">void</code> <code class="nf">main</code><code class="o">(</code><code class="n">String</code><code class="o">[]</code> <code class="n">args</code><code class="o">)</code> <code class="o">{</code>
<code class="n">String</code> <code class="n">imageFile</code> <code class="o">=</code> <code class="s">"L1-Light.jpg"</code><code class="o">;</code>
<code class="k">if</code> <code class="o">(</code><code class="n">args</code><code class="o">.</code><code class="na">length</code> <code class="o">></code> <code class="mi">0</code><code class="o">)</code>
<code class="n">imageFile</code> <code class="o">=</code> <code class="n">args</code><code class="o">[</code><code class="mi">0</code><code class="o">];</code>
<code class="c1">// Turn off double buffering</code>
<code class="c1">//RepaintManager.currentManager(null).setDoubleBufferingEnabled(false);</code>
<code class="n">Image</code> <code class="n">image</code> <code class="o">=</code> <code class="n">Toolkit</code><code class="o">.</code><code class="na">getDefaultToolkit</code><code class="o">().</code><code class="na">getImage</code><code class="o">(</code>
<code class="n">DragImage</code><code class="o">.</code><code class="na">class</code><code class="o">.</code><code class="na">getResource</code><code class="o">(</code><code class="n">imageFile</code><code class="o">));</code>
<code class="n">image</code> <code class="o">=</code> <code class="n">image</code><code class="o">.</code><code class="na">getScaledInstance</code><code class="o">(</code>
<code class="n">imageWidth</code><code class="o">,</code><code class="n">imageHeight</code><code class="o">,</code><code class="n">Image</code><code class="o">.</code><code class="na">SCALE_DEFAULT</code><code class="o">);</code>
<code class="n">JFrame</code> <code class="n">frame</code> <code class="o">=</code> <code class="k">new</code> <code class="n">JFrame</code><code class="o">(</code><code class="s">"DragImage"</code><code class="o">);</code>
<code class="n">frame</code><code class="o">.</code><code class="na">add</code><code class="o">(</code> <code class="k">new</code> <code class="n">DragImage</code><code class="o">(</code><code class="n">image</code><code class="o">)</code> <code class="o">);</code>
<code class="n">frame</code><code class="o">.</code><code class="na">setSize</code><code class="o">(</code><code class="mi">300</code><code class="o">,</code> <code class="mi">300</code><code class="o">);</code>
<code class="n">frame</code><code class="o">.</code><code class="na">setDefaultCloseOperation</code><code class="o">(</code> <code class="n">JFrame</code><code class="o">.</code><code class="na">EXIT_ON_CLOSE</code> <code class="o">);</code>
<code class="n">frame</code><code class="o">.</code><code class="na">setVisible</code><code class="o">(</code><code class="kc">true</code><code class="o">);</code>
<code class="o">}</code>
<code class="o">}</code></pre><div class="figure"><a id="learnjava3-CHP-20-FIG-4"/><div class="figure-contents"><div class="mediaobject"><a id="I_20_tt1167"/><img src="httpatomoreillycomsourceoreillyimages1707694.png" alt="The DragImage application"/></div></div><p class="title">Figure 20-4. The DragImage application</p></div><p>Run the application, optionally specifying an image file as a
command-line argument. Then try dragging the image around on the
pattern.</p><p><code class="literal">DragImage</code> is a custom component
that overrides the <code class="literal">JComponent paint()</code>
method to do its drawing. In the <code class="literal">main()</code>
method, we load the image and prescale it to improve performance. We then
create the <code class="literal">DragImage</code> component and
place it in the content pane. As the mouse is dragged, <code class="literal">DragImage</code> keeps track of its most recent
position in two instance variables, <code class="literal">imageX</code> and <code class="literal">imageY</code>. On each call to <code class="literal">mouseDragged()</code>, the coordinates are updated, and
<code class="literal">repaint()</code> is called to ask that the
display be updated. When <code class="literal">paint()</code> is
called, it looks at some parameters, draws the checkerboard pattern to
fill the applet’s area and finally paints the small version of the image
at the latest coordinates.</p><p>Now for a few arcane details about differences between <code class="literal">JComponent</code> and a plain AWT <code class="literal">Component</code>. First, the default <code class="literal">JComponent update()</code> method simply calls our
<code class="literal">paint()</code> method. Prior to Java 1.4, the
AWT <code class="literal">Component</code> class’s default <code class="literal">update()</code> method first cleared the screen area
using a <code class="literal">clearRect()</code> call before calling
<code class="literal">paint</code>. Remember that the difference
between <code class="literal">paint()</code> and <code class="literal">update()</code> is that <code class="literal">paint()</code> draws the entire area and <code class="literal">update()</code> assumes the screen region is intact
from the last draw. In AWT, <code class="literal">update()</code>
was overly conservative; in Swing, it’s more optimistic. This is
noteworthy if you are working with an older AWT-based application. In that
case, you can simply override <code class="literal">update()</code>
to call <code class="literal">paint()</code>.</p><p>A more important difference between AWT and Swing is that Swing
components by default perform <span class="emphasis"><em>double buffering</em></span> of the
output of their <code class="literal">paint()</code> method.</p><div class="sect2" title="Double Buffering"><div class="titlepage"><div><div><h2 class="title"><a id="learnjava3-CHP-20-SECT-8.1"/>Double Buffering</h2></div></div></div><p><a id="I_indexterm20_id814366" class="indexterm"/> <a id="I_indexterm20_id814373" class="indexterm"/>Double buffering means that instead of drawing directly on
the screen, Swing first performs drawing operations in an offscreen
buffer and then copies the completed work to the display in a single
painting operation, as shown in <a class="xref" href="ch20s08.html#learnjava3-CHP-20-FIG-5" title="Figure 20-5. Double buffering">Figure 20-5</a>. It takes the same amount of time
to do the drawing work, but once it’s done, double buffering
instantaneously updates our display so that the user does not perceive
any flickering or progressively rendered output.</p><p>You’ll see how to implement this technique yourself when we use an
offscreen buffer later in this chapter. However, Swing does this kind of
double buffering for you whenever you use a Swing component in a Swing
container. AWT components do not have automatic double buffering
capability.</p><p>It is interesting to take our example and turn off double
buffering to see the effect. Each Swing <code class="literal">JComponent</code> has a method called <a id="I_indexterm20_id814410" class="indexterm"/><code class="literal">setDoubleBuffered()</code>
that can be set to <code class="literal">false</code> in order to
disable the technique. Or you can disable it for all components using a
call to the Swing <a id="I_indexterm20_id814428" class="indexterm"/><code class="literal">RepaintManager</code>, as
we’ve indicated in comments in the example. Try uncommenting that line
of <a id="I_indexterm20_id814440" class="indexterm"/><code class="literal">DragImage</code> and observe
the difference in appearance.</p><div class="figure"><a id="learnjava3-CHP-20-FIG-5"/><div class="figure-contents"><div class="mediaobject"><a id="I_20_tt1168"/><img src="httpatomoreillycomsourceoreillyimages1707695.png" alt="Double buffering"/></div></div><p class="title">Figure 20-5. Double buffering</p></div><p>The difference is most dramatic when you are using a slow system
or performing complex drawing operations. Double buffering eliminates
all of the flickering. However, on a slow system, it can decrease
performance noticeably. In extreme cases (such as a game), it may be
beneficial to provide an option to disable double buffering.</p><p>Our example is pretty fast, but we’re still doing some wasted
drawing. Most of the background stays the same each time it’s painted.
You might try to make <code class="literal">paint()</code>
smarter, so that it wouldn’t redraw these areas, but remember that
<code class="literal">paint()</code> 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. Next, we’ll talk about clipping.</p></div><div class="sect2" title="Limiting Drawing with Clipping"><div class="titlepage"><div><div><h2 class="title"><a id="learnjava3-CHP-20-SECT-8.2"/>Limiting Drawing with Clipping</h2></div></div></div><p><a id="idx11082" class="indexterm"/> <a id="idx11095" class="indexterm"/>Whenever the mouse is dragged, <code class="literal">DragImage</code> responds by updating its coordinates
and calling <a id="I_indexterm20_id814536" class="indexterm"/><code class="literal">repaint()</code>. But <code class="literal">repaint()</code> by default causes the entire
component to be redrawn. Most of this drawing is unnecessary. It turns
out that there’s another version of <code class="literal">repaint()</code> that lets you specify a rectangular
area that should be drawn—in essence, a clipping region.</p><p>Why does it help to restrict the drawing area? Foremost, drawing
operations that fall outside the clipping region are not displayed. If a
drawing operation overlaps the clipping region, we see only the part
that’s inside. A second effect is that, in a good implementation, the
graphics context can recognize drawing operations that fall completely
outside the clipping region and ignore them altogether. Eliminating
unnecessary operations can save time if we’re doing something complex,
such as filling a bunch of polygons. This doesn’t save the time 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, as the calculations would be the
major time sink, not the drawing.)</p><p>So we can save some time in our application by redrawing only the
affected portion of the display. We can pick the smallest rectangular
area that includes both the old image position and the new image
position, as shown in <a class="xref" href="ch20s08.html#learnjava3-CHP-20-FIG-6" title="Figure 20-6. Determining the clipping region">Figure 20-6</a>. This
is the only portion of the display that really needs to change;
everything else stays the same.</p><div class="figure"><a id="learnjava3-CHP-20-FIG-6"/><div class="figure-contents"><div class="mediaobject"><a id="I_20_tt1169"/><img src="httpatomoreillycomsourceoreillyimages1707696.png" alt="Determining the clipping region"/></div></div><p class="title">Figure 20-6. Determining the clipping region</p></div><p>A smarter algorithm could save even more time by redrawing only
those regions that have changed. However, the simple clipping strategy
we’ve implemented here can be applied to many kinds of drawing and gives
good performance, particularly if the area being changed is
small.</p><p>One important thing to note is that, in addition to looking at the
new position, our updating operation now has to remember the last
position at which the image was drawn. Let’s fix our application so it
will use a specified clipping region. To keep this short and emphasize
the changes, we’ll take some liberties with design and make our next
example a subclass of <a id="I_indexterm20_id814618" class="indexterm"/><code class="literal">DragImage</code>. Let’s call
it <code class="literal">ClippedDragImage</code>.</p><a id="I_20_tt1170"/><pre class="programlisting"> <code class="kn">import</code> <code class="nn">java.awt.*</code><code class="o">;</code>
<code class="kn">import</code> <code class="nn">java.awt.event.*</code><code class="o">;</code>
<code class="kn">import</code> <code class="nn">javax.swing.*</code><code class="o">;</code>
<code class="kd">public</code> <code class="kd">class</code> <code class="nc">ClippedDragImage</code> <code class="kd">extends</code> <code class="n">DragImage</code> <code class="o">{</code>
<code class="kt">int</code> <code class="n">oldX</code><code class="o">,</code> <code class="n">oldY</code><code class="o">;</code>
<code class="kd">public</code> <code class="nf">ClippedDragImage</code><code class="o">(</code> <code class="n">Image</code> <code class="n">i</code> <code class="o">)</code> <code class="o">{</code> <code class="kd">super</code><code class="o">(</code><code class="n">i</code><code class="o">);</code> <code class="o">}</code>
<code class="kd">public</code> <code class="kt">void</code> <code class="nf">mouseDragged</code><code class="o">(</code><code class="n">MouseEvent</code> <code class="n">e</code><code class="o">)</code> <code class="o">{</code>
<code class="n">imageX</code> <code class="o">=</code> <code class="n">e</code><code class="o">.</code><code class="na">getX</code><code class="o">();</code>
<code class="n">imageY</code> <code class="o">=</code> <code class="n">e</code><code class="o">.</code><code class="na">getY</code><code class="o">();</code>
<code class="n">Rectangle</code> <code class="n">r</code> <code class="o">=</code> <code class="n">getAffectedArea</code><code class="o">(</code>
<code class="n">oldX</code><code class="o">,</code> <code class="n">oldY</code><code class="o">,</code> <code class="n">imageX</code><code class="o">,</code> <code class="n">imageY</code><code class="o">,</code> <code class="n">imageWidth</code><code class="o">,</code> <code class="n">imageHeight</code><code class="o">);</code>
<code class="n">repaint</code><code class="o">(</code><code class="n">r</code><code class="o">);</code> <code class="c1">// repaint just the affected part of the component</code>
<code class="n">oldX</code> <code class="o">=</code> <code class="n">imageX</code><code class="o">;</code>
<code class="n">oldY</code> <code class="o">=</code> <code class="n">imageY</code><code class="o">;</code>
<code class="o">}</code>
<code class="kd">private</code> <code class="n">Rectangle</code> <code class="nf">getAffectedArea</code><code class="o">(</code>
<code class="kt">int</code> <code class="n">oldx</code><code class="o">,</code> <code class="kt">int</code> <code class="n">oldy</code><code class="o">,</code> <code class="kt">int</code> <code class="n">newx</code><code class="o">,</code> <code class="kt">int</code> <code class="n">newy</code><code class="o">,</code> <code class="kt">int</code> <code class="n">width</code><code class="o">,</code> <code class="kt">int</code> <code class="n">height</code><code class="o">)</code>
<code class="o">{</code>
<code class="kt">int</code> <code class="n">x</code> <code class="o">=</code> <code class="n">Math</code><code class="o">.</code><code class="na">min</code><code class="o">(</code><code class="n">oldx</code><code class="o">,</code> <code class="n">newx</code><code class="o">);</code>
<code class="kt">int</code> <code class="n">y</code> <code class="o">=</code> <code class="n">Math</code><code class="o">.</code><code class="na">min</code><code class="o">(</code><code class="n">oldy</code><code class="o">,</code> <code class="n">newy</code><code class="o">);</code>
<code class="kt">int</code> <code class="n">w</code> <code class="o">=</code> <code class="o">(</code><code class="n">Math</code><code class="o">.</code><code class="na">max</code><code class="o">(</code><code class="n">oldx</code><code class="o">,</code> <code class="n">newx</code><code class="o">)</code> <code class="o">+</code> <code class="n">width</code><code class="o">)</code> <code class="o">-</code> <code class="n">x</code><code class="o">;</code>
<code class="kt">int</code> <code class="n">h</code> <code class="o">=</code> <code class="o">(</code><code class="n">Math</code><code class="o">.</code><code class="na">max</code><code class="o">(</code><code class="n">oldy</code><code class="o">,</code> <code class="n">newy</code><code class="o">)</code> <code class="o">+</code> <code class="n">height</code><code class="o">)</code> <code class="o">-</code> <code class="n">y</code><code class="o">;</code>
<code class="k">return</code> <code class="k">new</code> <code class="nf">Rectangle</code><code class="o">(</code><code class="n">x</code><code class="o">,</code> <code class="n">y</code><code class="o">,</code> <code class="n">w</code><code class="o">,</code> <code class="n">h</code><code class="o">);</code>
<code class="o">}</code>
<code class="kd">public</code> <code class="kd">static</code> <code class="kt">void</code> <code class="nf">main</code><code class="o">(</code><code class="n">String</code><code class="o">[]</code> <code class="n">args</code><code class="o">)</code> <code class="o">{</code>
<code class="n">String</code> <code class="n">imageFile</code> <code class="o">=</code> <code class="s">"L1-Light.jpg"</code><code class="o">;</code>
<code class="k">if</code> <code class="o">(</code><code class="n">args</code><code class="o">.</code><code class="na">length</code> <code class="o">></code> <code class="mi">0</code><code class="o">)</code>
<code class="n">imageFile</code> <code class="o">=</code> <code class="n">args</code><code class="o">[</code><code class="mi">0</code><code class="o">];</code>
<code class="c1">// Turn off double buffering</code>
<code class="c1">//RepaintManager.currentManager(null).setDoubleBufferingEnabled(false);</code>
<code class="n">Image</code> <code class="n">image</code> <code class="o">=</code> <code class="n">Toolkit</code><code class="o">.</code><code class="na">getDefaultToolkit</code><code class="o">().</code><code class="na">getImage</code><code class="o">(</code>
<code class="n">ClippedDragImage</code><code class="o">.</code><code class="na">class</code><code class="o">.</code><code class="na">getResource</code><code class="o">(</code><code class="n">imageFile</code><code class="o">));</code>
<code class="n">image</code> <code class="o">=</code> <code class="n">image</code><code class="o">.</code><code class="na">getScaledInstance</code><code class="o">(</code>
<code class="n">imageWidth</code><code class="o">,</code><code class="n">imageHeight</code><code class="o">,</code><code class="n">Image</code><code class="o">.</code><code class="na">SCALE_DEFAULT</code><code class="o">);</code>
<code class="n">JFrame</code> <code class="n">frame</code> <code class="o">=</code> <code class="k">new</code> <code class="n">JFrame</code><code class="o">(</code><code class="s">"ClippedDragImage"</code><code class="o">);</code>
<code class="n">frame</code><code class="o">.</code><code class="na">add</code><code class="o">(</code> <code class="k">new</code> <code class="n">ClippedDragImage</code><code class="o">(</code><code class="n">image</code><code class="o">)</code> <code class="o">);</code>
<code class="n">frame</code><code class="o">.</code><code class="na">setSize</code><code class="o">(</code><code class="mi">300</code><code class="o">,</code> <code class="mi">300</code><code class="o">);</code>
<code class="n">frame</code><code class="o">.</code><code class="na">setDefaultCloseOperation</code><code class="o">(</code> <code class="n">JFrame</code><code class="o">.</code><code class="na">EXIT_ON_CLOSE</code> <code class="o">);</code>
<code class="n">frame</code><code class="o">.</code><code class="na">setVisible</code><code class="o">(</code><code class="kc">true</code><code class="o">);</code>
<code class="o">}</code>
<code class="o">}</code></pre><p>You may or may not find that <code class="literal">ClippedDragImage</code> is significantly faster.
Modern desktop computers are so fast that this kind of operation is
child’s play for them. However, the fundamental technique is important
and applicable to more sophisticated applications.</p><p>What have we changed? First, we’ve overridden <a id="I_indexterm20_id814674" class="indexterm"/><code class="literal">mouseDragged()</code> so that
instead of setting the current coordinates of the image, it figures out
the area that has changed by using a new <code class="literal">private</code> method. <a id="I_indexterm20_id814691" class="indexterm"/><code class="literal">getAffectedArea()</code> takes
the new and old coordinates and the width and height of the image as
arguments. It determines the bounding rectangle as shown in <a class="xref" href="ch20s08.html#learnjava3-CHP-20-FIG-6" title="Figure 20-6. Determining the clipping region">Figure 20-6</a>, then calls <code class="literal">repaint()</code> to draw only the affected area of
the screen. <code class="literal">mouseDragged()</code> also saves
the current position by setting the <code class="literal">oldX</code> and <code class="literal">oldY</code> variables.</p><p>Try turning off double buffering on this example and compare it to
the unbuffered previous example to see how much less work is being done.
You probably won’t see the difference; computers are just too fast
nowadays. If you were using the 2D API to do some fancy rendering, it
might help a lot.<a id="I_indexterm20_id814738" class="indexterm"/><a id="I_indexterm20_id814745" class="indexterm"/></p></div><div class="sect2" title="Offscreen Drawing"><div class="titlepage"><div><div><h2 class="title"><a id="learnjava3-CHP-20-SECT-8.3"/>Offscreen Drawing</h2></div></div></div><p><a id="idx11096" class="indexterm"/> <a id="idx11105" class="indexterm"/>In addition to serving as buffers for double buffering,
offscreen images are useful for saving complex, hard-to-produce,
background information. We’ll look at a fun, simple example: the doodle
pad. <code class="literal">DoodlePad</code> is a simple drawing
tool that lets us scribble by dragging the mouse, as shown in <a class="xref" href="ch20s08.html#learnjava3-CHP-20-FIG-7" title="Figure 20-7. The DoodlePad application">Figure 20-7</a>. It draws into an offscreen image;
its <code class="literal">paint()</code> method simply copies the
image to the display area.</p><a id="I_20_tt1171"/><pre class="programlisting"> <code class="c1">//file: DoodlePad.java</code>
<code class="kn">import</code> <code class="nn">java.awt.*</code><code class="o">;</code>
<code class="kn">import</code> <code class="nn">java.awt.event.*</code><code class="o">;</code>
<code class="kn">import</code> <code class="nn">javax.swing.*</code><code class="o">;</code>
<code class="kd">public</code> <code class="kd">class</code> <code class="nc">DoodlePad</code>
<code class="o">{</code>
<code class="kd">public</code> <code class="kd">static</code> <code class="kt">void</code> <code class="nf">main</code><code class="o">(</code><code class="n">String</code><code class="o">[]</code> <code class="n">args</code><code class="o">)</code>
<code class="o">{</code>
<code class="n">JFrame</code> <code class="n">frame</code> <code class="o">=</code> <code class="k">new</code> <code class="n">JFrame</code><code class="o">(</code><code class="s">"DoodlePad"</code><code class="o">);</code>
<code class="n">frame</code><code class="o">.</code><code class="na">setLayout</code><code class="o">(</code><code class="k">new</code> <code class="n">BorderLayout</code><code class="o">());</code>
<code class="kd">final</code> <code class="n">DrawPad</code> <code class="n">drawPad</code> <code class="o">=</code> <code class="k">new</code> <code class="n">DrawPad</code><code class="o">();</code>
<code class="n">frame</code><code class="o">.</code><code class="na">add</code><code class="o">(</code><code class="n">drawPad</code><code class="o">,</code> <code class="n">BorderLayout</code><code class="o">.</code><code class="na">CENTER</code><code class="o">);</code>
<code class="n">JPanel</code> <code class="n">panel</code> <code class="o">=</code> <code class="k">new</code> <code class="n">JPanel</code><code class="o">();</code>
<code class="n">JButton</code> <code class="n">clearButton</code> <code class="o">=</code> <code class="k">new</code> <code class="n">JButton</code><code class="o">(</code><code class="s">"Clear"</code><code class="o">);</code>
<code class="n">clearButton</code><code class="o">.</code><code class="na">addActionListener</code><code class="o">(</code><code class="k">new</code> <code class="n">ActionListener</code><code class="o">()</code> <code class="o">{</code>
<code class="kd">public</code> <code class="kt">void</code> <code class="nf">actionPerformed</code><code class="o">(</code><code class="n">ActionEvent</code> <code class="n">e</code><code class="o">)</code> <code class="o">{</code>
<code class="n">drawPad</code><code class="o">.</code><code class="na">clear</code><code class="o">();</code>
<code class="o">}</code>
<code class="o">});</code>
<code class="n">panel</code><code class="o">.</code><code class="na">add</code><code class="o">(</code><code class="n">clearButton</code><code class="o">);</code>
<code class="n">frame</code><code class="o">.</code><code class="na">add</code><code class="o">(</code><code class="n">panel</code><code class="o">,</code> <code class="n">BorderLayout</code><code class="o">.</code><code class="na">SOUTH</code><code class="o">);</code>
<code class="n">frame</code><code class="o">.</code><code class="na">setSize</code><code class="o">(</code><code class="mi">280</code><code class="o">,</code> <code class="mi">300</code><code class="o">);</code>
<code class="n">frame</code><code class="o">.</code><code class="na">setDefaultCloseOperation</code><code class="o">(</code> <code class="n">JFrame</code><code class="o">.</code><code class="na">EXIT_ON_CLOSE</code> <code class="o">);</code>
<code class="n">frame</code><code class="o">.</code><code class="na">setVisible</code><code class="o">(</code><code class="kc">true</code><code class="o">);</code>
<code class="o">}</code>
<code class="o">}</code> <code class="c1">// end of class DoodlePad</code>
<code class="kd">class</code> <code class="nc">DrawPad</code> <code class="kd">extends</code> <code class="n">JComponent</code>
<code class="o">{</code>
<code class="n">Image</code> <code class="n">image</code><code class="o">;</code>
<code class="n">Graphics2D</code> <code class="n">graphics2D</code><code class="o">;</code>
<code class="kt">int</code> <code class="n">currentX</code><code class="o">,</code> <code class="n">currentY</code><code class="o">,</code> <code class="n">oldX</code><code class="o">,</code> <code class="n">oldY</code><code class="o">;</code>
<code class="kd">public</code> <code class="nf">DrawPad</code><code class="o">()</code> <code class="o">{</code>
<code class="n">setDoubleBuffered</code><code class="o">(</code><code class="kc">false</code><code class="o">);</code>
<code class="n">addMouseListener</code><code class="o">(</code><code class="k">new</code> <code class="n">MouseAdapter</code><code class="o">()</code> <code class="o">{</code>
<code class="kd">public</code> <code class="kt">void</code> <code class="nf">mousePressed</code><code class="o">(</code><code class="n">MouseEvent</code> <code class="n">e</code><code class="o">)</code> <code class="o">{</code>
<code class="n">oldX</code> <code class="o">=</code> <code class="n">e</code><code class="o">.</code><code class="na">getX</code><code class="o">();</code>
<code class="n">oldY</code> <code class="o">=</code> <code class="n">e</code><code class="o">.</code><code class="na">getY</code><code class="o">();</code>
<code class="o">}</code>
<code class="o">});</code>
<code class="n">addMouseMotionListener</code><code class="o">(</code><code class="k">new</code> <code class="n">MouseMotionAdapter</code><code class="o">()</code> <code class="o">{</code>
<code class="kd">public</code> <code class="kt">void</code> <code class="nf">mouseDragged</code><code class="o">(</code><code class="n">MouseEvent</code> <code class="n">e</code><code class="o">)</code> <code class="o">{</code>
<code class="n">currentX</code> <code class="o">=</code> <code class="n">e</code><code class="o">.</code><code class="na">getX</code><code class="o">();</code>
<code class="n">currentY</code> <code class="o">=</code> <code class="n">e</code><code class="o">.</code><code class="na">getY</code><code class="o">();</code>
<code class="k">if</code> <code class="o">(</code><code class="n">graphics2D</code> <code class="o">!=</code> <code class="kc">null</code><code class="o">)</code>
<code class="n">graphics2D</code><code class="o">.</code><code class="na">drawLine</code><code class="o">(</code><code class="n">oldX</code><code class="o">,</code> <code class="n">oldY</code><code class="o">,</code> <code class="n">currentX</code><code class="o">,</code> <code class="n">currentY</code><code class="o">);</code>
<code class="n">repaint</code><code class="o">();</code>
<code class="n">oldX</code> <code class="o">=</code> <code class="n">currentX</code><code class="o">;</code>
<code class="n">oldY</code> <code class="o">=</code> <code class="n">currentY</code><code class="o">;</code>
<code class="o">}</code>
<code class="o">});</code>
<code class="o">}</code>
<code class="kd">public</code> <code class="kt">void</code> <code class="nf">paintComponent</code><code class="o">(</code><code class="n">Graphics</code> <code class="n">g</code><code class="o">)</code> <code class="o">{</code>
<code class="k">if</code> <code class="o">(</code><code class="n">image</code> <code class="o">==</code> <code class="kc">null</code><code class="o">)</code> <code class="o">{</code>
<code class="n">image</code> <code class="o">=</code> <code class="n">createImage</code><code class="o">(</code><code class="n">getSize</code><code class="o">().</code><code class="na">width</code><code class="o">,</code> <code class="n">getSize</code><code class="o">().</code><code class="na">height</code><code class="o">);</code>
<code class="n">graphics2D</code> <code class="o">=</code> <code class="o">(</code><code class="n">Graphics2D</code><code class="o">)</code><code class="n">image</code><code class="o">.</code><code class="na">getGraphics</code><code class="o">();</code>
<code class="n">graphics2D</code><code class="o">.</code><code class="na">setRenderingHint</code><code class="o">(</code><code class="n">RenderingHints</code><code class="o">.</code><code class="na">KEY_ANTIALIASING</code><code class="o">,</code>
<code class="n">RenderingHints</code><code class="o">.</code><code class="na">VALUE_ANTIALIAS_ON</code><code class="o">);</code>
<code class="n">clear</code><code class="o">();</code>
<code class="o">}</code>
<code class="n">g</code><code class="o">.</code><code class="na">drawImage</code><code class="o">(</code><code class="n">image</code><code class="o">,</code> <code class="mi">0</code><code class="o">,</code> <code class="mi">0</code><code class="o">,</code> <code class="kc">null</code><code class="o">);</code>
<code class="o">}</code>
<code class="kd">public</code> <code class="kt">void</code> <code class="nf">clear</code><code class="o">()</code> <code class="o">{</code>
<code class="n">graphics2D</code><code class="o">.</code><code class="na">setPaint</code><code class="o">(</code><code class="n">Color</code><code class="o">.</code><code class="na">white</code><code class="o">);</code>
<code class="n">graphics2D</code><code class="o">.</code><code class="na">fillRect</code><code class="o">(</code><code class="mi">0</code><code class="o">,</code> <code class="mi">0</code><code class="o">,</code> <code class="n">getSize</code><code class="o">().</code><code class="na">width</code><code class="o">,</code> <code class="n">getSize</code><code class="o">().</code><code class="na">height</code><code class="o">);</code>
<code class="n">graphics2D</code><code class="o">.</code><code class="na">setPaint</code><code class="o">(</code><code class="n">Color</code><code class="o">.</code><code class="na">black</code><code class="o">);</code>
<code class="n">repaint</code><code class="o">();</code>
<code class="o">}</code>
<code class="o">}</code></pre><div class="figure"><a id="learnjava3-CHP-20-FIG-7"/><div class="figure-contents"><div class="mediaobject"><a id="I_20_tt1172"/><img src="httpatomoreillycomsourceoreillyimages1707697.png" alt="The DoodlePad application"/></div></div><p class="title">Figure 20-7. The DoodlePad application</p></div><p>Give it a try. Draw a nice moose or a sunset. We just drew a
lovely cartoon of Bill Gates. If you make a mistake, hit the Clear
button and start over.</p><p>The parts should be familiar by now. We made a type of <code class="literal">JComponent</code> called <code class="literal">DrawPad</code>. The new <code class="literal">DrawPad</code> component uses inner classes to supply
handlers for the <code class="literal">MouseListener</code> and
<code class="literal">MouseMotionListener</code> interfaces. We
used the <code class="literal">JComponent createImage()</code>
method to create an empty offscreen image buffer to hold our scribble.
Mouse-dragging events trigger us to draw lines into the offscreen image
and call <code class="literal">repaint()</code> to update the
display. <code class="literal">DrawPad</code>’s <code class="literal">paint()</code> method does a <code class="literal">drawImage()</code> to copy the offscreen drawing area
to the display. In this way, <code class="literal">DrawPad</code>
saves our sketch information.</p><p>What is unusual about <code class="literal">DrawPad</code>
is that it does some drawing outside of <code class="literal">paint()</code>. In this example, we want to let the
user scribble with the mouse, so we should respond to every mouse
movement. Therefore, we do our work, drawing to the offscreen buffer in
<code class="literal">mouseDragged()</code> itself. As a rule, we
should be careful about doing heavy work in event-handling methods
because we don’t want to interfere with other tasks that the windowing
system’s painting thread is performing. In this case, our line drawing
option should not be a burden, and our primary concern is getting as
close a coupling as possible between the mouse movement events and the
sketch on the screen. A more elaborate example might push coordinates
into a queue for some other drawing thread to consume, thus freeing up
the event handler thread.</p><p>In addition to drawing a line as the user drags the mouse, the
<code class="literal">mouseDragged()</code> handler maintains a
pair of previous coordinates to be used as a starting point for the next
line segment. The <code class="literal">mousePressed()</code>
handler resets the previous coordinates to the current mouse position
whenever the user moves the mouse. Finally, <code class="literal">DrawPad</code> provides a <code class="literal">clear()</code> method that clears the offscreen
buffer and calls <code class="literal">repaint()</code> to update
the display. The <code class="literal">DoodlePad</code> application ties the <code class="literal">clear()</code> method to an appropriately labeled
button through another anonymous inner class.</p><p>What if we wanted to do something with the image after the user
has finished scribbling on it? As we’ll see in the next chapter, we
could get 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 later. Think about how you might go about creating a
networked “bathroom wall,” where people could scribble on your web
pages.<a id="I_indexterm20_id815005" class="indexterm"/><a id="I_indexterm20_id815012" class="indexterm"/></p></div></div></body></html>