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