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