1   /**
2    * Copyright (c) 2000-2010 Liferay, Inc. All rights reserved.
3    *
4    * This library is free software; you can redistribute it and/or modify it under
5    * the terms of the GNU Lesser General Public License as published by the Free
6    * Software Foundation; either version 2.1 of the License, or (at your option)
7    * any later version.
8    *
9    * This library is distributed in the hope that it will be useful, but WITHOUT
10   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11   * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
12   * details.
13   */
14  
15  package com.liferay.portal.image;
16  
17  import com.liferay.portal.kernel.image.ImageProcessorUtil;
18  import com.liferay.portal.kernel.image.SpriteProcessor;
19  import com.liferay.portal.kernel.log.Log;
20  import com.liferay.portal.kernel.log.LogFactoryUtil;
21  import com.liferay.portal.kernel.util.ArrayUtil;
22  import com.liferay.portal.kernel.util.FileUtil;
23  import com.liferay.portal.kernel.util.PropertiesUtil;
24  import com.liferay.portal.kernel.util.SortedProperties;
25  import com.liferay.portal.kernel.util.StringPool;
26  import com.liferay.portal.kernel.util.StringUtil;
27  import com.liferay.portal.kernel.util.Validator;
28  
29  import java.awt.Point;
30  import java.awt.Transparency;
31  import java.awt.image.ColorModel;
32  import java.awt.image.DataBuffer;
33  import java.awt.image.DataBufferByte;
34  import java.awt.image.IndexColorModel;
35  import java.awt.image.Raster;
36  import java.awt.image.RenderedImage;
37  import java.awt.image.SampleModel;
38  
39  import java.io.File;
40  import java.io.FileInputStream;
41  import java.io.FileOutputStream;
42  import java.io.IOException;
43  
44  import java.util.ArrayList;
45  import java.util.Collections;
46  import java.util.List;
47  import java.util.Properties;
48  
49  import javax.imageio.ImageIO;
50  
51  import javax.media.jai.JAI;
52  import javax.media.jai.LookupTableJAI;
53  import javax.media.jai.PlanarImage;
54  import javax.media.jai.RasterFactory;
55  import javax.media.jai.TiledImage;
56  import javax.media.jai.operator.LookupDescriptor;
57  import javax.media.jai.operator.MosaicDescriptor;
58  import javax.media.jai.operator.TranslateDescriptor;
59  
60  import org.geotools.image.ImageWorker;
61  
62  /**
63   * <a href="SpriteProcessorImpl.java.html"><b><i>View Source</i></b></a>
64   *
65   * @author Brian Wing Shun Chan
66   */
67  public class SpriteProcessorImpl implements SpriteProcessor {
68  
69      static {
70          System.setProperty("com.sun.media.jai.disableMediaLib", "true");
71      }
72  
73      public Properties generate(
74              List<File> images, String spriteFileName,
75              String spritePropertiesFileName, String spritePropertiesRootPath,
76              int maxHeight, int maxWidth, int maxSize)
77          throws IOException {
78  
79          if (images.size() < 1) {
80              return null;
81          }
82  
83          if (spritePropertiesRootPath.endsWith(StringPool.SLASH) ||
84              spritePropertiesRootPath.endsWith(StringPool.BACK_SLASH)) {
85  
86              spritePropertiesRootPath = spritePropertiesRootPath.substring(
87                  0, spritePropertiesRootPath.length() - 1);
88          }
89  
90          File dir = images.get(0).getParentFile();
91  
92          File spritePropertiesFile = new File(
93              dir.toString() + StringPool.SLASH + spritePropertiesFileName);
94  
95          boolean build = false;
96  
97          long lastModified = 0;
98  
99          if (spritePropertiesFile.exists()) {
100             lastModified = spritePropertiesFile.lastModified();
101 
102             for (File image : images) {
103                 if (image.lastModified() > lastModified) {
104                     build = true;
105 
106                     break;
107                 }
108             }
109         }
110         else {
111             build = true;
112         }
113 
114         if (!build) {
115             String spritePropertiesString = FileUtil.read(spritePropertiesFile);
116 
117             if (Validator.isNull(spritePropertiesString)) {
118                 return null;
119             }
120             else {
121                 return PropertiesUtil.load(spritePropertiesString);
122             }
123         }
124 
125         List<RenderedImage> renderedImages = new ArrayList<RenderedImage>();
126 
127         Properties spriteProperties = new SortedProperties();
128 
129         float x = 0;
130         float y = 0;
131 
132         for (File file : images) {
133             String fileName = file.getName();
134 
135             if (file.length() > maxSize) {
136                 continue;
137             }
138 
139             FileInputStream fis = new FileInputStream(file);
140 
141             try {
142                 RenderedImage renderedImage = ImageIO.read(fis);
143 
144                 int height = renderedImage.getHeight();
145                 int width = renderedImage.getWidth();
146 
147                 if ((height <= maxHeight) && (width <= maxWidth)) {
148                     renderedImage = convert(renderedImage);
149 
150                     renderedImage = TranslateDescriptor.create(
151                         renderedImage, x, y, null, null);
152 
153                     renderedImages.add(renderedImage);
154 
155                     String key = StringUtil.replace(
156                         file.toString(), StringPool.BACK_SLASH,
157                         StringPool.SLASH);
158 
159                     key = key.substring(
160                         spritePropertiesRootPath.toString().length());
161 
162                     String value = (int)y + "," + height + "," + width;
163 
164                     spriteProperties.setProperty(key, value);
165 
166                     y += renderedImage.getHeight();
167                 }
168             }
169             catch (Exception e) {
170                 if (_log.isWarnEnabled()) {
171                     _log.warn("Unable to process " + file);
172                 }
173 
174                 if (_log.isDebugEnabled()) {
175                     _log.debug(e, e);
176                 }
177             }
178             finally {
179                 fis.close();
180             }
181         }
182 
183         if (renderedImages.size() <= 1) {
184             renderedImages.clear();
185             spriteProperties.clear();
186         }
187         else {
188 
189             // PNG
190 
191             RenderedImage renderedImage = MosaicDescriptor.create(
192                 (RenderedImage[])renderedImages.toArray(
193                     new RenderedImage[renderedImages.size()]),
194                 MosaicDescriptor.MOSAIC_TYPE_OVERLAY, null, null, null, null,
195                 null);
196 
197             File spriteFile = new File(
198                 dir.toString() + StringPool.SLASH + spriteFileName);
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                 dir.toString() + StringPool.SLASH +
216                     StringUtil.replace(spriteFileName, ".png", ".gif"));
217 
218             FileOutputStream fos = new FileOutputStream(spriteFile);
219 
220             try {
221                 ImageProcessorUtil.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         if (false) {
361             JAI.create("filestore", tiledImage, "test.png", "PNG");
362 
363             printImage(renderedImage);
364             printImage(tiledImage);
365         }
366 
367         return tiledImage;
368     }
369 
370     protected void printImage(RenderedImage renderedImage) {
371         SampleModel sampleModel = renderedImage.getSampleModel();
372 
373         int height = renderedImage.getHeight();
374         int width = renderedImage.getWidth();
375         int numOfBands = sampleModel.getNumBands();
376 
377         int[] pixels = new int[height * width * numOfBands];
378 
379         Raster raster = renderedImage.getData();
380 
381         raster.getPixels(0, 0, width, height, pixels);
382 
383         int offset = 0;
384 
385         for (int h = 0; h < height; h++) {
386             for (int w = 0; w < width; w++) {
387                 offset = (h * width * numOfBands) + (w * numOfBands);
388 
389                 System.out.print("[" + w + ", " + h + "] = ");
390 
391                 for (int b = 0; b < numOfBands; b++) {
392                     System.out.print(pixels[offset + b] + " ");
393                 }
394             }
395 
396             System.out.println();
397         }
398     }
399 
400     private static final int _NUM_OF_BANDS = 4;
401 
402     private static Log _log = LogFactoryUtil.getLog(SpriteProcessorImpl.class);
403 
404 }