davmail/archive/jsmooth-0.9.9-7-patch/src/net/charabia/jsmoothgen/application/ExeCompiler.java

535 lines
19 KiB
Java

/*
JSmooth: a VM wrapper toolkit for Windows
Copyright (C) 2003 Rodrigo Reyes <reyes@charabia.net>
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();
}
}
}