comparison servlet/src/digilib/image/ImageLoaderDocuImage.java @ 290:5d0c0da080ec gen2 scaleable_1

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