1   /**
2    * Copyright (c) 2000-2010 Liferay, Inc. All rights reserved.
3    *
4    * The contents of this file are subject to the terms of the Liferay Enterprise
5    * Subscription License ("License"). You may not use this file except in
6    * compliance with the License. You can obtain a copy of the License by
7    * contacting Liferay, Inc. See the License for the specific language governing
8    * permissions and limitations under the License, including but not limited to
9    * distribution rights of the Software.
10   *
11   *
12   *
13   */
14  
15  package com.liferay.portal.image;
16  
17  import com.liferay.portal.kernel.image.ImageBag;
18  import com.liferay.portal.kernel.image.ImageProcessorUtil;
19  import com.liferay.portal.kernel.image.SpriteProcessor;
20  import com.liferay.portal.kernel.log.Log;
21  import com.liferay.portal.kernel.log.LogFactoryUtil;
22  import com.liferay.portal.kernel.util.ArrayUtil;
23  import com.liferay.portal.kernel.util.CharPool;
24  import com.liferay.portal.kernel.util.FileUtil;
25  import com.liferay.portal.kernel.util.PropertiesUtil;
26  import com.liferay.portal.kernel.util.SortedProperties;
27  import com.liferay.portal.kernel.util.StringPool;
28  import com.liferay.portal.kernel.util.StringUtil;
29  import com.liferay.portal.kernel.util.Validator;
30  
31  import java.awt.Point;
32  import java.awt.Transparency;
33  import java.awt.image.ColorModel;
34  import java.awt.image.DataBuffer;
35  import java.awt.image.DataBufferByte;
36  import java.awt.image.IndexColorModel;
37  import java.awt.image.Raster;
38  import java.awt.image.RenderedImage;
39  import java.awt.image.SampleModel;
40  
41  import java.io.File;
42  import java.io.FileOutputStream;
43  import java.io.IOException;
44  
45  import java.util.ArrayList;
46  import java.util.Collections;
47  import java.util.List;
48  import java.util.Properties;
49  
50  import javax.imageio.ImageIO;
51  
52  import javax.media.jai.JAI;
53  import javax.media.jai.LookupTableJAI;
54  import javax.media.jai.PlanarImage;
55  import javax.media.jai.RasterFactory;
56  import javax.media.jai.TiledImage;
57  import javax.media.jai.operator.LookupDescriptor;
58  import javax.media.jai.operator.MosaicDescriptor;
59  import javax.media.jai.operator.TranslateDescriptor;
60  
61  import org.geotools.image.ImageWorker;
62  
63  /**
64   * <a href="SpriteProcessorImpl.java.html"><b><i>View Source</i></b></a>
65   *
66   * @author Brian Wing Shun Chan
67   */
68  public class SpriteProcessorImpl implements SpriteProcessor {
69  
70      static {
71          System.setProperty("com.sun.media.jai.disableMediaLib", "true");
72      }
73  
74      public Properties generate(
75              List<File> images, String spriteFileName,
76              String spritePropertiesFileName, String spritePropertiesRootPath,
77              int maxHeight, int maxWidth, int maxSize)
78          throws IOException {
79  
80          if (images.size() < 1) {
81              return null;
82          }
83  
84          if (spritePropertiesRootPath.endsWith(StringPool.SLASH) ||
85              spritePropertiesRootPath.endsWith(StringPool.BACK_SLASH)) {
86  
87              spritePropertiesRootPath = spritePropertiesRootPath.substring(
88                  0, spritePropertiesRootPath.length() - 1);
89          }
90  
91          File dir = images.get(0).getParentFile();
92  
93          File spritePropertiesFile = new File(
94              dir.toString() + StringPool.SLASH + spritePropertiesFileName);
95  
96          boolean build = false;
97  
98          long lastModified = 0;
99  
100         if (spritePropertiesFile.exists()) {
101             lastModified = spritePropertiesFile.lastModified();
102 
103             for (File image : images) {
104                 if (image.lastModified() > lastModified) {
105                     build = true;
106 
107                     break;
108                 }
109             }
110         }
111         else {
112             build = true;
113         }
114 
115         if (!build) {
116             String spritePropertiesString = FileUtil.read(spritePropertiesFile);
117 
118             if (Validator.isNull(spritePropertiesString)) {
119                 return null;
120             }
121             else {
122                 return PropertiesUtil.load(spritePropertiesString);
123             }
124         }
125 
126         List<RenderedImage> renderedImages = new ArrayList<RenderedImage>();
127 
128         Properties spriteProperties = new SortedProperties();
129 
130         float x = 0;
131         float y = 0;
132 
133         for (File file : images) {
134             if (file.length() > maxSize) {
135                 continue;
136             }
137 
138             try {
139                 ImageBag imageBag = ImageProcessorUtil.read(file);
140 
141                 RenderedImage renderedImage = imageBag.getRenderedImage();
142 
143                 int height = renderedImage.getHeight();
144                 int width = renderedImage.getWidth();
145 
146                 if ((height <= maxHeight) && (width <= maxWidth)) {
147                     renderedImage = convert(renderedImage);
148 
149                     renderedImage = TranslateDescriptor.create(
150                         renderedImage, x, y, null, null);
151 
152                     renderedImages.add(renderedImage);
153 
154                     String key = StringUtil.replace(
155                         file.toString(), CharPool.BACK_SLASH, CharPool.SLASH);
156 
157                     key = key.substring(
158                         spritePropertiesRootPath.toString().length());
159 
160                     String value = (int)y + "," + height + "," + width;
161 
162                     spriteProperties.setProperty(key, value);
163 
164                     y += renderedImage.getHeight();
165                 }
166             }
167             catch (Exception e) {
168                 if (_log.isWarnEnabled()) {
169                     _log.warn("Unable to process " + file);
170                 }
171 
172                 if (_log.isDebugEnabled()) {
173                     _log.debug(e, e);
174                 }
175             }
176         }
177 
178         if (renderedImages.size() <= 1) {
179             renderedImages.clear();
180             spriteProperties.clear();
181         }
182         else {
183 
184             // PNG
185 
186             RenderedImage renderedImage = MosaicDescriptor.create(
187                 renderedImages.toArray(
188                     new RenderedImage[renderedImages.size()]),
189                 MosaicDescriptor.MOSAIC_TYPE_OVERLAY, null, null, null, null,
190                 null);
191 
192             File spriteFile = new File(
193                 dir.toString() + StringPool.SLASH + spriteFileName);
194 
195             ImageIO.write(renderedImage, "png", spriteFile);
196 
197             if (lastModified > 0) {
198                 spriteFile.setLastModified(lastModified);
199             }
200 
201             ImageWorker imageWorker = new ImageWorker(renderedImage);
202 
203             imageWorker.forceIndexColorModelForGIF(true);
204 
205             // GIF
206 
207             renderedImage = imageWorker.getPlanarImage();
208 
209             spriteFile = new File(
210                 dir.toString() + StringPool.SLASH +
211                     StringUtil.replace(spriteFileName, ".png", ".gif"));
212 
213             FileOutputStream fos = new FileOutputStream(spriteFile);
214 
215             try {
216                 ImageProcessorUtil.encodeGIF(renderedImage, fos);
217             }
218             finally {
219                 fos.close();
220             }
221 
222             if (lastModified > 0) {
223                 spriteFile.setLastModified(lastModified);
224             }
225         }
226 
227         FileUtil.write(
228             spritePropertiesFile, PropertiesUtil.toString(spriteProperties));
229 
230         if (lastModified > 0) {
231             spritePropertiesFile.setLastModified(lastModified);
232         }
233 
234         return spriteProperties;
235     }
236 
237     protected RenderedImage convert(RenderedImage renderedImage)
238         throws Exception {
239 
240         int height = renderedImage.getHeight();
241         int width = renderedImage.getWidth();
242 
243         SampleModel sampleModel = renderedImage.getSampleModel();
244         ColorModel colorModel = renderedImage.getColorModel();
245 
246         Raster raster = renderedImage.getData();
247 
248         DataBuffer dataBuffer = raster.getDataBuffer();
249 
250         if (colorModel instanceof IndexColorModel) {
251             IndexColorModel indexColorModel = (IndexColorModel)colorModel;
252 
253             int mapSize = indexColorModel.getMapSize();
254 
255             byte[][] data = new byte[4][mapSize];
256 
257             indexColorModel.getReds(data[0]);
258             indexColorModel.getGreens(data[1]);
259             indexColorModel.getBlues(data[2]);
260             indexColorModel.getAlphas(data[3]);
261 
262             LookupTableJAI lookupTableJAI = new LookupTableJAI(data);
263 
264             renderedImage = LookupDescriptor.create(
265                 renderedImage, lookupTableJAI, null);
266         }
267         else if (sampleModel.getNumBands() == 2) {
268             List<Byte> bytesList = new ArrayList<Byte>(
269                 height * width * _NUM_OF_BANDS);
270 
271             List<Byte> tempBytesList = new ArrayList<Byte>(_NUM_OF_BANDS);
272 
273             for (int i = 0; i < dataBuffer.getSize(); i++) {
274                 int mod = (i + 1) % 2;
275 
276                 int elemPos = i;
277 
278                 if (mod == 0) {
279                     tempBytesList.add((byte)dataBuffer.getElem(elemPos - 1));
280                     tempBytesList.add((byte)dataBuffer.getElem(elemPos - 1));
281                 }
282 
283                 tempBytesList.add((byte)dataBuffer.getElem(elemPos));
284 
285                 if (mod == 0) {
286                     Collections.reverse(tempBytesList);
287 
288                     bytesList.addAll(tempBytesList);
289 
290                     tempBytesList.clear();
291                 }
292             }
293 
294             byte[] data = ArrayUtil.toArray(
295                 bytesList.toArray(new Byte[bytesList.size()]));
296 
297             DataBuffer newDataBuffer = new DataBufferByte(data, data.length);
298 
299             renderedImage = createRenderedImage(
300                 renderedImage, height, width, newDataBuffer);
301         }
302         else if (colorModel.getTransparency() != Transparency.TRANSLUCENT) {
303             List<Byte> bytesList = new ArrayList<Byte>(
304                 height * width * _NUM_OF_BANDS);
305 
306             List<Byte> tempBytesList = new ArrayList<Byte>(_NUM_OF_BANDS);
307 
308             for (int i = 0; i < dataBuffer.getSize(); i++) {
309                 int mod = (i + 1) % 3;
310 
311                 int elemPos = i;
312 
313                 tempBytesList.add((byte)dataBuffer.getElem(elemPos));
314 
315                 if (mod == 0) {
316                     tempBytesList.add((byte)255);
317 
318                     Collections.reverse(tempBytesList);
319 
320                     bytesList.addAll(tempBytesList);
321 
322                     tempBytesList.clear();
323                 }
324             }
325 
326             byte[] data = ArrayUtil.toArray(
327                 bytesList.toArray(new Byte[bytesList.size()]));
328 
329             DataBuffer newDataBuffer = new DataBufferByte(data, data.length);
330 
331             renderedImage = createRenderedImage(
332                 renderedImage, height, width, newDataBuffer);
333         }
334 
335         return renderedImage;
336     }
337 
338     protected RenderedImage createRenderedImage(
339         RenderedImage renderedImage, int height, int width,
340         DataBuffer dataBuffer) {
341 
342         SampleModel sampleModel =
343             RasterFactory.createPixelInterleavedSampleModel(
344                 DataBuffer.TYPE_BYTE, width, height, _NUM_OF_BANDS);
345         ColorModel colorModel = PlanarImage.createColorModel(sampleModel);
346 
347         TiledImage tiledImage = new TiledImage(
348             0, 0, width, height, 0, 0, sampleModel, colorModel);
349 
350         Raster raster = RasterFactory.createWritableRaster(
351             sampleModel, dataBuffer, new Point(0, 0));
352 
353         tiledImage.setData(raster);
354 
355         if (false) {
356             JAI.create("filestore", tiledImage, "test.png", "PNG");
357 
358             printImage(renderedImage);
359             printImage(tiledImage);
360         }
361 
362         return tiledImage;
363     }
364 
365     protected void printImage(RenderedImage renderedImage) {
366         SampleModel sampleModel = renderedImage.getSampleModel();
367 
368         int height = renderedImage.getHeight();
369         int width = renderedImage.getWidth();
370         int numOfBands = sampleModel.getNumBands();
371 
372         int[] pixels = new int[height * width * numOfBands];
373 
374         Raster raster = renderedImage.getData();
375 
376         raster.getPixels(0, 0, width, height, pixels);
377 
378         int offset = 0;
379 
380         for (int h = 0; h < height; h++) {
381             for (int w = 0; w < width; w++) {
382                 offset = (h * width * numOfBands) + (w * numOfBands);
383 
384                 System.out.print("[" + w + ", " + h + "] = ");
385 
386                 for (int b = 0; b < numOfBands; b++) {
387                     System.out.print(pixels[offset + b] + " ");
388                 }
389             }
390 
391             System.out.println();
392         }
393     }
394 
395     private static final int _NUM_OF_BANDS = 4;
396 
397     private static Log _log = LogFactoryUtil.getLog(SpriteProcessorImpl.class);
398 
399 }