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