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.util.FileImpl;
026    
027    import com.sun.media.jai.codec.ImageCodec;
028    import com.sun.media.jai.codec.ImageDecoder;
029    import com.sun.media.jai.codec.ImageEncoder;
030    
031    import java.awt.Graphics2D;
032    import java.awt.Graphics;
033    import java.awt.Image;
034    import java.awt.image.BufferedImage;
035    import java.awt.image.DataBuffer;
036    import java.awt.image.IndexColorModel;
037    import java.awt.image.RenderedImage;
038    import java.awt.image.SampleModel;
039    import java.awt.image.WritableRaster;
040    
041    import java.io.File;
042    import java.io.IOException;
043    import java.io.InputStream;
044    import java.io.OutputStream;
045    
046    import java.util.Enumeration;
047    import java.util.concurrent.ExecutionException;
048    import java.util.concurrent.Future;
049    import java.util.concurrent.TimeUnit;
050    import java.util.concurrent.TimeoutException;
051    
052    import javax.imageio.ImageIO;
053    
054    import javax.media.jai.RenderedImageAdapter;
055    
056    import net.jmge.gif.Gif89Encoder;
057    
058    import org.im4java.core.IMOperation;
059    
060    /**
061     * @author Brian Wing Shun Chan
062     * @author Alexander Chow
063     */
064    public class ImageToolImpl implements ImageTool {
065    
066            public static ImageTool getInstance() {
067                    return _instance;
068            }
069    
070            public Future<RenderedImage> convertCMYKtoRGB(byte[] bytes, String type) {
071                    ImageMagick imageMagick = getImageMagick();
072    
073                    if (!imageMagick.isEnabled()) {
074                            return null;
075                    }
076    
077                    File inputFile = _fileUtil.createTempFile(type);
078                    File outputFile = _fileUtil.createTempFile(type);
079    
080                    try {
081                            _fileUtil.write(inputFile, bytes);
082    
083                            IMOperation imOperation = new IMOperation();
084    
085                            imOperation.addRawArgs("-format", "%[colorspace]");
086                            imOperation.addImage(inputFile.getPath());
087    
088                            String[] output = imageMagick.identify(imOperation.getCmdArgs());
089    
090                            if ((output.length == 1) && output[0].equalsIgnoreCase("CMYK")) {
091                                    if (_log.isInfoEnabled()) {
092                                            _log.info("The image is in the CMYK colorspace");
093                                    }
094    
095                                    imOperation = new IMOperation();
096    
097                                    imOperation.addRawArgs("-colorspace", "RGB");
098                                    imOperation.addImage(inputFile.getPath());
099                                    imOperation.addImage(outputFile.getPath());
100    
101                                    Future<?> future = imageMagick.convert(
102                                            imOperation.getCmdArgs());
103    
104                                    return new RenderedImageFuture(future, outputFile, type);
105                            }
106                    }
107                    catch (Exception e) {
108                            if (_log.isErrorEnabled()) {
109                                    _log.error(e, e);
110                            }
111                    }
112                    finally {
113                            _fileUtil.delete(inputFile);
114                            _fileUtil.delete(outputFile);
115                    }
116    
117                    return null;
118            }
119    
120            public BufferedImage convertImageType(BufferedImage sourceImage, int type) {
121                    BufferedImage targetImage = new BufferedImage(
122                            sourceImage.getWidth(), sourceImage.getHeight(), type);
123    
124                    Graphics2D graphics = targetImage.createGraphics();
125    
126                    graphics.drawRenderedImage(sourceImage, null);
127    
128                    graphics.dispose();
129    
130                    return targetImage;
131            }
132    
133            public void encodeGIF(RenderedImage renderedImage, OutputStream os)
134                    throws IOException {
135    
136                    if (JavaDetector.isJDK6()) {
137                            ImageIO.write(renderedImage, TYPE_GIF, os);
138                    }
139                    else {
140                            BufferedImage bufferedImage = getBufferedImage(renderedImage);
141    
142                            if (!(bufferedImage.getColorModel() instanceof IndexColorModel)) {
143                                    bufferedImage = convertImageType(
144                                            bufferedImage, BufferedImage.TYPE_BYTE_INDEXED);
145                            }
146    
147                            Gif89Encoder encoder = new Gif89Encoder(bufferedImage);
148    
149                            encoder.encode(os);
150                    }
151            }
152    
153            public void encodeWBMP(RenderedImage renderedImage, OutputStream os)
154                    throws IOException {
155    
156                    BufferedImage bufferedImage = getBufferedImage(renderedImage);
157    
158                    SampleModel sampleModel = bufferedImage.getSampleModel();
159    
160                    int type = sampleModel.getDataType();
161    
162                    if ((bufferedImage.getType() != BufferedImage.TYPE_BYTE_BINARY) ||
163                            (type < DataBuffer.TYPE_BYTE) || (type > DataBuffer.TYPE_INT) ||
164                            (sampleModel.getNumBands() != 1) ||
165                            (sampleModel.getSampleSize(0) != 1)) {
166    
167                            BufferedImage binaryImage = new BufferedImage(
168                                    bufferedImage.getWidth(), bufferedImage.getHeight(),
169                                    BufferedImage.TYPE_BYTE_BINARY);
170    
171                            Graphics graphics = binaryImage.getGraphics();
172    
173                            graphics.drawImage(bufferedImage, 0, 0, null);
174    
175                            renderedImage = binaryImage;
176                    }
177    
178                    if (!ImageIO.write(renderedImage, "wbmp", os)) {
179    
180                            // See http://www.jguru.com/faq/view.jsp?EID=127723
181    
182                            os.write(0);
183                            os.write(0);
184                            os.write(toMultiByte(bufferedImage.getWidth()));
185                            os.write(toMultiByte(bufferedImage.getHeight()));
186    
187                            DataBuffer dataBuffer = bufferedImage.getData().getDataBuffer();
188    
189                            int size = dataBuffer.getSize();
190    
191                            for (int i = 0; i < size; i++) {
192                                    os.write((byte)dataBuffer.getElem(i));
193                            }
194                    }
195            }
196    
197            public BufferedImage getBufferedImage(RenderedImage renderedImage) {
198                    if (renderedImage instanceof BufferedImage) {
199                            return (BufferedImage)renderedImage;
200                    }
201                    else {
202                            RenderedImageAdapter adapter = new RenderedImageAdapter(
203                                    renderedImage);
204    
205                            return adapter.getAsBufferedImage();
206                    }
207            }
208    
209            public byte[] getBytes(RenderedImage renderedImage, String contentType)
210                    throws IOException {
211    
212                    UnsyncByteArrayOutputStream baos = new UnsyncByteArrayOutputStream();
213    
214                    write(renderedImage, contentType, baos);
215    
216                    return baos.toByteArray();
217            }
218    
219            public ImageBag read(byte[] bytes) {
220                    RenderedImage renderedImage = null;
221                    String type = TYPE_NOT_AVAILABLE;
222    
223                    Enumeration<ImageCodec> enu = ImageCodec.getCodecs();
224    
225                    while (enu.hasMoreElements()) {
226                            ImageCodec codec = enu.nextElement();
227    
228                            if (codec.isFormatRecognized(bytes)) {
229                                    type = codec.getFormatName();
230    
231                                    renderedImage = read(bytes, type);
232    
233                                    break;
234                            }
235                    }
236    
237                    if (type.equals("jpeg")) {
238                            type = TYPE_JPEG;
239                    }
240    
241                    return new ImageBag(renderedImage, type);
242            }
243    
244            public ImageBag read(File file) throws IOException {
245                    return read(_fileUtil.getBytes(file));
246            }
247    
248            public ImageBag read(InputStream inputStream) throws IOException {
249                    return read(_fileUtil.getBytes(inputStream));
250            }
251    
252            public RenderedImage scale(RenderedImage renderedImage, int width) {
253                    if (width <= 0) {
254                            return renderedImage;
255                    }
256    
257                    int imageHeight = renderedImage.getHeight();
258                    int imageWidth = renderedImage.getWidth();
259    
260                    double factor = (double) width / imageWidth;
261    
262                    int scaledHeight = (int)(factor * imageHeight);
263                    int scaledWidth = width;
264    
265                    BufferedImage bufferedImage = getBufferedImage(renderedImage);
266    
267                    int type = bufferedImage.getType();
268    
269                    if (type == 0) {
270                            type = BufferedImage.TYPE_INT_ARGB;
271                    }
272    
273                    BufferedImage scaledBufferedImage = new BufferedImage(
274                            scaledWidth, scaledHeight, type);
275    
276                    Graphics graphics = scaledBufferedImage.getGraphics();
277    
278                    Image scaledImage = bufferedImage.getScaledInstance(
279                            scaledWidth, scaledHeight, Image.SCALE_SMOOTH);
280    
281                    graphics.drawImage(scaledImage, 0, 0, null);
282    
283                    return scaledBufferedImage;
284            }
285    
286            public RenderedImage scale(
287                    RenderedImage renderedImage, int maxHeight, int maxWidth) {
288    
289                    int imageHeight = renderedImage.getHeight();
290                    int imageWidth = renderedImage.getWidth();
291    
292                    if (maxHeight == 0) {
293                            maxHeight = imageHeight;
294                    }
295    
296                    if (maxWidth == 0) {
297                            maxWidth = imageWidth;
298                    }
299    
300                    if ((imageHeight <= maxHeight) && (imageWidth <= maxWidth)) {
301                            return renderedImage;
302                    }
303    
304                    double factor = Math.min(
305                            (double)maxHeight / imageHeight, (double)maxWidth / imageWidth);
306    
307                    int scaledHeight = Math.max(1, (int)(factor * imageHeight));
308                    int scaledWidth = Math.max(1, (int)(factor * imageWidth));
309    
310                    BufferedImage bufferedImage = getBufferedImage(renderedImage);
311    
312                    int type = bufferedImage.getType();
313    
314                    if (type == 0) {
315                            type = BufferedImage.TYPE_INT_ARGB;
316                    }
317    
318                    BufferedImage scaledBufferedImage = null;
319    
320                    if ((type == BufferedImage.TYPE_BYTE_BINARY) ||
321                            (type == BufferedImage.TYPE_BYTE_INDEXED)) {
322    
323                            IndexColorModel indexColorModel =
324                                    (IndexColorModel)bufferedImage.getColorModel();
325    
326                            BufferedImage tempBufferedImage = new BufferedImage(
327                                    1, 1, type, indexColorModel);
328    
329                            int bits = indexColorModel.getPixelSize();
330                            int size = indexColorModel.getMapSize();
331    
332                            byte[] reds = new byte[size];
333    
334                            indexColorModel.getReds(reds);
335    
336                            byte[] greens = new byte[size];
337    
338                            indexColorModel.getGreens(greens);
339    
340                            byte[] blues = new byte[size];
341    
342                            indexColorModel.getBlues(blues);
343    
344                            WritableRaster writableRaster = tempBufferedImage.getRaster();
345    
346                            int pixel = writableRaster.getSample(0, 0, 0);
347    
348                            IndexColorModel scaledIndexColorModel = new IndexColorModel(
349                                    bits, size, reds, greens, blues, pixel);
350    
351                            scaledBufferedImage = new BufferedImage(
352                                    scaledWidth, scaledHeight, type, scaledIndexColorModel);
353                    }
354                    else {
355                            scaledBufferedImage = new BufferedImage(
356                                    scaledWidth, scaledHeight, type);
357                    }
358    
359                    Graphics graphics = scaledBufferedImage.getGraphics();
360    
361                    Image scaledImage = bufferedImage.getScaledInstance(
362                            scaledWidth, scaledHeight, Image.SCALE_SMOOTH);
363    
364                    graphics.drawImage(scaledImage, 0, 0, null);
365    
366                    return scaledBufferedImage;
367            }
368    
369            public void write(
370                            RenderedImage renderedImage, String contentType, OutputStream os)
371                    throws IOException {
372    
373                    if (contentType.contains(TYPE_BMP)) {
374                            ImageEncoder imageEncoder = ImageCodec.createImageEncoder(
375                                    TYPE_BMP, os, null);
376    
377                            imageEncoder.encode(renderedImage);
378                    }
379                    else if (contentType.contains(TYPE_GIF)) {
380                            encodeGIF(renderedImage, os);
381                    }
382                    else if (contentType.contains(TYPE_JPEG) ||
383                                     contentType.contains("jpeg")) {
384    
385                            ImageIO.write(renderedImage, "jpeg", os);
386                    }
387                    else if (contentType.contains(TYPE_PNG)) {
388                            ImageIO.write(renderedImage, TYPE_PNG, os);
389                    }
390                    else if (contentType.contains(TYPE_TIFF) ||
391                                     contentType.contains("tif")) {
392    
393                            ImageEncoder imageEncoder = ImageCodec.createImageEncoder(
394                                    TYPE_TIFF, os, null);
395    
396                            imageEncoder.encode(renderedImage);
397                    }
398            }
399    
400            protected ImageMagick getImageMagick() {
401                    if (_imageMagick == null) {
402                            _imageMagick = ImageMagickImpl.getInstance();
403    
404                            _imageMagick.reset();
405                    }
406    
407                    return _imageMagick;
408            }
409    
410            protected RenderedImage read(byte[] bytes, String type) {
411                    RenderedImage renderedImage = null;
412    
413                    try {
414                            if (type.equals(TYPE_JPEG)) {
415                                    type = "jpeg";
416                            }
417    
418                            ImageDecoder decoder = ImageCodec.createImageDecoder(
419                                    type, new UnsyncByteArrayInputStream(bytes), null);
420    
421                            renderedImage = decoder.decodeAsRenderedImage();
422                    }
423                    catch (IOException ioe) {
424                            if (_log.isDebugEnabled()) {
425                                    _log.debug(type + ": " + ioe.getMessage());
426                            }
427                    }
428    
429                    return renderedImage;
430            }
431    
432            protected byte[] toMultiByte(int intValue) {
433                    int numBits = 32;
434                    int mask = 0x80000000;
435    
436                    while ((mask != 0) && ((intValue & mask) == 0)) {
437                            numBits--;
438                            mask >>>= 1;
439                    }
440    
441                    int numBitsLeft = numBits;
442                    byte[] multiBytes = new byte[(numBitsLeft + 6) / 7];
443    
444                    int maxIndex = multiBytes.length - 1;
445    
446                    for (int b = 0; b <= maxIndex; b++) {
447                            multiBytes[b] = (byte)((intValue >>> ((maxIndex - b) * 7)) & 0x7f);
448    
449                            if (b != maxIndex) {
450                                    multiBytes[b] |= (byte)0x80;
451                            }
452                    }
453    
454                    return multiBytes;
455            }
456    
457            private static Log _log = LogFactoryUtil.getLog(ImageToolImpl.class);
458    
459            private static ImageTool _instance = new ImageToolImpl();
460    
461            private static FileImpl _fileUtil = FileImpl.getInstance();
462            private static ImageMagick _imageMagick;
463    
464            private class RenderedImageFuture implements Future<RenderedImage> {
465    
466                    public RenderedImageFuture(
467                            Future<?> future, File outputFile, String type) {
468    
469                            _future = future;
470                            _outputFile = outputFile;
471                            _type = type;
472                    }
473    
474                    public boolean cancel(boolean mayInterruptIfRunning) {
475                            if (_future.isCancelled() || _future.isDone()) {
476                                    return false;
477                            }
478    
479                            _future.cancel(true);
480    
481                            return true;
482                    }
483    
484                    public RenderedImage get()
485                            throws ExecutionException, InterruptedException {
486    
487                            _future.get();
488    
489                            byte[] bytes = new byte[0];
490    
491                            try {
492                                    bytes = _fileUtil.getBytes(_outputFile);
493                            }
494                            catch (IOException e) {
495                                    throw new ExecutionException(e);
496                            }
497    
498                            return read(bytes, _type);
499                    }
500    
501                    public RenderedImage get(long timeout, TimeUnit timeUnit)
502                            throws ExecutionException, InterruptedException, TimeoutException {
503    
504                            _future.get(timeout, timeUnit);
505    
506                            byte[] bytes = new byte[0];
507    
508                            try {
509                                    bytes = _fileUtil.getBytes(_outputFile);
510                            }
511                            catch (IOException ioe) {
512                                    throw new ExecutionException(ioe);
513                            }
514    
515                            return read(bytes, _type);
516                    }
517    
518                    public boolean isCancelled() {
519                            return _future.isCancelled();
520                    }
521    
522                    public boolean isDone() {
523                            return _future.isDone();
524                    }
525    
526                    private final Future<?> _future;
527                    private final File _outputFile;
528                    private final String _type;
529    
530            }
531    
532    }