1
|
1 /* ImageLoaderDocuImage -- Image class implementation using JDK 1.4 ImageLoader
|
|
2
|
531
|
3 Digital Image Library servlet components
|
1
|
4
|
531
|
5 Copyright (C) 2002, 2003 Robert Casties (robcast@mail.berlios.de)
|
1
|
6
|
531
|
7 This program is free software; you can redistribute it and/or modify it
|
|
8 under the terms of the GNU General Public License as published by the
|
|
9 Free Software Foundation; either version 2 of the License, or (at your
|
|
10 option) any later version.
|
|
11
|
|
12 Please read license.txt for the full details. A copy of the GPL
|
|
13 may be found at http://www.gnu.org/copyleft/lgpl.html
|
1
|
14
|
531
|
15 You should have received a copy of the GNU General Public License
|
|
16 along with this program; if not, write to the Free Software
|
|
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
18 */
|
1
|
19
|
|
20 package digilib.image;
|
|
21
|
531
|
22 import java.awt.Image;
|
|
23 import java.awt.Rectangle;
|
|
24 import java.awt.RenderingHints;
|
|
25 import java.awt.geom.AffineTransform;
|
|
26 import java.awt.geom.Rectangle2D;
|
|
27 import java.awt.image.AffineTransformOp;
|
|
28 import java.awt.image.BufferedImage;
|
|
29 import java.awt.image.ConvolveOp;
|
|
30 import java.awt.image.Kernel;
|
|
31 import java.awt.image.RescaleOp;
|
|
32 import java.io.File;
|
|
33 import java.io.IOException;
|
|
34 import java.io.OutputStream;
|
|
35 import java.io.RandomAccessFile;
|
|
36 import java.util.Arrays;
|
|
37 import java.util.Iterator;
|
1
|
38
|
531
|
39 import javax.imageio.IIOImage;
|
|
40 import javax.imageio.ImageIO;
|
|
41 import javax.imageio.ImageReadParam;
|
|
42 import javax.imageio.ImageReader;
|
|
43 import javax.imageio.ImageWriteParam;
|
|
44 import javax.imageio.ImageWriter;
|
|
45 import javax.imageio.stream.FileImageInputStream;
|
|
46 import javax.imageio.stream.ImageInputStream;
|
|
47 import javax.imageio.stream.ImageOutputStream;
|
1
|
48
|
531
|
49 import digilib.io.FileOpException;
|
|
50 import digilib.io.FileOps;
|
|
51 import digilib.io.ImageFile;
|
|
52 import digilib.io.ImageFileset;
|
1
|
53
|
531
|
54 /** Implementation of DocuImage using the ImageLoader API of Java 1.4 and Java2D. */
|
1
|
55 public class ImageLoaderDocuImage extends DocuImageImpl {
|
|
56
|
531
|
57 /** image object */
|
|
58 protected BufferedImage img;
|
|
59
|
|
60 /** interpolation type */
|
|
61 protected RenderingHints renderHint;
|
1
|
62
|
531
|
63 /** ImageIO image reader */
|
|
64 protected ImageReader reader;
|
1
|
65
|
531
|
66 /** File that was read */
|
|
67 protected File imgFile;
|
|
68
|
|
69 /* loadSubimage is supported. */
|
|
70 public boolean isSubimageSupported() {
|
|
71 return true;
|
|
72 }
|
1
|
73
|
531
|
74 public void setQuality(int qual) {
|
|
75 quality = qual;
|
|
76 renderHint = new RenderingHints(null);
|
|
77 // hint.put(RenderingHints.KEY_ANTIALIASING,
|
|
78 // RenderingHints.VALUE_ANTIALIAS_OFF);
|
|
79 // setup interpolation quality
|
|
80 if (qual > 0) {
|
|
81 logger.debug("quality q1");
|
|
82 renderHint.put(RenderingHints.KEY_INTERPOLATION,
|
|
83 RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
|
84 } else {
|
|
85 logger.debug("quality q0");
|
|
86 renderHint.put(RenderingHints.KEY_INTERPOLATION,
|
|
87 RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
|
|
88 }
|
|
89 }
|
|
90
|
|
91 public int getHeight() {
|
|
92 int h = 0;
|
|
93 try {
|
|
94 if (img == null) {
|
|
95 h = reader.getHeight(0);
|
|
96 } else {
|
|
97 h = img.getHeight();
|
|
98 }
|
|
99 } catch (IOException e) {
|
|
100 logger.debug("error in getHeight", e);
|
|
101 }
|
|
102 return h;
|
|
103 }
|
1
|
104
|
531
|
105 public int getWidth() {
|
|
106 int w = 0;
|
|
107 try {
|
|
108 if (img == null) {
|
|
109 w = reader.getWidth(0);
|
|
110 } else {
|
|
111 w = img.getWidth();
|
|
112 }
|
|
113 } catch (IOException e) {
|
|
114 logger.debug("error in getHeight", e);
|
|
115 }
|
|
116 return w;
|
|
117 }
|
|
118
|
|
119 /* returns a list of supported image formats */
|
|
120 public Iterator<String> getSupportedFormats() {
|
|
121 String[] formats = ImageIO.getReaderFormatNames();
|
|
122 return Arrays.asList(formats).iterator();
|
|
123 }
|
1
|
124
|
531
|
125 /** Check image size and type and store in ImageFile f */
|
|
126 public boolean identify(ImageFile imgf) throws IOException {
|
|
127 // try parent method first
|
|
128 if (super.identify(imgf)) {
|
|
129 return true;
|
|
130 }
|
|
131 // fileset to store the information
|
|
132 ImageFileset imgfs = imgf.getParent();
|
|
133 File f = imgf.getFile();
|
|
134 if (f == null) {
|
|
135 throw new IOException("File not found!");
|
|
136 }
|
|
137 logger.debug("identifying (ImageIO) " + f);
|
|
138 /*
|
|
139 * try ImageReader
|
|
140 */
|
|
141 RandomAccessFile raf = new RandomAccessFile(f, "r");
|
|
142 ImageInputStream istream = ImageIO.createImageInputStream(raf);
|
|
143 Iterator<ImageReader> readers = ImageIO.getImageReaders(istream);
|
|
144 if (readers.hasNext()) {
|
|
145 ImageReader reader = readers.next();
|
|
146 /* are there more readers? */
|
|
147 logger.debug("ImageIO: this reader: " + reader.getClass());
|
|
148 while (readers.hasNext()) {
|
|
149 logger.debug("ImageIO: next reader: "
|
|
150 + readers.next().getClass());
|
|
151 }
|
|
152 try {
|
|
153 reader.setInput(istream);
|
|
154 ImageSize d = new ImageSize(reader.getWidth(0), reader.getHeight(0));
|
|
155 imgf.setSize(d);
|
|
156 //String t = reader.getFormatName();
|
|
157 String t = FileOps.mimeForFile(f);
|
|
158 imgf.setMimetype(t);
|
|
159 //logger.debug(" format:"+t);
|
|
160 if (imgfs != null) {
|
|
161 imgfs.setAspect(d);
|
|
162 }
|
|
163 return true;
|
|
164 } finally {
|
|
165 // dispose the reader to free resources
|
|
166 reader.dispose();
|
|
167 raf.close();
|
|
168 }
|
|
169 }
|
|
170 throw new FileOpException("ERROR: unknown image file format!");
|
1
|
171 }
|
|
172
|
531
|
173
|
|
174 /* load image file */
|
|
175 public void loadImage(ImageFile f) throws FileOpException {
|
|
176 logger.debug("loadImage " + f.getFile());
|
|
177 try {
|
|
178 img = ImageIO.read(f.getFile());
|
|
179 if (img == null) {
|
|
180 throw new FileOpException("Unable to load File!");
|
|
181 }
|
|
182 } catch (IOException e) {
|
|
183 throw new FileOpException("Error reading image.");
|
|
184 }
|
|
185 }
|
|
186
|
|
187 /**
|
|
188 * Get an ImageReader for the image file.
|
|
189 *
|
|
190 * @return
|
|
191 */
|
|
192 public ImageReader getReader(ImageFile f) throws IOException {
|
|
193 logger.debug("preloadImage " + f.getFile());
|
|
194 if (reader != null) {
|
|
195 logger.debug("Reader was not null!");
|
|
196 // clean up old reader
|
|
197 dispose();
|
|
198 }
|
|
199 // System.gc();
|
|
200 RandomAccessFile rf = new RandomAccessFile(f.getFile(), "r");
|
|
201 ImageInputStream istream = new FileImageInputStream(rf);
|
|
202 // Iterator readers = ImageIO.getImageReaders(istream);
|
|
203 String mt = f.getMimetype();
|
|
204 logger.debug("File type:" + mt);
|
|
205 Iterator<ImageReader> readers = ImageIO.getImageReadersByMIMEType(mt);
|
|
206 if (!readers.hasNext()) {
|
|
207 throw new FileOpException("Unable to load File!");
|
|
208 }
|
|
209 reader = readers.next();
|
|
210 /* are there more readers? */
|
|
211 logger.debug("ImageIO: this reader: " + reader.getClass());
|
|
212 while (readers.hasNext()) {
|
|
213 logger.debug("ImageIO: next reader: " + readers.next().getClass());
|
|
214 }
|
|
215 // */
|
|
216 reader.setInput(istream);
|
|
217 imgFile = f.getFile();
|
|
218 return reader;
|
|
219 }
|
|
220
|
|
221 /* Load an image file into the Object. */
|
|
222 public void loadSubimage(ImageFile f, Rectangle region, int prescale)
|
|
223 throws FileOpException {
|
|
224 logger.debug("loadSubimage");
|
|
225 // System.gc();
|
|
226 try {
|
|
227 if ((reader == null) || (imgFile != f.getFile())) {
|
|
228 getReader(f);
|
|
229 }
|
|
230 // set up reader parameters
|
|
231 ImageReadParam readParam = reader.getDefaultReadParam();
|
|
232 readParam.setSourceRegion(region);
|
|
233 if (prescale > 1) {
|
|
234 readParam.setSourceSubsampling(prescale, prescale, 0, 0);
|
|
235 }
|
|
236 // read image
|
|
237 logger.debug("loading..");
|
|
238 img = reader.read(0, readParam);
|
|
239 logger.debug("loaded");
|
|
240 } catch (IOException e) {
|
|
241 throw new FileOpException("Unable to load File!");
|
|
242 }
|
|
243 if (img == null) {
|
|
244 throw new FileOpException("Unable to load File!");
|
|
245 }
|
|
246 }
|
1
|
247
|
531
|
248 /* write image of type mt to Stream */
|
|
249 public void writeImage(String mt, OutputStream ostream)
|
|
250 throws FileOpException {
|
|
251 logger.debug("writeImage");
|
|
252 // setup output
|
|
253 ImageWriter writer = null;
|
|
254 ImageOutputStream imgout = null;
|
|
255 try {
|
|
256 imgout = ImageIO.createImageOutputStream(ostream);
|
|
257 if (mt == "image/jpeg") {
|
|
258 /*
|
|
259 * JPEG doesn't do transparency so we have to convert any RGBA
|
|
260 * image to RGB :-( *Java2D BUG*
|
|
261 */
|
|
262 if (img.getColorModel().hasAlpha()) {
|
|
263 logger.debug("BARF: JPEG with transparency!!");
|
|
264 int w = img.getWidth();
|
|
265 int h = img.getHeight();
|
|
266 // BufferedImage.TYPE_INT_RGB seems to be fastest (JDK1.4.1,
|
|
267 // OSX)
|
|
268 int destType = BufferedImage.TYPE_INT_RGB;
|
|
269 BufferedImage img2 = new BufferedImage(w, h, destType);
|
|
270 img2.createGraphics().drawImage(img, null, 0, 0);
|
|
271 img = img2;
|
|
272 }
|
|
273 writer = (ImageWriter) ImageIO.getImageWritersByFormatName(
|
|
274 "jpeg").next();
|
|
275 if (writer == null) {
|
|
276 throw new FileOpException("Unable to get JPEG writer");
|
|
277 }
|
|
278 ImageWriteParam param = writer.getDefaultWriteParam();
|
|
279 if (quality > 1) {
|
|
280 // change JPEG compression quality
|
|
281 param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
|
282 //logger.debug("JPEG qual before: "
|
|
283 // + Float.toString(param.getCompressionQuality()));
|
|
284 param.setCompressionQuality(0.9f);
|
|
285 //logger.debug("JPEG qual now: "
|
|
286 // + Float.toString(param.getCompressionQuality()));
|
|
287 }
|
|
288 writer.setOutput(imgout);
|
|
289 // render output
|
|
290 logger.debug("writing");
|
|
291 writer.write(null, new IIOImage(img, null, null), param);
|
|
292 } else if (mt == "image/png") {
|
|
293 // render output
|
|
294 writer = (ImageWriter) ImageIO.getImageWritersByFormatName(
|
|
295 "png").next();
|
|
296 if (writer == null) {
|
|
297 throw new FileOpException("Unable to get PNG writer");
|
|
298 }
|
|
299 writer.setOutput(imgout);
|
|
300 logger.debug("writing");
|
|
301 writer.write(img);
|
|
302 } else {
|
|
303 // unknown mime type
|
|
304 throw new FileOpException("Unknown mime type: " + mt);
|
|
305 }
|
1
|
306
|
531
|
307 } catch (IOException e) {
|
|
308 throw new FileOpException("Error writing image.");
|
|
309 } finally {
|
|
310 // clean up
|
|
311 if (writer != null) {
|
|
312 writer.dispose();
|
|
313 }
|
|
314 }
|
|
315 }
|
|
316
|
|
317 public void scale(double scale, double scaleY) throws ImageOpException {
|
|
318 logger.debug("scale");
|
|
319 /* for downscaling in high quality the image is blurred first */
|
|
320 if ((scale <= 0.5) && (quality > 1)) {
|
|
321 int bl = (int) Math.floor(1 / scale);
|
|
322 blur(bl);
|
|
323 }
|
|
324 /* then scaled */
|
|
325 AffineTransformOp scaleOp = new AffineTransformOp(AffineTransform
|
|
326 .getScaleInstance(scale, scale), renderHint);
|
|
327 BufferedImage scaledImg = null;
|
|
328 // enforce destination image type (*Java2D BUG*)
|
|
329 int type = img.getType();
|
|
330 // FIXME: which type would be best?
|
|
331 if ((quality > 0) && (type != 0)) {
|
|
332 logger.debug("creating destination image");
|
|
333 Rectangle2D dstBounds = scaleOp.getBounds2D(img);
|
|
334 scaledImg = new BufferedImage((int) dstBounds.getWidth(),
|
|
335 (int) dstBounds.getHeight(), type);
|
|
336 }
|
|
337 logger.debug("scaling...");
|
|
338 scaledImg = scaleOp.filter(img, scaledImg);
|
|
339 if (scaledImg == null) {
|
|
340 throw new ImageOpException("Unable to scale");
|
|
341 }
|
|
342 // DEBUG
|
|
343 logger.debug("destination image type " + scaledImg.getType());
|
|
344 logger.debug("SCALE: " + scale + " ->" + scaledImg.getWidth() + "x"
|
|
345 + scaledImg.getHeight());
|
|
346 img = scaledImg;
|
|
347 scaledImg = null;
|
|
348 }
|
1
|
349
|
531
|
350 public void blur(int radius) throws ImageOpException {
|
|
351 // DEBUG
|
|
352 logger.debug("blur: " + radius);
|
|
353 // minimum radius is 2
|
|
354 int klen = Math.max(radius, 2);
|
|
355 // FIXME: use constant kernels for most common sizes
|
|
356 int ksize = klen * klen;
|
|
357 // kernel is constant 1/k
|
|
358 float f = 1f / ksize;
|
|
359 float[] kern = new float[ksize];
|
|
360 for (int i = 0; i < ksize; i++) {
|
|
361 kern[i] = f;
|
|
362 }
|
|
363 Kernel blur = new Kernel(klen, klen, kern);
|
|
364 // blur with convolve operation
|
|
365 ConvolveOp blurOp = new ConvolveOp(blur, ConvolveOp.EDGE_NO_OP,
|
|
366 renderHint);
|
|
367 // blur needs explicit destination image type for color *Java2D BUG*
|
|
368 BufferedImage blurredImg = null;
|
|
369 if (img.getType() == BufferedImage.TYPE_3BYTE_BGR) {
|
|
370 blurredImg = new BufferedImage(img.getWidth(), img.getHeight(), img
|
|
371 .getType());
|
|
372 }
|
|
373 blurredImg = blurOp.filter(img, blurredImg);
|
|
374 if (blurredImg == null) {
|
|
375 throw new ImageOpException("Unable to scale");
|
|
376 }
|
|
377 img = blurredImg;
|
|
378 }
|
|
379
|
|
380 public void crop(int x_off, int y_off, int width, int height)
|
|
381 throws ImageOpException {
|
|
382 // setup Crop
|
|
383 BufferedImage croppedImg = img.getSubimage(x_off, y_off, width, height);
|
|
384 // DEBUG
|
|
385 // util.dprintln(2, " time
|
|
386 // "+(System.currentTimeMillis()-startTime)+"ms");
|
|
387 if (croppedImg == null) {
|
|
388 throw new ImageOpException("Unable to crop");
|
|
389 }
|
|
390 logger.debug("CROP:" + croppedImg.getWidth() + "x"
|
|
391 + croppedImg.getHeight());
|
|
392 img = croppedImg;
|
|
393 }
|
|
394
|
|
395 public void enhance(float mult, float add) throws ImageOpException {
|
|
396 /*
|
|
397 * Only one constant should work regardless of the number of bands
|
|
398 * according to the JDK spec. Doesn't work on JDK 1.4 for OSX and Linux
|
|
399 * (at least). RescaleOp scaleOp = new RescaleOp( (float)mult,
|
|
400 * (float)add, null); scaleOp.filter(img, img);
|
|
401 */
|
|
402
|
|
403 /* The number of constants must match the number of bands in the image. */
|
|
404 int ncol = img.getColorModel().getNumComponents();
|
|
405 float[] dm = new float[ncol];
|
|
406 float[] da = new float[ncol];
|
|
407 for (int i = 0; i < ncol; i++) {
|
|
408 dm[i] = (float) mult;
|
|
409 da[i] = (float) add;
|
|
410 }
|
|
411 RescaleOp scaleOp = new RescaleOp(dm, da, null);
|
|
412 scaleOp.filter(img, img);
|
|
413 }
|
|
414
|
|
415 public void enhanceRGB(float[] rgbm, float[] rgba) throws ImageOpException {
|
|
416
|
|
417 /*
|
|
418 * The number of constants must match the number of bands in the image.
|
|
419 * We do only 3 (RGB) bands.
|
|
420 */
|
|
421
|
|
422 int ncol = img.getColorModel().getNumColorComponents();
|
|
423 if ((ncol != 3) || (rgbm.length != 3) || (rgba.length != 3)) {
|
|
424 logger
|
|
425 .debug("ERROR(enhance): unknown number of color bands or coefficients ("
|
|
426 + ncol + ")");
|
|
427 return;
|
|
428 }
|
|
429 RescaleOp scaleOp = new RescaleOp(rgbOrdered(rgbm), rgbOrdered(rgba),
|
|
430 null);
|
|
431 scaleOp.filter(img, img);
|
|
432 }
|
1
|
433
|
531
|
434 /**
|
|
435 * Ensures that the array f is in the right order to map the images RGB
|
|
436 * components. (not shure what happens
|
|
437 */
|
|
438 public float[] rgbOrdered(float[] fa) {
|
|
439 /*
|
|
440 * TODO: this is UGLY, UGLY!!
|
|
441 */
|
|
442 float[] fb;
|
|
443 int t = img.getType();
|
|
444 if (img.getColorModel().hasAlpha()) {
|
|
445 fb = new float[4];
|
|
446 if ((t == BufferedImage.TYPE_INT_ARGB)
|
|
447 || (t == BufferedImage.TYPE_INT_ARGB_PRE)) {
|
|
448 // RGB Type
|
|
449 fb[0] = fa[0];
|
|
450 fb[1] = fa[1];
|
|
451 fb[2] = fa[2];
|
|
452 fb[3] = 1f;
|
|
453 } else {
|
|
454 // this isn't tested :-(
|
|
455 fb[0] = 1f;
|
|
456 fb[1] = fa[0];
|
|
457 fb[2] = fa[1];
|
|
458 fb[3] = fa[2];
|
|
459 }
|
|
460 } else {
|
|
461 fb = new float[3];
|
|
462 if (t == BufferedImage.TYPE_3BYTE_BGR) {
|
|
463 // BGR Type (actually it looks like RBG...)
|
|
464 fb[0] = fa[0];
|
|
465 fb[1] = fa[2];
|
|
466 fb[2] = fa[1];
|
|
467 } else {
|
|
468 fb[0] = fa[0];
|
|
469 fb[1] = fa[1];
|
|
470 fb[2] = fa[2];
|
|
471 }
|
|
472 }
|
|
473 return fb;
|
|
474 }
|
1
|
475
|
531
|
476 public void rotate(double angle) throws ImageOpException {
|
|
477 // setup rotation
|
|
478 double rangle = Math.toRadians(angle);
|
|
479 // create offset to make shure the rotated image has no negative
|
|
480 // coordinates
|
|
481 double w = img.getWidth();
|
|
482 double h = img.getHeight();
|
|
483 AffineTransform trafo = new AffineTransform();
|
|
484 // center of rotation
|
|
485 double x = (w / 2);
|
|
486 double y = (h / 2);
|
|
487 trafo.rotate(rangle, x, y);
|
|
488 // try rotation to see how far we're out of bounds
|
|
489 AffineTransformOp rotOp = new AffineTransformOp(trafo, renderHint);
|
|
490 Rectangle2D rotbounds = rotOp.getBounds2D(img);
|
|
491 double xoff = rotbounds.getX();
|
|
492 double yoff = rotbounds.getY();
|
|
493 // move image back in line
|
|
494 trafo
|
|
495 .preConcatenate(AffineTransform.getTranslateInstance(-xoff,
|
|
496 -yoff));
|
|
497 // transform image
|
|
498 rotOp = new AffineTransformOp(trafo, renderHint);
|
|
499 BufferedImage rotImg = rotOp.filter(img, null);
|
|
500 // calculate new bounding box
|
|
501 // Rectangle2D bounds = rotOp.getBounds2D(img);
|
|
502 if (rotImg == null) {
|
|
503 throw new ImageOpException("Unable to rotate");
|
|
504 }
|
|
505 img = rotImg;
|
|
506 // crop new image (with self-made rounding)
|
|
507 /*
|
|
508 * img = rotImg.getSubimage( (int) (bounds.getX()+0.5), (int)
|
|
509 * (bounds.getY()+0.5), (int) (bounds.getWidth()+0.5), (int)
|
|
510 * (bounds.getHeight()+0.5));
|
|
511 */
|
|
512 }
|
|
513
|
|
514 public void mirror(double angle) throws ImageOpException {
|
|
515 // setup mirror
|
|
516 double mx = 1;
|
|
517 double my = 1;
|
|
518 double tx = 0;
|
|
519 double ty = 0;
|
|
520 if (Math.abs(angle - 0) < epsilon) { // 0 degree
|
|
521 mx = -1;
|
|
522 tx = getWidth();
|
|
523 } else if (Math.abs(angle - 90) < epsilon) { // 90 degree
|
|
524 my = -1;
|
|
525 ty = getHeight();
|
|
526 } else if (Math.abs(angle - 180) < epsilon) { // 180 degree
|
|
527 mx = -1;
|
|
528 tx = getWidth();
|
|
529 } else if (Math.abs(angle - 270) < epsilon) { // 270 degree
|
|
530 my = -1;
|
|
531 ty = getHeight();
|
|
532 } else if (Math.abs(angle - 360) < epsilon) { // 360 degree
|
|
533 mx = -1;
|
|
534 tx = getWidth();
|
|
535 }
|
|
536 AffineTransformOp mirOp = new AffineTransformOp(new AffineTransform(mx,
|
|
537 0, 0, my, tx, ty), renderHint);
|
|
538 BufferedImage mirImg = mirOp.filter(img, null);
|
|
539 if (mirImg == null) {
|
|
540 throw new ImageOpException("Unable to mirror");
|
|
541 }
|
|
542 img = mirImg;
|
|
543 }
|
|
544
|
|
545 /*
|
|
546 * (non-Javadoc)
|
|
547 *
|
|
548 * @see java.lang.Object#finalize()
|
|
549 */
|
|
550 protected void finalize() throws Throwable {
|
|
551 dispose();
|
|
552 super.finalize();
|
|
553 }
|
|
554
|
|
555 public void dispose() {
|
|
556 // we must dispose the ImageReader because it keeps the filehandle
|
|
557 // open!
|
|
558 if (reader != null) {
|
|
559 reader.dispose();
|
|
560 reader = null;
|
|
561 }
|
|
562 img = null;
|
|
563 }
|
|
564
|
|
565 public Image getImage(){
|
|
566 return (Image) img;
|
|
567 }
|
|
568
|
|
569
|
1
|
570 }
|