Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

Java 2D & Web Pages


Oct03: Java 2D &Web Pages

Graphics and dynamic data

Paul is a member of the technical staff at AudioAudit. He can be reached at [email protected].


When you think of Java 2D, you usually think of Swing applications that render graphics onto displays. The only way web applications can access screens is via browsers, which retrieve and display images. However, because the images are provided by servers, you really need to think in terms of server-side Java 2D. In this article, I show how to add graphics containing dynamic data to web pages using the Java 2D API—a set of classes for advanced 2D graphics and imaging that encompasses line art, text, and images in a single comprehensive model.

A Web Application with Graphics

One of my web sites, http://www.tremblett .ca/, features a server-side Java 2D application. The web page (Figure 1) contains a navigation bar and HTML form containing radio buttons, a text-entry field, and a submit button. The only unfamiliar element is probably the "ballot validation number" (in the gray box), which prevents scripted bulk submission of HTML forms. This validation technique assumes that it is difficult to write a script that reads a string from an image and transcribes it into a text-entry field, especially when the rules can be randomly varied; for example, enter only the digits that appear in red, enter only the odd digits and uppercase characters, and so on.

Implementing this technique using server-side Java 2D is Poll.jps (available electronically; see "Resource Center," page 5), the JavaServer Page (JSP) that emits the web page in Figure 1. The JSP action in:

<jsp:useBean id="ballotBean" scope="session"

class="ca.tremblett.beans.BallotBean"/>

defines a JavaBean (Listing One) with a scope of session referred to in the JSP by the identifier ballotBean, and that is an instance of ca.tremblett.beans.BallotBean. The bean's constructor uses the Random class to generate a series of five random numbers that are used to select five alphanumeric characters from the string that contains all of the characters allowed in a ballot control number. It stores a String containing the ballot control number in an instance variable, which is returned by the getter method getBallotControlNumber().

In Poll.jsp, the line <%@ taglib prefix="ballot" uri="WEB-INF/tld/ballot.tld"%> is a JSP taglib directive that declares that the page uses a tag library. It specifies the Uniform Resource Identifier (URI) from which the path to the XML document that describes the library can be found. This XML document is called a "Tag Library Descriptor" (TLD). The taglib directive also associates the prefix ballot with usage of actions in the library.

At approximately line 137 in Poll.jsp, the line <ballot:getBallotControlNumber/> indicates that the tag library defined earlier and identified by the prefix ballot contains a getBallotControlNumber element type that is to be included in the page. Listing Two contains the TLD and the <tag-class> tag, which specifies that the tag handler implementation class for the tag getBallotControlNumber is specified by the <name> tag that precedes it—ca.tremblett.taglib.ballot.GetBallotControlNumberTag. GetBallotControlNumberTag.java (available electronically) is the source code for this class.

Since the ballot control number is generated by the BallotBean, you start by retrieving an instance of BallotBean from the session object (if one exists). If the session object does not contain a BallotBean object stored with the key ballotBean, I use the Beans.instantiate() method to create an instance and save it in the session object.

Next, get an instance of the class ImageWriter by invoking the method getPNGImageWriter(). JDK 1.4 introduced the package javax.imageio, which contains the basic classes and interfaces for describing the contents of image files, including metadata and thumbnails. The javax.imageio package also has tools for controlling the image reading/writing process, performing transcoding between formats, and reporting errors. One class in javax.imageio is ImageWriter, an abstract superclass for encoding and writing images. If you examine the getPNGImageWriter() method, you see that to obtain an ImageWriter, you start by invoking the static method getImageWritersByFormatName() of the ImageIO class. Specify ".png" (Portable Network Graphics) as the format to obtain a writer. I chose Portable Network Graphics because it is patent free and rendered most uniformly by a variety of browsers. The getImageWritersByFormatName() method returns an Iterator containing all currently registered ImageWriters that claim to be able to encode the specified format. For simplicity, I chose the first element.

The image you generate must ultimately be written to an output stream associated with a file that can be requested by the browser. To get this stream, invoke getImageOutputStream() and pass the path name of the real directory that corresponds to the /images directory (in the context in which the web application is running) as an argument. The getImageOutputStream() method creates a temporary file in the specified directory. The file has the prefix "pollControl_" and the extension ".png." It passes the file name to the static method createImageOutputStream() of class ImageIO and returns the resulting instance of ImageOutputStream.

Don't write directly to the output stream, but rather to a BufferedImage. You get this buffered image by invoking the method getControlNumberImage(), which takes as an argument a String containing the random number returned by the getBallotControlNumber() method of the BallotBean retrieved from the session object.

Looking at the getControlNumberImage() method, you start by passing a width of 1200, height of 1200, and image type of TYPE_INT_RGB to the constructor of the BufferedImage class. TYPE_INT_RGB represents an image with 8-bit RGB color components packed into integer pixels. To draw in the BufferedImage, you need a Graphics2D, which you get by invoking the createGraphics() method of the instance of BufferedImage. The rendering engine represented by this Graphics2D object is designed to do the best possible job for any given device. It accepts hints that it incorporates into its rendering algorithms. I use the setRenderingHint() method to request that the rendering engine use antialiased fonts.

You next invoke the fillBackground() method, which creates a Color object using a value of 0xcc for the red, green, and blue components. I generally try to limit my choice of colors to nondithering using colors that limit their RGB values to 0x33, 0x66, 0x99, xcc, and 0xff. (For more on nondithered colors, see http://www.lynda.com/hex.html.) Pass the instance of Color to the Graphic2D's setPaint() method to set the Color attribute of the graphics context. Next, create a Rectangle2D object, set its interior color using g.fill(), and draw it using g.draw().

The ballot control number that was received as an argument is text, so rendering it requires a Font. Create an instance of Font that uses the sanserif logical font—one of the five font families defined by the Java platform that must be supported by any Java run-time environment. After getting the Font() object, pass it as an argument to the Graphic2D object's setFont() method. After the graphic context's font has been set, get the rendering context of the font using getFontRenderContext() and pass the font rendering context and the String containing the ballot control number to the Font object's createGlyphVector() method. The GlyphVector that is returned is a collection of glyphs containing geometric information for the placement of each glyph in a transformed coordinate space that corresponds to the device on which the GlyphVector is ultimately displayed (or, in this case, drawn).

To make the ballot control number awkward to read by text-scanning software, you invoke randomlyShearGlyphs(). It generates pairs of random double-precision values that it uses to create a shearing transformation using the static method getShearInstance() of AffineTransform. The matrix representing the returned transform is:

[ 1 shx 0 ]

[ shy 1 0 ]

[ 0 0 1 ]

where shx and shy are the values passed to getShearInstance(). Figure 2(a) shows the bounding rectangles for each of the transformed glyphs as well as the bounding rectangle that encloses the sheared glyphs. Even though no drawing has taken place yet, I included the glyphs to make things a little easier to see.

It is possible that the transformation could result in a portion of the left-most character falling outside the user space of the graphics context, so you use adjustForNegativeShear() to compensate for this. This method determines the minimum X and Y values for the array of glyphs, and uses these values as arguments to the translate() method. This method concatenates the current Graphics2D Transform with a translation transform represented by this matrix:

[ 1 0 tx ]

[ 0 1 ty ]

[ 0 0 1 ]

where tx and ty are the values passed to the translate() method. Subsequent rendering is translated by the specified distance relative to the previous position.

After performing the adjustment, get the containing rectangle that represents the area into which the sheared glyphs will ultimately be drawn. This containing rectangle is the blue rectangle in Figure 2(b). You can see its position relative to the original containing rectangle in Figure 2(a).

Now it's time to actually draw the glyphs that represent the ballot control numbers, so you invoke drawGlyphs(). I use a nondithering color with RGB components of 0x66, 0x66, and 0x66. I also use an AlphaComposite object to set the degree to which the rendered characters are transparent. Figure 2(c) shows the rendered glyphs. The edges are bumpy because I drew them using a BasicStroke that draws a dotted line using a dash pattern of two opaque sections followed by two transparent sections. This is just another attempt to foil software that might try to use edge-detection to read the ballot control number. The green rectangle you see is the rectangle calculated by the getOuterFrame().

Now call drawEllipticalCrossHatching(). This method calls the methods drawHorizontalEllipses() and drawVerticalEllipses(). The drawing performed by drawHorizontalEllipses() is shown in Figure 2(d). The trick in drawing such a series of shapes lies in not burdening yourself with devising an algorithm that calculates the position at which each shape is to be drawn, but rather drawing the same figure repetitively and transforming the graphics context each time you finish drawing. You can think of this as "leave the pen where it is; move the paper." After drawVerticalEllipses() has been invoked, the ballot control number looks like Figure 2(e). You can now see why I needed the green rectangle calculated by getOuterFrame().

Finally, use getSubImage() to obtain only that portion of the large BufferedImage that actually contains the ballot control number. Near the end of the doStartTag() method (see GetBallotControl NumberTag.java, available electronically), you write the image to the ImageWriter and close the ImageOutputStream. Then you get the actual name of the file containing the image and save it as a session attribute using the key "fileName." You get the output stream being used as a JSP writer and write HTML that looks like <img src="filename.png">. The final result is that the <ballot:getBallotControlNumber/> tag in Poll.jsp is replaced by <img src="images/pollControl_57932.png">.

Presenting Dynamic Data Graphically

After you complete the ballot in Figure 1 and click on the Complete Poll button, your browser displays Figure 3. Here's how that screen was generated: The <form> tag in Poll.jsp looks like this:

<form name="ballot" method="post" action=

"<%=request.getContextPath()%>/servlet/ca.trem- blett.VoteServlet">

The action element specifies that the POST request constructed (using the elements of the form) is sent to the servlet code contained in the class ca.tremblett.VoteServlet (see VoteServlet.java, available electronically). The servlet's init() method uses the Java Naming and Directory Interface (JNDI) to get a DataSource object that it later uses to get a connection to a MySQL database containing the table used to record each ballot.

The doPost() method uses the Reflection API to invoke a method that it constructs dynamically using the value of the hidden field command contained in the HTML form in Poll.jsp. Although it is not entirely necessary in the present situation, using this dynamic dispatch technique enables me to extend the servlet to process other commands by simply adding an entry to the commands[] array and writing a method with a name that corresponds to the name of the new command (a technique I first discussed in my article "Java Reflection," DDJ, January 1998). Looking at the vote() method that processes the vote command, you see that it contains code that:

  • Prevents the casting of multiple votes in the same session by checking the value of the variable ballotEligible in the BallotBean.
  • Checks to make sure that one of the radio buttons was checked.

  • Checks to make sure that the ballot control number was transcribed from the image into the text-entry field.

  • Retrieves the value of the ballot control number that was saved in the BallotBean and checks that it matches the one that was entered in the text-entry field.

If these conditions are met, you save the value of the checked radio button in the stats table in the polldb1 database. Listing Three (cr_stats.sql) is the SQL code used to create the stats table.

After setting the ballotEligible variable in the BallotBean to false to prevent duplicate votes, you forward the request to PollResults.jsp (available electronically).

PollResults.jsp contains the custom tag <ballot:getPollResults/>. If you examine the taglib directive at the top of the PollResults.jsp, you see that the URL gets you to the TLD from which you can ascertain that the handler code is found in the class GetPollResultsTag. The code for this class is in GetPollResultsTag.java (available electronically). In the doStart() method, you retrieve ballot results from the stats table. The results are in the count[] array. I used these values to draw a pie chart containing a different color slice for each of the three choices users can make. I use the same technique I applied to draw the ellipses created when I generated the image containing the ballot control number; but instead of drawing complete ellipses, I draw arcs with central angles that are the same percentage of a full circle as the percentage the arc represents of the total ballots cast.

The final result is that the <ballot:getPollResults/> tag in PollResults.jsp is replaced by <img src="images/pollresults_12169.png">.

The only remaining task, which is beyond the scope of this article, is the addition of code that deletes old copies of the .png files so that they do not accumulate and cause the server to run out of space.

Conclusion

When appropriate, you can and should provide users with a graphical representation of data in lieu of rows and columns of plain text. Server-side Java 2D incorporated into standard web application components provides you with an easy way to do this.

DDJ

Listing One

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE taglib
        PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
        "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<!-- template test -->
<taglib>
    <tlib-version>1.0</tlib-version>
    <jsp-version>1.2</jsp-version>
    <short-name>ballot</short-name>
    <uri>/ballot</uri>
    <display-name>ballot</display-name>
    <description>A blank tag library template. 
    </description>    
    <!-- Forte4J_TLDX:  This comment contains code generation 
      information. Do not delete.
    <tldx>
        <tagHandlerGenerationRoot>
          C:\paul\DrDobbs\WEB-INF\classes
        </tagHandlerGenerationRoot>
        <JarFile></JarFile>
    </tldx>
    -->    
        <tag>
        <name>getBallotControlNumber</name>
        <tag-class>
          ca.tremblett.taglib.ballot.GetBallotControlNumberTag
        </tag-class>
        <body-content>JSP</body-content>
        <display-name>getBallotControlNumber</display-name>
        <description></description>
        <!-- Forte4J_TLDX:  This comment contains code generation 
          information. Do not delete.
        <tldx>
            <packagename>ca.tremblett.taglib.ballot</packagename>
            <extendsSupportClass>TRUE</extendsSupportClass>
            <supportClass>BodyTagSupport</supportClass>
            <implementsTryCatchFinally>
              FALSE
            </implementsTryCatchFinally>
            <findparent>FALSE</findparent>
        </tldx>
        -->
        <example></example>
    </tag>
    <tag>
        <name>getPollResults</name>
        <tag-class>
          ca.tremblett.taglib.ballot.GetPollResultsTag
        </tag-class>
        <body-content>JSP</body-content>
        <display-name>getPollResults</display-name>
        <small-icon></small-icon>
        <large-icon></large-icon>
        <description></description>
        <!-- Forte4J_TLDX:  This comment contains code generation 
          information. Do not delete.
        <tldx>
            <packagename>ca.tremblett.taglib.ballot</packagename>
            <extendsSupportClass>TRUE</extendsSupportClass>
            <supportClass>BodyTagSupport</supportClass>
            <implementsTryCatchFinally>
              FALSE
            </implementsTryCatchFinally>
            <findparent>FALSE</findparent>
            <parenttype></parenttype>
            <parentvariable></parentvariable>
        </tldx>
        -->
        <example></example>
    </tag>
    <!-- Validators are new in JSP1.2. You may have zero or one 
      validator in a tag library. They look like this:
      <validator>
          <validator-class>
            org.your.web.app.ValidateTaglibUsage
          </validator-class>
          <init-param>
             <param-name>aparameter</param-name>
             <param-value>value</param-value>
             <description>Describe this parameter</description>
      </init-param>
      </validator>
   -->

   <!-- listeners are new in JSP1.2. You may have as many listeners 
      as you like in a tag library. They look like this:
     <listener>
        <listener-class>
           org.your.web.app.TaglibListener1
        </listener-class> 
     </listener>
   -->
</taglib>

Back to Article

Listing Two

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE taglib
        PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
        "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
<!-- template test -->
<taglib>
    <tlib-version>1.0</tlib-version>
    <jsp-version>1.2</jsp-version>
    <short-name>ballot</short-name>
    <uri>/ballot</uri>
    <display-name>ballot</display-name>
    <description>A blank tag library template. 
    </description>    
    <!-- Forte4J_TLDX:  This comment contains code generation 
      information. Do not delete.
    <tldx>
        <tagHandlerGenerationRoot>
          C:\paul\DrDobbs\WEB-INF\classes
        </tagHandlerGenerationRoot>
        <JarFile></JarFile>
    </tldx>
    -->    
        <tag>
        <name>getBallotControlNumber</name>
        <tag-class>
          ca.tremblett.taglib.ballot.GetBallotControlNumberTag
        </tag-class>
        <body-content>JSP</body-content>
        <display-name>getBallotControlNumber</display-name>
        <description></description>
        <!-- Forte4J_TLDX:  This comment contains code generation 
          information. Do not delete.
        <tldx>
            <packagename>ca.tremblett.taglib.ballot</packagename>
            <extendsSupportClass>TRUE</extendsSupportClass>
            <supportClass>BodyTagSupport</supportClass>
            <implementsTryCatchFinally>
              FALSE
            </implementsTryCatchFinally>
            <findparent>FALSE</findparent>
        </tldx>
        -->
        <example></example>
    </tag>
    <tag>
        <name>getPollResults</name>
        <tag-class>
          ca.tremblett.taglib.ballot.GetPollResultsTag
        </tag-class>
        <body-content>JSP</body-content>
        <display-name>getPollResults</display-name>
        <small-icon></small-icon>
        <large-icon></large-icon>
        <description></description>
        <!-- Forte4J_TLDX:  This comment contains code generation 
          information. Do not delete.
        <tldx>
            <packagename>ca.tremblett.taglib.ballot</packagename>
            <extendsSupportClass>TRUE</extendsSupportClass>
            <supportClass>BodyTagSupport</supportClass>
            <implementsTryCatchFinally>
              FALSE
            </implementsTryCatchFinally>
            <findparent>FALSE</findparent>
            <parenttype></parenttype>
            <parentvariable></parentvariable>
        </tldx>
        -->
        <example></example>
    </tag>
    <!-- Validators are new in JSP1.2. You may have zero or one 
      validator in a tag library. They look like this:
      <validator>
          <validator-class>
            org.your.web.app.ValidateTaglibUsage
          </validator-class>
          <init-param>
             <param-name>aparameter</param-name>
             <param-value>value</param-value>
             <description>Describe this parameter</description>
      </init-param>
      </validator>
   -->
   <!-- listeners are new in JSP1.2. You may have as many listeners 
      as you like in a tag library. They look like this:
     <listener>
        <listener-class>
           org.your.web.app.TaglibListener1
        </listener-class> 
     </listener>
   -->
</taglib>

Back to Article

Listing Three

drop table stats;
create table stats (
  visitdate date,
  medium set('1','2','3'))type=bdb;





Back to Article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.