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