| ImageDecoder.java |
/*
* @author Douglas A. Lyon
* @version Oct 12, 2002.10:57:23 AM
*/
package ip.ppm;
import java.awt.image.ColorModel;
import java.awt.image.ImageConsumer;
import java.awt.image.ImageProducer;
import java.io.IOException;
import java.io.InputStream;
import java.util.Vector;
// ImageDecoder - abstract class for reading in an image
//
// Copyright (C) 1996 by Jef Poskanzer <jef@acme.com>. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
// Visit the ACME Labs Java page for up-to-date versions of this and other
// fine Java utilities: http://www.acme.com/java/
/// Abstract class for reading in an image.
// <P>
// A framework for classes that read in and decode an image in
// a particular file format.
// <P>
// This provides a very simplified rendition of the ImageProducer interface.
// It requires the decoder to read the image a row at a time. It requires
// use of the RGBdefault color model.
// If you want more flexibility you can always implement ImageProducer
// directly.
// <P>
// <A HREF="/resources/classes/Acme/JPM/Decoders/ImageDecoder.java">Fetch the software.</A><BR>
// <A HREF="/resources/classes/Acme.tar.gz">Fetch the entire Acme package.</A>
// <P>
// @see PpmDecoder
// @see Acme.JPM.Encoders.ImageEncoder
public abstract class ImageDecoder implements ImageProducer {
private InputStream in;
private int width, height;
private boolean[] rowsRead;
private int[][] rgbPixels;
private boolean startedRead = false;
private boolean gotSize = false;
private boolean err = false;
private boolean producing = false;
private Vector consumers = new Vector();
private static final ColorModel model = ColorModel.getRGBdefault();
/// Constructor.
// @param in The stream to read the bytes from.
public ImageDecoder(InputStream in) {
this.in = in;
}
// Methods that subclasses implement.
/// Subclasses implement this to read in enough of the image stream
// to figure out the width and height.
abstract void readHeader(InputStream in) throws IOException;
/// Subclasses implement this to return the width, or -1 if not known.
abstract int getWidth();
/// Subclasses implement this to return the height, or -1 if not known.
abstract int getHeight();
/// Subclasses implement this to read pixel data into the rgbRow
// array, an int[width]. One int per pixel, no offsets or padding,
// RGBdefault (AARRGGBB) color model.
abstract void readRow(InputStream in, int row, int[] rgbRow) throws IOException;
// Our own methods.
void readImage() {
try {
readHeader(in);
width = getWidth();
height = getHeight();
if (width == -1 || height == -1)
err = true;
else {
rowsRead = new boolean[height];
for (int row = 0; row < height; ++row)
rowsRead[row] = false;
gotSize = true;
notifyThem();
rgbPixels = new int[height][width];
for (int row = 0; row < height; ++row) {
readRow(in, row, rgbPixels[row]);
rowsRead[row] = true;
notifyThem();
}
}
} catch (IOException e) {
err = true;
width = -1;
height = -1;
rowsRead = null;
rgbPixels = null;
}
}
private synchronized void notifyThem() {
notifyAll();
}
void sendImage() {
// Grab the list of consumers, in case it changes while we're sending.
ImageConsumer[] c = new ImageConsumer[consumers.size()];
int i;
for (i = 0; i < c.length; ++i)
c[i] = (ImageConsumer) consumers.elementAt(i);
// Try to be as parallel as possible.
waitForSize();
for (i = 0; i < c.length; ++i)
sendHead(c[i]);
for (int row = 0; row < height; ++row)
for (i = 0; i < c.length; ++i)
sendPixelRow(c[i], row);
for (i = 0; i < c.length; ++i)
sendTail(c[i]);
producing = false;
}
private synchronized void waitForSize() {
while ((!err) && (!gotSize)) {
try {
wait();
} catch (InterruptedException ignore) {
}
}
}
private synchronized void waitForRow(int row) {
while ((!err) && (!rowsRead[row])) {
try {
wait();
} catch (InterruptedException ignore) {
}
}
}
private void sendHead(ImageConsumer ic) {
if (err)
return;
ic.setDimensions(width, height);
ic.setColorModel(model);
ic.setHints(
ImageConsumer.TOPDOWNLEFTRIGHT | ImageConsumer.COMPLETESCANLINES |
ImageConsumer.SINGLEPASS | ImageConsumer.SINGLEFRAME);
}
private void sendPixelRow(ImageConsumer ic, int row) {
if (err)
return;
waitForRow(row);
if (err)
return;
ic.setPixels(0, row, width, 1, model, rgbPixels[row], 0, width);
}
private void sendTail(ImageConsumer ic) {
if (err)
ic.imageComplete(ImageConsumer.IMAGEERROR);
else
ic.imageComplete(ImageConsumer.STATICIMAGEDONE);
}
// Methods from ImageProducer.
/// This method is used to register an ImageConsumer with the
// ImageProducer for access to the image data during a later
// reconstruction of the Image. The ImageProducer may, at its
// discretion, start delivering the image data to the consumer
// using the ImageConsumer interface immediately, or when the
// next available image reconstruction is triggered by a call
// to the startProduction method.
// @see #startProduction
public void addConsumer(ImageConsumer ic) {
if (ic != null && !isConsumer(ic))
consumers.addElement(ic);
}
/// This method determines if a given ImageConsumer object
// is currently registered with this ImageProducer as one
// of its consumers.
public boolean isConsumer(ImageConsumer ic) {
return consumers.contains(ic);
}
/// This method removes the given ImageConsumer object
// from the list of consumers currently registered to
// receive image data. It is not considered an error
// to remove a consumer that is not currently registered.
// The ImageProducer should stop sending data to this
// consumer as soon as is feasible.
public void removeConsumer(ImageConsumer ic) {
consumers.removeElement(ic);
}
/// This method both registers the given ImageConsumer object
// as a consumer and starts an immediate reconstruction of
// the image data which will then be delivered to this
// consumer and any other consumer which may have already
// been registered with the producer. This method differs
// from the addConsumer method in that a reproduction of
// the image data should be triggered as soon as possible.
// @see #addConsumer
public void startProduction(ImageConsumer ic) {
addConsumer(ic);
if (!startedRead) {
startedRead = true;
new ImageDecoderRead(this);
}
if (!producing) {
producing = true;
sendImage();
}
}
/// This method is used by an ImageConsumer to request that
// the ImageProducer attempt to resend the image data one
// more time in TOPDOWNLEFTRIGHT order so that higher
// quality conversion algorithms which depend on receiving
// pixels in order can be used to produce a better output
// version of the image. The ImageProducer is free to
// ignore this call if it cannot resend the data in that
// order. If the data can be resent, then the ImageProducer
// should respond by executing the following minimum set of
// ImageConsumer method calls:
// <PRE>
// ic.setHints( TOPDOWNLEFTRIGHT | [otherhints] );
// ic.setPixels( [...] ); // as many times as needed
// ic.imageComplete( [status] );
// </PRE>
// @see ImageConsumer#setHints
public void requestTopDownLeftRightResend(ImageConsumer ic) {
addConsumer(ic);
waitForSize();
sendHead(ic);
for (int row = 0; row < height; ++row)
sendPixelRow(ic, row);
sendTail(ic);
}
}
class ImageDecoderRead extends Thread {
private ImageDecoder parent;
public ImageDecoderRead(ImageDecoder parent) {
this.parent = parent;
start();
}
// Methods from Runnable.
public void run() {
parent.readImage();
}
}