/Users/lyon/j4p/src/ip/ppm/ImageDecoder.java
|
1 /*
2 * @author Douglas A. Lyon
3 * @version Oct 12, 2002.10:57:23 AM
4 */
5 package ip.ppm;
6
7 import java.awt.image.ColorModel;
8 import java.awt.image.ImageConsumer;
9 import java.awt.image.ImageProducer;
10 import java.io.IOException;
11 import java.io.InputStream;
12 import java.util.Vector;
13
14
15 // ImageDecoder - abstract class for reading in an image
16 //
17 // Copyright (C) 1996 by Jef Poskanzer <jef@acme.com>. All rights reserved.
18 //
19 // Redistribution and use in source and binary forms, with or without
20 // modification, are permitted provided that the following conditions
21 // are met:
22 // 1. Redistributions of source code must retain the above copyright
23 // notice, this list of conditions and the following disclaimer.
24 // 2. Redistributions in binary form must reproduce the above copyright
25 // notice, this list of conditions and the following disclaimer in the
26 // documentation and/or other materials provided with the distribution.
27 //
28 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
29 // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31 // ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
32 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
34 // OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37 // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 // SUCH DAMAGE.
39 //
40 // Visit the ACME Labs Java page for up-to-date versions of this and other
41 // fine Java utilities: http://www.acme.com/java/
42
43
44
45 /// Abstract class for reading in an image.
46 // <P>
47 // A framework for classes that read in and decode an image in
48 // a particular file format.
49 // <P>
50 // This provides a very simplified rendition of the ImageProducer interface.
51 // It requires the decoder to read the image a row at a time. It requires
52 // use of the RGBdefault color model.
53 // If you want more flexibility you can always implement ImageProducer
54 // directly.
55 // <P>
56 // <A HREF="/resources/classes/Acme/JPM/Decoders/ImageDecoder.java">Fetch the software.</A><BR>
57 // <A HREF="/resources/classes/Acme.tar.gz">Fetch the entire Acme package.</A>
58 // <P>
59 // @see PpmDecoder
60 // @see Acme.JPM.Encoders.ImageEncoder
61
62 public abstract class ImageDecoder implements ImageProducer {
63
64 private InputStream in;
65 private int width, height;
66 private boolean[] rowsRead;
67 private int[][] rgbPixels;
68 private boolean startedRead = false;
69 private boolean gotSize = false;
70 private boolean err = false;
71 private boolean producing = false;
72 private Vector consumers = new Vector();
73 private static final ColorModel model = ColorModel.getRGBdefault();
74
75
76 /// Constructor.
77 // @param in The stream to read the bytes from.
78 public ImageDecoder(InputStream in) {
79 this.in = in;
80 }
81
82
83 // Methods that subclasses implement.
84
85 /// Subclasses implement this to read in enough of the image stream
86 // to figure out the width and height.
87 abstract void readHeader(InputStream in) throws IOException;
88
89 /// Subclasses implement this to return the width, or -1 if not known.
90 abstract int getWidth();
91
92 /// Subclasses implement this to return the height, or -1 if not known.
93 abstract int getHeight();
94
95 /// Subclasses implement this to read pixel data into the rgbRow
96 // array, an int[width]. One int per pixel, no offsets or padding,
97 // RGBdefault (AARRGGBB) color model.
98 abstract void readRow(InputStream in, int row, int[] rgbRow) throws IOException;
99
100
101 // Our own methods.
102
103 void readImage() {
104 try {
105 readHeader(in);
106 width = getWidth();
107 height = getHeight();
108 if (width == -1 || height == -1)
109 err = true;
110 else {
111 rowsRead = new boolean[height];
112 for (int row = 0; row < height; ++row)
113 rowsRead[row] = false;
114 gotSize = true;
115 notifyThem();
116 rgbPixels = new int[height][width];
117 for (int row = 0; row < height; ++row) {
118 readRow(in, row, rgbPixels[row]);
119 rowsRead[row] = true;
120 notifyThem();
121 }
122 }
123 } catch (IOException e) {
124 err = true;
125 width = -1;
126 height = -1;
127 rowsRead = null;
128 rgbPixels = null;
129 }
130 }
131
132 private synchronized void notifyThem() {
133 notifyAll();
134 }
135
136 void sendImage() {
137 // Grab the list of consumers, in case it changes while we're sending.
138 ImageConsumer[] c = new ImageConsumer[consumers.size()];
139 int i;
140 for (i = 0; i < c.length; ++i)
141 c[i] = (ImageConsumer) consumers.elementAt(i);
142 // Try to be as parallel as possible.
143 waitForSize();
144 for (i = 0; i < c.length; ++i)
145 sendHead(c[i]);
146 for (int row = 0; row < height; ++row)
147 for (i = 0; i < c.length; ++i)
148 sendPixelRow(c[i], row);
149 for (i = 0; i < c.length; ++i)
150 sendTail(c[i]);
151 producing = false;
152 }
153
154 private synchronized void waitForSize() {
155 while ((!err) && (!gotSize)) {
156 try {
157 wait();
158 } catch (InterruptedException ignore) {
159 }
160 }
161 }
162
163 private synchronized void waitForRow(int row) {
164 while ((!err) && (!rowsRead[row])) {
165 try {
166 wait();
167 } catch (InterruptedException ignore) {
168 }
169 }
170 }
171
172 private void sendHead(ImageConsumer ic) {
173 if (err)
174 return;
175 ic.setDimensions(width, height);
176 ic.setColorModel(model);
177 ic.setHints(
178 ImageConsumer.TOPDOWNLEFTRIGHT | ImageConsumer.COMPLETESCANLINES |
179 ImageConsumer.SINGLEPASS | ImageConsumer.SINGLEFRAME);
180 }
181
182 private void sendPixelRow(ImageConsumer ic, int row) {
183 if (err)
184 return;
185 waitForRow(row);
186 if (err)
187 return;
188 ic.setPixels(0, row, width, 1, model, rgbPixels[row], 0, width);
189 }
190
191 private void sendTail(ImageConsumer ic) {
192 if (err)
193 ic.imageComplete(ImageConsumer.IMAGEERROR);
194 else
195 ic.imageComplete(ImageConsumer.STATICIMAGEDONE);
196
197 }
198
199
200 // Methods from ImageProducer.
201
202 /// This method is used to register an ImageConsumer with the
203 // ImageProducer for access to the image data during a later
204 // reconstruction of the Image. The ImageProducer may, at its
205 // discretion, start delivering the image data to the consumer
206 // using the ImageConsumer interface immediately, or when the
207 // next available image reconstruction is triggered by a call
208 // to the startProduction method.
209 // @see #startProduction
210 public void addConsumer(ImageConsumer ic) {
211 if (ic != null && !isConsumer(ic))
212 consumers.addElement(ic);
213 }
214
215 /// This method determines if a given ImageConsumer object
216 // is currently registered with this ImageProducer as one
217 // of its consumers.
218 public boolean isConsumer(ImageConsumer ic) {
219 return consumers.contains(ic);
220 }
221
222 /// This method removes the given ImageConsumer object
223 // from the list of consumers currently registered to
224 // receive image data. It is not considered an error
225 // to remove a consumer that is not currently registered.
226 // The ImageProducer should stop sending data to this
227 // consumer as soon as is feasible.
228 public void removeConsumer(ImageConsumer ic) {
229 consumers.removeElement(ic);
230 }
231
232 /// This method both registers the given ImageConsumer object
233 // as a consumer and starts an immediate reconstruction of
234 // the image data which will then be delivered to this
235 // consumer and any other consumer which may have already
236 // been registered with the producer. This method differs
237 // from the addConsumer method in that a reproduction of
238 // the image data should be triggered as soon as possible.
239 // @see #addConsumer
240 public void startProduction(ImageConsumer ic) {
241 addConsumer(ic);
242 if (!startedRead) {
243 startedRead = true;
244 new ImageDecoderRead(this);
245 }
246 if (!producing) {
247 producing = true;
248 sendImage();
249 }
250 }
251
252 /// This method is used by an ImageConsumer to request that
253 // the ImageProducer attempt to resend the image data one
254 // more time in TOPDOWNLEFTRIGHT order so that higher
255 // quality conversion algorithms which depend on receiving
256 // pixels in order can be used to produce a better output
257 // version of the image. The ImageProducer is free to
258 // ignore this call if it cannot resend the data in that
259 // order. If the data can be resent, then the ImageProducer
260 // should respond by executing the following minimum set of
261 // ImageConsumer method calls:
262 // <PRE>
263 // ic.setHints( TOPDOWNLEFTRIGHT | [otherhints] );
264 // ic.setPixels( [...] ); // as many times as needed
265 // ic.imageComplete( [status] );
266 // </PRE>
267 // @see ImageConsumer#setHints
268 public void requestTopDownLeftRightResend(ImageConsumer ic) {
269 addConsumer(ic);
270 waitForSize();
271 sendHead(ic);
272 for (int row = 0; row < height; ++row)
273 sendPixelRow(ic, row);
274 sendTail(ic);
275 }
276
277 }
278
279
280 class ImageDecoderRead extends Thread {
281
282 private ImageDecoder parent;
283
284 public ImageDecoderRead(ImageDecoder parent) {
285 this.parent = parent;
286 start();
287 }
288
289 // Methods from Runnable.
290
291 public void run() {
292 parent.readImage();
293 }
294
295 }
296