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