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