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