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