.
.
Core Java
Technologies Technical Tips
.
   View this issue as simple text February 10, 2004    

In this Issue

Welcome to the Core Java Technologies Tech Tips for February 10, 2004. Here you'll get tips on using core Java technologies and APIs, such as those in Java 2 Platform, Standard Edition (J2SE).

This issue covers:

-Styling Digital Images with ConvolveOp
-Using HttpURLConnection to Access Web Pages

These tips were developed using Java 2 SDK, Standard Edition, v 1.4.2.

This issue of the Core Java Technologies Tech Tips is written by Daniel H. Steinberg, Director of Java Offerings for Dim Sum Thinking, Inc, and editor-in-chief for java.net.

See the Subscribe/Unsubscribe note at the end of this newsletter to subscribe to Tech Tips that focus on technologies and products in other Java platforms.

.
.

STYLING DIGITAL IMAGES WITH CONVOLVEOP

Sometimes the images produced by your digital camera might not be exactly what you want. For example, you might want to blur them or sharpen them. Or you might want to brighten them or tone them down. The java.awt.image package contains utilities for making these kinds of changes to digital images. Using these utilities, you can achieve some of the same effects that you find in high-end commercial imaging packages.

In this tip, you will use ConvolveOp, a class in the java.awt.image package, to alter a digital image. ConvolveOp implements a convolution from a source to the destination. You can think of a convolution an operation that replaces each pixel in an image with some combination of the original pixel and its neighbors. You specify the combination by using an array of floats. The floats are multiples of the pixels. From this array you create an instance of a Kernel, and use this instance to perform the convolution.

For example, if the array is a three-by-three array like this:

   a b c
   d e f
   g h i

Then the resulting convolution replaces the center pixel with the sum of:

  • a times the value of the pixel above and to the left of center
  • b times the value of the pixel above the center
  • c times the pixel above and to the right of the center
  • and so on

In other words, you are multiplying the value of the pixel by the value in the kernel at the corresponding position, and adding them up. If the sum of the entries in the array is 1, then the resulting image will have the same dynamic range as the original image.

Here's a more concrete. Consider the following three-by-three array:

     0   0.2   0
    0.2  0.2  0.2
     0   0.2   0

This array corresponds to replacing each pixel with the average of its value together with the pixels to the right, left, above, and below. The effect is to blur the picture a bit.

Now consider the following three-by-three array. It corresponds to an identity operation. Applying it as part of a convolution changes nothing. In other words, it replaces each pixel with its previous value.

   0 0 0
   0 1 0
   0 0 0

However, if the 1 in the previous array is replaced with a 2, the resulting image is brighter and a bit washed out. Replacing the 1 with a .5 results in a darker image.

There is nothing magic about a three-by-three array. If your goal is to perform a convolution that is symmetric about the center and which depends on the center pixel, you need a square array with an odd dimension. A five-by-five, seven-by-seven, and any other odd-dimension array would work just as well as a three-by-three array. The previous example could even be implemented using a one-by-one array. For instance, in the following method, you create an array of floats containing a single element. You then create a new one-by-one Kernel object from this element, and pass the object to the ConvolveOp constructor. Finally you apply the BufferedImageOp to your original BufferedImage to obtain the altered image.

   private void setBrightnessFactor(float multiple) {
     float[] brightKernel = {multiple};
     BufferedImageOp bright
       = new ConvolveOp(new Kernel(1, 1, brightKernel));
     convolvedImage 
       = bright.filter(originalImage, null);
     repaint();

   }

This method highlights two requirements. One requirement is that you need to override the paintComponent() method to redraw the new image every time it changes.

   public void paintComponent(Graphics g) {
     g.drawImage(convolvedImage, 0, 0, this);
   }

The second requirement is that you need to first create a BufferedImage to which you can apply these effects. In this case, read in test.jpg and create an Image from it. Then create a BufferedImage with the same dimensions as the original image. As a last step, you need to draw the image onto the BufferedImage.

   Image image = new ImageIcon("test.jpg").getImage();
   originalImage 
     = new BufferedImage(image.getWidth(null),
       image.getHeight(null), 
       BufferedImage.TYPE_INT_RGB);
   Graphics g = originalImage.createGraphics();
   g.drawImage(image, 0, 0, null);
   g.dispose();

Instead of using a one-dimensional kernel, you can also use a three-by-three kernel through the following version of setBrightnessFactor.

   private void setBrightnessFactor(float multiple) {
      float[] brightKernel = {0,    0,     0,
                              0, multiple, 0,
                              0,    0,     0};
      BufferedImageOp bright
        = new ConvolveOp(new Kernel(3, 3, brightKernel));
      convolvedImage 
        = bright.filter(originalImage, null);
      repaint();
   }

Here is a sample program, BrightnessChanger, that creates a viewer for an image. The viewer includes a slider that you can use to control the brightness by altering the value of the multiple being used. You will need a test image. In this case, the image name is set to test.jpg. The image should be in the directory in which you run the BrightnessChanger program. You can find a fun sample image to use.

    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.ImageIcon;
    import javax.swing.JSlider;
    import javax.swing.event.ChangeListener;
    import javax.swing.event.ChangeEvent;
    import java.awt.Graphics;
    import java.awt.Image;
    import java.awt.BorderLayout;
    import java.awt.image.Kernel;
    import java.awt.image.ConvolveOp;
    import java.awt.image.BufferedImageOp;
    import java.awt.image.BufferedImage;

    public class BrightnessChanger extends JPanel {

       private BufferedImage originalImage;
       private BufferedImage convolvedImage;
       private JSlider slide = new JSlider(1,50,10);

       BrightnessChanger() {
         createBufferedImages();
         setUpJFrame();
       }

       private void createBufferedImages() {
         Image image
           = new ImageIcon("test.jpg").getImage();
         originalImage
           = new BufferedImage(image.getWidth(null),
             image.getHeight(null),
             BufferedImage.TYPE_INT_RGB);
         convolvedImage
           = new BufferedImage(image.getWidth(null),
             image.getHeight(null),
             BufferedImage.TYPE_INT_RGB);
         Graphics g = originalImage.createGraphics();
         g.drawImage(image, 0, 0, null);
         g.dispose();
         setBrightnessFactor(1);
       }

       private void setUpJFrame() {
         JFrame myFrame = new JFrame("Image Brightness");
         myFrame.setSize(convolvedImage.getWidth(),
                         convolvedImage.getHeight());
         myFrame.getContentPane().setLayout(
           new BorderLayout());
         myFrame.getContentPane().add(
           this, BorderLayout.CENTER);
         slide.addChangeListener(
           new BrightnessListener());
         myFrame.getContentPane().add(
           slide,BorderLayout.SOUTH);
         myFrame.setDefaultCloseOperation(
           JFrame.EXIT_ON_CLOSE);
         myFrame.setVisible(true);
       }

       private void setBrightnessFactor(float multiple) {
         float[] brightKernel = {multiple};
         BufferedImageOp bright
           = new ConvolveOp(
               new Kernel(1, 1, brightKernel));
         bright.filter(originalImage, convolvedImage);
         repaint();

       }

        public void paintComponent(Graphics g) {
         g.drawImage(convolvedImage, 0, 0, this);
       }

       class BrightnessListener implements ChangeListener{
         public void stateChanged(
           ChangeEvent changeEvent) {
             setBrightnessFactor(
               (float)(slide.getValue())/10);
         }
       }

       public static void main(String[] args) {
           new BrightnessChanger();
         }

    }

The BrightnessChanger example serves as an introduction to convolutions, and not as a recommended way to control the brightness of your images. In a future tech tip, you will see how to use some of the controls targeted at color and brightness.

In addition to using square, odd-dimensional arrays for the kernel of the convolution, you need to use (for the most part) symmetric arrays. The next example uses three-by-three arrays that are built from the following three components:

             0  0  0         0  1  0              1  0  1
   IDENTITY  0  1  0   EDGE  1  0  1      CORNER  0  0  0
             0  0  0         0  1  0              1  0  1

The resulting array is built from linear combinations of these building blocks. As long as the sum of the entries is not zero, you normalize the kernel by dividing by this sum. That way, you get an image that has the same intensity as the original image. For example, if you add the EDGE and the IDENTITY you get the following:

   0  1  0  
   1  1  1  
   0  1  0

The sum of the entries is 5. You then normalize by dividing each entry by 5 to obtain the following "blur" array:

    0  0.2  0
   .2  0.2 0.2
    0  0.2  0

Here is the code for creating these kernels from parameters for multiples of the three base arrays.

   private Kernel getKernel(
     int corner, int edge, int identity) {
      float[] kernel = new float[9];
      int sum = corner * 4 + edge * 4 + identity;
      if (sum == 0) sum = 1;
      for (int i = 0; i < 9; i++) {
        kernel[i] = (corner * CORNER[i]
          + edge * EDGE[i]
          + identity * IDENTITY[i]) / sum;
    }
    return new Kernel(3, 3, kernel);
 }

After you have the kernel, you perform the convolution much as before.

   void convolveImage(Kernel kernel) {
      BufferedImageOp convolve
        = new ConvolveOp(kernel);
      buffImage = convolve.filter(buffImage, null);
      repaint();
   }

Now let's run a test program, named Convolve. When you run Convolve, you need to enter three command-line parameters. These are are int values corresponding to the multiples of CORNER, EDGE, and IDENTITY. For example, if you enter:

   java Convolve 1 0 0 

you should see a somewhat blurred image. Again, you need an image named test.jpg.

   import javax.swing.ImageIcon;
   import javax.swing.JFrame;
   import javax.swing.JPanel;
   import java.awt.image.BufferedImage;
   import java.awt.image.BufferedImageOp;
   import java.awt.image.ConvolveOp;
   import java.awt.image.Kernel;
   import java.awt.Image;
   import java.awt.Graphics;

   public class Convolve extends JPanel {
      private BufferedImage buffImage;

      private final float[] IDENTITY = {0, 0, 0,
                                        0, 1, 0,
                                        0, 0, 0};

      private final float[] EDGE = {0, 1, 0,
                                    1, 0, 1,
                                    0, 1, 0};

      private final float[] CORNER = {1, 0, 1,
                                      0, 0, 0,
                                      1, 0, 1};


      Convolve(int corner, int edge, int identity) {
        createBufferedImages();
        setUpJFrame();
        convolveImage(getKernel(corner, edge, identity));
      }

      private void createBufferedImages() {
        Image image 
          = new ImageIcon("test.jpg").getImage();
        buffImage 
          = new BufferedImage(image.getWidth(null),
            image.getHeight(null), 
            BufferedImage.TYPE_INT_RGB);
        Graphics g = buffImage.createGraphics();
        g.drawImage(image, 0, 0, null);
        g.dispose();
      }

      private void setUpJFrame() {
        JFrame myFrame = new JFrame("Image Brightness");
        myFrame.setSize(buffImage.getWidth(),
          buffImage.getHeight());
        myFrame.getContentPane().add(this);
        myFrame.setDefaultCloseOperation(
          JFrame.EXIT_ON_CLOSE);
        myFrame.setVisible(true);
      }

      void convolveImage(Kernel kernel) {
        BufferedImageOp convolve
          = new ConvolveOp(kernel);
        buffImage = convolve.filter(buffImage, null);
        repaint();

      }

      private Kernel getKernel(
        int corner, int edge, int identity) {
         float[] kernel = new float[9];
         int sum = corner * 4 + edge * 4 + identity;
         if (sum == 0) sum = 1;
         for (int i = 0; i < 9; i++) {
           kernel[i] = (corner * CORNER[i]
             + edge * EDGE[i]
             + identity * IDENTITY[i]) / sum;
         }
        return new Kernel(3, 3, kernel);
      }

      public void paintComponent(Graphics g) {
        g.drawImage(buffImage, 0, 0, this);
      }


      public static void main(String[] args) {
        if (args.length != 3) {
          System.out.println("Usage: java Convolve" +
                             " corner edge identity");
          System.out.println("where corner, edge, " +
                             "and identity are ints");
          System.exit(0);
        }
        int corner = Integer.parseInt(args[0]);
        int edge = Integer.parseInt(args[1]);
        int identity = Integer.parseInt(args[2]);
        new Convolve(corner, edge, identity);

      }

   }

Try other combinations of the three command-line parameters. For example, enter -1 -1 8 to see highlights of the edges of objects. This cannot be normalized as the sum is zero. Or enter 0 -1 5 for a sharper view of the original image. You can easily extend this example to five-by-five arrays and higher, with other symmetric primitives.

For more information on using ConvoleOp, see Chapter 5: "Imaging" in the Programmer's Guide to the Java 2D API.

.
.

USING HTTPURLCONNECTION TO ACCESS WEB PAGES

This tip shows you how you can use HttpURLConnection and its subclass HttpsURLConnection to access secure web pages. You'll also see how to easily follow a redirect from a non-secure page to a secure one. For more information about HTTP and HTTPS, see the HTTP 1.1 RFC 2616 and the HTTPS RFC 2818.

As a first example, let's use HttpURLConnection in the following WebPageReader program to connect to a given URL, and then print the contents of the page to standard out.

   import java.net.URL;
   import java.net.MalformedURLException;
   import java.net.URLConnection;
   import java.io.IOException;
   import java.io.BufferedReader;
   import java.io.InputStreamReader;

   public class WebPageReader {

      private static URLConnection connection;

      private static void connect( String urlString ) {
        try {
          URL url = new URL(urlString);
          connection = url.openConnection();
        } catch (MalformedURLException e){
          e.printStackTrace();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }

      private static void readContents() {
        BufferedReader in = null;
        try {
          in = new BufferedReader(
            new InputStreamReader(
              connection.getInputStream()));

          String inputLine;
          while (
            (inputLine = in.readLine()) != null) {
            System.out.println(inputLine);
          }
        } catch (IOException e) {
          e.printStackTrace();
        }
      }

      public static void main(String[] args) {
        if (args.length != 1) {
          System.err.println("usage: java WebPageReader "
                                             + "<url>");
          System.exit(0);
        }
        connect(args[0]);
        readContents();
      }
   }

Note that if you are behind a firewall, you need to have the proxyHost and proxyPort variables set as follows:

   http.proxyHost=webcache
   http.proxyPort=8080

   https.proxyHost=webcache
   https.proxyPort=8080

You can do this by using the command line flag -D or by using calls to System.setProperty().

After compiling WebPageReader, you can list the contents of the Core Java Technologies Tech Tips home page with the following command:

   java WebPageReader 
     http://java.sun.com/developer/JDCTechTips/

URLConnection is an abstract class. The openConnection() method in the URL class returns the appropriate concrete subclass to read the specified URL. This will be either a subclass of HttpURLConnection or HttpsURLConnection if you pass in an http or https URL. If you add

   System.out.println(connection.getClass());

to the line following the call to openConnection(), you will see that the connection returns an instance of the underlying implementation class of HttpURLConnection. For example, you might see the following:

   class sun.net.www.protocol.http.HttpURLConnection

Similarly, you can use the same WebPageReader code to read a secure page like this:

   java WebPageReader https://today.dev.java.net

In this latter case, you should find that connection is of type HttpsURLConnection, a subclass of HttpURLConnection. Specifically, you should see the underlying implementation class such as the following:

   class sun.net.www.protocol.https.HttpsURLConnectionImpl

Generally, when you type a URL into a browser, you don't know whether the target page is a secure page. In other words, to display the today.dev.java.net page, you enter http://today.dev.java.net. You expect the browser to redirect you if necessary, and perform the appropriate handshake to seamlessly connect you to https://today.dev.java.net. Let's see if the WebPageReader program does the required redirection.

   java WebPageReader http://today.dev.java.net

Instead of being redirected to the page you want, you should see the following message:

   <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
   <HTML><HEAD>
   <TITLE>301 Moved Permanently</TITLE>
   </HEAD><BODY>
   <H1>Moved Permanently</H1>
   The document has moved
   <A HREF="https://today.dev.java.net/">here</A>.<P>
   <HR>
   <ADDRESS>
      Apache/1.3.26 Server at today.dev.java.net Port 80
   </ADDRESS>
   </BODY></HTML>

It is difficult to see from this information, but the problem is not with the redirection. If you run the program with the URL http://linux.java.net, it properly redirects to http://community.java.net/linux, and you get the contents you requested. To take a closer look at what's going on, you need to explicitly use an HttpURLConnection. Let's keep things simple by eliminating the code that prints the contents of the web page to standard out. Here is the RedirectingReader:

   import java.net.URL;
   import java.net.MalformedURLException;
   import java.net.HttpURLConnection;
   import java.io.IOException;

   public class RedirectingReader {

      private static HttpURLConnection connection;

      private static void connect( String urlString ) {
        try {
          URL url = new URL(urlString);
          connection
              = (HttpURLConnection)url.openConnection();
          System.out.println(connection.getURL());
          System.out.println(
              connection.getResponseCode() +
              " " + connection.getResponseMessage());
          System.out.println(connection.getURL());
        } catch (MalformedURLException e){
          e.printStackTrace();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }

      public static void main(String[] args) {
        if (args.length != 1) {
          System.err.println(
               "usage: java WebPageReader "
               + "<url>");
          System.exit(0);
        }
        connect(args[0]);
      }
   }

Compile and then run RedirectingReader with a URL that redirects like this:

   java RedirectingReader http://linux.java.net/

You should see something like the following output:

   http://linux.java.net/
   200 OK
   http://community.java.net/linux/

The first line will quickly appear. You have created a URL from the String input and then instantiated your HttpURLConnection object. Then there's a pause while you request http://linux.java.net. During this time the page is redirecting, so the response code and message that you receive are from the redirected page. This redirected page is listed in the third line of the output.

You can see what happens if you don't allow redirection by adding the following line below URL url = new URL(urlString);

   HttpURLConnection.setFollowRedirects(false);

Now your output should look like this:

   http://linux.java.net/
   302 Found
   http://linux.java.net/

This indicates that a 302 error has been encountered. It means that you have not followed the redirect, and the URL stays the same. Before proceeding, remove the line you just added and rerun the RedirectingReader program to verify that redirection is again working.

Now rerun RedirectingReader once more, and pass in http://today.dev.java.net. You should see the following output:

   http://today.dev.java.net
   301 Moved Permanently
   http://today.dev.java.net

This indicates that the program was not able to handle the "Moved Permanently" error, and redirect. This is the desired default behavior. You cannot redirect between http and https seamlessly, for security concerns. The information on where to redirect is contained in the header of this response. Here is the full response header for the previous request:

   HTTP/1.1 301 Moved Permanently
   Date: Tue, 03 Feb 2004 01:38:43 GMT
   Server: Apache/1.3.26 (Unix) mod_ssl/2.8.10 
   OpenSSL/0.9.6b
   mod_jk/1.2.1
   Location: https://today.dev.java.net/
   Content-type: text/html; charset=iso-8859-1

You have seen that getResponseCode() and getResponseMessage() methods in HttpURLConnection return the information contained on the first line of this response. You can also use the getHeaderField() method, and pass in the name of a header field as a String. For example, getHeaderField("Location") returns the value https://today.dev.java.net/.

You can find more details about the format of the request and response headers in the HTTP 1.1 and HTTPS RFCs mentioned at the beginning of this tip. In a 300 level response, the "Location:" should provide the value of the location for the redirection. To explore this, add the following check to see if a 301 or 302 error is received:

   private static boolean mustRedirect(int code){
      if (code == HttpURLConnection.HTTP_MOVED_PERM ||
          code == HttpURLConnection.HTTP_MOVED_TEMP)  {
      System.out.println("Received error " + code +
                         ", trying secure redirect");
        return true;
      } else return false;
   }

Recall that you only get a 301 or 302 response code if the redirect cannot be handled automatically. So far, this meant that the redirect required an HttpsURLConnection and not an HttpURLConnection. Follow the redirection information provided in the "Location:" field by resetting the values of url and connection from this new information, as follows:

   url = new URL("https://"+ url.getHost()
                                + url.getFile());
   connection
        = (HttpsURLConnection)url.openConnection();

Finally, let's put this all together to get a web reader than can redirect to secure pages when needed.

   import javax.net.ssl.HttpsURLConnection;
   import java.net.URL;
   import java.net.MalformedURLException;
   import java.net.HttpURLConnection;
   import java.io.IOException;
   import java.io.BufferedReader;
   import java.io.InputStreamReader;

   public class RedirectingReader {
     private static HttpURLConnection connection;
     private static URL url;

     private static void connect(String urlString) {
       try {
         url = new URL(urlString);
         connection
           = (HttpURLConnection) url.openConnection();
         int code = connection.getResponseCode();
         if (mustRedirect(code))
          secureRedirect(
            connection.getHeaderField("Location"));
         readContents();
       } catch (MalformedURLException e) {
         e.printStackTrace();
       } catch (IOException e) {
         e.printStackTrace();
       }
     }

     private static boolean mustRedirect(int code) {
       if (code == HttpURLConnection.HTTP_MOVED_PERM ||
         code == HttpURLConnection.HTTP_MOVED_TEMP) {
         return true;
       } else
         return false;
     }

     private static void secureRedirect(String location)
       throws IOException {
       System.out.println(location);
       url = new URL(location);
       connection
         = (HttpsURLConnection) url.openConnection();
     }

     private static void readContents() {
       BufferedReader in = null;
       try {
         in = new BufferedReader(
           new InputStreamReader(
             connection.getInputStream()));

         String inputLine;
         while ((inputLine = in.readLine()) != null) {
           System.out.println(inputLine);
         }
       } catch (IOException e) {
         e.printStackTrace();
       }
     }

     public static void main(String[] args) {
       if (args.length != 1) {
         System.err.println("usage: java WebPageReader "
           + "<url>");
         System.exit(0);
       }
       connect(args[0]);
     }
   }

Compile this updated RedirectingReader, and run it with different inputs to verify that the behavior is as it should be. For example, try http://today.dev.java.net and http://linux.java.net. Running RedirectingReader with secure and non-secure web pages that do not redirect also works as it should. You can now go back and eliminate all of the println() method calls and add the readContents() method from the original WebReader program.

.
.
Reader Feedback
Excellent   Good   Fair   Poor  

If you have other comments or ideas for future technical tips, please type them here:

Comments:
If you would like a reply to your comment, please submit your email address:
Note: We may not respond to all submitted comments.

Have a question about Java programming? Use Java Online Support.

.
.

IMPORTANT: Please read our Terms of Use, Privacy, and Licensing policies:
http://www.sun.com/share/text/termsofuse.html
http://www.sun.com/privacy/
http://developers.sun.com/dispatcher.jsp?uid=6910008


Comments? Send your feedback on the Core Java Technologies Tech Tips to: http://developers.sun.com/contact/feedback.jsp?category=newslet

Subscribe to other Java developer Tech Tips:

- Enterprise Java Technologies Tech Tips. Get tips on using enterprise Java technologies and APIs, such as those in the Java 2 Platform, Enterprise Edition (J2EE).
- Wireless Developer Tech Tips. Get tips on using wireless Java technologies and APIs, such as those in the Java 2 Platform, Micro Edition (J2ME).

To subscribe to these and other JDC publications:
- Go to the JDC Newsletters and Publications page, choose the newsletters you want to subscribe to and click "Update".
- To unsubscribe, go to the subscriptions page, uncheck the appropriate checkbox, and click "Update".


ARCHIVES: You'll find the Core Java Technologies Tech Tips archives at:
http://java.sun.com/developer/JDCTechTips/index.html


Copyright 2004 Sun Microsystems, Inc. All rights reserved.
4150 Network Circle, Santa Clara, CA 95054 USA.


This document is protected by copyright. For more information, see:
http://java.sun.com/developer/copyright.html


Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks or registered trademarks (http://www.sun.com/suntrademarks/) of Sun Microsystems, Inc. in the United States and other countries.

Sun Microsystems,
Inc.
.
.