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