001: /*
002: JSmooth: a VM wrapper toolkit for Windows
003: Copyright (C) 2003 Rodrigo Reyes <reyes@charabia.net>
004:
005: This program is free software; you can redistribute it and/or modify
006: it under the terms of the GNU General Public License as published by
007: the Free Software Foundation; either version 2 of the License, or
008: (at your option) any later version.
009:
010: This program is distributed in the hope that it will be useful,
011: but WITHOUT ANY WARRANTY; without even the implied warranty of
012: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013: GNU General Public License for more details.
014:
015: You should have received a copy of the GNU General Public License
016: along with this program; if not, write to the Free Software
017: Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
018:
019: */
020:
021: package net.charabia.jsmoothgen.application;
022:
023: import net.charabia.jsmoothgen.skeleton.*;
024: import net.charabia.jsmoothgen.pe.*;
025:
026: import java.io.*;
027: import java.nio.*;
028: import java.nio.channels.*;
029: import java.util.*;
030: import java.awt.*;
031: import java.awt.image.*;
032: import java.lang.reflect.*;
033: import net.charabia.util.codec.*;
034:
035: public class ExeCompiler {
036: private java.util.Vector m_errors = new java.util.Vector();
037: private Vector m_listeners = new Vector();
038:
039: public interface StepListener {
040: public void setNewState(int percentComplete, String state);
041:
042: public void failed();
043:
044: public void complete();
045: }
046:
047: public void addListener(ExeCompiler.StepListener listener) {
048: m_listeners.add(listener);
049:
050: }
051:
052: public void cleanErrors() {
053: m_errors.removeAllElements();
054: }
055:
056: public java.util.Vector getErrors() {
057: return m_errors;
058: }
059:
060: public class CompilerRunner implements Runnable {
061: private File m_skelroot;
062: private SkeletonBean m_skel;
063: private JSmoothModelBean m_data;
064: private File m_out;
065: private File m_basedir;
066:
067: public CompilerRunner(File skelroot, SkeletonBean skel,
068: File basedir, JSmoothModelBean data, File out) {
069: m_skelroot = skelroot;
070: m_skel = skel;
071: m_data = data;
072: m_out = out;
073: m_basedir = basedir;
074: }
075:
076: public void run() {
077: try {
078: compile(m_skelroot, m_skel, m_basedir, m_data, m_out);
079: } catch (Exception exc) {
080: exc.printStackTrace();
081: }
082: }
083:
084: public ExeCompiler getCompiler() {
085: return ExeCompiler.this ;
086: }
087: }
088:
089: public ExeCompiler.CompilerRunner getRunnable(File skelroot,
090: SkeletonBean skel, File basedir, JSmoothModelBean data,
091: File out) {
092: return new CompilerRunner(skelroot, skel, basedir, data, out);
093: }
094:
095: public void compileAsync(File skelroot, SkeletonBean skel,
096: File basedir, JSmoothModelBean data, File out) {
097: Thread t = new Thread(new CompilerRunner(skelroot, skel,
098: basedir, data, out));
099: t.start();
100: }
101:
102: public boolean compile(File skelroot, SkeletonBean skel,
103: File basedir, JSmoothModelBean data, File out)
104: throws Exception {
105: try {
106: fireStepChange(0, "Starting compilation");
107:
108: File pattern = new File(skelroot, skel.getExecutableName());
109: if (pattern.exists() == false) {
110: m_errors.add("Error: Can't find any skeleton at "
111: + skelroot);
112: fireFailedChange();
113: return false;
114: }
115:
116: fireStepChange(10, "Scanning skeleton...");
117: PEFile pe = new PEFile(pattern);
118: pe.open();
119: PEResourceDirectory resdir = pe.getResourceDirectory();
120:
121: boolean resb = false;
122:
123: //
124: // Adds the jar only if the user selected one
125: //
126: if (data.getEmbeddedJar() == true) {
127: if (data.getJarLocation() == null) {
128: m_errors.add("Error: Jar is not specified!");
129: fireFailedChange();
130: return false;
131: }
132:
133: fireStepChange(40, "Loading Jar...");
134: File jarloc = concFile(basedir, new File(data
135: .getJarLocation()));
136: if (jarloc.exists() == false) {
137: m_errors.add("Error: Can't find jar at " + jarloc);
138: fireFailedChange();
139: return false;
140: }
141:
142: ByteBuffer jardata = load(jarloc);
143:
144: fireStepChange(60, "Adding Jar to Resources...");
145: resb = resdir.replaceResource(skel
146: .getResourceCategory(),
147: skel.getResourceJarId(), 1033, jardata);
148: if (resb == false) {
149: m_errors
150: .add("Error: Can't replace jar resource! It is probably missing from the skeleton.");
151: fireFailedChange();
152: return false;
153: }
154: }
155:
156: fireStepChange(70, "Adding Properties to Resources...");
157: String props = PropertiesBuilder.makeProperties(basedir,
158: data);
159: ByteBuffer propdata = convert(props);
160: resb = resdir.replaceResource(skel.getResourceCategory(),
161: skel.getResourcePropsId(), 1033, propdata);
162:
163: if (data.getIconLocation() != null) {
164: fireStepChange(80, "Loading icon...");
165: String iconpath;
166: if (new java.io.File(data.getIconLocation())
167: .isAbsolute())
168: iconpath = data.getIconLocation();
169: else
170: iconpath = new java.io.File(basedir, data
171: .getIconLocation()).getAbsolutePath();
172:
173: Image img = getScaledImage(iconpath, 32, 32);
174: Hashtable set = calculateColorCount(img);
175: // System.out.println("COLORS TOTAL 4: " + set.size());
176:
177: if (img != null) {
178: net.charabia.jsmoothgen.pe.res.ResIcon resicon = new net.charabia.jsmoothgen.pe.res.ResIcon(
179: img);
180: pe.replaceDefaultIcon(resicon);
181: }
182: }
183:
184: fireStepChange(90, "Saving exe...");
185: pe.dumpTo(out);
186:
187: // System.out.println("PROPERTIES:\n" + props);
188:
189: fireCompleteChange();
190: return true;
191: } catch (Exception exc) {
192: m_errors.add("Error: " + exc.getMessage());
193: exc.printStackTrace();
194: fireFailedChange();
195: return false;
196: }
197: }
198:
199: public Image[] loadImages(String path) {
200: File f = new File(path);
201:
202: if (path.toUpperCase().endsWith(".ICO")) {
203: //
204: // Try to load with our ico codec...
205: //
206: try {
207: java.awt.Image[] images = net.charabia.util.codec.IcoCodec
208: .loadImages(f);
209: if ((images != null) && (images.length > 0)) {
210: return images;
211: }
212: } catch (java.io.IOException exc) {
213: exc.printStackTrace();
214: }
215: }
216:
217: //
218: // defaults to the standard java loading process
219: //
220: javax.swing.ImageIcon icon = new javax.swing.ImageIcon(path,
221: "default icon");
222: java.awt.Image[] imgs = new java.awt.Image[1];
223: imgs[0] = icon.getImage();
224: return imgs;
225: }
226:
227: public void checkImageLoaded(Image img) {
228: MediaTracker mtrack = new MediaTracker(new Canvas());
229: mtrack.addImage(img, 1);
230: try {
231: mtrack.waitForAll();
232: } catch (InterruptedException e) {
233: }
234: }
235:
236: private Hashtable calculateColorCount(Image img) {
237: int width = img.getWidth(null);
238: int height = img.getHeight(null);
239: int[] pixels = new int[width * height];
240: PixelGrabber grabber = new PixelGrabber(img, 0, 0, width,
241: height, pixels, 0, width);
242: try {
243: grabber.grabPixels();
244: } catch (InterruptedException e) {
245: System.err.println("interrupted waiting for pixels!");
246: // throw new Exception("Can't load the image provided",e);
247: }
248:
249: Hashtable result = new Hashtable();
250: int colorindex = 0;
251: for (int i = 0; i < pixels.length; i++) {
252: int pix = pixels[i];
253: if (((pix >> 24) & 0xFF) > 0) {
254: pix &= 0x00FFFFFF;
255: Integer pixi = new Integer(pix);
256: Object o = result.get(pixi);
257: if (o == null) {
258: result.put(pixi, new Integer(colorindex++));
259: }
260: // if (colorindex > 256)
261: // return result;
262: }
263: }
264: return result;
265: }
266:
267: public BufferedImage getQuantizedImage(Image img) {
268: int width = img.getWidth(null);
269: int height = img.getHeight(null);
270: int[][] data = new int[width][height];
271:
272: int[] pixelbuffer = new int[width * height];
273: PixelGrabber grabber = new PixelGrabber(img, 0, 0, width,
274: height, pixelbuffer, 0, width);
275: try {
276: grabber.grabPixels();
277: } catch (InterruptedException e) {
278: System.err.println("interrupted waiting for pixels!");
279: throw new RuntimeException("Can't load the image provided",
280: e);
281: }
282: for (int i = 0; i < pixelbuffer.length; i++) {
283: data[i % width][i / width] = pixelbuffer[i];
284: }
285:
286: int[][] savedata = new int[width][height];
287:
288: for (int y = 0; y < height; y++)
289: for (int x = 0; x < width; x++)
290: savedata[x][y] = data[x][y];
291:
292: int[] palette = net.charabia.util.codec.Quantize.quantizeImage(
293: data, 255);
294: byte[] cmap = new byte[256 * 4];
295:
296: for (int i = 0; i < palette.length; i++) {
297: // System.out.println(" i= " + (i));
298: cmap[(i * 4)] = (byte) ((palette[i] >> 16) & 0xFF);
299: cmap[(i * 4) + 1] = (byte) ((palette[i] >> 8) & 0xFF);
300: cmap[(i * 4) + 2] = (byte) (palette[i] & 0xFF);
301: cmap[(i * 4) + 3] = (byte) 0xFF;
302: }
303:
304: IndexColorModel colmodel = new IndexColorModel(8,
305: palette.length, cmap, 0, true, 0);
306: BufferedImage result = new BufferedImage(width, height,
307: BufferedImage.TYPE_INT_ARGB);
308: //
309: // The normal manner of quantizing would be to run
310: // result.setRGB(0,0, width, height, pixelbuffer, 0, width);
311: // where result is a BufferedImage of
312: // BufferedImage.TYPE_BYTE_INDEXED type. Unfortunately, I
313: // couldn't make it work. So, here is a work-around that
314: // should work similarly.
315: //
316: java.util.Hashtable set = new java.util.Hashtable();
317: for (int y = 0; y < height; y++) {
318: for (int x = 0; x < width; x++) {
319: int alpha = (savedata[x][y] >> 24) & 0xFF;
320: if (alpha == 0) {
321: result.setRGB(x, y, 0);
322: // System.out.print(".");
323: } else {
324: int rgb = colmodel.getRGB(data[x][y]);
325: rgb |= 0xFF000000;
326: set.put(new Integer(rgb), new Integer(rgb));
327: result.setRGB(x, y, rgb);
328: // System.out.print("*");
329: }
330: }
331: // System.out.println("");
332: }
333:
334: return result;
335: }
336:
337: public Image checkImageSize(Image img, int width, int height) {
338: int w = img.getWidth(null);
339: int h = img.getHeight(null);
340: if ((w == width) && (h == height))
341: return img;
342: return null;
343: }
344:
345: public Image getScaledImage(String path, int width, int height) {
346: Image[] orgimages = loadImages(path);
347:
348: if ((orgimages == null) || (orgimages.length == 0))
349: return null;
350:
351: for (int i = 0; i < orgimages.length; i++)
352: checkImageLoaded(orgimages[i]);
353:
354: // System.out.println("Loaded " + orgimages.length + " images");
355: for (int i = 0; (i < orgimages.length); i++) {
356: int w = orgimages[i].getWidth(null);
357: int h = orgimages[i].getHeight(null);
358: // System.out.println("Size of " + i + " = " + w + "," + h);
359: }
360:
361: //
362: // We prefer 32x32 pictures, then 64x64, then 16x16...
363: //
364: Image selected = null;
365: for (int i = 0; (i < orgimages.length) && (selected == null); i++)
366: selected = checkImageSize(orgimages[i], 32, 32);
367: for (int i = 0; (i < orgimages.length) && (selected == null); i++)
368: selected = checkImageSize(orgimages[i], 64, 64);
369: for (int i = 0; (i < orgimages.length) && (selected == null); i++)
370: selected = checkImageSize(orgimages[i], 16, 16);
371:
372: if (selected != null) {
373: return getQuantizedImage(selected);
374: }
375:
376: //
377: // If there is no 32x32, 64x64, nor 16x16, then we scale the
378: // biggest image to be 32x32... This should happen mainly when
379: // loading an image from a png of gif file, and in most case
380: // there is only one image on the array.
381: //
382: int maxsize = 0;
383: Image biggest = null;
384: for (int i = 0; (i < orgimages.length) && (selected == null); i++) {
385: int size = orgimages[i].getWidth(null)
386: * orgimages[i].getHeight(null);
387: if (size > maxsize) {
388: maxsize = size;
389: biggest = orgimages[i];
390: }
391: }
392:
393: if (biggest != null) {
394: Image result = biggest.getScaledInstance(32, 32,
395: Image.SCALE_AREA_AVERAGING);
396: checkImageLoaded(result);
397: return getQuantizedImage(result);
398: }
399: //
400: // Here, we have failed and return null
401: //
402: return null;
403: }
404:
405: private ByteBuffer load(File in) throws Exception {
406: FileInputStream fis = new FileInputStream(in);
407: ByteBuffer data = ByteBuffer.allocate((int) in.length());
408: data.order(ByteOrder.LITTLE_ENDIAN);
409: FileChannel fischan = fis.getChannel();
410: fischan.read(data);
411: data.position(0);
412: fis.close();
413:
414: return data;
415: }
416:
417: private ByteBuffer convert(String data) {
418: ByteBuffer result = ByteBuffer.allocate(data.length() + 1);
419: result.position(0);
420:
421: for (int i = 0; i < data.length(); i++) {
422: result.put((byte) data.charAt(i));
423: }
424: result.put((byte) 0);
425:
426: result.position(0);
427: return result;
428: }
429:
430: static public File concFile(File root, File name) {
431: if (name.isAbsolute())
432: return name;
433:
434: return new File(root, name.toString());
435: }
436:
437: public void fireStepChange(int percentComplete, String state) {
438: for (Iterator i = m_listeners.iterator(); i.hasNext();) {
439: ExeCompiler.StepListener l = (ExeCompiler.StepListener) i
440: .next();
441: l.setNewState(percentComplete, state);
442: }
443: }
444:
445: public void fireFailedChange() {
446: for (Iterator i = m_listeners.iterator(); i.hasNext();) {
447: ExeCompiler.StepListener l = (ExeCompiler.StepListener) i
448: .next();
449: l.failed();
450: }
451: }
452:
453: public void fireCompleteChange() {
454: for (Iterator i = m_listeners.iterator(); i.hasNext();) {
455: ExeCompiler.StepListener l = (ExeCompiler.StepListener) i
456: .next();
457: l.complete();
458: }
459: }
460:
461: }
|