001 package org.mycore.services.imaging;
002
003 import java.awt.Dimension;
004 import java.awt.Point;
005 import java.awt.RenderingHints;
006 import java.awt.image.BufferedImage;
007 import java.awt.image.renderable.ParameterBlock;
008 import java.io.IOException;
009 import java.io.InputStream;
010 import java.io.OutputStream;
011
012 import javax.imageio.ImageIO;
013 import javax.media.jai.BorderExtender;
014 import javax.media.jai.InterpolationBicubic2;
015 import javax.media.jai.JAI;
016 import javax.media.jai.PlanarImage;
017
018 import org.apache.log4j.Logger;
019
020 import com.sun.media.jai.codec.ImageCodec;
021 import com.sun.media.jai.codec.ImageEncoder;
022 import com.sun.media.jai.codec.JPEGEncodeParam;
023 import com.sun.media.jai.codec.MemoryCacheSeekableStream;
024 import com.sun.media.jai.codec.PNGEncodeParam;
025 import com.sun.media.jai.codec.SeekableStream;
026 import com.sun.media.jai.codec.TIFFEncodeParam;
027
028 /**
029 * This implementation of ImgProcessor is responsible for the manipulation of
030 * image data. It offers methods for scaling and croping of an image and a two
031 * encoding possibility (JPEG, TIFF). For TIFF-Encoding ones can set the tile
032 * size, default tile size is 480.<br>
033 *
034 * @version 1.00 8/08/2006<br>
035 * @author Vu Huu Chi
036 * @linkplain
037 *
038 */
039
040 public class MCRImgProcessor implements ImgProcessor {
041 private static Logger LOGGER = Logger.getLogger(MCRImgProcessor.class.getName());
042
043 private PlanarImage image = null;
044
045 protected float scaleFactor = 0;
046
047 private float jpegQuality = 0.5F;
048
049 private Dimension origSize = null;
050
051 private int tileWidth = 480;
052
053 private int tileHeight = 480;
054
055 private int useEncoder = JPEG_ENC;
056
057 private boolean transparent = false;
058
059 private String fileFormat = "";
060
061 /**
062 * The JPEG encoder
063 */
064 static public final int JPEG_ENC = 0;
065
066 /**
067 * The TIFF encoder
068 */
069 static public final int TIFF_ENC = 1;
070
071 /**
072 * The PNG encoder
073 */
074 static public final int PNG_ENC = 2;
075
076 MCRImgProcessor() {
077 origSize = new Dimension(0, 0);
078
079 }
080
081 // Interface ImgProcessor
082 public float getJpegQuality() {
083 return jpegQuality;
084 }
085
086 public void setJpegQuality(float jpegQuality) {
087 this.jpegQuality = jpegQuality;
088 }
089
090 public void setTileSize(int tileWidth, int tileHeight) {
091 this.tileWidth = tileWidth;
092 this.tileHeight = tileHeight;
093 }
094
095 public void useEncoder(int Encoder) throws Exception {
096 if (Encoder == JPEG_ENC || Encoder == TIFF_ENC || Encoder == PNG_ENC)
097 useEncoder = Encoder;
098 else
099 throw new Exception("MCRImgProcessor.useEncoder accept only MCRImgProcessor.JPEG_ENC or MCRImgProcessor.TIFF_ENC as parameter!");
100 }
101
102 /*
103 * (non-Javadoc)
104 *
105 * @see org.mycore.services.imaging.ImgProcessor#resizeFitWidth(java.io.InputStream,
106 * int, java.io.OutputStream)
107 */
108 public void resizeFitWidth(InputStream input, int newWidth, OutputStream output) {
109 image = loadImageMEMCache(input);
110
111 if (newWidth != origSize.width)
112 image = fitWidth(image, newWidth);
113
114 encode(output);
115 }
116
117 public void resizeFitHeight(InputStream input, int newHeight, OutputStream output) {
118 image = loadImageMEMCache(input);
119
120 if (newHeight != origSize.height)
121 image = fitHeight(image, newHeight);
122
123 encode(output);
124 }
125
126 public void resize(InputStream input, int newWidth, int newHeight, OutputStream output) {
127 image = loadImageMEMCache(input);
128 // loadImageIO(input);
129
130 if (newWidth != origSize.width && newHeight != origSize.height)
131 try {
132 image = resizeImage(image, newWidth, newHeight);
133 } catch (Exception e) {
134 // TODO Auto-generated catch block
135 e.printStackTrace();
136 }
137
138 encode(output);
139 }
140
141 public void scale(InputStream input, float scaleFactor, OutputStream output) {
142 image = loadImageMEMCache(input);
143
144 // if (scaleFactor != 1 || useEncoder == PNG_ENC)
145 if (scaleFactor != 1)
146 image = scaleImage(image, scaleFactor);
147
148 encode(output);
149 }
150
151 public void scaleROI(InputStream input, int xTopPos, int yTopPos, int boundWidth, int boundHeight, float scaleFactor, OutputStream output) {
152 image = loadImageMEMCache(input);
153 // loadImageIO(input);
154
155 LOGGER.debug("Loading Image succesfull!");
156
157 // Point scaleTopCorner = new Point((int) (xTopPos / scaleFactor), (int)
158 // (yTopPos / scaleFactor));
159 Point scaleTopCorner = new Point(xTopPos, yTopPos);
160 Dimension scaleBoundary = new Dimension((int) (boundWidth / scaleFactor), (int) (boundHeight / scaleFactor));
161
162 if (scaleBoundary.width > origSize.width)
163 scaleBoundary.width = origSize.width;
164
165 if (scaleBoundary.height > origSize.height)
166 scaleBoundary.height = origSize.height;
167
168 LOGGER.debug("MCRImgProcessor - scaleROI#");
169 LOGGER.debug("ScaleFactor: " + scaleFactor);
170 LOGGER.debug("scaleTopCorner: " + scaleTopCorner);
171 LOGGER.debug("scaleBoundary.width: " + scaleBoundary.width);
172 LOGGER.debug("scaleBoundary.height: " + scaleBoundary.height);
173 LOGGER.debug("origSize.width: " + origSize.width);
174 LOGGER.debug("origSize.height: " + origSize.height);
175
176 if (scaleBoundary.width < origSize.width || scaleBoundary.height < origSize.height)
177 image = crop(image, scaleTopCorner, scaleBoundary);
178
179 // if (scaleFactor != 1 || useEncoder == PNG_ENC) {
180 // if (scaleFactor != 1) {
181 image = scaleImage(image, scaleFactor);
182 // }
183
184 encode(output);
185 }
186
187 public float getScaleFactor() {
188 return scaleFactor;
189 }
190
191 public Dimension getOrigSize() throws Exception {
192 if (image == null)
193 throw new Exception("No loaded image in " + this.getClass().getName() + "!");
194 return origSize;
195 }
196
197 public Dimension getCurrentSize() throws Exception {
198 if (image == null)
199 throw new Exception("No loaded image in " + this.getClass().getName() + "!");
200 return new Dimension(image.getWidth(), image.getHeight());
201 }
202
203 public Dimension getImageSize(InputStream input) {
204 PlanarImage image = loadImageMEMCache(input);
205 return new Dimension(image.getWidth(), image.getHeight());
206 }
207
208 public void encode(InputStream input, OutputStream output, int encoder) throws Exception {
209 useEncoder(encoder);
210 resize(input, origSize.width, origSize.height, output);
211 }
212
213 // ****************************************************************************
214
215 public void loadImage(InputStream input) {
216 image = loadImageMEMCache(input);
217 // loadImageIO(input);
218 }
219
220 public PlanarImage loadImageIO(InputStream input) {
221 PlanarImage image = null;
222 if (input == null)
223 LOGGER.debug("Loading a NULL image.. not good!");
224
225 try {
226 BufferedImage buffImage = ImageIO.read(input);
227 if (buffImage.getTransparency() != BufferedImage.OPAQUE) {
228 LOGGER.debug("Loading a transparent image..");
229 setTransparent(true);
230 } else
231 LOGGER.debug("Loading a opague image..");
232 image = PlanarImage.wrapRenderedImage(buffImage);
233 } catch (NullPointerException e) {
234 LOGGER.debug("Loading with imageIO failed, trying JAI instead.");
235 try {
236 input.reset();
237 } catch (IOException e1) {
238 // TODO Auto-generated catch block
239 e1.printStackTrace();
240 }
241 image = loadImageMEMCache(input);
242 } catch (IOException e) {
243 // TODO Auto-generated catch block
244 e.printStackTrace();
245 }
246
247 return image;
248 }
249
250 public boolean hasCorrectTileSize() {
251 boolean hasCorrectSize = false;
252
253 if (image.getNumXTiles() > 1 && image.getNumYTiles() > 1 && image.getTileWidth() == tileWidth && image.getTileHeight() == tileHeight)
254 hasCorrectSize = true;
255
256 return hasCorrectSize;
257 }
258
259 public void resizeFitWidth(int newWidth) {
260 if (newWidth != origSize.width)
261 image = fitWidth(image, newWidth);
262 }
263
264 public void resizeFitHeight(int newHeight) {
265 if (newHeight != origSize.height)
266 image = fitHeight(image, newHeight);
267 }
268
269 public void resize(int newWidth, int newHeight) {
270 if (newWidth != origSize.width && newHeight != origSize.height)
271 try {
272 image = resizeImage(image, newWidth, newHeight);
273 } catch (Exception e) {
274 // TODO Auto-generated catch block
275 e.printStackTrace();
276 }
277 }
278
279 public void scale(float scaleFactor) {
280 if (scaleFactor != 1)
281 image = scaleImage(image, scaleFactor);
282 }
283
284 public void scaleROI(int xTopPos, int yTopPos, int boundWidth, int boundHeight, float scaleFactor) {
285 Point scaleTopCorner = new Point((int) (xTopPos / scaleFactor), (int) (yTopPos / scaleFactor));
286 Dimension scaleBoundary = new Dimension((int) (boundWidth / scaleFactor), (int) (boundHeight / scaleFactor));
287
288 if (scaleBoundary.width > origSize.width)
289 scaleBoundary.width = origSize.width;
290
291 if (scaleBoundary.height > origSize.height)
292 scaleBoundary.height = origSize.height;
293
294 if (scaleBoundary.width < origSize.width || scaleBoundary.height < origSize.height)
295 image = crop(image, scaleTopCorner, scaleBoundary);
296
297 if (scaleFactor != 1) {
298 image = scaleImage(image, scaleFactor);
299 }
300 }
301
302 public void jpegEncode(OutputStream output) {
303 jpegEncode(image, output, jpegQuality);
304 }
305
306 public void encodeIO(OutputStream output, String type) {
307 // jpegEncode(image, output, jpegQuality);
308 try {
309 ImageIO.write(image, type, output);
310 } catch (IOException e) {
311 // TODO Auto-generated catch block
312 e.printStackTrace();
313 }
314 }
315
316 public void tiffEncode(OutputStream output) {
317 tiffEncode(image, output, true, tileWidth, tileHeight);
318 }
319
320 public void pngEncode(OutputStream output) {
321 pngEncode(image, output, true);
322 }
323
324 // End: Interface implementation
325
326 /** ************************************************************************ */
327 // Image operation using JAI
328 /**
329 * loadImageFileCache - load an input stream of image data into to JAI. JAI
330 * use a cache in form of a file on the HDD for the image data. This is
331 * slower than using the RAM but for large files it's the only solution.
332 *
333 * @param input
334 * @return PlanarImage
335 */
336 public PlanarImage loadImageFileCache(InputStream input) {
337 SeekableStream stream = SeekableStream.wrapInputStream(input, true);
338 String[] decodeArray = ImageCodec.getDecoderNames(stream);
339
340 if (decodeArray.length == 1 && (decodeArray[0].equals("png") || decodeArray[0].equals("gif"))) {
341 // if (decodeArray.length == 1 && (decodeArray[0].equals("png"))) {
342 try {
343 useEncoder(PNG_ENC);
344 } catch (Exception e) {
345 // TODO Auto-generated catch block
346 e.printStackTrace();
347 }
348 }
349
350 image = JAI.create("stream", stream);
351
352 origSize.width = image.getWidth();
353 origSize.height = image.getHeight();
354 LOGGER.info("File loading successfull - FileCache");
355 LOGGER.info("origSize.width: " + origSize.width);
356 LOGGER.info("origSize.height: " + origSize.height);
357 return image;
358 }
359
360 private PlanarImage loadImageMEMCache(InputStream input) {
361 MemoryCacheSeekableStream stream = new MemoryCacheSeekableStream(input);
362 String[] decodeArray = ImageCodec.getDecoderNames(stream);
363
364 if (decodeArray.length == 1 && (decodeArray[0].equals("png") || decodeArray[0].equals("gif"))) {
365 // if (decodeArray.length == 1 && (decodeArray[0].equals("png"))) {
366 try {
367 useEncoder(PNG_ENC);
368 } catch (Exception e) {
369 // TODO Auto-generated catch block
370 e.printStackTrace();
371 }
372 }
373
374 image = JAI.create("stream", stream);
375
376 origSize.width = image.getWidth();
377 origSize.height = image.getHeight();
378 LOGGER.info("Loading MEM Cache");
379 LOGGER.info("origSize.width: " + origSize.width);
380 LOGGER.info("origSize.height: " + origSize.height);
381 return image;
382 }
383
384 private PlanarImage crop(PlanarImage img, Point topCorner, Dimension boundary) {
385 LOGGER.debug("croping at " + topCorner + " with dimension " + boundary);
386 Dimension origSize = new Dimension(img.getWidth(), img.getHeight());
387
388 if (topCorner.x < 0)
389 topCorner.x = 0;
390 if (topCorner.y < 0)
391 topCorner.y = 0;
392
393 if (topCorner.x + boundary.width > origSize.width)
394 topCorner.x = origSize.width - boundary.width;
395 if (topCorner.y + boundary.height > origSize.height)
396 topCorner.y = origSize.height - boundary.height;
397
398 ParameterBlock pb = new ParameterBlock();
399 pb.addSource(img);
400 pb.add((float) topCorner.x); // The xScale
401 pb.add((float) topCorner.y); // The yScale
402 pb.add((float) boundary.width); // The x translation
403 pb.add((float) boundary.height); // The y translation
404
405 return JAI.create("crop", pb, noBorder());
406
407 }
408
409 private PlanarImage scaleImage(PlanarImage img, float scaleFactor) {
410 LOGGER.debug("scaling using factor: " + scaleFactor);
411 setScaleFactor(scaleFactor);
412 ParameterBlock pb = new ParameterBlock();
413 pb.addSource(img); // The source image
414 pb.add(scaleFactor); // The xScale
415 pb.add(scaleFactor); // The yScale
416 pb.add(0.0F); // The x translation
417 pb.add(0.0F); // The y translation
418 pb.add(new InterpolationBicubic2(3)); // The interpolation
419 return JAI.create("scale", pb, noBorder());
420 }
421
422 private PlanarImage fitWidth(PlanarImage img, int newWidth) {
423 int origWidth = img.getWidth();
424 float xScale = (float) newWidth / (float) origWidth;
425 return scaleImage(img, xScale);
426 }
427
428 private PlanarImage fitHeight(PlanarImage img, int newHeight) {
429 int origHeight = img.getHeight();
430 float yScale = (float) newHeight / (float) origHeight;
431 return scaleImage(img, yScale);
432 }
433
434 private PlanarImage resizeImage(PlanarImage img, int newWidth, int newHeight) throws Exception {
435 if (newWidth <= 0 || newHeight <= 0)
436 throw new Exception("newWidth and newHeight should be > 0!");
437
438 float scaleFactor = 1;
439
440 LOGGER.debug("MCRImgProcessor - resizeImage *");
441 LOGGER.debug("newWidth: " + newWidth);
442 LOGGER.debug("newHeight: " + newHeight);
443 LOGGER.debug("origSize.width: " + origSize.width);
444 LOGGER.debug("origSize.height: " + origSize.height);
445
446 if (newWidth != origSize.width || newHeight != origSize.height) {
447 LOGGER.debug("MCRImgProcessor - resizeImage");
448 LOGGER.debug("in IF");
449 int origWidth = img.getWidth();
450 int origHeight = img.getHeight();
451 float xScale = (float) newWidth / (float) origWidth;
452 float yScale = (float) newHeight / (float) origHeight;
453 scaleFactor = (yScale < xScale) ? yScale : xScale;
454 }
455
456 return scaleImage(img, scaleFactor);
457 }
458
459 // Encoding Part
460 public void encode(OutputStream output, int encoder) {
461 if (useEncoder != PNG_ENC)
462 try {
463 useEncoder(encoder);
464 } catch (Exception e) {
465 e.printStackTrace();
466 }
467 else
468 setTransparent(false);
469
470 encode(output, transparent);
471 }
472
473 public void encode(OutputStream output) {
474 encode(output, transparent);
475 }
476
477 private void encode(OutputStream output, boolean transparent) {
478 if (transparent) {
479 LOGGER.debug("Image is transparent.");
480 useEncoder = PNG_ENC;
481 } else
482 LOGGER.debug("Image is opague.");
483
484 if (useEncoder == JPEG_ENC) {
485 LOGGER.debug("MCRImgProcessor - encode");
486 LOGGER.debug("JPEG_ENC");
487
488 jpegEncode(image, output, jpegQuality);
489 } else if (useEncoder == TIFF_ENC) {
490 LOGGER.debug("MCRImgProcessor - encode");
491 LOGGER.debug("TIFF_ENC");
492 tiffEncode(image, output, true, tileWidth, tileHeight);
493 } else if (useEncoder == PNG_ENC) {
494 LOGGER.debug("MCRImgProcessor - encode");
495 LOGGER.debug("PNG_ENC");
496 // pngEncode(image, output, transparent);
497 encodeIO(output, "png");
498 }
499 }
500
501 private void jpegEncode(PlanarImage imgInput, OutputStream output, float encodeQuality) {
502 // Encode JPEG
503 JPEGEncodeParam jpegParam = new JPEGEncodeParam();
504 jpegParam.setQuality(jpegQuality);
505 ImageEncoder enc = ImageCodec.createImageEncoder("JPEG", output, jpegParam);
506 try {
507 enc.encode(imgInput);
508 // output.close();
509 } catch (Exception e) {
510 // TODO change Exeption
511 System.out.println("IOException at JPEG encoding..");
512 e.printStackTrace();
513 }
514 }
515
516 private void tiffEncode(PlanarImage imgInput, OutputStream output, boolean writeTiled, int tileWidth, int tileHeight) {
517 // Encode TIFF
518 TIFFEncodeParam tiffParam = new TIFFEncodeParam();
519
520 if (writeTiled) {
521 tiffParam.setTileSize(tileWidth, tileHeight);
522 tiffParam.setWriteTiled(writeTiled);
523 }
524
525 ImageEncoder enc = ImageCodec.createImageEncoder("TIFF", output, tiffParam);
526 try {
527 enc.encode(imgInput);
528 // output.close();
529 } catch (Exception e) {
530 // TODO change Exeption
531 System.out.println("IOException at TIFF encoding..");
532 e.printStackTrace();
533 }
534 }
535
536 private void pngEncode(PlanarImage imgInput, OutputStream output, boolean transparent) {
537 // Encode PNG
538 PNGEncodeParam.RGB pngParam = new PNGEncodeParam.RGB();
539
540 if (!transparent) {
541 pngParam.unsetTransparency();
542 }
543
544 ImageEncoder enc = ImageCodec.createImageEncoder("png", output, pngParam);
545 try {
546 enc.encode(imgInput);
547 // output.close();
548 } catch (Exception e) {
549 // TODO change Exeption
550 System.out.println("IOException at PNG encoding..");
551 e.printStackTrace();
552 }
553 }
554
555 private RenderingHints noBorder() {
556 BorderExtender extender = BorderExtender.createInstance(BorderExtender.BORDER_COPY);
557 RenderingHints hints = new RenderingHints(JAI.KEY_BORDER_EXTENDER, extender);
558 return hints;
559 }
560
561 public void setScaleFactor(float scaleFactor) {
562 this.scaleFactor = scaleFactor;
563 }
564
565 public boolean isTransparent() {
566 return transparent;
567 }
568
569 public void setTransparent(boolean transparent) {
570 this.transparent = transparent;
571 }
572
573 public String getFileFormat() {
574 return fileFormat;
575 }
576
577 public void setFileFormat(String fileFormat) {
578 this.fileFormat = fileFormat;
579 }
580 }