ORIGINAL DRAFT

Much of the business attraction to Web publishing comes from the ability to update content without the high costs of printing and distribution. The content can be static, dynamic or interactive. Most of the the images you’ll find on the web today are static, however, though it’s possible to generate or modify images from a web server on-demand, increasing the flexibility available to content providers, artists, designers and marketing organizations.

This article demonstrates a few techniques for generating dynamic images from a web server. These techniques are not exclusive to the web. They can be used from browsers or from other TCP/IP-enabled software, providing a foundation for building an Imaging Application Server, capable of delivering new or modified images on-the-fly. Servlet-enabled web servers provide a secure and reliable high-level architecture, ideal for developing Application Servers, dedicated to tasks that may or may not relate directly to the web.

This article shows you how you can map the Java Image Producer/Consumer model onto servlets to generate and filter images using simple URLs. We’ll build three servlets: the GradientServlet, which produces dynamic color gradient images; the ButtonServlet, which filters GIF images into buttons; and the BrightnessServlet, which lets you adjust color brightness on images. These are merely examples of what can be done with servlets, but they clarify a number of issues and present an innovative approach to serving up image content on the web.

Image Producers, Filters and Servlets

Java 1.0 and 1.1 use a Producer/Consumer model to permit asynchronous loading of images. Primarily, this was a design choice made to accommodate Applets on the Web, so it tends to mimic the way browsers function. While Java 1.2 introduces two new models for handing images more effectively, we want our design to be as portable as possible, so we’ll use this model to maintain compatibility.

Figure 1: Producer-Consumer Model.

Figure 1: Producer-Consumer Model.

The Producer/Consumer model allows us to use an Observer class to display the actual image on a screen or to print it out. The Producer generates pixels from a source, which might be a file, a dynamically created image or a filtered image. An image Consumer can read the image ColorModel and the pixel data and process it in arbitrary ways. A class that implements the ImageConsumer and ImageProducer interfaces can be used to filter the data from one image into another, along a Producer/Consumer chain. By implication, the Producer/Filter/Consumer chain can accommodate an arbitrary number of image filters.

Figure 2: Producer-Filter-Consumer.

Figure 2: Producer-Filter-Consumer.

Java Servlets represent an optimized solution between CGI programs and tightly-coupled Web server extensions. Servlets are considerably more practical, since the code is portable, typically more efficient than CGI scripts, and easier to implement than vendor-dependent server extensions. The first round of web Servlet implementations was somewhat inconsistent and required programming with the lowest common denominator, but the Java Web Server (JWS) from Sun paved the way for a more complete solution which seems to be maturing rapidly. The JWS introduced a number of capabilities which have now been implemented more widely.

Servlets are based on the Request/Response model implemented in HTTP, but they do not directly depend on the protocol. There are two packages in the Java servlet extensions - javax.servlet, which implements the basic servlet classes; and javax.servlet.http, which provides classes at a higher-level of abstraction with more of an HTTP flavor. Implementing basic servlets requires a class that typically inherits from GenericServlet and a service method with a request and response object in its two arguments. The same model can be applied at a higher level by extending the HttpServlet and implementing the same method or by implementing the methods which map onto HTTP commands, such as doHead, doGet, doPut, doPost, etc.

Figure 3: Request-Response Model.

Figure 3: Request-Response Model.

The most recent Servlet implementations support servlet-chaining which allows the output of one servlet to be fed into the input of another, implementing something akin to a pipe operation under UNIX or DOS. This can be used to perform a number of interesting tricks, one of which we’ll be exploring in this article.

Figure 4: Request-Chain-Response.

Figure 4: Request-Chain-Response.

You may have noticed that the Producer/Consumer model and the Request/Response paradigm have several things in common. Furthermore, the ability to filter an image in a Producer/Filter/Consumer chain is not unlike Servlet chaining principles. In fact, there is enough overlap between these views that several possibilities begin to emerge if you think about it for a moment.

It should be easy enough to use an ImageProducer to generate an image on-demand when an HTTP request is made. This is so close to the mechanism involved when you load an image file in a web page, that the servlet would be useful in exactly the same context. You could embed images which are generated dynamically into a web page as easily as static images. This clearly presents us with a fertile landscape, waiting for innovation.

What’s more, by using Servlet chaining, you could filter images the same way you would if you were using a local ImageFilter. This might be useful where the image needed adjustment, perhaps to handle different browsers or platforms, but also when the image was generated dynamically and you needed to apply some changes before delivery.

Configuration Requirements

To run the servlets we’ll implement in this article, you’ll need either the Java Web Server or another server which implements the proper Servlet extensions. I’d like to recommend either the Java Web Server or JRun from Live Software. I personally work mostly under Windows NT and have both the Microsoft Internet Server and the Java Web Server from Sun on my machines (Atrieva uses the JWS commercially and purchased multiple licenses, though we run our production versions under Solaris). I use the MS server all the time to publish requirements and specifications internally from my machine, but I’ve also run the JWS because it had the most advanced servlet engine at the time. JRun changes that for the better. It implements all the functionality you need, its free (in its basic form - you can upgrade to more capabilities) and its available for the Netscape, Microsoft, Apache and other web servers, on multiple platforms. There are other solutions as well, but if you need to leverage the server you already use, JRun is a great choice.

The servlets we’ll work with depend on JDK 1.1 and the most recent Servlet API (documentation and classes are available with both JWS and JRun). We’re also making the assumption that working with Java 1.1 today, means having the Swing set handy, so the test harness for the image classes is implemented with JFC 1.0.3. Its easy enough to make it more generic if you need to, and Swing is only used in the test harness, but I leave that up to you if you feel so compelled. Finally, we use some handy image classes from the Acme collection from Jef Poskanzer, who’s volume of work is quite remarkable. In particular, we’ll use the Acme GIFEncoder class to produce GIF formatted output from our servlets.

To run the servlets, you merely need to download the code from the Java Pro web site and put the class files in your web server’s servlets directory.

The GradienServlet

Producing dynamic image content can take on many forms. You can imagine charts, graphs or other graphical representations being generated from real-time data. Another possible use is in permitting more control by the author of a document at design time. We’ll be developing a servlet which produces a gradient from left to right between any two colors in any image size the caller specifies. HTML requests can be delivered with arbitrary parameters on the command line or as a result of a POST operation from a form. We’ll demonstrate using command line arguments, which have the form:

http://host/servlet/name?var1=val1&var2=val2...

The var1 and var2 tokens are variable names and val1 and val2 are values associated with their respective names. The URL includes the host name, servlet directory and the name of the servlet being called. You can set up aliases for servlets which make them look exactly like HTML, or other source documents or images, but we’ll assume the simplest scenario in this article to avoid the extra complexity.

The GradientServlet will respond to an HTTP request, process the arguments, call on the GradientImageProducer to produce an image and encode the result as a GIF-format stream on the output. We’ll make ImageProducer development a little easier by implementing an AbstractImageProducer class which we can easily extend to implement many kinds of ImageProducer.

Figure 5: Gradients in a Web Page.

Figure 5: Gradients in a Web Page.

To create a gradient, we’ll use a process called interpolation which creates a sequence of points along a line between two end points. In our case, we’ll interpolate the color along each pixel of the image, from left to right, in the GradientImageProducer. This lets us create arbitrary gradients usign a simple, reusable algorithm.

Its typically easier to deal with commonalities by abstracting them up the class inheritance tree. Since classes which implement the ImageProducer interface must duplicate a lot of common code, it makes sense to develop an AbstractImageProducer from which our real producers will inherit. The ImageProducer interface requires the following methods:

  • addConsumer(ImageConsumer)
  • removeConsumer(ImageConsumer)
  • isConsumer(ImageConsumer)
  • requestTopDownLeftRightResend(ImageConsumer)
  • startProduction(ImageConsumer)

The addConsumer, removeConsumer and isConsumer methods manage or check the list of consumers associated with the producer we are working with. Actual image production starts when a request is made by the Consumer, calling either requestTopDownLeftRightResent directly, or more typically startProduction, which iterates through each ImageConsumer to send the pixels. The startProduction method also adds a new ImageConsumer to the list of consumers, on the fly.

Listing 1 shows the code for the AbstractImageProducer. Notice that the only unimplemented, abstract method is the produceImage method, which rpovides the image width and height in its two arguments. The pixels array stores the individual image pixels and its up to the produceImage method to allocate that array. Let’s look at the GradientImageProducer to see how we do this in practice. You can, of course, use the same mechanism to produce other images by subclassing AbstractImageProducer anytime.

Listing 2 shows the code for the GradientImageProducer. Most of that code is setup work, collecting the image width and height, along with the source and target colors for the gradient. The gradient array produced by the ColorUtil class interpolate method interpolate (discussed later in this article) is used to actually generate the image pixels. Its much more efficient to preprocess and cache the gradient array, which we access later in the produceImage method to populate the actual image pixels. The produceImage method merely traverses the whole pixels array and puts appropriate gradient values in each position.

Listing 3 shows code for the AbstractServlet class. Each of the servlets in this article inherit from the AbstractServlet so we can keep the commonalities in one place. This class, in turn, inherits from the GenericServlet class provided as part of the Servlet API. There are only four methods used by the servlet subclasses. The most important one is the exception method, which routes the stack trace to a web page when exceptions are thrown. We put all the code in a subclassed servlet into a try/catch structure so we can pass the response object and any encountered exceptions to this method when something goes wrong. This makes it easier to diagnose problems, but its not designed to be overly user friendly.

The getParameter method overcomes the deprecated error you’ll get if you try to use the getParameter method supplied by the Servlet API, since the preferred approach is to use getParameterValues (though Sun does plan to undeprecate this call). We also intercept null values and provide a default. The getInteger method makes it slightly easier to handle integer parameters. The getColor method makes it easier to handle color values.

The mapping between an ImageProducer and a servlet that produces the image is not very difficult, although it did take a fair amount of experimenting before I got it right.

Listing 4 shows the code for the GradientServlet. We implement the service method and ignore the incoming stream, testing only for values which are posted or sent along the command line. Having retrieved these values, we set the content type to reflect the fact that our output stream will return a GIF image. Then, we get the ServletOutputStream and use it with the Acme GIFEncoder to generate the image from our ImageProducer. That’s all there is to it!

The ButtonServlet

One of the more common image operations on the web is taking a picture and making a button out of it. There are numerous approaches for doing this, most of which just put a light colored edge on the top and left side and a dark edge at the bottom and right side. A more interesting look requires manipulating the edge pixels and darkening them individually to get the desired effect. By brightening the top left and darkening the bottom right, the button looks lifted. Reversing the process makes the button look pressed.

Figure 6: Filtered Buttons from Gradients.

Figure 6: Filtered Buttons from Gradients.

The ButtonServlet provides access to a handful of variables to determine the size, border thickness and whether the button is lifted or pressed. The ButtonImageFilter simply takes the edges of the image being filtered and darkens or lightens them appropriately, tapering the corners so that the visual effect is complete.

Its possible to implemented complicated image filters by inheriting directly from the ImageFilter class, but the Java API includes something called the RGBImageFilter which makes it easier to develop filters which act directly on pixels. If you need to change image dimensions; if your image filter depends on positional information; or if you can’t overwrite pixel values while processing, you can’t use an RGBImageFilter. To change the brightness of every pixel, you can do it the easy way, but we need to be a little more sophisticated to handle our ButtonImageFilter.

Listing 5 shows the AbstractImageFilter code. This class was created to make developing more sophisticated filters a little easier. We allocate an array of pixels and set the image dimension values (width and height) when a call is made to setDimensions. We then capture the image data (as an image producer) when setPixels is called, for either a DirectColorModel, which uses integers to represent each pixel, or for an IndexColorModel, which uses byte indexes into a color table to represent each pixel color. In either case, we process the pixels in a way that produces a DirectColorModel representation. Finally, we process the data when the imageComplete method is called and push the resulting pixels out to the image consumer.

The AbstractImageFilter calls an abstract method which you have to implement to actually do the work. The processPixels method should be implemented in a subclass, which automatically has access to the width and height values, and the pixels array. You can do any kind of processing you like this way, including completely substituting the pixels array and/or changing dimensions.

Some of the best looking buttons appearing on web pages are often the ones that take an image and taper the edges with the right color adjustments. The ButtonImageFilter does exactly that, allowing you to control the thickness of the border and whether the button will look pressed or lifted. The ButtonImageFilter class inherits from the AbstractImageFilter and implements a processPixels method which loops through all the pixels, calling a filterRGB method in exactly the same way an RGBImageFilter does, though we have much more control this way than we would if we inherited from RGBImageFilter.

Listing 6 shows the ButtonImageFilter code. We have to test which side of the image we’re on and apply our calculations to brighten or darken the pixels at the proper locations. Since we know the x and y positions and the image width and height, this is all pretty straight forward. For each pixel which is on the upper left, we call the brighter method and call the darker method for bottom right pixels. The real trick here is that we account for the corner tapering in the upper left pixels and ignore them on the bottom right, since the earlier conditions will have returned before reaching that part of the code. The darker/brighter calls are reversed if the image is sunken instead of lifted.

The darker/brighter and interpolation calculations are part of the ColorUtil class (available on the web site). There are three types of darker and brighter calls, which accept different levels of abstraction. The real work is done in the clauses which get the pixel values and return a processed integer. Those clauses accept the red, green and blue factors, which are floating point values between 0 and 1. These represent how much to brighten or darken a pixel. To make life easier at a higher level, we can pass red, green and blue percent values, which are turned into factor values by those methods. Finally, at the highest level, we can pass a single percent value, which is applied to each of the color components.

The interpolate method creates an array of colors between two end points. We separate each of the red, green and blue components for the source and target colors and calculate the increment for each. Having done this, we fill an array with the colors that represent each transition between the source and target colors.

GIFImageSource is a deceivingly simple class which is the result of diging trough several books, including "Java Secrets" from IDG as well as source code from a number of existing commercial libraries, along with some reverse-engineered documentation from Sun’s classes which were posted on the web. The GIFImageSource class is risky because it accesses the sun.awt.image package, which is outside the published API and may be subject to changes that cannot be predicted.

Having said that, its seems safe enough to use it, given that access to GIF streams is not a feature thats about to dissapear. Futhermore, the published API classes rely on this mechanism to load images from a URL. You can wrap the GIFImageSource around any open input stream and easily read the data into an image filter. You don’t need to understand how it works to make use of it.

The GIFImageSource class implements the ImageProducer interface by inheriting from URLImageSource, which needs the right decoder to do its job. Our class hard-codes a local file URL as a base and passes it to the parent class constructor. We save the input stream we’ll be reading from in a method variable and reference it in the getDecoder method, where we create a new instance of GIFImageDecoder from the Sun classes and return it.

Listing 7 shows code for the ButtonServlet. We use a GIFImageSource to load the GIF image from the incoming ServletInputStream. Having taken the incoming stream and converted it into an ImageProducer, we can apply our ButtonFilter and use the GIFEncoder to send the output back through the ServletOutputStream. As with the GradientServlet, we collect a few parameters up front and use default values to avoid missing argument problems.

The BrightnessServlet

The ButtonImageFilter darkens and brightens pixels ina given image. Sometimes an image or a photograph is too dark or too bright and needs to be adjusted this way. This is especially common with the disparity between monitors, notably between Mac and Windows platforms. We won’t talk about the browser detection you would have to do to accomplish this. Instead, we’ll consider brightening or darkening an image to be useful in a variety of circumstances and implement it based on those assumptions alone.

Figure 7: Filtered Brightness from Gradients.

Figure 7: Filtered Brightness from Gradients.

To brighten or darken a color, we can apply a little math to each pixel and control the percentage of change. We can use the darken and brighten calls available in our ColorUtil class. We’ll write our BrightnessImageFilter to handle individual color components but we’ll expose only the single percentage option in the BrightnessServlet. This serves our purposes well enough ad represents the most likely usage pattern. Its easy enough to extend the functionality if you want more control.

The code for the BrightnessImageFilter class is show in Listing 8. The approach is very similar to the ButtonImageFilter, though we put more code inline for performance reasons and use a percentage value for the three colors, or each of the colors individually. The constructors precalculate the percentage factors and store those instead of the integer values to make things quicker. Unlike the ButtonImageFilter, the BrightnessImageFilter applies a calculation to every pixel in the image, so speeding up the process is slightly more important. The filterRGB method does all the actual work, setting each of the pixel values.

Listing 9 shows code for the BrightnessServlet class. This servlet is virually identical to the ButtonServlet, with the BrightnessImageFilter being applied instead of the ButtonImageFiler. The arguments are only slightly different.. Naturally, you can take the same framework and apply it to virtually any ImageFilter you want to make available from a web server.

Infinite Possibilities

We’ve only skimmed the surface in this article. Using the ImageProducer/Servlet technique, you can generate all kinds of dynamic image content, from simple gradients to complex art, data maps, navigational tools, views into unseen worlds, charts, graphs… anything you can imagine. On each image, or on existing pictures, diagrams, textures, icons and other static content, you can apply image filters that curve, buttonize, adjust, cajole, taper, twist, twirl, shade or transform your image in any of the limitless ways pixels can be manipulated. You can apply these alone or in sequence, providing an endless variety of combinations.

Listing 1

public abstract class AbstractImageProducer
  implements ImageProducer
{
  protected Vector consumers = new Vector();
  protected int[] pixels;
  protected int width, height;

  public AbstractImageProducer(int width, int height)
  {
    this.width = width;
    this.height = height;
  }

  public synchronized void addConsumer(ImageConsumer consumer)
  {
    if (!consumers.contains(consumer))
    {
       consumers.addElement(consumer);
    }
  }

  public synchronized boolean isConsumer(ImageConsumer consumer)
  {
    return consumers.contains(consumer);
  }

  public synchronized void removeConsumer(ImageConsumer consumer)
  {
    if (isConsumer(consumer))
    {
      consumers.removeElement(consumer);
    }
  }

  public void startProduction(ImageConsumer consumer)
  {
    addConsumer(consumer);
    Vector list = (Vector)consumers.clone();
    for(int i = 0; i < list.size(); i++)
    {
      consumer = (ImageConsumer)list.elementAt(i);
      requestTopDownLeftRightResend(consumer);
      consumers.removeElement(consumer);
    }
  }
    
  public void requestTopDownLeftRightResend(ImageConsumer consumer)
  {
    consumer.setDimensions(width, height);
    consumer.setHints(ImageConsumer.TOPDOWNLEFTRIGHT |
      ImageConsumer.SINGLEPASS |
      ImageConsumer.SINGLEFRAME);
    produceImage(width, height);
    consumer.setPixels(0, 0, width, height,
      ColorModel.getRGBdefault(), pixels, 0, width);
    consumer.imageComplete(ImageConsumer.STATICIMAGEDONE);
  }

  public abstract void produceImage(int w, int h);
}

Listing 2

public class GradientImageProducer
  extends AbstractImageProducer
{
  protected int[] gradient;
	
  public GradientImageProducer(int width, int height)
  {
    this(width, height, Color.black, Color.white);
  }

  public GradientImageProducer(int width, int height,
    Color source, Color target)
  {
    super(width, height);
    gradient = ColorUtil.interpolate(source, target, width);
  }

  public void produceImage(int w, int h)
  {
    int index = 0;
    pixels = new int[w * h];
    for (int y = 0; y < h; y++)
    {
      for(int x = 0; x < w; x++)
      {
        pixels[index++] = gradient[x];
      }
    }
  }
}

Listing 3

public abstract class AbstractServlet extends GenericServlet
{
  public int getInteger(ServletRequest req, String name, int def)
  {
    return Integer.parseInt(getParameter(req, name, "" + def));
  }

  public Color getColor(ServletRequest req, String name, String def)
  {
    return Color.decode(getParameter(req, name, def));
  }

  public String getParameter(ServletRequest req, String name, String def)
  {
    String[] values = req.getParameterValues(name);
    if (values == null) return def;
    return values[0];
  }

  public void exception(ServletResponse res, Exception exception)
  {
    try
    {
      res.setContentType("text/html");
      PrintStream out = new PrintStream(res.getOutputStream());
      out.println("<html><head>");
      out.println("<title>Imaging Servlet Exception</title>");
      out.println("</head><body>");
      out.println("<h1>Imaging Servlet Error</h1>");
      out.println("<code>");
      exception.printStackTrace(out);
      out.println("</code></body></html>");
    }
    catch (IOException e) {}
  }
}

Listing 4

public class GradientServlet extends AbstractServlet
{
  public void service(ServletRequest req, ServletResponse res)
    throws ServletException, IOException
  {
    try
    {
      int w = getInteger(req, "width", 64);
      int h = getInteger(req, "height", 200);
      Color source = getColor(req, "source", "0x0000FF");
      Color target = getColor(req, "target", "0xFF0000");
		
      res.setContentType("image/gif");
      ServletOutputStream out = res.getOutputStream();
      GifEncoder encoder = new GifEncoder(
        new GradientImageProducer(w, h, source, target),
        out);
      encoder.encode();
      out.flush();
      out.close();
    }
    catch (Exception e)
    {
      exception(res, e);
    }
  }
}

Listing 5

public abstract class AbstractImageFilter extends ImageFilter
{
  protected int width;
  protected int height;
  protected int pixels[];

  public AbstractImageFilter() {}

  public void setHints(int hints)
  {
    consumer.setHints(hints & ~ImageConsumer.COMPLETESCANLINES);
  }

  public void setDimensions(int width, int height)
  {
    this.width = width;
    this.height = height;
    pixels = new int[width * height];
    consumer.setDimensions(width, height);
  }

  public void setPixels(int x, int y, int w, int h,
    ColorModel model, byte[] pix, int offset, int scansize)
  {
    for (int i = 0; i < h; i++)
    {
      int source = i * scansize + offset;
      int target = (y + i) * w;
			
      for (int j = 0; j < w; j++)
      {
        pixels[target + x + j] =
          model.getRGB(pix[source + j] & 0xff);
      }
    }
  }
			
  public void setPixels(int x, int y, int w, int h,
    ColorModel model, int[] pix, int offset, int scansize)
  {
    for (int i = 0; i < h; i++)
    {
      int source = i * scansize + offset;
      int target = (y + i) * w;
      for (int j = 0; j < w; j++)
      {
        pixels[target + x + j] =
          model.getRGB(pix[source + j]);
      }
    }
  }

  public void imageComplete(int status)
  {
    processPixels();
    consumer.setPixels(0, 0, width, height,
      ColorModel.getRGBdefault(), pixels, 0, width);
    super.imageComplete(status);
  }

  public abstract void processPixels();
}

Listing 6

public class ButtonImageFilter extends AbstractImageFilter
{
  protected int border;
  protected boolean raised;
  protected int percent;

  public ButtonImageFilter(int border, boolean raised, int percent)
  {
    this.border = border;
    this.raised = raised;
    this.percent = percent;
  }

  public void processPixels()
  {
    for (int y = 0; y < height; y++)
    {
      for (int x = 0; x < width; x++)
      {
        pixels[y * width + x] =
          filterRGB(x, y, pixels[y * width + x]);
      }
    }
  }
	
  public int filterRGB(int x, int y, int rgb)
  {
    int right = width - border - 1;
    int bottom = height - border - 1;
    if (x < border && y < (height - x) && y >= x)
    {
      if (raised) return ColorUtil.brighter(rgb, percent);
      return ColorUtil.darker(rgb, percent);
    }
    if (y < border && x < (width - y) && x >= y)
    {
      if (raised) return ColorUtil.brighter(rgb, percent);
      return ColorUtil.darker(rgb, percent);
    }
    if (x > right || y > bottom)
    {
      if (raised) return ColorUtil.darker(rgb, percent);
      return ColorUtil.brighter(rgb, percent);
    }
    return rgb;
  }
}

Listing 7

public class ButtonServlet extends AbstractServlet
{
  public void service(ServletRequest req, ServletResponse res)
    throws ServletException, IOException
  {
    try
    {
      InputStream in = req.getInputStream();
      String path = getParameter(req, "url", "");
      if (!path.equals(""))
      {
        URL url = new URL(path);
        URLConnection connection = url.openConnection();
        in = connection.getInputStream();
      }
      int thickness = getInteger(req, "thickness", 7);
      boolean lift = (getInteger(req, "lift", 1) != 0);
      int shade = getInteger(req, "shade", 33);
		
      res.setContentType("image/gif");
      ServletOutputStream out = res.getOutputStream();
      GifEncoder encoder = new GifEncoder(
        new FilteredImageSource(
          new GIFImageSource(in),
          new ButtonImageFilter(thickness, lift, shade)),
        out);
      encoder.encode();
      out.flush();
      out.close();
    }
    catch (Exception e)
    {
      exception(res, e);
    }
  }
}

Listing 8

public class BrightnessImageFilter extends RGBImageFilter
{
  protected boolean brighten;
  protected float rFactor, gFactor, bFactor;

  public BrightnessImageFilter(int percent)
  {
    this(true, percent, percent, percent);
  }	

  public BrightnessImageFilter(boolean brighten, int percent)
  {
    this(brighten, percent, percent, percent);
  }	

  public BrightnessImageFilter(
    int rPercent, int gPercent, int bPercent)
  {
    this(true, rPercent, gPercent, bPercent);
  }	

  public BrightnessImageFilter(boolean brighten,
    int rPercent, int gPercent, int bPercent)
  {
    this.brighten = brighten;
    rFactor = (float)rPercent / 100f;
    gFactor = (float)bPercent / 100f;
    bFactor = (float)gPercent / 100f;
  }
	
  public int filterRGB(int x, int y, int rgb)
  {
    if (brighten)
    {
      return ColorUtil.brighter(rgb, rFactor, gFactor, bFactor);
    }
    else
    {
      return ColorUtil.darker(rgb, rFactor, gFactor, bFactor);
    }
  }
}

Listing 9

public class BrightnessServlet extends AbstractServlet
{
  public void service(ServletRequest req, ServletResponse res)
    throws ServletException, IOException
  {
    try
    {
      InputStream in = req.getInputStream();
      String path = getParameter(req, "url", "");
      if (!path.equals(""))
      {
        URL url = new URL(path);
        URLConnection connection = url.openConnection();
        in = connection.getInputStream();
      }
      boolean b = (getInteger(req, "brighter", 1) != 0);
      int p = getInteger(req, "percent", 5);
			
      res.setContentType("image/gif");
      ServletOutputStream out = res.getOutputStream();
      GifEncoder encoder = new GifEncoder(
        new FilteredImageSource(
          new GIFImageSource(in),
          new BrightnessImageFilter(b, p)),
        out);
      encoder.encode();
      out.flush();
      out.close();
    }
    catch (Exception e)
    {
      exception(res, e);
    }
  }
}