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