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