001    /**
002     * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.image;
016    
017    import com.liferay.portal.kernel.image.ImageBag;
018    import com.liferay.portal.kernel.image.ImageMagick;
019    import com.liferay.portal.kernel.image.ImageTool;
020    import com.liferay.portal.kernel.io.unsync.UnsyncByteArrayInputStream;
021    import com.liferay.portal.kernel.io.unsync.UnsyncByteArrayOutputStream;
022    import com.liferay.portal.kernel.log.Log;
023    import com.liferay.portal.kernel.log.LogFactoryUtil;
024    import com.liferay.portal.kernel.util.JavaDetector;
025    import com.liferay.portal.kernel.util.PropsKeys;
026    import com.liferay.portal.model.Image;
027    import com.liferay.portal.model.impl.ImageImpl;
028    import com.liferay.portal.util.FileImpl;
029    import com.liferay.portal.util.PropsUtil;
030    
031    import com.sun.media.jai.codec.ImageCodec;
032    import com.sun.media.jai.codec.ImageDecoder;
033    import com.sun.media.jai.codec.ImageEncoder;
034    
035    import java.awt.Graphics2D;
036    import java.awt.Graphics;
037    import java.awt.image.BufferedImage;
038    import java.awt.image.DataBuffer;
039    import java.awt.image.IndexColorModel;
040    import java.awt.image.RenderedImage;
041    import java.awt.image.SampleModel;
042    import java.awt.image.WritableRaster;
043    
044    import java.io.File;
045    import java.io.IOException;
046    import java.io.InputStream;
047    import java.io.OutputStream;
048    
049    import java.util.Arrays;
050    import java.util.Enumeration;
051    import java.util.concurrent.ExecutionException;
052    import java.util.concurrent.Future;
053    import java.util.concurrent.TimeUnit;
054    import java.util.concurrent.TimeoutException;
055    
056    import javax.imageio.ImageIO;
057    
058    import javax.media.jai.RenderedImageAdapter;
059    
060    import net.jmge.gif.Gif89Encoder;
061    
062    import org.im4java.core.IMOperation;
063    
064    /**
065     * @author Brian Wing Shun Chan
066     * @author Alexander Chow
067     * @author Shuyang Zhou
068     */
069    public class ImageToolImpl implements ImageTool {
070    
071            public static ImageTool getInstance() {
072                    return _instance;
073            }
074    
075            public void afterPropertiesSet() {
076                    ClassLoader classLoader = getClass().getClassLoader();
077    
078                    try {
079                            InputStream is = classLoader.getResourceAsStream(
080                                    PropsUtil.get(PropsKeys.IMAGE_DEFAULT_SPACER));
081    
082                            if (is == null) {
083                                    _log.error("Default spacer is not available");
084                            }
085    
086                            _defaultSpacer = getImage(is);
087                    }
088                    catch (Exception e) {
089                            _log.error(
090                                    "Unable to configure the default spacer: " + e.getMessage());
091                    }
092    
093                    try {
094                            InputStream is = classLoader.getResourceAsStream(
095                                    PropsUtil.get(PropsKeys.IMAGE_DEFAULT_COMPANY_LOGO));
096    
097                            if (is == null) {
098                                    _log.error("Default company logo is not available");
099                            }
100    
101                            _defaultCompanyLogo = getImage(is);
102                    }
103                    catch (Exception e) {
104                            _log.error(
105                                    "Unable to configure the default company logo: " +
106                                            e.getMessage());
107                    }
108    
109                    try {
110                            InputStream is = classLoader.getResourceAsStream(
111                                    PropsUtil.get(PropsKeys.IMAGE_DEFAULT_ORGANIZATION_LOGO));
112    
113                            if (is == null) {
114                                    _log.error("Default organization logo is not available");
115                            }
116    
117                            _defaultOrganizationLogo = getImage(is);
118                    }
119                    catch (Exception e) {
120                            _log.error(
121                                    "Unable to configure the default organization logo: " +
122                                            e.getMessage());
123                    }
124    
125                    try {
126                            InputStream is = classLoader.getResourceAsStream(
127                                    PropsUtil.get(PropsKeys.IMAGE_DEFAULT_USER_FEMALE_PORTRAIT));
128    
129                            if (is == null) {
130                                    _log.error("Default user female portrait is not available");
131                            }
132    
133                            _defaultUserFemalePortrait = getImage(is);
134                    }
135                    catch (Exception e) {
136                            _log.error(
137                                    "Unable to configure the default user female portrait: " +
138                                            e.getMessage());
139                    }
140    
141                    try {
142                            InputStream is = classLoader.getResourceAsStream(
143                                    PropsUtil.get(PropsKeys.IMAGE_DEFAULT_USER_MALE_PORTRAIT));
144    
145                            if (is == null) {
146                                    _log.error("Default user male portrait is not available");
147                            }
148    
149                            _defaultUserMalePortrait = getImage(is);
150                    }
151                    catch (Exception e) {
152                            _log.error(
153                                    "Unable to configure the default user male portrait: " +
154                                            e.getMessage());
155                    }
156            }
157    
158            public Future<RenderedImage> convertCMYKtoRGB(byte[] bytes, String type) {
159                    ImageMagick imageMagick = getImageMagick();
160    
161                    if (!imageMagick.isEnabled()) {
162                            return null;
163                    }
164    
165                    File inputFile = _fileUtil.createTempFile(type);
166                    File outputFile = _fileUtil.createTempFile(type);
167    
168                    try {
169                            _fileUtil.write(inputFile, bytes);
170    
171                            IMOperation imOperation = new IMOperation();
172    
173                            imOperation.addRawArgs("-format", "%[colorspace]");
174                            imOperation.addImage(inputFile.getPath());
175    
176                            String[] output = imageMagick.identify(imOperation.getCmdArgs());
177    
178                            if ((output.length == 1) && output[0].equalsIgnoreCase("CMYK")) {
179                                    if (_log.isInfoEnabled()) {
180                                            _log.info("The image is in the CMYK colorspace");
181                                    }
182    
183                                    imOperation = new IMOperation();
184    
185                                    imOperation.addRawArgs("-colorspace", "RGB");
186                                    imOperation.addImage(inputFile.getPath());
187                                    imOperation.addImage(outputFile.getPath());
188    
189                                    Future<?> future = imageMagick.convert(
190                                            imOperation.getCmdArgs());
191    
192                                    return new RenderedImageFuture(future, outputFile, type);
193                            }
194                    }
195                    catch (Exception e) {
196                            if (_log.isErrorEnabled()) {
197                                    _log.error(e, e);
198                            }
199                    }
200                    finally {
201                            _fileUtil.delete(inputFile);
202                            _fileUtil.delete(outputFile);
203                    }
204    
205                    return null;
206            }
207    
208            public BufferedImage convertImageType(BufferedImage sourceImage, int type) {
209                    BufferedImage targetImage = new BufferedImage(
210                            sourceImage.getWidth(), sourceImage.getHeight(), type);
211    
212                    Graphics2D graphics = targetImage.createGraphics();
213    
214                    graphics.drawRenderedImage(sourceImage, null);
215    
216                    graphics.dispose();
217    
218                    return targetImage;
219            }
220    
221            public void encodeGIF(RenderedImage renderedImage, OutputStream os)
222                    throws IOException {
223    
224                    if (JavaDetector.isJDK6()) {
225                            ImageIO.write(renderedImage, TYPE_GIF, os);
226                    }
227                    else {
228                            BufferedImage bufferedImage = getBufferedImage(renderedImage);
229    
230                            if (!(bufferedImage.getColorModel() instanceof IndexColorModel)) {
231                                    bufferedImage = convertImageType(
232                                            bufferedImage, BufferedImage.TYPE_BYTE_INDEXED);
233                            }
234    
235                            Gif89Encoder encoder = new Gif89Encoder(bufferedImage);
236    
237                            encoder.encode(os);
238                    }
239            }
240    
241            public void encodeWBMP(RenderedImage renderedImage, OutputStream os)
242                    throws IOException {
243    
244                    BufferedImage bufferedImage = getBufferedImage(renderedImage);
245    
246                    SampleModel sampleModel = bufferedImage.getSampleModel();
247    
248                    int type = sampleModel.getDataType();
249    
250                    if ((bufferedImage.getType() != BufferedImage.TYPE_BYTE_BINARY) ||
251                            (type < DataBuffer.TYPE_BYTE) || (type > DataBuffer.TYPE_INT) ||
252                            (sampleModel.getNumBands() != 1) ||
253                            (sampleModel.getSampleSize(0) != 1)) {
254    
255                            BufferedImage binaryImage = new BufferedImage(
256                                    bufferedImage.getWidth(), bufferedImage.getHeight(),
257                                    BufferedImage.TYPE_BYTE_BINARY);
258    
259                            Graphics graphics = binaryImage.getGraphics();
260    
261                            graphics.drawImage(bufferedImage, 0, 0, null);
262    
263                            renderedImage = binaryImage;
264                    }
265    
266                    if (!ImageIO.write(renderedImage, "wbmp", os)) {
267    
268                            // See http://www.jguru.com/faq/view.jsp?EID=127723
269    
270                            os.write(0);
271                            os.write(0);
272                            os.write(toMultiByte(bufferedImage.getWidth()));
273                            os.write(toMultiByte(bufferedImage.getHeight()));
274    
275                            DataBuffer dataBuffer = bufferedImage.getData().getDataBuffer();
276    
277                            int size = dataBuffer.getSize();
278    
279                            for (int i = 0; i < size; i++) {
280                                    os.write((byte)dataBuffer.getElem(i));
281                            }
282                    }
283            }
284    
285            public BufferedImage getBufferedImage(RenderedImage renderedImage) {
286                    if (renderedImage instanceof BufferedImage) {
287                            return (BufferedImage)renderedImage;
288                    }
289                    else {
290                            RenderedImageAdapter adapter = new RenderedImageAdapter(
291                                    renderedImage);
292    
293                            return adapter.getAsBufferedImage();
294                    }
295            }
296    
297            public byte[] getBytes(RenderedImage renderedImage, String contentType)
298                    throws IOException {
299    
300                    UnsyncByteArrayOutputStream baos = new UnsyncByteArrayOutputStream();
301    
302                    write(renderedImage, contentType, baos);
303    
304                    return baos.toByteArray();
305            }
306    
307            public Image getDefaultCompanyLogo() {
308                    return _defaultCompanyLogo;
309            }
310    
311            public Image getDefaultOrganizationLogo() {
312                    return _defaultOrganizationLogo;
313            }
314    
315            public Image getDefaultSpacer() {
316                    return _defaultSpacer;
317            }
318    
319            public Image getDefaultUserFemalePortrait() {
320                    return _defaultUserFemalePortrait;
321            }
322    
323            public Image getDefaultUserMalePortrait() {
324                    return _defaultUserMalePortrait;
325            }
326    
327            public Image getImage(byte[] bytes) throws IOException {
328                    if (bytes == null) {
329                            return null;
330                    }
331    
332                    ImageBag imageBag = read(bytes);
333    
334                    RenderedImage renderedImage = imageBag.getRenderedImage();
335    
336                    if (renderedImage == null) {
337                            throw new IOException("Unable to decode image");
338                    }
339    
340                    String type = imageBag.getType();
341    
342                    int height = renderedImage.getHeight();
343                    int width = renderedImage.getWidth();
344                    int size = bytes.length;
345    
346                    Image image = new ImageImpl();
347    
348                    image.setTextObj(bytes);
349                    image.setType(type);
350                    image.setHeight(height);
351                    image.setWidth(width);
352                    image.setSize(size);
353    
354                    return image;
355            }
356    
357            public Image getImage(File file) throws IOException {
358                    byte[] bytes = _fileUtil.getBytes(file);
359    
360                    return getImage(bytes);
361            }
362    
363            public Image getImage(InputStream is) throws IOException {
364                    byte[] bytes = _fileUtil.getBytes(is, -1, true);
365    
366                    return getImage(bytes);
367            }
368    
369            public Image getImage(InputStream is, boolean cleanUpStream)
370                    throws IOException {
371    
372                    byte[] bytes = _fileUtil.getBytes(is, -1, cleanUpStream);
373    
374                    return getImage(bytes);
375            }
376    
377            public boolean isNullOrDefaultSpacer(byte[] bytes) {
378                    if ((bytes == null) || (bytes.length == 0) ||
379                            (Arrays.equals(bytes, getDefaultSpacer().getTextObj()))) {
380    
381                            return true;
382                    }
383                    else {
384                            return false;
385                    }
386            }
387    
388            public ImageBag read(byte[] bytes) {
389                    RenderedImage renderedImage = null;
390                    String type = TYPE_NOT_AVAILABLE;
391    
392                    Enumeration<ImageCodec> enu = ImageCodec.getCodecs();
393    
394                    while (enu.hasMoreElements()) {
395                            ImageCodec codec = enu.nextElement();
396    
397                            if (codec.isFormatRecognized(bytes)) {
398                                    type = codec.getFormatName();
399    
400                                    renderedImage = read(bytes, type);
401    
402                                    break;
403                            }
404                    }
405    
406                    if (type.equals("jpeg")) {
407                            type = TYPE_JPEG;
408                    }
409    
410                    return new ImageBag(renderedImage, type);
411            }
412    
413            public ImageBag read(File file) throws IOException {
414                    return read(_fileUtil.getBytes(file));
415            }
416    
417            public ImageBag read(InputStream inputStream) throws IOException {
418                    return read(_fileUtil.getBytes(inputStream));
419            }
420    
421            public RenderedImage scale(RenderedImage renderedImage, int width) {
422                    if (width <= 0) {
423                            return renderedImage;
424                    }
425    
426                    int imageHeight = renderedImage.getHeight();
427                    int imageWidth = renderedImage.getWidth();
428    
429                    double factor = (double)width / imageWidth;
430    
431                    int scaledHeight = (int)(factor * imageHeight);
432                    int scaledWidth = width;
433    
434                    BufferedImage bufferedImage = getBufferedImage(renderedImage);
435    
436                    int type = bufferedImage.getType();
437    
438                    if (type == 0) {
439                            type = BufferedImage.TYPE_INT_ARGB;
440                    }
441    
442                    BufferedImage scaledBufferedImage = new BufferedImage(
443                            scaledWidth, scaledHeight, type);
444    
445                    Graphics graphics = scaledBufferedImage.getGraphics();
446    
447                    java.awt.Image scaledImage = bufferedImage.getScaledInstance(
448                            scaledWidth, scaledHeight, java.awt.Image.SCALE_SMOOTH);
449    
450                    graphics.drawImage(scaledImage, 0, 0, null);
451    
452                    return scaledBufferedImage;
453            }
454    
455            public RenderedImage scale(
456                    RenderedImage renderedImage, int maxHeight, int maxWidth) {
457    
458                    int imageHeight = renderedImage.getHeight();
459                    int imageWidth = renderedImage.getWidth();
460    
461                    if (maxHeight == 0) {
462                            maxHeight = imageHeight;
463                    }
464    
465                    if (maxWidth == 0) {
466                            maxWidth = imageWidth;
467                    }
468    
469                    if ((imageHeight <= maxHeight) && (imageWidth <= maxWidth)) {
470                            return renderedImage;
471                    }
472    
473                    double factor = Math.min(
474                            (double)maxHeight / imageHeight, (double)maxWidth / imageWidth);
475    
476                    int scaledHeight = Math.max(1, (int)(factor * imageHeight));
477                    int scaledWidth = Math.max(1, (int)(factor * imageWidth));
478    
479                    BufferedImage bufferedImage = getBufferedImage(renderedImage);
480    
481                    int type = bufferedImage.getType();
482    
483                    if (type == 0) {
484                            type = BufferedImage.TYPE_INT_ARGB;
485                    }
486    
487                    BufferedImage scaledBufferedImage = null;
488    
489                    if ((type == BufferedImage.TYPE_BYTE_BINARY) ||
490                            (type == BufferedImage.TYPE_BYTE_INDEXED)) {
491    
492                            IndexColorModel indexColorModel =
493                                    (IndexColorModel)bufferedImage.getColorModel();
494    
495                            BufferedImage tempBufferedImage = new BufferedImage(
496                                    1, 1, type, indexColorModel);
497    
498                            int bits = indexColorModel.getPixelSize();
499                            int size = indexColorModel.getMapSize();
500    
501                            byte[] reds = new byte[size];
502    
503                            indexColorModel.getReds(reds);
504    
505                            byte[] greens = new byte[size];
506    
507                            indexColorModel.getGreens(greens);
508    
509                            byte[] blues = new byte[size];
510    
511                            indexColorModel.getBlues(blues);
512    
513                            WritableRaster writableRaster = tempBufferedImage.getRaster();
514    
515                            int pixel = writableRaster.getSample(0, 0, 0);
516    
517                            IndexColorModel scaledIndexColorModel = new IndexColorModel(
518                                    bits, size, reds, greens, blues, pixel);
519    
520                            scaledBufferedImage = new BufferedImage(
521                                    scaledWidth, scaledHeight, type, scaledIndexColorModel);
522                    }
523                    else {
524                            scaledBufferedImage = new BufferedImage(
525                                    scaledWidth, scaledHeight, type);
526                    }
527    
528                    Graphics graphics = scaledBufferedImage.getGraphics();
529    
530                    java.awt.Image scaledImage = bufferedImage.getScaledInstance(
531                            scaledWidth, scaledHeight, java.awt.Image.SCALE_SMOOTH);
532    
533                    graphics.drawImage(scaledImage, 0, 0, null);
534    
535                    return scaledBufferedImage;
536            }
537    
538            public void write(
539                            RenderedImage renderedImage, String contentType, OutputStream os)
540                    throws IOException {
541    
542                    if (contentType.contains(TYPE_BMP)) {
543                            ImageEncoder imageEncoder = ImageCodec.createImageEncoder(
544                                    TYPE_BMP, os, null);
545    
546                            imageEncoder.encode(renderedImage);
547                    }
548                    else if (contentType.contains(TYPE_GIF)) {
549                            encodeGIF(renderedImage, os);
550                    }
551                    else if (contentType.contains(TYPE_JPEG) ||
552                                     contentType.contains("jpeg")) {
553    
554                            ImageIO.write(renderedImage, "jpeg", os);
555                    }
556                    else if (contentType.contains(TYPE_PNG)) {
557                            ImageIO.write(renderedImage, TYPE_PNG, os);
558                    }
559                    else if (contentType.contains(TYPE_TIFF) ||
560                                     contentType.contains("tif")) {
561    
562                            ImageEncoder imageEncoder = ImageCodec.createImageEncoder(
563                                    TYPE_TIFF, os, null);
564    
565                            imageEncoder.encode(renderedImage);
566                    }
567            }
568    
569            protected ImageMagick getImageMagick() {
570                    if (_imageMagick == null) {
571                            _imageMagick = ImageMagickImpl.getInstance();
572    
573                            _imageMagick.reset();
574                    }
575    
576                    return _imageMagick;
577            }
578    
579            protected RenderedImage read(byte[] bytes, String type) {
580                    RenderedImage renderedImage = null;
581    
582                    try {
583                            if (type.equals(TYPE_JPEG)) {
584                                    type = "jpeg";
585                            }
586    
587                            ImageDecoder decoder = ImageCodec.createImageDecoder(
588                                    type, new UnsyncByteArrayInputStream(bytes), null);
589    
590                            renderedImage = decoder.decodeAsRenderedImage();
591                    }
592                    catch (IOException ioe) {
593                            if (_log.isDebugEnabled()) {
594                                    _log.debug(type + ": " + ioe.getMessage());
595                            }
596                    }
597    
598                    return renderedImage;
599            }
600    
601            protected byte[] toMultiByte(int intValue) {
602                    int numBits = 32;
603                    int mask = 0x80000000;
604    
605                    while ((mask != 0) && ((intValue & mask) == 0)) {
606                            numBits--;
607                            mask >>>= 1;
608                    }
609    
610                    int numBitsLeft = numBits;
611                    byte[] multiBytes = new byte[(numBitsLeft + 6) / 7];
612    
613                    int maxIndex = multiBytes.length - 1;
614    
615                    for (int b = 0; b <= maxIndex; b++) {
616                            multiBytes[b] = (byte)((intValue >>> ((maxIndex - b) * 7)) & 0x7f);
617    
618                            if (b != maxIndex) {
619                                    multiBytes[b] |= (byte)0x80;
620                            }
621                    }
622    
623                    return multiBytes;
624            }
625    
626            private static Log _log = LogFactoryUtil.getLog(ImageToolImpl.class);
627    
628            private static ImageTool _instance = new ImageToolImpl();
629    
630            private static FileImpl _fileUtil = FileImpl.getInstance();
631            private static ImageMagick _imageMagick;
632    
633            private Image _defaultCompanyLogo;
634            private Image _defaultOrganizationLogo;
635            private Image _defaultSpacer;
636            private Image _defaultUserFemalePortrait;
637            private Image _defaultUserMalePortrait;
638    
639            private class RenderedImageFuture implements Future<RenderedImage> {
640    
641                    public RenderedImageFuture(
642                            Future<?> future, File outputFile, String type) {
643    
644                            _future = future;
645                            _outputFile = outputFile;
646                            _type = type;
647                    }
648    
649                    public boolean cancel(boolean mayInterruptIfRunning) {
650                            if (_future.isCancelled() || _future.isDone()) {
651                                    return false;
652                            }
653    
654                            _future.cancel(true);
655    
656                            return true;
657                    }
658    
659                    public RenderedImage get()
660                            throws ExecutionException, InterruptedException {
661    
662                            _future.get();
663    
664                            byte[] bytes = new byte[0];
665    
666                            try {
667                                    bytes = _fileUtil.getBytes(_outputFile);
668                            }
669                            catch (IOException e) {
670                                    throw new ExecutionException(e);
671                            }
672    
673                            return read(bytes, _type);
674                    }
675    
676                    public RenderedImage get(long timeout, TimeUnit timeUnit)
677                            throws ExecutionException, InterruptedException, TimeoutException {
678    
679                            _future.get(timeout, timeUnit);
680    
681                            byte[] bytes = new byte[0];
682    
683                            try {
684                                    bytes = _fileUtil.getBytes(_outputFile);
685                            }
686                            catch (IOException ioe) {
687                                    throw new ExecutionException(ioe);
688                            }
689    
690                            return read(bytes, _type);
691                    }
692    
693                    public boolean isCancelled() {
694                            return _future.isCancelled();
695                    }
696    
697                    public boolean isDone() {
698                            return _future.isDone();
699                    }
700    
701                    private final Future<?> _future;
702                    private final File _outputFile;
703                    private final String _type;
704    
705            }
706    
707    }