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.ImageToolUtil;
019    import com.liferay.portal.kernel.image.SpriteProcessor;
020    import com.liferay.portal.kernel.log.Log;
021    import com.liferay.portal.kernel.log.LogFactoryUtil;
022    import com.liferay.portal.kernel.util.ArrayUtil;
023    import com.liferay.portal.kernel.util.ContextPathUtil;
024    import com.liferay.portal.kernel.util.FileUtil;
025    import com.liferay.portal.kernel.util.JavaConstants;
026    import com.liferay.portal.kernel.util.PropertiesUtil;
027    import com.liferay.portal.kernel.util.SortedProperties;
028    import com.liferay.portal.kernel.util.StringUtil;
029    import com.liferay.portal.kernel.util.Validator;
030    import com.liferay.util.PropertyComparator;
031    
032    import java.awt.Point;
033    import java.awt.Transparency;
034    import java.awt.image.ColorModel;
035    import java.awt.image.DataBuffer;
036    import java.awt.image.DataBufferByte;
037    import java.awt.image.IndexColorModel;
038    import java.awt.image.Raster;
039    import java.awt.image.RenderedImage;
040    import java.awt.image.SampleModel;
041    
042    import java.io.File;
043    import java.io.FileOutputStream;
044    import java.io.IOException;
045    
046    import java.net.URL;
047    import java.net.URLConnection;
048    
049    import java.util.ArrayList;
050    import java.util.Collections;
051    import java.util.List;
052    import java.util.Properties;
053    
054    import javax.imageio.ImageIO;
055    
056    import javax.media.jai.LookupTableJAI;
057    import javax.media.jai.PlanarImage;
058    import javax.media.jai.RasterFactory;
059    import javax.media.jai.TiledImage;
060    import javax.media.jai.operator.LookupDescriptor;
061    import javax.media.jai.operator.MosaicDescriptor;
062    import javax.media.jai.operator.TranslateDescriptor;
063    
064    import javax.servlet.ServletContext;
065    
066    import org.geotools.image.ImageWorker;
067    
068    /**
069     * @author Brian Wing Shun Chan
070     */
071    public class SpriteProcessorImpl implements SpriteProcessor {
072    
073            public Properties generate(
074                            ServletContext servletContext, List<URL> imageURLs,
075                            String spriteRootDirName, String spriteFileName,
076                            String spritePropertiesFileName, String rootPath, int maxHeight,
077                            int maxWidth, int maxSize)
078                    throws IOException {
079    
080                    if (imageURLs.size() < 1) {
081                            return null;
082                    }
083    
084                    Collections.sort(imageURLs, new PropertyComparator("path"));
085    
086                    File spriteRootDir = null;
087    
088                    if (Validator.isNull(spriteRootDirName)) {
089                            File tempDir = (File)servletContext.getAttribute(
090                                    JavaConstants.JAVAX_SERVLET_CONTEXT_TEMPDIR);
091    
092                            spriteRootDir = new File(tempDir, SpriteProcessor.PATH);
093                    }
094                    else {
095                            spriteRootDir = new File(spriteRootDirName);
096                    }
097    
098                    spriteRootDir.mkdirs();
099    
100                    File spritePropertiesFile = new File(
101                            spriteRootDir, spritePropertiesFileName);
102    
103                    File spritePropertiesParentFile = spritePropertiesFile.getParentFile();
104    
105                    spritePropertiesParentFile.mkdirs();
106    
107                    boolean build = false;
108    
109                    long lastModified = 0;
110    
111                    if (spritePropertiesFile.exists()) {
112                            lastModified = spritePropertiesFile.lastModified();
113    
114                            URLConnection urlConnection = null;
115    
116                            for (URL imageURL : imageURLs) {
117                                    urlConnection = imageURL.openConnection();
118    
119                                    if ((urlConnection != null) &&
120                                            (urlConnection.getLastModified() > lastModified)) {
121    
122                                            build = true;
123    
124                                            break;
125                                    }
126                            }
127                    }
128                    else {
129                            build = true;
130                    }
131    
132                    if (!build) {
133                            String spritePropertiesString = FileUtil.read(spritePropertiesFile);
134    
135                            if (Validator.isNull(spritePropertiesString)) {
136                                    return null;
137                            }
138                            else {
139                                    return PropertiesUtil.load(spritePropertiesString);
140                            }
141                    }
142    
143                    List<RenderedImage> renderedImages = new ArrayList<RenderedImage>();
144    
145                    Properties spriteProperties = new SortedProperties();
146    
147                    float x = 0;
148                    float y = 0;
149    
150                    URLConnection urlConnection = null;
151    
152                    for (URL imageURL : imageURLs) {
153                            urlConnection = imageURL.openConnection();
154    
155                            if ((urlConnection != null) &&
156                                    (urlConnection.getContentLength() > maxSize)) {
157    
158                                    continue;
159                            }
160    
161                            try {
162                                    ImageBag imageBag = ImageToolUtil.read(
163                                            urlConnection.getInputStream());
164    
165                                    RenderedImage renderedImage = imageBag.getRenderedImage();
166    
167                                    int height = renderedImage.getHeight();
168                                    int width = renderedImage.getWidth();
169    
170                                    if ((height <= maxHeight) && (width <= maxWidth)) {
171                                            renderedImage = convert(renderedImage);
172    
173                                            renderedImage = TranslateDescriptor.create(
174                                                    renderedImage, x, y, null, null);
175    
176                                            renderedImages.add(renderedImage);
177    
178                                            String key = imageURL.getPath();
179    
180                                            int pos = key.indexOf(rootPath);
181    
182                                            if (pos == 0) {
183                                                    key = key.substring(rootPath.length());
184                                            }
185    
186                                            String contextPath = ContextPathUtil.getContextPath(
187                                                    servletContext);
188    
189                                            key = contextPath.concat(key);
190    
191                                            String value = (int)y + "," + height + "," + width;
192    
193                                            spriteProperties.setProperty(key, value);
194    
195                                            y += renderedImage.getHeight();
196                                    }
197                            }
198                            catch (Exception e) {
199                                    if (_log.isWarnEnabled()) {
200                                            _log.warn("Unable to process " + imageURL);
201                                    }
202    
203                                    if (_log.isDebugEnabled()) {
204                                            _log.debug(e, e);
205                                    }
206                            }
207                    }
208    
209                    if (renderedImages.size() <= 1) {
210                            renderedImages.clear();
211                            spriteProperties.clear();
212                    }
213                    else {
214    
215                            // PNG
216    
217                            RenderedImage renderedImage = MosaicDescriptor.create(
218                                    renderedImages.toArray(
219                                            new RenderedImage[renderedImages.size()]),
220                                    MosaicDescriptor.MOSAIC_TYPE_OVERLAY, null, null, null, null,
221                                    null);
222    
223                            File spriteFile = new File(spriteRootDir, spriteFileName);
224    
225                            spriteFile.mkdirs();
226    
227                            ImageIO.write(renderedImage, "png", spriteFile);
228    
229                            if (lastModified > 0) {
230                                    spriteFile.setLastModified(lastModified);
231                            }
232    
233                            ImageWorker imageWorker = new ImageWorker(renderedImage);
234    
235                            imageWorker.forceIndexColorModelForGIF(true);
236    
237                            // GIF
238    
239                            renderedImage = imageWorker.getPlanarImage();
240    
241                            spriteFile = new File(
242                                    spriteRootDir,
243                                    StringUtil.replace(spriteFileName, ".png", ".gif"));
244    
245                            FileOutputStream fos = new FileOutputStream(spriteFile);
246    
247                            try {
248                                    ImageToolUtil.encodeGIF(renderedImage, fos);
249                            }
250                            finally {
251                                    fos.close();
252                            }
253    
254                            if (lastModified > 0) {
255                                    spriteFile.setLastModified(lastModified);
256                            }
257                    }
258    
259                    FileUtil.write(
260                            spritePropertiesFile, PropertiesUtil.toString(spriteProperties));
261    
262                    if (lastModified > 0) {
263                            spritePropertiesFile.setLastModified(lastModified);
264                    }
265    
266                    return spriteProperties;
267            }
268    
269            protected RenderedImage convert(RenderedImage renderedImage)
270                    throws Exception {
271    
272                    int height = renderedImage.getHeight();
273                    int width = renderedImage.getWidth();
274    
275                    SampleModel sampleModel = renderedImage.getSampleModel();
276                    ColorModel colorModel = renderedImage.getColorModel();
277    
278                    Raster raster = renderedImage.getData();
279    
280                    DataBuffer dataBuffer = raster.getDataBuffer();
281    
282                    if (colorModel instanceof IndexColorModel) {
283                            IndexColorModel indexColorModel = (IndexColorModel)colorModel;
284    
285                            int mapSize = indexColorModel.getMapSize();
286    
287                            byte[][] data = new byte[4][mapSize];
288    
289                            indexColorModel.getReds(data[0]);
290                            indexColorModel.getGreens(data[1]);
291                            indexColorModel.getBlues(data[2]);
292                            indexColorModel.getAlphas(data[3]);
293    
294                            LookupTableJAI lookupTableJAI = new LookupTableJAI(data);
295    
296                            renderedImage = LookupDescriptor.create(
297                                    renderedImage, lookupTableJAI, null);
298                    }
299                    else if (sampleModel.getNumBands() == 2) {
300                            List<Byte> bytesList = new ArrayList<Byte>(
301                                    height * width * _NUM_OF_BANDS);
302    
303                            List<Byte> tempBytesList = new ArrayList<Byte>(_NUM_OF_BANDS);
304    
305                            for (int i = 0; i < dataBuffer.getSize(); i++) {
306                                    int mod = (i + 1) % 2;
307    
308                                    int elemPos = i;
309    
310                                    if (mod == 0) {
311                                            tempBytesList.add((byte)dataBuffer.getElem(elemPos - 1));
312                                            tempBytesList.add((byte)dataBuffer.getElem(elemPos - 1));
313                                    }
314    
315                                    tempBytesList.add((byte)dataBuffer.getElem(elemPos));
316    
317                                    if (mod == 0) {
318                                            Collections.reverse(tempBytesList);
319    
320                                            bytesList.addAll(tempBytesList);
321    
322                                            tempBytesList.clear();
323                                    }
324                            }
325    
326                            byte[] data = ArrayUtil.toArray(
327                                    bytesList.toArray(new Byte[bytesList.size()]));
328    
329                            DataBuffer newDataBuffer = new DataBufferByte(data, data.length);
330    
331                            renderedImage = createRenderedImage(
332                                    renderedImage, height, width, newDataBuffer);
333                    }
334                    else if (colorModel.getTransparency() != Transparency.TRANSLUCENT) {
335                            List<Byte> bytesList = new ArrayList<Byte>(
336                                    height * width * _NUM_OF_BANDS);
337    
338                            List<Byte> tempBytesList = new ArrayList<Byte>(_NUM_OF_BANDS);
339    
340                            for (int i = 0; i < dataBuffer.getSize(); i++) {
341                                    int mod = (i + 1) % 3;
342    
343                                    int elemPos = i;
344    
345                                    tempBytesList.add((byte)dataBuffer.getElem(elemPos));
346    
347                                    if (mod == 0) {
348                                            tempBytesList.add((byte)255);
349    
350                                            Collections.reverse(tempBytesList);
351    
352                                            bytesList.addAll(tempBytesList);
353    
354                                            tempBytesList.clear();
355                                    }
356                            }
357    
358                            byte[] data = ArrayUtil.toArray(
359                                    bytesList.toArray(new Byte[bytesList.size()]));
360    
361                            DataBuffer newDataBuffer = new DataBufferByte(data, data.length);
362    
363                            renderedImage = createRenderedImage(
364                                    renderedImage, height, width, newDataBuffer);
365                    }
366    
367                    return renderedImage;
368            }
369    
370            protected RenderedImage createRenderedImage(
371                    RenderedImage renderedImage, int height, int width,
372                    DataBuffer dataBuffer) {
373    
374                    SampleModel sampleModel =
375                            RasterFactory.createPixelInterleavedSampleModel(
376                                    DataBuffer.TYPE_BYTE, width, height, _NUM_OF_BANDS);
377                    ColorModel colorModel = PlanarImage.createColorModel(sampleModel);
378    
379                    TiledImage tiledImage = new TiledImage(
380                            0, 0, width, height, 0, 0, sampleModel, colorModel);
381    
382                    Raster raster = RasterFactory.createWritableRaster(
383                            sampleModel, dataBuffer, new Point(0, 0));
384    
385                    tiledImage.setData(raster);
386    
387                    /*javax.media.jai.JAI.create(
388                            "filestore", tiledImage, "test.png", "PNG");
389    
390                    printImage(renderedImage);
391                    printImage(tiledImage);*/
392    
393                    return tiledImage;
394            }
395    
396            protected void printImage(RenderedImage renderedImage) {
397                    SampleModel sampleModel = renderedImage.getSampleModel();
398    
399                    int height = renderedImage.getHeight();
400                    int width = renderedImage.getWidth();
401                    int numOfBands = sampleModel.getNumBands();
402    
403                    int[] pixels = new int[height * width * numOfBands];
404    
405                    Raster raster = renderedImage.getData();
406    
407                    raster.getPixels(0, 0, width, height, pixels);
408    
409                    int offset = 0;
410    
411                    for (int h = 0; h < height; h++) {
412                            for (int w = 0; w < width; w++) {
413                                    offset = (h * width * numOfBands) + (w * numOfBands);
414    
415                                    System.out.print("[" + w + ", " + h + "] = ");
416    
417                                    for (int b = 0; b < numOfBands; b++) {
418                                            System.out.print(pixels[offset + b] + " ");
419                                    }
420                            }
421    
422                            System.out.println();
423                    }
424            }
425    
426            private static final int _NUM_OF_BANDS = 4;
427    
428            private static Log _log = LogFactoryUtil.getLog(SpriteProcessorImpl.class);
429    
430    }