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