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             if (file.length() > maxSize) {
134                 continue;
135             }
136 
137             FileInputStream fis = new FileInputStream(file);
138 
139             try {
140                 RenderedImage renderedImage = ImageIO.read(fis);
141 
142                 int height = renderedImage.getHeight();
143                 int width = renderedImage.getWidth();
144 
145                 if ((height <= maxHeight) && (width <= maxWidth)) {
146                     renderedImage = convert(renderedImage);
147 
148                     renderedImage = TranslateDescriptor.create(
149                         renderedImage, x, y, null, null);
150 
151                     renderedImages.add(renderedImage);
152 
153                     String key = StringUtil.replace(
154                         file.toString(), StringPool.BACK_SLASH,
155                         StringPool.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             finally {
177                 fis.close();
178             }
179         }
180 
181         if (renderedImages.size() <= 1) {
182             renderedImages.clear();
183             spriteProperties.clear();
184         }
185         else {
186 
187             // PNG
188 
189             RenderedImage renderedImage = MosaicDescriptor.create(
190                 renderedImages.toArray(
191                     new RenderedImage[renderedImages.size()]),
192                 MosaicDescriptor.MOSAIC_TYPE_OVERLAY, null, null, null, null,
193                 null);
194 
195             File spriteFile = new File(
196                 dir.toString() + StringPool.SLASH + spriteFileName);
197 
198             ImageIO.write(renderedImage, "png", spriteFile);
199 
200             if (lastModified > 0) {
201                 spriteFile.setLastModified(lastModified);
202             }
203 
204             ImageWorker imageWorker = new ImageWorker(renderedImage);
205 
206             imageWorker.forceIndexColorModelForGIF(true);
207 
208             // GIF
209 
210             renderedImage = imageWorker.getPlanarImage();
211 
212             spriteFile = new File(
213                 dir.toString() + StringPool.SLASH +
214                     StringUtil.replace(spriteFileName, ".png", ".gif"));
215 
216             FileOutputStream fos = new FileOutputStream(spriteFile);
217 
218             try {
219                 ImageProcessorUtil.encodeGIF(renderedImage, fos);
220             }
221             finally {
222                 fos.close();
223             }
224 
225             if (lastModified > 0) {
226                 spriteFile.setLastModified(lastModified);
227             }
228         }
229 
230         FileUtil.write(
231             spritePropertiesFile, PropertiesUtil.toString(spriteProperties));
232 
233         if (lastModified > 0) {
234             spritePropertiesFile.setLastModified(lastModified);
235         }
236 
237         return spriteProperties;
238     }
239 
240     protected RenderedImage convert(RenderedImage renderedImage)
241         throws Exception {
242 
243         int height = renderedImage.getHeight();
244         int width = renderedImage.getWidth();
245 
246         SampleModel sampleModel = renderedImage.getSampleModel();
247         ColorModel colorModel = renderedImage.getColorModel();
248 
249         Raster raster = renderedImage.getData();
250 
251         DataBuffer dataBuffer = raster.getDataBuffer();
252 
253         if (colorModel instanceof IndexColorModel) {
254             IndexColorModel indexColorModel = (IndexColorModel)colorModel;
255 
256             int mapSize = indexColorModel.getMapSize();
257 
258             byte[][] data = new byte[4][mapSize];
259 
260             indexColorModel.getReds(data[0]);
261             indexColorModel.getGreens(data[1]);
262             indexColorModel.getBlues(data[2]);
263             indexColorModel.getAlphas(data[3]);
264 
265             LookupTableJAI lookupTableJAI = new LookupTableJAI(data);
266 
267             renderedImage = LookupDescriptor.create(
268                 renderedImage, lookupTableJAI, null);
269         }
270         else if (sampleModel.getNumBands() == 2) {
271             List<Byte> bytesList = new ArrayList<Byte>(
272                 height * width * _NUM_OF_BANDS);
273 
274             List<Byte> tempBytesList = new ArrayList<Byte>(_NUM_OF_BANDS);
275 
276             for (int i = 0; i < dataBuffer.getSize(); i++) {
277                 int mod = (i + 1) % 2;
278 
279                 int elemPos = i;
280 
281                 if (mod == 0) {
282                     tempBytesList.add((byte)dataBuffer.getElem(elemPos - 1));
283                     tempBytesList.add((byte)dataBuffer.getElem(elemPos - 1));
284                 }
285 
286                 tempBytesList.add((byte)dataBuffer.getElem(elemPos));
287 
288                 if (mod == 0) {
289                     Collections.reverse(tempBytesList);
290 
291                     bytesList.addAll(tempBytesList);
292 
293                     tempBytesList.clear();
294                 }
295             }
296 
297             byte[] data = ArrayUtil.toArray(
298                 bytesList.toArray(new Byte[bytesList.size()]));
299 
300             DataBuffer newDataBuffer = new DataBufferByte(data, data.length);
301 
302             renderedImage = createRenderedImage(
303                 renderedImage, height, width, newDataBuffer);
304         }
305         else if (colorModel.getTransparency() != Transparency.TRANSLUCENT) {
306             List<Byte> bytesList = new ArrayList<Byte>(
307                 height * width * _NUM_OF_BANDS);
308 
309             List<Byte> tempBytesList = new ArrayList<Byte>(_NUM_OF_BANDS);
310 
311             for (int i = 0; i < dataBuffer.getSize(); i++) {
312                 int mod = (i + 1) % 3;
313 
314                 int elemPos = i;
315 
316                 tempBytesList.add((byte)dataBuffer.getElem(elemPos));
317 
318                 if (mod == 0) {
319                     tempBytesList.add((byte)255);
320 
321                     Collections.reverse(tempBytesList);
322 
323                     bytesList.addAll(tempBytesList);
324 
325                     tempBytesList.clear();
326                 }
327             }
328 
329             byte[] data = ArrayUtil.toArray(
330                 bytesList.toArray(new Byte[bytesList.size()]));
331 
332             DataBuffer newDataBuffer = new DataBufferByte(data, data.length);
333 
334             renderedImage = createRenderedImage(
335                 renderedImage, height, width, newDataBuffer);
336         }
337 
338         return renderedImage;
339     }
340 
341     protected RenderedImage createRenderedImage(
342         RenderedImage renderedImage, int height, int width,
343         DataBuffer dataBuffer) {
344 
345         SampleModel sampleModel =
346             RasterFactory.createPixelInterleavedSampleModel(
347                 DataBuffer.TYPE_BYTE, width, height, _NUM_OF_BANDS);
348         ColorModel colorModel = PlanarImage.createColorModel(sampleModel);
349 
350         TiledImage tiledImage = new TiledImage(
351             0, 0, width, height, 0, 0, sampleModel, colorModel);
352 
353         Raster raster = RasterFactory.createWritableRaster(
354             sampleModel, dataBuffer, new Point(0, 0));
355 
356         tiledImage.setData(raster);
357 
358         if (false) {
359             JAI.create("filestore", tiledImage, "test.png", "PNG");
360 
361             printImage(renderedImage);
362             printImage(tiledImage);
363         }
364 
365         return tiledImage;
366     }
367 
368     protected void printImage(RenderedImage renderedImage) {
369         SampleModel sampleModel = renderedImage.getSampleModel();
370 
371         int height = renderedImage.getHeight();
372         int width = renderedImage.getWidth();
373         int numOfBands = sampleModel.getNumBands();
374 
375         int[] pixels = new int[height * width * numOfBands];
376 
377         Raster raster = renderedImage.getData();
378 
379         raster.getPixels(0, 0, width, height, pixels);
380 
381         int offset = 0;
382 
383         for (int h = 0; h < height; h++) {
384             for (int w = 0; w < width; w++) {
385                 offset = (h * width * numOfBands) + (w * numOfBands);
386 
387                 System.out.print("[" + w + ", " + h + "] = ");
388 
389                 for (int b = 0; b < numOfBands; b++) {
390                     System.out.print(pixels[offset + b] + " ");
391                 }
392             }
393 
394             System.out.println();
395         }
396     }
397 
398     private static final int _NUM_OF_BANDS = 4;
399 
400     private static Log _log = LogFactoryUtil.getLog(SpriteProcessorImpl.class);
401 
402 }