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.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.security.pacl.DoPrivileged;
023    import com.liferay.portal.kernel.servlet.ServletContextUtil;
024    import com.liferay.portal.kernel.util.ArrayUtil;
025    import com.liferay.portal.kernel.util.ContextPathUtil;
026    import com.liferay.portal.kernel.util.FileUtil;
027    import com.liferay.portal.kernel.util.JavaConstants;
028    import com.liferay.portal.kernel.util.PropertiesUtil;
029    import com.liferay.portal.kernel.util.SortedProperties;
030    import com.liferay.portal.kernel.util.Validator;
031    import com.liferay.util.PropertyComparator;
032    
033    import java.awt.Point;
034    import java.awt.Transparency;
035    import java.awt.image.ColorModel;
036    import java.awt.image.DataBuffer;
037    import java.awt.image.DataBufferByte;
038    import java.awt.image.IndexColorModel;
039    import java.awt.image.Raster;
040    import java.awt.image.RenderedImage;
041    import java.awt.image.SampleModel;
042    
043    import java.io.File;
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    /**
067     * @author Brian Wing Shun Chan
068     */
069    @DoPrivileged
070    public class SpriteProcessorImpl implements SpriteProcessor {
071    
072            @Override
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                    FileUtil.mkdirs(spriteRootDir);
099    
100                    File spritePropertiesFile = new File(
101                            spriteRootDir, spritePropertiesFileName);
102    
103                    File spritePropertiesParentFile = spritePropertiesFile.getParentFile();
104    
105                    FileUtil.mkdirs(spritePropertiesParentFile);
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 = ServletContextUtil.getResourcePath(imageURL);
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                            File spriteDir = spriteFile.getParentFile();
226    
227                            FileUtil.mkdirs(spriteDir);
228    
229                            ImageIO.write(renderedImage, "png", spriteFile);
230    
231                            if (lastModified > 0) {
232                                    spriteFile.setLastModified(lastModified);
233                            }
234                    }
235    
236                    FileUtil.write(
237                            spritePropertiesFile, PropertiesUtil.toString(spriteProperties));
238    
239                    if (lastModified > 0) {
240                            spritePropertiesFile.setLastModified(lastModified);
241                    }
242    
243                    return spriteProperties;
244            }
245    
246            protected RenderedImage convert(RenderedImage renderedImage)
247                    throws Exception {
248    
249                    int height = renderedImage.getHeight();
250                    int width = renderedImage.getWidth();
251    
252                    SampleModel sampleModel = renderedImage.getSampleModel();
253                    ColorModel colorModel = renderedImage.getColorModel();
254    
255                    Raster raster = renderedImage.getData();
256    
257                    DataBuffer dataBuffer = raster.getDataBuffer();
258    
259                    if (colorModel instanceof IndexColorModel) {
260                            IndexColorModel indexColorModel = (IndexColorModel)colorModel;
261    
262                            int mapSize = indexColorModel.getMapSize();
263    
264                            byte[][] data = new byte[4][mapSize];
265    
266                            indexColorModel.getReds(data[0]);
267                            indexColorModel.getGreens(data[1]);
268                            indexColorModel.getBlues(data[2]);
269                            indexColorModel.getAlphas(data[3]);
270    
271                            LookupTableJAI lookupTableJAI = new LookupTableJAI(data);
272    
273                            renderedImage = LookupDescriptor.create(
274                                    renderedImage, lookupTableJAI, null);
275                    }
276                    else if (sampleModel.getNumBands() == 2) {
277                            List<Byte> bytesList = new ArrayList<Byte>(
278                                    height * width * _NUM_OF_BANDS);
279    
280                            List<Byte> tempBytesList = new ArrayList<Byte>(_NUM_OF_BANDS);
281    
282                            for (int i = 0; i < dataBuffer.getSize(); i++) {
283                                    int mod = (i + 1) % 2;
284    
285                                    int elemPos = i;
286    
287                                    if (mod == 0) {
288                                            tempBytesList.add((byte)dataBuffer.getElem(elemPos - 1));
289                                            tempBytesList.add((byte)dataBuffer.getElem(elemPos - 1));
290                                    }
291    
292                                    tempBytesList.add((byte)dataBuffer.getElem(elemPos));
293    
294                                    if (mod == 0) {
295                                            Collections.reverse(tempBytesList);
296    
297                                            bytesList.addAll(tempBytesList);
298    
299                                            tempBytesList.clear();
300                                    }
301                            }
302    
303                            byte[] data = ArrayUtil.toArray(
304                                    bytesList.toArray(new Byte[bytesList.size()]));
305    
306                            DataBuffer newDataBuffer = new DataBufferByte(data, data.length);
307    
308                            renderedImage = createRenderedImage(
309                                    renderedImage, height, width, newDataBuffer);
310                    }
311                    else if (colorModel.getTransparency() != Transparency.TRANSLUCENT) {
312                            List<Byte> bytesList = new ArrayList<Byte>(
313                                    height * width * _NUM_OF_BANDS);
314    
315                            List<Byte> tempBytesList = new ArrayList<Byte>(_NUM_OF_BANDS);
316    
317                            for (int i = 0; i < dataBuffer.getSize(); i++) {
318                                    int mod = (i + 1) % 3;
319    
320                                    int elemPos = i;
321    
322                                    tempBytesList.add((byte)dataBuffer.getElem(elemPos));
323    
324                                    if (mod == 0) {
325                                            tempBytesList.add((byte)255);
326    
327                                            Collections.reverse(tempBytesList);
328    
329                                            bytesList.addAll(tempBytesList);
330    
331                                            tempBytesList.clear();
332                                    }
333                            }
334    
335                            byte[] data = ArrayUtil.toArray(
336                                    bytesList.toArray(new Byte[bytesList.size()]));
337    
338                            DataBuffer newDataBuffer = new DataBufferByte(data, data.length);
339    
340                            renderedImage = createRenderedImage(
341                                    renderedImage, height, width, newDataBuffer);
342                    }
343    
344                    return renderedImage;
345            }
346    
347            protected RenderedImage createRenderedImage(
348                    RenderedImage renderedImage, int height, int width,
349                    DataBuffer dataBuffer) {
350    
351                    SampleModel sampleModel =
352                            RasterFactory.createPixelInterleavedSampleModel(
353                                    DataBuffer.TYPE_BYTE, width, height, _NUM_OF_BANDS);
354                    ColorModel colorModel = PlanarImage.createColorModel(sampleModel);
355    
356                    TiledImage tiledImage = new TiledImage(
357                            0, 0, width, height, 0, 0, sampleModel, colorModel);
358    
359                    Raster raster = RasterFactory.createWritableRaster(
360                            sampleModel, dataBuffer, new Point(0, 0));
361    
362                    tiledImage.setData(raster);
363    
364                    /*javax.media.jai.JAI.create(
365                            "filestore", tiledImage, "test.png", "PNG");
366    
367                    printImage(renderedImage);
368                    printImage(tiledImage);*/
369    
370                    return tiledImage;
371            }
372    
373            protected void printImage(RenderedImage renderedImage) {
374                    SampleModel sampleModel = renderedImage.getSampleModel();
375    
376                    int height = renderedImage.getHeight();
377                    int width = renderedImage.getWidth();
378                    int numOfBands = sampleModel.getNumBands();
379    
380                    int[] pixels = new int[height * width * numOfBands];
381    
382                    Raster raster = renderedImage.getData();
383    
384                    raster.getPixels(0, 0, width, height, pixels);
385    
386                    int offset = 0;
387    
388                    for (int h = 0; h < height; h++) {
389                            for (int w = 0; w < width; w++) {
390                                    offset = (h * width * numOfBands) + (w * numOfBands);
391    
392                                    System.out.print("[" + w + ", " + h + "] = ");
393    
394                                    for (int b = 0; b < numOfBands; b++) {
395                                            System.out.print(pixels[offset + b] + " ");
396                                    }
397                            }
398    
399                            System.out.println();
400                    }
401            }
402    
403            private static final int _NUM_OF_BANDS = 4;
404    
405            private static Log _log = LogFactoryUtil.getLog(SpriteProcessorImpl.class);
406    
407    }