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