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    }