/* JSmooth: a VM wrapper toolkit for Windows Copyright (C) 2003 Rodrigo Reyes <[email protected]> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package net.charabia.jsmoothgen.application; import net.charabia.jsmoothgen.pe.PEFile; import net.charabia.jsmoothgen.pe.PEResourceDirectory; import net.charabia.jsmoothgen.skeleton.SkeletonBean; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.awt.image.IndexColorModel; import java.awt.image.PixelGrabber; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.FileChannel; import java.util.Hashtable; import java.util.Iterator; import java.util.Vector; public class ExeCompiler { private java.util.Vector m_errors = new java.util.Vector(); private Vector m_listeners = new Vector(); public interface StepListener { public void setNewState(int percentComplete, String state); public void failed(); public void complete(); } public void addListener(ExeCompiler.StepListener listener) { m_listeners.add(listener); } public void cleanErrors() { m_errors.removeAllElements(); } public java.util.Vector getErrors() { return m_errors; } public class CompilerRunner implements Runnable { private File m_skelroot; private SkeletonBean m_skel; private JSmoothModelBean m_data; private File m_out; private File m_basedir; public CompilerRunner(File skelroot, SkeletonBean skel, File basedir, JSmoothModelBean data, File out) { m_skelroot = skelroot; m_skel = skel; m_data = data; m_out = out; m_basedir = basedir; } public void run() { try { compile(m_skelroot, m_skel, m_basedir, m_data, m_out); } catch (Exception exc) { exc.printStackTrace(); } } public ExeCompiler getCompiler() { return ExeCompiler.this; } } public ExeCompiler.CompilerRunner getRunnable(File skelroot, SkeletonBean skel, File basedir, JSmoothModelBean data, File out) { return new CompilerRunner(skelroot, skel, basedir, data, out); } public void compileAsync(File skelroot, SkeletonBean skel, File basedir, JSmoothModelBean data, File out) { Thread t = new Thread(new CompilerRunner(skelroot, skel, basedir, data, out)); t.start(); } public boolean compile(File skelroot, SkeletonBean skel, File basedir, JSmoothModelBean data, File out) throws Exception { try { fireStepChange(0, "Starting compilation"); File pattern = new File(skelroot, skel.getExecutableName()); if (pattern.exists() == false) { m_errors.add("Error: Can't find any skeleton at " + skelroot); fireFailedChange(); return false; } fireStepChange(10, "Scanning skeleton..."); PEFile pe = new PEFile(pattern); pe.open(); PEResourceDirectory resdir = pe.getResourceDirectory(); boolean resb = false; // // Adds the jar only if the user selected one // if (data.getEmbeddedJar() == true) { if (data.getJarLocation() == null) { m_errors.add("Error: Jar is not specified!"); fireFailedChange(); return false; } fireStepChange(40, "Loading Jar..."); File jarloc = concFile(basedir, new File(data.getJarLocation())); if (jarloc.exists() == false) { m_errors.add("Error: Can't find jar at " + jarloc); fireFailedChange(); return false; } ByteBuffer jardata = load(jarloc); fireStepChange(60, "Adding Jar to Resources..."); resb = resdir.replaceResource(skel.getResourceCategory(), skel.getResourceJarId(), 1033, jardata); if (resb == false) { m_errors.add("Error: Can't replace jar resource! It is probably missing from the skeleton."); fireFailedChange(); return false; } } fireStepChange(70, "Adding Properties and Manifest to Resources..."); // Properties... String props = PropertiesBuilder.makeProperties(basedir, data); ByteBuffer propdata = convert(props); resb = resdir.replaceResource(skel.getResourceCategory(), skel.getResourcePropsId(), 1033, propdata); // Manifest... String manifest = PropertiesBuilder.makeManifest(data, skel); if (manifest == null || manifest.isEmpty()) { manifest = ""; } ByteBuffer manifestData = convert(manifest); resb = resdir.replaceManifest(1, 1033, manifestData); if (data.getIconLocation() != null) { fireStepChange(80, "Loading icon..."); String iconpath; if (new java.io.File(data.getIconLocation()).isAbsolute()) iconpath = data.getIconLocation(); else iconpath = new java.io.File(basedir, data.getIconLocation()).getAbsolutePath(); Image img = getScaledImage(iconpath, 32, 32); //Hashtable set = calculateColorCount(img); // System.out.println("COLORS TOTAL 4: " + set.size()); if (img != null) { net.charabia.jsmoothgen.pe.res.ResIcon32 resicon = new net.charabia.jsmoothgen.pe.res.ResIcon32(img); pe.replaceDefaultIcon(resicon); } } fireStepChange(90, "Saving exe..."); pe.dumpTo(out); // System.out.println("PROPERTIES:\n" + props); fireCompleteChange(); return true; } catch (Exception exc) { m_errors.add("Error: " + exc.getMessage()); exc.printStackTrace(); fireFailedChange(); return false; } } public Image[] loadImages(String path) { File f = new File(path); if (path.toUpperCase().endsWith(".ICO")) { // // Try to load with our ico codec... // try { java.awt.Image[] images = net.charabia.util.codec.IcoCodec.loadImages(f); if ((images != null) && (images.length > 0)) { return images; } } catch (java.io.IOException exc) { exc.printStackTrace(); } } // // defaults to the standard java loading process // BufferedImage bufferedImage; try { bufferedImage = ImageIO.read(f); javax.swing.ImageIcon icon = new javax.swing.ImageIcon(bufferedImage, "default icon"); java.awt.Image[] imgs = new java.awt.Image[1]; imgs[0] = icon.getImage(); return imgs; } catch (IOException e) { e.printStackTrace(); } return null; } public void checkImageLoaded(Image img) { MediaTracker mtrack = new MediaTracker(new Canvas()); mtrack.addImage(img, 1); try { mtrack.waitForAll(); } catch (InterruptedException e) { } } private Hashtable calculateColorCount(Image img) { int width = img.getWidth(null); int height = img.getHeight(null); int[] pixels = new int[width * height]; PixelGrabber grabber = new PixelGrabber(img, 0, 0, width, height, pixels, 0, width); try { grabber.grabPixels(); } catch (InterruptedException e) { System.err.println("interrupted waiting for pixels!"); // throw new Exception("Can't load the image provided",e); } Hashtable result = new Hashtable(); int colorindex = 0; for (int i = 0; i < pixels.length; i++) { int pix = pixels[i]; if (((pix >> 24) & 0xFF) > 0) { pix &= 0x00FFFFFF; Integer pixi = new Integer(pix); Object o = result.get(pixi); if (o == null) { result.put(pixi, new Integer(colorindex++)); } // if (colorindex > 256) // return result; } } return result; } public BufferedImage getQuantizedImage(Image img) { // 32 bit ico file already loaded as BufferedImage if (img instanceof BufferedImage) { return (BufferedImage) img; } else { int width = img.getWidth(null); int height = img.getHeight(null); int[][] data = new int[width][height]; int[] pixelbuffer = new int[width * height]; PixelGrabber grabber = new PixelGrabber(img, 0, 0, width, height, pixelbuffer, 0, width); try { grabber.grabPixels(); } catch (InterruptedException e) { System.err.println("interrupted waiting for pixels!"); throw new RuntimeException("Can't load the image provided", e); } for (int i = 0; i < pixelbuffer.length; i++) { data[i % width][i / width] = pixelbuffer[i]; } int[][] savedata = new int[width][height]; for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) savedata[x][y] = data[x][y]; int[] palette = net.charabia.util.codec.Quantize.quantizeImage(data, 255); byte[] cmap = new byte[256 * 4]; for (int i = 0; i < palette.length; i++) { // System.out.println(" i= " + (i)); cmap[(i * 4)] = (byte) ((palette[i] >> 16) & 0xFF); cmap[(i * 4) + 1] = (byte) ((palette[i] >> 8) & 0xFF); cmap[(i * 4) + 2] = (byte) (palette[i] & 0xFF); cmap[(i * 4) + 3] = (byte) 0xFF; } IndexColorModel colmodel = new IndexColorModel(8, palette.length, cmap, 0, true, 0); BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); // // The normal manner of quantizing would be to run // result.setRGB(0,0, width, height, pixelbuffer, 0, width); // where result is a BufferedImage of // BufferedImage.TYPE_BYTE_INDEXED type. Unfortunately, I // couldn't make it work. So, here is a work-around that // should work similarly. // java.util.Hashtable set = new java.util.Hashtable(); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int alpha = (savedata[x][y] >> 24) & 0xFF; if (alpha == 0) { result.setRGB(x, y, 0); // System.out.print("."); } else { int rgb = colmodel.getRGB(data[x][y]); rgb |= 0xFF000000; set.put(new Integer(rgb), new Integer(rgb)); result.setRGB(x, y, rgb); // System.out.print("*"); } } // System.out.println(""); } return result; } } public Image checkImageSize(Image img, int width, int height) { int w = img.getWidth(null); int h = img.getHeight(null); if ((w == width) && (h == height)) return img; return null; } public Image getScaledImage(String path, int width, int height) { Image[] orgimages = loadImages(path); if ((orgimages == null) || (orgimages.length == 0)) return null; for (int i = 0; i < orgimages.length; i++) checkImageLoaded(orgimages[i]); // System.out.println("Loaded " + orgimages.length + " images"); for (int i = 0; (i < orgimages.length); i++) { int w = orgimages[i].getWidth(null); int h = orgimages[i].getHeight(null); // System.out.println("Size of " + i + " = " + w + "," + h); } // // We prefer 32x32 pictures, then 64x64, then 16x16... // Image selected = null; for (int i = 0; (i < orgimages.length) && (selected == null); i++) selected = checkImageSize(orgimages[i], 32, 32); for (int i = 0; (i < orgimages.length) && (selected == null); i++) selected = checkImageSize(orgimages[i], 64, 64); for (int i = 0; (i < orgimages.length) && (selected == null); i++) selected = checkImageSize(orgimages[i], 16, 16); if (selected != null) { return getQuantizedImage(selected); } // // If there is no 32x32, 64x64, nor 16x16, then we scale the // biggest image to be 32x32... This should happen mainly when // loading an image from a png of gif file, and in most case // there is only one image on the array. // int maxsize = 0; Image biggest = null; for (int i = 0; (i < orgimages.length) && (selected == null); i++) { int size = orgimages[i].getWidth(null) * orgimages[i].getHeight(null); if (size > maxsize) { maxsize = size; biggest = orgimages[i]; } } if (biggest != null) { BufferedImage result = getScaledInstance((BufferedImage) biggest, 32, 32, RenderingHints.VALUE_INTERPOLATION_BILINEAR, true); checkImageLoaded(result); return getQuantizedImage(result); } // // Here, we have failed and return null // return null; } /** * Convenience method that returns a scaled instance of the * provided {@code BufferedImage}. * * @param img the original image to be scaled * @param targetWidth the desired width of the scaled instance, * in pixels * @param targetHeight the desired height of the scaled instance, * in pixels * @param hint one of the rendering hints that corresponds to * {@code RenderingHints.KEY_INTERPOLATION} (e.g. * {@code RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR}, * {@code RenderingHints.VALUE_INTERPOLATION_BILINEAR}, * {@code RenderingHints.VALUE_INTERPOLATION_BICUBIC}) * @param higherQuality if true, this method will use a multi-step * scaling technique that provides higher quality than the usual * one-step technique (only useful in downscaling cases, where * {@code targetWidth} or {@code targetHeight} is * smaller than the original dimensions, and generally only when * the {@code BILINEAR} hint is specified) * @return a scaled version of the original {@code BufferedImage} */ public BufferedImage getScaledInstance(BufferedImage img, int targetWidth, int targetHeight, Object hint, boolean higherQuality) { int type = (img.getTransparency() == Transparency.OPAQUE) ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; BufferedImage ret = (BufferedImage) img; int w, h; if (higherQuality) { // Use multi-step technique: start with original size, then // scale down in multiple passes with drawImage() // until the target size is reached w = img.getWidth(); h = img.getHeight(); } else { // Use one-step technique: scale directly from original // size to target size with a single drawImage() call w = targetWidth; h = targetHeight; } do { if (higherQuality && w > targetWidth) { w /= 2; if (w < targetWidth) { w = targetWidth; } } if (higherQuality && h > targetHeight) { h /= 2; if (h < targetHeight) { h = targetHeight; } } BufferedImage tmp = new BufferedImage(w, h, type); Graphics2D g2 = tmp.createGraphics(); g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint); g2.drawImage(ret, 0, 0, w, h, null); g2.dispose(); ret = tmp; } while (w != targetWidth || h != targetHeight); return ret; } private ByteBuffer load(File in) throws Exception { FileInputStream fis = new FileInputStream(in); ByteBuffer data = ByteBuffer.allocate((int) in.length()); data.order(ByteOrder.LITTLE_ENDIAN); FileChannel fischan = fis.getChannel(); fischan.read(data); data.position(0); fis.close(); return data; } private ByteBuffer convert(String data) { data = data.replace("\r\n", "\n").replace("\n", "\r\n"); ByteBuffer result = ByteBuffer.allocate(data.length()); result.position(0); for (int i = 0; i < data.length(); i++) { result.put((byte) data.charAt(i)); } // result.put((byte)0); result.position(0); return result; } static public File concFile(File root, File name) { if (name.isAbsolute()) return name; return new File(root, name.toString()); } public void fireStepChange(int percentComplete, String state) { for (Iterator i = m_listeners.iterator(); i.hasNext();) { ExeCompiler.StepListener l = (ExeCompiler.StepListener) i.next(); l.setNewState(percentComplete, state); } } public void fireFailedChange() { for (Iterator i = m_listeners.iterator(); i.hasNext();) { ExeCompiler.StepListener l = (ExeCompiler.StepListener) i.next(); l.failed(); } } public void fireCompleteChange() { for (Iterator i = m_listeners.iterator(); i.hasNext();) { ExeCompiler.StepListener l = (ExeCompiler.StepListener) i.next(); l.complete(); } } }