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