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